tape.tools
You must be familiar with tape.module
and tape.mvc
(particularly the
controller part) before proceeding. Depending on pieces used, require as:
(:require
[tape.tools.current.controller :as current.c]
[tape.tools.timeouts.controller :as timeouts.c]
[tape.tools.intervals.controller :as intervals.c]
[tape.tools.ui.form :as form :include-macros true])
A form/lens*
is a helper for making cursors that read from a subscription and
write by dispatching an event.
(defn form-fields []
(let [lens (form/lens* ::posts.c/post ::posts.c/field)
title (r/cursor lens [:title])]
[:input {:value @title
:on-change #(reset! title (-> % .-target .-value))}]))
When the title
cursor above is reset!
, it dispatches an event:
[::posts.c/field :title "some value"]
. When it is deref
'ed it reads the
(:title @(rf/subscription [::posts.c/post]))
value.
The form/field
helper creates an input over a cursor source (ratom or get-set
fn).
(defn form-fields []
(let [lens (form/lens* ::posts.c/post ::posts.c/field)]
[:<>
[form/field {:type :text, :source lens, :field :title}]
[form/field {:type :textarea, :source lens, :field :description}]]))
We assume the following 2 controller functions are present:
(ns blog.app.posts.controller ...)
(defn ^::c/event-db field [db [_ k v]] (assoc-in db [::post k] v))
(defn ^::c/sub post [db _] (::post db))
Ergonomic API uses macros with symbols of controller event handlers. Such symbols can be navigated via IDE "jump to definition". The macros are macroexpanded in the API above, so there’s no performance penalty at runtime.
(form/lens posts.c/post posts.c/field)
The form/field-with-list-errors
is a drop-in replacement for form/field
that adds the class is-danger
on the input when there are :errors
on the
input map m
, and also lists the errors below.
The form/when-valid
helper can be used on forms to to check and show the
HTML5 Constraint Validation API.
(defn save-button []
(let [save #(rf/dispatch [::posts.c/save])]
[:button {:on-click (form/when-valid save)} "Save"]))
If the form to which the button above belongs is valid, on clicking the button
the save
function is called; if the form is invalid the save
function is
not called, and the form validity is reported.
We have two controllers with event handlers and effect handlers for setting and
clearing timeouts and intervals. Don’t forget to add ::timeouts.c/module
&
::intervals.c/module
to the modules config map.
A timeout is a map with 3 positions: the number of milliseconds, an optional event that gets dispatched when the timeout is set (with the timeout id added) and an event that gets dispatched when the timer times out:
{:ms 3000
:set [::was-set] ;; optional, dispatched as [::was-set timeout-id]
:timeout [::timed-out]}
Similarly, an interval:
{:ms 3000
:set [::was-set] ;; dispatched as [::was-set interval-id]
:interval [::period-reached]}
These are given as arguments to events or effects, as follows:
[::timeouts.c/set a-timeout] ;; event
[::timeouts.c/clear a-timeout-id] ;; event
{::timeouts.c/set a-timeout} ;; effect
{::timeouts.c/clear a-timeout-id} ;; effect
[::intervals.c/set an-interval] ;; event
[::intervals.c/clear an-interval-id] ;; event
{::intervals.c/set an-interval} ;; effect
{::intervals.c/clear an-interval-id} ;; effect
The "current page" that changes alongside the URL (whether via hash-change or
History API) is so established on the web that we decided to make it available
by default. Add ::current.c/module
to the modules config map.
The "current view" is set in app-db under ::current.c/view
. It’s value is an
event keyword (controller namespaced), for example:
:blog.app.posts.controller/index
.
The "current view" is automatically set in app-db by an interceptor if not set
already from the event handler, if there exists a view corresponding to the
event, per the naming convention. This interceptor is added to all handlers that
have matching views; example: blog.app.posts.controller/index
→
blog.app.posts.view/index
.
There are two subscriptions:
-
(rf/subscribe [::current.c/view])
yields the event set. -
(rf/subscribe [::current.c/view-fn])
yields the view function that can be rendered in Reagent layouts.