dev / templates / app-cljs-rooms / client / src / __NAME__ / app / dom.cljs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
;; NODE BUILDING — the whole reason this scaffold has no `esc` function.
;;
;; Every string a rooms app renders is untrusted: a room label is typed by
;; whoever named the room, a display name by whoever joined, a note by whoever
;; posted it. So the demo screens ship with no `innerHTML` string templates and
;; nothing to escape: `txt` sets `textContent`, and there is no path here
;; through which a value can become markup. test/source-hygiene.test.mjs holds
;; that as an ABSOLUTE — its raw-HTML allowlist is EMPTY — so a fresh scaffold
;; starts green instead of starting by weakening its own gate.
;;
;; Keep it that way. If you catch yourself reaching for `innerHTML`, add a
;; builder here instead; the moment you genuinely need a budgeted exception,
;; the hygiene test says how to record one (a number that may only fall).
(ns __NAME__.app.dom
  (:require [ardegazu.rooms.js :as j]))

(defn el
  "A fresh element with a class."
  [tag cls]
  (let [node (js/document.createElement tag)]
    (when (j/truthy? cls) (set! (.-className node) cls))
    node))

(defn txt
  "A fresh element carrying `s` AS TEXT. `textContent`, never `innerHTML`: `s`
   is a label, a name or a note from another member as often as not."
  [tag cls s]
  (let [node (el tag cls)]
    (set! (.-textContent node) (js* "String(~{})" (j/nn s "")))
    node))

(defn set-text!
  "Replace one node's text. Same rule, same reason."
  [node s]
  (when (j/truthy? node)
    (set! (.-textContent ^js node) (js* "String(~{})" (j/nn s ""))))
  js/undefined)

(defn clear!
  "Empty a node. `textContent = \"\"` removes every child, and unlike
   `innerHTML = \"\"` it is not a markup sink at all."
  [node]
  (set! (.-textContent ^js node) "")
  js/undefined)

(defn add!
  "Append children to `parent`, skipping nils, and return `parent`."
  [parent & kids]
  (doseq [k kids] (when (some? k) (.appendChild ^js parent k)))
  parent)

(defn btn
  "A button. `kind` is the class; `on-tap` is wired here so no caller has to
   remember to. `type=\"button\"` unless told otherwise — a bare <button> inside
   a <form> submits it, which has silently sent more than one form in this
   suite. (The demo's submit buttons are built with `txt` for exactly that
   reason: inside a <form>, the default submit type is the point.)"
  [kind label on-tap]
  (let [b (txt "button" kind label)]
    (set! (.-type b) "button")
    (when (some? on-tap) (.addEventListener b "click" (fn [] (on-tap) js/undefined)))
    b))

(defn input
  "A text input with a placeholder and an optional maxlength."
  [cls placeholder maxlen]
  (let [i (el "input" cls)]
    (set! (.-type i) "text")
    (set! (.-autocomplete i) "off")
    (set! (.-placeholder i) (j/nn placeholder ""))
    (when (j/truthy? maxlen) (.setAttribute i "maxlength" (js* "String(~{})" maxlen)))
    i))

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/dev.git