Liking cljdoc? Tell your friends :D

remuda.render

Boundary marking and fragment extraction.

Resolves three problems that declaring boundaries separately created, all with one mechanism: :render runs once and marks its own boundaries, and the engine extracts fragments from the resulting tree.

  1. :render-at duplicated :render. The slice had to maintain two copies of the same markup — including id construction — which had to agree or patches silently targeted nothing. Gone: there is one render function.
  2. :boundaries had to be declared separately from the render that implements them, so the two could drift. Gone: boundaries are discovered from the render output.
  3. Ids were derived by the engine (component-id + path) while :render emitted whatever the author wrote. They disagreed, and every patch targeted a non-existent element with no error. Gone: the id is read from the rendered element, so the author picks it and the engine cannot disagree.

Usage — wrap each patchable region in boundary:

(defn render [{:keys [count items] :as view}]
  (boundary []
    [:div {:id "root"}
     (boundary [:count] [:strong {:id "count"} count])
     [:ul (for [i items]
            (boundary [:items (:id i)]
              [:li {:id (str "row-" (:id i))} (:text i)]))]]))

The path given to boundary is a view path — the same vocabulary the diff engine emits — which is what lets a changed path resolve to the element that owns it. Keyed collection items use their key, not their index, matching the diff engine's addressing.

Marking is done with metadata, for the same reason keyed collections are (remuda.diff): metadata does not participate in =, so it cannot perturb value comparison.

What the tree has to look like

Nothing here is HTML-specific — the examples use hiccup because that is what the datastar binding renders, but the tag vocabulary is irrelevant and [:panel {...} [:dial {...} 7]] extracts exactly as well as a <div>. Two structural requirements are real, though, and neither is checked:

  1. extract descends through vectors and seqs only. A tree whose children live under map values{:kind :window :kids [...]} — yields only the root boundary and silently loses every nested one. There is no error; patches for those paths simply never resolve.
  2. A node's id is read as (:id (second node)). So a marked node should be a vector whose second element is an attribute map. Without one, :id is nil and the binding falls back to deriving a target from the path, which is the drift problem point 3 above exists to remove.

Both are satisfied by anything hiccup-shaped, which is why they have never bitten. They are written down because "remuda does not know about HTML" is true and "remuda accepts any tree" is not.

Boundary marking and fragment extraction.

Resolves three problems that declaring boundaries separately created, all with
one mechanism: **`:render` runs once and marks its own boundaries, and the engine
extracts fragments from the resulting tree.**

1. `:render-at` duplicated `:render`. The slice had to maintain two copies of
   the same markup — including id construction — which had to agree or patches
   silently targeted nothing. Gone: there is one render function.
2. `:boundaries` had to be declared separately from the render that implements
   them, so the two could drift. Gone: boundaries are discovered from the
   render output.
3. Ids were *derived* by the engine (`component-id + path`) while `:render`
   emitted whatever the author wrote. They disagreed, and every patch targeted
   a non-existent element with no error. Gone: the id is read from the
   rendered element, so the author picks it and the engine cannot disagree.

Usage — wrap each patchable region in `boundary`:

    (defn render [{:keys [count items] :as view}]
      (boundary []
        [:div {:id "root"}
         (boundary [:count] [:strong {:id "count"} count])
         [:ul (for [i items]
                (boundary [:items (:id i)]
                  [:li {:id (str "row-" (:id i))} (:text i)]))]]))

The path given to `boundary` is a *view* path — the same vocabulary the diff
engine emits — which is what lets a changed path resolve to the element that
owns it. Keyed collection items use their key, not their index, matching the
diff engine's addressing.

Marking is done with metadata, for the same reason keyed collections are
(`remuda.diff`): metadata does not participate in `=`, so it cannot
perturb value comparison.

## What the tree has to look like

Nothing here is HTML-specific — the examples use hiccup because that is what the
datastar binding renders, but the tag vocabulary is irrelevant and `[:panel {...}
[:dial {...} 7]]` extracts exactly as well as a `<div>`. Two structural
requirements are real, though, and neither is checked:

1. **`extract` descends through vectors and seqs only.** A tree whose children
   live under map *values* — `{:kind :window :kids [...]}` — yields only the root
   boundary and silently loses every nested one. There is no error; patches for
   those paths simply never resolve.
2. **A node's id is read as `(:id (second node))`.** So a marked node should be a
   vector whose second element is an attribute map. Without one, `:id` is nil and
   the binding falls back to deriving a target from the path, which is the
   drift problem point 3 above exists to remove.

Both are satisfied by anything hiccup-shaped, which is why they have never bitten.
They are written down because "remuda does not know about HTML" is true and
"remuda accepts any tree" is not.
raw docstring

boundaryclj

(boundary path node)

Marks node as the patchable region owning view path path.

Returns node unchanged apart from metadata, so a marked element composes anywhere an unmarked one would.

Marks `node` as the patchable region owning view path `path`.

Returns `node` unchanged apart from metadata, so a marked element composes
anywhere an unmarked one would.
sourceraw docstring

boundary-pathclj

(boundary-path x)
source

boundary?clj

(boundary? x)
source

extractclj

(extract tree)

Walks a rendered tree, returning {path {:node ... :id ...}} for every marked boundary.

Nested boundaries are all collected: an item inside a marked list is found along with the list and the root, so a patch can target whichever granularity the diff asked for.

Descends through vectors and seqs only — see the namespace docstring. A tree nesting its children under map values loses every boundary below the root, with no error.

Walks a rendered `tree`, returning `{path {:node ... :id ...}}` for every
marked boundary.

Nested boundaries are all collected: an item inside a marked list is found
along with the list and the root, so a patch can target whichever granularity
the diff asked for.

Descends through **vectors and seqs only** — see the namespace docstring. A tree
nesting its children under map values loses every boundary below the root, with
no error.
sourceraw docstring

resolve-boundaryclj

(resolve-boundary boundary-paths path)

Nearest ancestor of path present in boundary-paths.

View paths are finer grained than DOM elements — [:items 2 :text] addresses a text node — so a changed path must resolve up to the element that owns it. Falls back to [], the component root.

:* in a declared path still matches any key, which keeps hand-declared boundary sets working; extracted boundaries name concrete keys and need no wildcard.

Nearest ancestor of `path` present in `boundary-paths`.

View paths are finer grained than DOM elements — `[:items 2 :text]` addresses a
text node — so a changed path must resolve up to the element that owns it.
Falls back to `[]`, the component root.

`:*` in a declared path still matches any key, which keeps hand-declared
boundary sets working; extracted boundaries name concrete keys and need no
wildcard.
sourceraw docstring

validateclj

(validate boundaries)

Returns a seq of problems with an extracted boundary map, empty if sound.

Catches the failure phase 2 hit silently: a boundary with no :id yields a fragment nothing can target, so a patch for it disappears with no error. This turns that into something a test or a dev-mode check can assert on.

Returns a seq of problems with an extracted boundary map, empty if sound.

Catches the failure phase 2 hit silently: a boundary with no `:id` yields a
fragment nothing can target, so a patch for it disappears with no error. This
turns that into something a test or a dev-mode check can assert on.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close