Or, building a counter app.
Here is Web/MX
in a tl;dr nutshell:
Let us look at each of those in the context of a simple counter app.
We still program with HTML and CSS:
(defn a-counter []
(div {:class :intro}
(h2 "The count is now....")
(p {:class :intro-counter} "42")
(button {:class :push-button
:onclick #(js/alert "RSN")} "+")))
Where HTML has: <tag attributes*> children* </tag>
...
...Web/MX has: (tag [HTML-attributes* [custom-state]] children*)
CLJS keywords become strings in HTML, and boolean HTML attributes need special handling. Otherwise, MDN is your guide.
Any component can pull information it needs from anywhere, first navigating to another object, then simply reading its properties. Matrix internals record the dependency transparently and trigger redraws automatically.
(defn a-counter []
(div {:class [:intro]}
(div {}
{:name :a-counter
:count 3}
(h2 "The count is now…")
(p {:class :intro-counter}
(str "…" (mget (mx-par me) :count)))) ;; <======
(div (mapv (fn [idx] (span (str idx "...")))
(range (mget (fmu :a-counter me) :count)))))). ;; <======
The counter display will change when the counter changes, but we need the next example to prove that.
Any handler can navigate to any property to change it.
(defn a-counter []
(div {:class [:intro]}
(div {:class "intro"}
{:name :a-counter
:count (cI 3)} ;; 1
(h2 "The count is now…")
(p {:class :intro-counter}
(str "…" (mget (mx-par me) :count)))
(button {:class :push-button
:onclick (cF (fn [event] ;; 2
(let [counter (fm! :a-counter me)] ;; 3
(mswap! counter :count inc))))} ;; 4
; just a random demonstration of dynamic, interdependent state...
(div (mapv (fn [idx] (span (str idx "...")))
(range (mget (fmu :a-counter me) :count))))))
(cI <value>)
tells MX that the property :count can and might be changed by imperative code;FM!
family search utility to navigate to the :a-counter; andWhile that may sound uncontrolled, there is method to the madness: the property mutation gets propagated fully throughout the dag, consistently and without glitches, before the mswap!
returns.
If our formulas deciding the rest of the application state are correct, the app will be correct.
Those three points above cover everything essential to Matrix, but one edge case comes up often enough, and is exceptional enough, to deserve mention: observers acting on the DAG. A vital quality of Cell observers is that act only outside the Matrix dataflow. But it is not uncommon, when developing MX code, to encounter a use case where the dataflow can usefully detect a need to mutate an input cell. These are often cases where the user by design has control, but the system wants to offer a U/X nicety by automatically providing a user input.
To this end, MX does allow observers to mset!/mswap! of input Cells, but only by enqueueing said mutations for later execution, immediately following the processing of the current mutation. In the example below, we want the user to control the counter, but we also want an automatic safeguard should the count reach a "dangerous" level.
(defn start-stop-button []
(button {:class :pushbutton
:style "border-color:white"
:onclick #(mswap! (fmu :a-counter (evt-md %)) :ticking? not)}
(if (mget (fmu :a-counter me) :ticking?)
"Stop" "Start")))
(defn a-counter []
(div {:class "intro"}
{:name :a-counter
:danger-count 5 ;; <============================
:ticking? (cI false)
:ticker (cF+ [:watch (fn [_ _ newv prior _]
(when (integer? prior)
(js/clearInterval prior)))]
(when (mget me :ticking?)
(js/setInterval #(mswap! me :count inc) 1000)))
:count (cI 0 :watch (fn [_ me new-ct _ _]
(when (> new-ct (mget me :danger-count))
(with-cc :tickofff ;; <=====================
(mset! me :ticking? false)))))
}
(h2 "The count is now…")
(span {:class :intro-counter}
(str (mget (mx-par me) :count)))
(start-stop-button)))
Reactivity is neat, so we want to use it everywhere, even with software that knows nothing about Matrix. In this next example, we use some simple "glue code" to connect the non-reactive js/setInterval
with reactive Matrix elements.
(defn a-counter []
(div {:class "intro"}
{:name :a-counter
:count (cI 0)
:ticker (cF (js/setInterval ;; 1
#(mswap! me :count inc) ;; 2
1000))}
(h2 "The count is now…")
(span {:class :intro-counter}
(str (mget (mx-par me) :count)))))
mswap!
to accurately update the :count and the entire DAG.The resulting dependency graph, automatically detected, can be used by a generic engine to handle the tedious, error-prone work of keeping state consistent. The more utilities and libraries we "wrap" with Matrix, the bigger the productivity win.
With the state management burden lifted, the developer can concentrate on the application, and is encouraged to make it even more responsive.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close