{"id":13609300,"url":"https://github.com/cljfx/cljfx","last_synced_at":"2025-05-14T21:09:02.600Z","repository":{"id":41328748,"uuid":"162845532","full_name":"cljfx/cljfx","owner":"cljfx","description":"Declarative, functional and extensible wrapper of JavaFX inspired by better parts of react and re-frame","archived":false,"fork":false,"pushed_at":"2025-04-09T13:34:31.000Z","size":1436,"stargazers_count":988,"open_issues_count":37,"forks_count":48,"subscribers_count":26,"default_branch":"master","last_synced_at":"2025-04-10T11:02:45.208Z","etag":null,"topics":["cljfx","clojure","fn-fx","functional","javafx","re-frame","react","reagent"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cljfx.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"vlaaad"}},"created_at":"2018-12-22T22:02:32.000Z","updated_at":"2025-04-09T13:34:33.000Z","dependencies_parsed_at":"2024-11-06T15:18:51.916Z","dependency_job_id":"7159febc-a700-4b2f-b5f0-a02306402e68","html_url":"https://github.com/cljfx/cljfx","commit_stats":{"total_commits":276,"total_committers":18,"mean_commits":"15.333333333333334","dds":0.2246376811594203,"last_synced_commit":"ddea0fe5e3eca5e8b53f435c43db407b10e37480"},"previous_names":[],"tags_count":72,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cljfx%2Fcljfx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cljfx%2Fcljfx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cljfx%2Fcljfx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cljfx%2Fcljfx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cljfx","download_url":"https://codeload.github.com/cljfx/cljfx/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254190352,"owners_count":22029628,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cljfx","clojure","fn-fx","functional","javafx","re-frame","react","reagent"],"created_at":"2024-08-01T19:01:33.976Z","updated_at":"2025-05-14T21:08:57.579Z","avatar_url":"https://github.com/cljfx.png","language":"Clojure","readme":"![Logo](doc/logo.png)\n\n[![Clojars Project](https://img.shields.io/clojars/v/cljfx.svg?logo=clojure\u0026logoColor=white)](https://clojars.org/cljfx)\n[![Slack Channel](https://img.shields.io/badge/slack-cljfx@clojurians-blue.svg?logo=slack)](https://clojurians.slack.com/messages/cljfx/)\n![Clojure CI](https://github.com/cljfx/cljfx/workflows/Clojure%20CI/badge.svg)\n\nCljfx is a declarative, functional and extensible wrapper of JavaFX\ninspired by better parts of react and re-frame.\n\n## Rationale\n\nI wanted to have an elegant, declarative and composable UI\nlibrary for JVM and couldn't find one. Cljfx is inspired by react,\nreagent, re-frame and fn-fx.\n\nLike react, it allows to specify only desired layout, and handles\nall actual changes underneath. Unlike react (and web in general) it does\nnot impose xml-like structure of everything possibly having multiple\nchildren, thus it uses maps instead of hiccup for describing layout.\n\nLike reagent, it allows to specify component descriptions using simple\nconstructs such as data and functions. Unlike reagent, it rejects using\nmultiple stateful reactive atoms for state and instead prefers composing\nui in more pure manner.\n\nLike re-frame, it provides an approach to building large applications\nusing subscriptions and events to separate view from logic. Unlike\nre-frame, it has no hard-coded global state, and subscriptions work on\nreferentially transparent values instead of ever-changing atoms.\n\nLike fn-fx, it wraps underlying JavaFX library so developer can describe\neverything with clojure data. Unlike fn-fx, it is more dynamic, allowing\nusers to use maps and functions instead of macros and deftypes, and has\nmore explicit and extensible lifecycle for components.\n\n## Installation and requirements\n\nCljfx uses `tools.deps`, so you can add this repo with latest sha as a \ndependency:\n```edn\n cljfx {:git/url \"https://github.com/cljfx/cljfx\" :sha \"\u003cinsert-sha-here\u003e\"}\n```\nCljfx is also published on Clojars, so you can add `cljfx` as a maven\ndependency, current version is on this badge: \n\n[![Cljfx on Clojars](https://clojars.org/cljfx/cljfx/latest-version.svg)](https://clojars.org/cljfx/cljfx)\n\nMinimum required version of clojure is 1.10.\n\nWhen depending on git coordinates, minimum required Java version is 11. When using maven \ndependency, both Java 8 (assumes it has JavaFX provided in JRE) and Java 11 (via openjfx \ndependency) are supported. You don't need to configure anything in this regard: correct \nclassifiers are picked up automatically. \n\nPlease note that JavaFX 8 is outdated and has problems some people consider severe: it \ndoes not support HiDPI scaling on Linux, and sometimes crashes JVM on macOS Mojave. You \nshould prefer JDK 11.\n\n## Overview\n\n### Hello world\n\nComponents in cljfx are described by maps with `:fx/type` key. By\ndefault, fx-type can be:\n- a keyword corresponding to some JavaFX class\n- a function, which receives this map as argument and returns\n  another description\n- an implementation of Lifecycle protocol (more on that in [extending \n  cljfx](#extending-cljfx) section)\n\nMinimal example:\n```clj\n(ns example\n  (:require [cljfx.api :as fx]))\n\n(fx/on-fx-thread\n  (fx/create-component\n    {:fx/type :stage\n     :showing true\n     :title \"Cljfx example\"\n     :width 300\n     :height 100\n     :scene {:fx/type :scene\n             :root {:fx/type :v-box\n                    :alignment :center\n                    :children [{:fx/type :label\n                                :text \"Hello world\"}]}}}))\n```\nEvaluating this code will create and show a window:\n\n![](doc/hello-world.png)\n\nThe overall mental model of these descriptions is this:\n- whenever you need a JavaFX class, use map where `:fx/type` key has a\n  value of a kebab-cased keyword derived from that class name\n- other keys in this map represent JavaFX properties of that class (also\n  in kebab-case);\n- if prop x can be changed by user, there is a corresponding\n  `:on-x-changed` prop for observing these changes\n\n### Renderer\n\nTo be truly useful, there should be some state and changes over time,\nfor this matter there is a renderer abstraction, which is a function \nthat you may call whenever you want with new description, and cljfx will \nadvance all the mutable state underneath to match this description. \nExample:\n```clj\n(def renderer\n  (fx/create-renderer))\n\n(defn root [{:keys [showing]}]\n  {:fx/type :stage\n   :showing showing\n   :scene {:fx/type :scene\n           :root {:fx/type :v-box\n                  :padding 50\n                  :children [{:fx/type :button\n                              :text \"close\"\n                              :on-action (fn [_]\n                                           (renderer {:fx/type root\n                                                      :showing false}))}]}}})\n\n(renderer {:fx/type root\n           :showing true})\n```\nEvaluating this code will show this:\n\n![](doc/app-example.png)\n\nClicking `close` button will hide this window.\n\nRenderer batches descriptions and re-renders views on fx thread only \nwith last received description, so it is safe to call many times at \nonce. Calls to renderer function return derefable that will contain \ncomponent value with most recent description.\n\n### Atoms\n\nExample above works, but it's not very convenient: what we'd really like\nis to have a single global state as a value in an atom, derive our\ndescription of JavaFX state from this value, and change this atom's\ncontents instead. Here is how it's done:\n```clj\n;; Define application state\n\n(def *state\n  (atom {:title \"App title\"}))\n\n;; Define render functions\n\n(defn title-input [{:keys [title]}]\n  {:fx/type :text-field\n   :on-text-changed #(swap! *state assoc :title %)\n   :text title})\n\n(defn root [{:keys [title]}]\n  {:fx/type :stage\n   :showing true\n   :title title\n   :scene {:fx/type :scene\n           :root {:fx/type :v-box\n                  :children [{:fx/type :label\n                              :text \"Window title input\"}\n                             {:fx/type title-input\n                              :title title}]}}})\n\n;; Create renderer with middleware that maps incoming data - description -\n;; to component description that can be used to render JavaFX state.\n;; Here description is just passed as an argument to function component.\n\n(def renderer\n  (fx/create-renderer\n    :middleware (fx/wrap-map-desc assoc :fx/type root)))\n\n;; Convenient way to add watch to an atom + immediately render app\n\n(fx/mount-renderer *state renderer)\n```\nEvaluating code above pops up this window:\n\n![](doc/state-example.png)\n\nEditing input then immediately updates displayed app title.\n\n### Map events\n\nConsider this example:\n\n```clj\n(defn todo-view [{:keys [text id done]}]\n  {:fx/type :h-box\n   :children [{:fx/type :check-box\n               :selected done\n               :on-selected-changed #(swap! *state assoc-in [:by-id id :done] %)}\n              {:fx/type :label\n               :style {:-fx-text-fill (if done :grey :black)}\n               :text text}]})\n```\n\nThere are problems with using functions as event handlers:\n1. Performing mutation from these handlers requires coupling with that\nstate, thus making `todo-view` dependent on mutable `*state`\n2. Updating state from listeners complects logic with view, making\napplication messier over time\n3. There are unnecessary reassignments to `on-selected-changed`:\nfunctions have no equality semantics other than their identity, so on\nevery change to this view (for example, when changing it's text),\n`on-selected-changed` will be replaced with another function with same\nbehavior.\n\nTo mitigate these problems, cljfx allows to define event handlers as\narbitrary maps, and provide a function to a renderer that performs \nactual handling of these map-events (with additional `:fx/event` key \ncontaining dispatched event):\n\n```clj\n;; Define view as just data\n\n(defn todo-view [{:keys [text id done]}]\n  {:fx/type :h-box\n   :spacing 5\n   :padding 5\n   :children [{:fx/type :check-box\n               :selected done\n               :on-selected-changed {:event/type ::set-done :id id}}\n              {:fx/type :label\n               :style {:-fx-text-fill (if done :grey :black)}\n               :text text}]})\n\n;; Define single map-event-handler that does mutation\n\n(defn map-event-handler [event]\n  (case (:event/type event)\n    ::set-done (swap! *state assoc-in [:by-id (:id event) :done] (:fx/event event))))\n\n;; Provide map-event-handler to renderer as an option\n\n(fx/mount-renderer\n  *state\n  (fx/create-renderer\n    :middleware (fx/wrap-map-desc assoc :fx/type root)\n    :opts {:fx.opt/map-event-handler map-event-handler}))\n\n```\n\nYou can see full example at [examples/e09_todo_app.clj](examples/e09_todo_app.clj).\n\n### Interactive development\n\nAnother useful aspect of renderer function that should be used during \ndevelopment is refresh functionality: you can call renderer function \nwith zero args and it will recreate all the components with current \ndescription.\n\nSee walk-through in [examples/e12_interactive_development.clj](examples/e12_interactive_development.clj)\nas an example of how to iterate on cljfx app in REPL.\n\n### Dev tools\n\nCheck out [cljfx/dev](https://github.com/cljfx/dev) for tools that might help \nyou when developing cljfx applications. These tools include:\n- specs and validation, both for individual cljfx descriptions and running apps;\n- helper reference for existing types and their props;\n- cljfx component stack reporting in exceptions to help with debugging. \n\n### Styling\n\nIterating on styling is usually cumbersome: styles are defined in \nexternal files, they are not reloaded on change, they are opaque: you \ncan't refer from the code to values defined in CSS. Cljfx has a \ncomplementary library that aims to help with all those problems: \n[cljfx/css](https://github.com/cljfx/css).\n\n### Special keys\n\nSometimes components accept specially treated keys. Main uses are:\n\n1. Reordering of nodes (instead of re-creating them) in parents that may\n   have many children. Descriptions that have `:fx/key` during\n   advancing get reordered instead of recreated if their position in\n   child list is changed. Consider this example:\n   ```clj\n   (let [component-1 (fx/create-component\n                       {:fx/type :v-box\n                        :children [{:fx/type :label\n                                    :fx/key 1\n                                    :text \"- buy milk\"}\n                                   {:fx/type :label\n                                    :fx/key 2\n                                    :text \"- buy socks\"}]})\n         [milk-1 socks-1] (vec (.getChildren (fx/instance component-1)))\n         component-2 (fx/advance-component\n                       component-1\n                       {:fx/type :v-box\n                        :children [{:fx/type :label\n                                    :fx/key 2\n                                    :text \"- buy socks\"}\n                                   {:fx/type :label\n                                    :fx/key 1\n                                    :text \"- buy milk\"}]})\n         [socks-2 milk-2] (vec (.getChildren (fx/instance component-2)))]\n     (and (identical? milk-1 milk-2)\n          (identical? socks-1 socks-2)))\n   =\u003e true\n   ```\n   With `:fx/key`-s specified, advancing of this component reordered\n   children of VBox, and didn't change text of any labels, because their\n   descriptions stayed the same.\n2. Providing extra props available in certain contexts. If node is\n   placed inside a pane, pane can layout it differently by looking into\n   properties map of a node. Nodes placed in ButtonBar can have\n   OS-specific ordering depending on assigned ButtonData. These\n   properties can be specified via keywords namespaced by container's\n   fx-type. Example:\n   ```clj\n   (fx/on-fx-thread\n     (fx/create-component\n       {:fx/type :stage\n        :showing true\n        :scene {:fx/type :scene\n                :root {:fx/type :stack-pane\n                       :children [{:fx/type :rectangle\n                                   :width 200\n                                   :height 200\n                                   :fill :lightgray}\n                                  {:fx/type :label\n                                   :stack-pane/alignment :bottom-left\n                                   :stack-pane/margin 5\n                                   :text \"bottom-left\"}\n                                  {:fx/type :label\n                                   :stack-pane/alignment :top-right\n                                   :stack-pane/margin 5\n                                   :text \"top-right\"}]}}}))\n   ```\n   Evaluating code above produces this window:\n\n   ![](doc/pane-example.png)\n\n   For a more complete example of available pane keys, see\n   [examples/e07_extra_props.clj](examples/e07_extra_props.clj)\n\n### Factory props\n\nThere are some props in JavaFX that represent not a value, but a way to\nconstruct a value from some input:\n- `:page-factory` in pagination, you can use function receiving page\n  index and returning any component description for this prop (see\n  example in [examples/e06_pagination.clj](examples/e06_pagination.clj))\n- various versions of `:cell-factory` in controls designed to display\n  multiples of items (table views, list views etc.) can be described \n  using the following form:\n  ```clj\n  {:fx/cell-type :list-cell\n   :describe (fn [item] {:text (my.ns/item-as-text item)})} \n  ```\n  The lifecycle of cells is a bit different than lifecycle of other \n  components: JavaFX pools a minimal amount of cells needed to be shown\n  at the same time and updates them on scrolling. This is great for \n  performance, but it imposes a restriction: cell type is \"static\". \n  That's why cljfx uses `:fx/cell-type` that *has* to be a keyword (like \n  `:list-cell` or `:table-cell`) and a separate `:describe` function \n  that receives an item and returns a prop map for that cell type. \n  There are various usage examples available in\n  [examples/e16_cell_factories.clj](examples/e16_cell_factories.clj)\n\n### Subscriptions and contexts\n\nOnce application becomes complex enough, you can find yourself passing\nvery big chunks of state everywhere. Consider this example: you develop\na task tracker for an organization. A typical task view on a dashboard\ndisplays a description of that task and an assignee. Required state for\nthis view is plain and simple, just a simple data like that:\n```clj\n{:title \"Fix NPE on logout during full moon\"\n :state :todo\n :assignee {:id 42 :name \"Fred\"}}\n```\nThen one day comes a requirement: users of this task tracker should be\nable to change assignee from the dashboard. Now, we need a combo-box\nwith all assignable users to render such a view, and required data becomes\nthis:\n```clj\n{:title \"Fix NPE on logout during full moon\"\n :state :todo\n :assignee {:id 42 :name \"Fred\"}\n :users [{:id 42 :name \"Fred\"}\n         {:id 43 :name \"Alice\"}\n         {:id 44 :name \"Rick\"}]}\n```\nAnd you need to compute it once in one place and then pass it along\nmultiple layers of ui to this view. This is undesirable:\n- it will lead to unnecessary re-renderings of views that just pass data\n  further when it changes\n- it complects reasoning about what actually a view needs: is it just a\n  task? or a task with some precomputed attributes?\n\nTo mitigate this problem, cljfx introduces optional abstraction called\n**context**, which is inspired by re-frame's subscriptions. Context is a\nblack-box wrapper around application state (usually a map), with 2 functions to \nlook inside the wrapped state:\n1. `fx/sub-val` that subscribes a function to the *value* wrapped in the context \n   directly (usually it's used for data accessors like `get` or `get-in`);\n2. `fx/sub-ctx` that subscribes a function to the *context* itself, which is \n   then used by the function to subscribe to some view of the wrapped value \n   indirectly (can be used for slower computations like sorting).\n\nReturned values from subscription functions are memoized in this context\n(so it actually is a *memoization* context), and subsequent `sub-*` calls\nwill result in cache lookup. The best thing about context is that not\nonly does it support updating wrapped values via `swap-context` and\n`reset-context`, it also reuses this memoization cache to minimize\nre-calculation of subscription functions in successors of this context.\nThis is done via tracking of `fx/sub-*` calls inside subscription\nfunctions, and checking if their dependencies changed. Example:\n```clj\n(def context-1\n  (fx/create-context\n    {:tasks [{:text \"Buy milk\" :done false}\n             {:text \"Buy socks\" :done true}]}))\n\n;; Simple subscription function that depends on :tasks key of wrapped map. Whenever value\n;; of :tasks key \"changes\" (meaning whenever there will be created a new context with\n;; different value on :tasks key), subscribing to this function will lead to a call to\n;; this function instead of cache lookup\n(defn task-count [context]\n  (count (fx/sub-val context :tasks)))\n\n;; Using subscription functions:\n(fx/sub-ctx context-1 task-count) ; =\u003e 2\n\n;; Another subscription function depends on :tasks key of wrapped map\n(defn remaining-task-count [context]\n  (count (remove :done (fx/sub-val context :tasks))))\n\n(fx/sub-ctx context-1 remaining-task-count) ; =\u003e 1\n\n;; Indirect subscription function that depends on 2 previously defined subscription\n;; functions, which means that whenever value returned by `task-count` or\n;; `remaining-task-count` changes, subscribing to this function will lead to a call\n;; instead of cache lookup\n(defn task-summary [context]\n  (prn :task-summary)\n  (format \"Tasks: %d/%d\"\n          (fx/sub-ctx context remaining-task-count)\n          (fx/sub-ctx context task-count)))\n\n(fx/sub-ctx context-1 task-summary) ; (prints :task-summary) =\u003e \"Tasks: 1/2\"\n\n;; Creating derived context that reuses cache from `context-1`\n(def context-2\n  (fx/swap-context context-1 assoc-in [:tasks 0 :text] \"Buy bread\"))\n\n;; Validating that cache entry is reused. Even though we updated :tasks key, there is no\n;; reason to call `task-summary` again, because it's dependencies, even though\n;; recalculated, return the same values\n(fx/sub-ctx context-2 task-summary) ; (does not print anything) =\u003e \"Tasks: 1/2\"\n```\n\nThis tracking imposes a restriction on subscription functions: they\nshould not call `fx/sub-*` after they return (which is possible if they\nreturn lazy sequence that calls `fx/sub-*` during element calculation).\n\nNote that all functions subscribed with `fx/sub-val` are always invalidated for \nderived contexts, so they should be reasonably fast (like `get`). Their upside \nis that they are decoupled from context completely: since they receive wrapped \nvalue as first argument, any function can be used. Functions subscribed with \n`fx/sub-ctx`, on the other hand, are invalidated only when their dependencies \nchange, so they can be slower (like `sort`). Their downside is coupling to \ncljfx — they receive context as first argument. \n\nUsing context in cljfx application requires 2 things:\n- passing context to all lifecycles in a component graph, which is done\n  by using `fx/wrap-context-desc` middleware\n- using special lifecycle (`fx/fn-\u003elifecycle-with-context`) for function\n  fx-types that uses this context\n\nMinimal app example using contexts:\n```clj\n;; you will need core.cache dependency if you are going to use contexts!\n(require '[clojure.core.cache :as cache])\n\n;; Define application state as context\n\n(def *state\n  (atom (fx/create-context {:title \"Hello world\"} cache/lru-cache-factory)))\n\n;; Every description function receives context at `:fx/context` key\n\n(defn root [{:keys [fx/context]}]\n  {:fx/type :stage\n   :showing true\n   :scene {:fx/type :scene\n           :root {:fx/type :h-box\n                  :children [{:fx/type :label\n                              :text (fx/sub context :title)}]}}})\n\n(def renderer\n  (fx/create-renderer\n    :middleware (comp\n                  ;; Pass context to every lifecycle as part of option map\n                  fx/wrap-context-desc\n                  (fx/wrap-map-desc (fn [_] {:fx/type root})))\n    :opts {:fx.opt/type-\u003elifecycle #(or (fx/keyword-\u003elifecycle %)\n                                        ;; For functions in `:fx/type` values, pass\n                                        ;; context from option map to these functions\n                                        (fx/fn-\u003elifecycle-with-context %))}))\n\n(fx/mount-renderer *state renderer)\n```\n\nUsing contexts effectively makes every fx-type function a subscription\nfunction, so no-lazy-fx-subs-in-returns restriction applies to them too.\nOn a plus side, it makes re-rendering more efficient: fx-type components\nget re-rendered only when their subscription values change.\n\nFor a bigger example see\n[examples/e15_task_tracker.clj](examples/e15_task_tracker.clj).\n\n#### Preventing cache from growing forever\n\nAnother point of concern for context is cache size. By default it will grow\nforever, which at certain point might become problematic, and we may\nwant to trade some cpu cycles for recalculations to decrease memory\nconsumption. There is a perfect library for it:\n[core.cache](https://github.com/clojure/core.cache). `fx/create-context`\nsupports cache factory (a function taking initial cache map and\nreturning cache) as a second argument. What kind of cache\nto use is a question with no easy answer, you probably should try\ndifferent caches and see what is a better fit for your app.\n\nCljfx has a *runtime* optional dependency on `core.cache`: you need to add\nit yourself if you are going to use contexts.\n\n### Event handling on steroids\n\nWhile using maps to describe events is a good step towards mostly pure\napplications, there is still a room for improvement:\n- many event handlers dereference app state, which makes them coupled \n  with an atom: mutable place\n- almost every event handler still mutates app state, which also makes \n  them coupled\n\nCljfx borrows solutions to these problems from re-frame, providing\nmap event handler wrappers that allow having co-effects (pure inputs) and \neffects (pure outputs). Lets walk through this example event handler and see \nhow we can make it pure:\n\n```clj\n(def *state\n  (atom {:todos []}))\n\n(defn handle [event]\n  (let [state @*state\n        {:keys [event/type text]} event]\n    (case type\n      ::add-todo (reset! *state (update state :todos conj {:text text :done false})))))\n\n;; usage:\n(handle {:event/type ::add-todo :text \"Buy milk\"})\n```\n\n1. Co-effects: `wrap-co-effects`\n\n   It would be nice to not have to deref state atom and instead receive \n   it as an argument, and that is what co-effects are for. Co-effect is \n   a term taken from re-frame, and it means current state as data, as \n   presented to event handler. In cljfx you describe co-effects as a map\n   from arbitrary key to function that produces some data that is then \n   passed to handler:\n   ```clj\n   (defn handle [event]\n     ;; receive state as part of an event\n     (let [{:keys [event/type text state]} event]\n       (case type\n         ::add-todo (reset! *state (update state :todos conj {:text text :done false})))))\n         \n   (def actual-handler \n     (-\u003e handle\n         (fx/wrap-co-effects {:state #(deref *state)})))\n   \n   ;; usage:\n   (actual-handler {:event/type ::add-todo :text \"Buy milk\"})\n   ```\n2. Effects: `wrap-effects`\n\n   Instead of performing side-effecting operations from handlers, we can\n   return data that describes how to perform these side-effecting \n   operations. `fx/wrap-effects` uses that data to perform side effects.\n   You describe effects as a map from arbitrary keys to side-effecting\n   function. A wrapped handler in turn should return a seqable of \n   2-element vectors. First element is a key used to find side-effecting\n   function, and second is an argument to it:\n   ```clj\n   (defn handle [event]\n     (let [{:keys [event/type text state]} event]\n       (case type\n         ;; Now handlers not only receive just data, they also return just data\n         ;; Returning map is a convenience option that can be used as a return\n         ;; value, and sequences like [[:state ...] [:state ...]] are fine too \n         ::add-todo {:state (update state :todos conj {:text text :done false})})))\n   \n   (def actual-handler\n     (-\u003e handle\n         (fx/wrap-co-effects {:state #(deref *state)})\n         (fx/wrap-effects {:state (fn [state _] (reset! *state state))})))\n   ```\n   In addition to value provided by wrapped handler, side-effecting \n   function receives a function they can call to dispatch new events.\n   While it's useless for resetting state, it can be useful in other\n   circumstances. One is you can create a `:dispatch` effect that\n   dispatches other events, and another is you can describe\n   asynchronous operations such as http requests as just data. Since\n   effect handlers are run on the UI thread, you should delegate the\n   execution of potentially blocking effects to a different thread\n   using this approach. Examples of both can be found at\n   [examples/e18_pure_event_handling.clj](examples/e18_pure_event_handling.clj).\n   This approach allows to specify side effects in a few places, and\n   then have easily testable handlers:\n   ```clj\n   (handle {:event/type ::add-todo\n            :text \"Buy milk\"\n            :state {:todos []}})\n   =\u003e {:state {:todos [{:text \"Buy milk\", :done false}]}}\n   ;; data in, data out, no mocks necessary! \n   ```\n\n\n### How does it actually work\n\nThere are 3 main building blocks of cljfx: components, lifecycles and\nmutators. Each are represented by protocols, here they are:\n```clj\n(defprotocol Component\n  :extend-via-metadata true\n  (instance [this]))\n\n(defprotocol Lifecycle\n  :extend-via-metadata true\n  (create [this desc opts])\n  (advance [this component desc opts])\n  (delete [this component opts]))\n\n(defprotocol Mutator\n  :extend-via-metadata true\n  (assign! [this instance coerce value])\n  (replace! [this instance coerce old-value new-value])\n  (retract! [this instance coerce value]))\n```\nComponent is an immutable value representing some object in some state\n(that object may be mutable — usually it's a javafx object), that also\nhas a reference to said object instance.\n\nLifecycle is well, a lifecycle of a component. Component gets created \nfrom a description once, advanced to new description zero or more times, \nand then deleted. Cljfx is a composition of multiple different \nlifecycles, each useful in their own place. `opts` is a map that \ncontains some data used by different lifecycles. 2 opt keys that are \nused by default in cljfx are:\n- `:fx.opt/type-\u003elifecycle` — used in `dynamic` lifecycle to select what\n  lifecycle will be actually used for description based by value in\n  `:fx/type` key.\n- `:fx.opt/map-event-handler` — used in `event-handler` lifecycle that\n  checks if event handler is a map, and if it is, call function provided\n  by this key when event happens. It should be noted, that such event\n  handlers receive additional key in a map (`:fx/event`) that contains\n  event object, which may be context dependent: for JavaFX change\n  listeners it's a new value, for JavaFX event handlers it's an event,\n  for runnables it's `nil`, etc.\n\nAnother notable lifecycle is `cljfx.composite/lifecycle`: it\nmanages mutable JavaFX objects: creates instance in `create`, advances\nany changes to props (each individual prop may be seen as lifecycle +\nmutator), and has some useful macros to simplify generating composite\nlifecycles for concrete classes.\n\nFinally, mutator is a part of prop in composite lifecycles that\nperforms actual mutation on instance when values change. It also\nreceives `coerce` function which is called on value before applying it.\nMost common mutator is `setter`, but there are some other, for example,\n`property-change-listener`, which uses `addListener` and\n`removeListener`.\n\n### Extending cljfx\n\nCljfx might have some missing parts that you'll want to fill. Not \neverything can be configured with lifecycle opts and renderer \nmiddleware, and in that case you are encouraged to create and use \nextension lifecycles. Fx-types in descriptions can be implementations of \nLifecycle protocol, and with this escape hatch you get a lot more \nfreedom. Since these lifecycles can introduce different meanings for \nwhat descriptions mean in their context, they should stand out from \nother keyword or function lifecycles, and convention is to have `ext-` \nprefix in their names. \n \n#### Included extension lifecycles\n\n1. `fx/ext-instance-factory`\n\n   Using this extension lifecycle you can simply create a component \n   using 0-argument factory function:\n   ```clj\n   (fx/instance\n     (fx/create-component\n       {:fx/type fx/ext-instance-factory\n        :create #(Duration/valueOf \"10ms\")}))\n   =\u003e #object[javafx.util.Duration 0x2f5eb358 \"10.0 ms\"]\n   ```\n   \n2. `fx/ext-on-instance-lifecycle`\n\n   You can use this lifecycle to additionally setup/tear down instance\n   of otherwise declaratively created value:\n   ```clj\n   (fx/instance\n     (fx/create-component\n       {:fx/type fx/ext-on-instance-lifecycle\n        :on-created #(prn \"created\" %)\n        :desc {:fx/type fx/ext-instance-factory\n               :create #(Duration/valueOf \"10ms\")}}))\n   ;; prints \"created\" #object[javafx.util.Duration 0x284cdce9 \"10.0 ms\"]\n   =\u003e #object[javafx.util.Duration 0x284cdce9 \"10.0 ms\"]\n   ```\n   \n3. `fx/ext-let-refs` and `fx/ext-get-ref`\n\n   You can create managed components outside of component tree using \n   `fx/ext-let-refs`, and then use instances of them, possibly in \n   multiple places, using `fx/ext-get-ref`:\n   ```clj\n   {:fx/type fx/ext-let-refs\n    :refs {::button-a {:fx/type :button\n                       :text \"Press Alt+A to focus on me\"}}\n    :desc {:fx/type :v-box\n           :children [{:fx/type :label\n                       :text \"Mnemonic _A\"\n                       :mnemonic-parsing true\n                       :label-for {:fx/type fx/ext-get-ref\n                                   :ref ::button-a}}\n                      {:fx/type fx/ext-get-ref\n                       :ref ::button-a}]}}\n   ```\n   One use case is for using references in props that expect nodes in a \n   scene graph (such as label's `:label-for`), and another is having \n   dialogs defined close to usage places, you can find an example of \n   such dialog at [examples/e22_button_with_confirmation_dialog.clj](examples/e22_button_with_confirmation_dialog.clj) \n\n4. `fx/ext-set-env` and `fx/ext-get-env`\n\n   You can put any values into component tree environment with `fx/ext-set-env`, and then\n   retrieve values from this environment with `fx/ext-get-env`:\n   ```clj\n   {:fx/type fx/ext-set-env\n    :env {::global-text-style {:-fx-text-fill :red}}\n    :desc {:fx/type :v-box\n           :children [{:fx/type fx/ext-get-env\n                       :env {::global-text-style :style}\n                       :desc {:fx/type :label \n                              ;; will receive :style prop that makes text red\n                              :text \"Hello world\"}}]}}\n   ```\n\n5. `fx/ext-many`\n\n   Usually props that expect collections of elements already ask for a\n   collection of descriptions, but there might be cases where you want to manage\n   a coll even though you are asked for a single element. In this case you can\n   use `fx/ext-many` to describe multiple of components, for example, to show\n   multiple windows at once:\n   ```clj\n   (fx/on-fx-thread\n     (fx/create-component\n       {:fx/type fx/ext-many\n        :desc [{:fx/type :stage\n                :showing true}\n               {:fx/type :stage\n                :showing true}]}))\n   ```\n   See [examples/e10_multiple_windows.clj](examples/e10_multiple_windows.clj)\n   and [examples/e17_dialogs.clj](examples/e17_dialogs.clj)\n\n6. `fx/make-ext-with-props`\n\n   Using this function you can create extension lifecycles that handle whatever\n   additional props you need. These props will be applied after props of\n   original lifecycle. There are some predefined lifecycles providing extra\n   props:\n   - for controlling default selection models in TabPane, ListView,\n   TableView, TreeView and TreeTableView, see [examples/e27_selection_models.clj](examples/e27_selection_models.clj);\n   - for extra WebView knobs, see [examples/e39_web_view_extensions.clj](examples/e39_web_view_extensions.clj).\n\nExamples of included extension lifecycles are available at\n[examples/e21_extension_lifecycles.clj](examples/e21_extension_lifecycles.clj).\n\n#### Writing extension lifecycles\n\nIf that's not enough, you can write your own, but this requires more \nthorough knowledge of cljfx: take a look at \n[cljfx.lifecycle](src/cljfx/lifecycle.clj) namespace to see how other \nlifecycles are implemented.\n\n#### Wrapping other java-based JavaFX components\n\nThere is `cljfx.composite/props` macro to create a prop-map for \narbitrary Java class. Also there is a `cljfx.composite/describe` macro\nthat allows to construct a lifecycle from a class and a prop map, and \nplenty of examples in `cljfx.fx.*` namespaces that can help you make\ncustom java components for JavaFX cljfx-friendly.\n\n### Combining it all together\n\nNow that every piece is laid out, it's time to combine them into \napplication. What suits your needs is up to you, but if you plan to \nbuild something non-trivial, you'll probably want to combine all of the\npieces, and easiest way to start is using `create-app` function. It \naccepts app atom, event handler and function producing view description\nand wires them all together:\n```clj\n(def app\n  (fx/create-app *context\n    :event-handler handle-event\n    :desc-fn (fn [_]\n               {:fx/type root-view})))\n```\nUsing that as a starting point, you can build your application using \npure functions for everything: views, subscriptions, events. \n`create-app` also allows some optional settings, such as `:effects`,\n`:co-effects` and `:async-agent-options` for configuring event handling \nand `:renderer-middleware` for configuring renderer. An example of such\napplication can be found at \n[examples/e20_markdown_editor.clj](examples/e20_markdown_editor.clj).\n\n### Gotchas\n\n#### `:fx/key` should be put on descriptions in a list, not inside these descriptions\n\nFor example:\n```clj\n;; Don't do it, this won't work:\n\n(defn item-view [{:keys [item]}]\n  {:fx/type :label\n   ;; Do not specify `:fx/key` here!\n   :fx/key (:id item)\n   :text (:title item)})\n\n(defn item-list-view [items]\n  {:fx/type :v-box\n   :children (for [i items]\n               {:fx/type item-view\n                :item i})})\n```\nLifecycle that manages lists of things (`dynamics`) can't see how it's\nelements will unfold, so it needs to have `:fx/key`-s where it can see\nthem — in the element descriptions that it gets:\n```clj\n;; Do this to specify `:fx/key`-s:\n\n(defn item-view [{:keys [item]}]\n  {:fx/type :label\n   :text (:title item)})\n\n(defn item-list-view [items]\n  {:fx/type :v-box\n   :children (for [i items]\n               {:fx/type item-view\n                ;; Put `:fx/key` to description that is a part of a list\n                :fx/key (:id i)\n                :item i})})\n```\n#### `:fx/type` is for mutable objects only\n\nLifecycles describe how things change, and some things in JavaFX don't\nchange. For example, `Insets` class represents an immutable value, so\nwhen describing padding you don't need a map with `:fx/type` key:\n```clj\n{:fx/type :region\n :padding {:top 10 :bottom 10 :left 10 :right 10}}\n```\nIt doesn't have to be a map at all:\n```clj\n{:fx/type :region\n :padding 10}\n```\nHow does it work? Instead of using lifecycle there is a coercion\nmechanism that transforms values before assigning them to a model, most\nof them are in `cljfx.coerce` namespace.\n\n#### Coercion\n\nSome notable coercion examples and approaches:\n- all enums and enum-like things can be expressed as kebab-cased\n  keywords, for example `:red` for colors, `:crosshair` for cursors\n- you still can use actual instances of target classes, for example\n  `Cursor/CROSSHAIR` for cursors\n- for classes with 1-arg constructors you can supply just that, for\n  example url string for images\n- for classes with multi-arg constructors you can supply args as a map,\n  for example map with `:url` and `:background-loading` for images\n- styles can be specified as maps, for example\n  `{:-fx-background-color :lightgray}`\n- durations can be specified as vector like `[10 :ms]` or `[2 :h]`\n- key combinations can be vectors. There are 2 flavors of key\n  combinations in JavaFX: KeyCodeCombination, created if last element of\n  that vector is keyword, for example, `[:ctrl :period]`, and\n  KeyCharacterCombination, created if last element of that vector is\n  string, for example `[:ctrl \".\"]`\n\n#### Differences with JavaFX\n\nThere are some \"synthetic\" properties that provide needed functionality\nusually used through some other API:\n- Canvas has a `:draw` prop that is a function that receives Canvas as\n  an argument and should use it to draw on it ([example](examples/e28_canvas.clj))\n- MediaPlayer has `:state` prop that can be either `:playing`,\n  `:paused` or `:stopped`, and will call `play`/`pause`/`stop` methods\n  on media player when this prop is changed\n- `:url` prop of WebView will call `load` method on this view's web\n  engine\n  \n#### AOT-compilation is complicated\n\nRequiring cljfx starts a JavaFX application thread, which makes sense for repl and running\napplication, but problematic for AOT compilation. To turn off this behavior for \ncompilation, you should set `cljfx.skip-javafx-initialization` java property to `true` for \nyour compilation task. This can be done in `lein` or `clj` by specifying the following \njvm opts:\n```clj\n:jvm-opts [\"-Dcljfx.skip-javafx-initialization=true\"] \n```\nPlease note that while this will help in most cases, you still might have compilation \nrelated issues if your code imports JavaFX classes from `javafx.scene.control` package: \nclasses defined there require JavaFX runtime to be running by accessing it in \n[Control](https://github.com/javafxports/openjdk-jfx/blob/develop/modules/javafx.controls/src/main/java/javafx/scene/control/Control.java)'s \nstatic initializer. If you need to do that in your application code, you should not skip \nJavaFX initialization, and instead make your build tool call \n`(javafx.application.Platform/exit)` when it finished compiling. \n\n#### No local mutable state\n\nOne thing that is easy to do in react/reagent, but actually complects\nthings, is local mutable state: every component can have its own\nmutable state that lives independently of overall app state. This\nmakes reasoning about state of the app harder: you need to take lots of\nsmall pieces into account. Another problem is this state is unreliable,\nbecause it is only here when a component is here. If it gets\nrecreated, for example, after closing some panel it resides in and\nreopening it back, this state will be lost. Sometimes we want this\nbehavior, sometimes we don't, and it's possible to choose whether this\nstate will be retained or not only if it's a part of a global app state.\n\n#### No controlled props\n\nIn react, setting `value` prop on text input makes it controlled,\nmeaning it can't be changed unless there is also a change listener\nupdating this value on typing. This is much harder to do in JavaFX, so\nthere is no such thing. But you still can keep typed text in sync with\ninternal state by having both `:text` and `:on-text-changed` props (see\nexample in [examples/e09_todo_app.clj](examples/e09_todo_app.clj))\n\n## More examples\n\nThere are various examples available in [examples](examples) folder.\nTo try them out:\n1. Clone this repo and cd into it:\n   ```shell\n   git clone https://github.com/cljfx/cljfx.git\n   cd cljfx \n   ```\n2. Ensure you have java 11 installed.\n3. Launch repl with `:examples` alias and require examples:\n   ```shell\n   clj -A:examples\n   # Clojure 1.10\n   # user=\u003e (require 'e15-task-tracker)\n   # nil ;; window appears\n   ```\n\n## Full project examples\n\nFull project examples are in the [example-projects](example-projects) directory.\nConsult the example project's README.md for usage.\n\n- [Splash/loading screens and preloaders](example-projects/splash/README.md)\n\n## More information\n\nIf you want to learn more about [JavaFX](https://openjfx.io/index.html), its documentation\nis available [here](https://openjfx.io/javadoc/15/index.html).\n\nIf you want to learn more about [React](https://reactjs.org/) programming model cljfx is \nbased on, there is an in-depth guide to it: \n[React as UI Runtime](https://overreacted.io/react-as-a-ui-runtime/). Feel free to skip \nsections about hooks since cljfx does not have them.\n\nI also gave [a talk about cljfx](https://www.youtube.com/watch?v=xcMNTKFmEgI) — it goes \nfrom basic building blocks to how you build reactive applications and provides some \ncontext to why I created it. Slides are \n[here](https://docs.google.com/presentation/d/1576PE2NCbZifGqeg8Ya6RfycrWKVlBNLSW6bPgA527Q/).\n\n## API's stability, public and internal code\n\nNewer versions of cljfx should never introduce breaking changes, so if \nan update broke something, please file a bug report. Growth of cljfx \nshould happen only by accretion (providing more), relaxation (requiring \nless) and fixation (bashing bugs).\n\nThis applies to public API of cljfx. `cljfx.api` namespace and all \nbehaviors that can be observed by using it are a public API. Other \nnamespaces have a docstring stating what is and is not a public API.\n\nCurrent shapes of values implementing `Lifecycle`, `Component` and \n`Mutator` protocols are internal and subject to change: treat them as \na protocol implementations only. Context is not a protocol, but it's \nshape is internal too. \n\nKeywords with `fx` namespace in component descriptions are reserved: new\nones may be introduced.\n\n## Getting help\n\nFeel free to ask questions [on Slack](https://clojurians.slack.com/messages/cljfx/)\nor create an issue. Have a look at [previously asked questions](https://github.com/cljfx/cljfx/issues?utf8=%E2%9C%93\u0026q=is%3Aissue+label%3Aquestion).\n\n## Food for thought\n\nInternal list of ideas to explore:\n\n- missing observable maps: Scene's getMnemonics\n- `:row-factory` in tree-view/tree-table-view should be similar to cell \n  factories\n- are controlled props possible? (controls, also stage's `:showing`)\n- wrap-factory may use some memoizing and advancing\n- add tests for various lifecycles and re-calculations\n- update to same desc should be identical (component-vec)\n- expand on props and composite lifecycle. What's known about them:\n  - ctor:\n    - scene requires root, root can be replaced afterwards\n    - xy-chart requires axis, they can't be replaced afterwards\n  - prop in composite lifecycle may be a map or a function taking\n    instance and returning prop!\n  - changing media should re-create media player\n- big app with everything in it to check if/how it works (generative\n  tests maybe?)\n- if animation is to be implemented, it probably should be done as in\n  https://popmotion.io/\n- declarative timers? problem is to figure out start/loop semantics. \n  Examples:\n  - caret in custom text input may have timer that restarts on typing\n  - flipbook animation player needs to restart timer on FPS settings \n    change \n","funding_links":["https://github.com/sponsors/vlaaad"],"categories":["Clojure","Community","Frameworks"],"sub_categories":["Other Languages"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcljfx%2Fcljfx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcljfx%2Fcljfx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcljfx%2Fcljfx/lists"}