refactor: use hiccup instead of string concatenation in todomvc
Replace hand-built HTML strings with hiccup vectors rendered by a minimal hiccup→HTML converter (example/todomvc/pocketbook/hiccup.cljs). Supports CSS-style selectors (:div.class#id), attribute maps, void elements, and nested children.
This commit is contained in:
68
example/todomvc/pocketbook/hiccup.cljs
Normal file
68
example/todomvc/pocketbook/hiccup.cljs
Normal file
@@ -0,0 +1,68 @@
|
||||
(ns pocketbook.hiccup
|
||||
"Minimal hiccup → HTML string renderer."
|
||||
(:require [clojure.string :as str]))
|
||||
|
||||
(def ^:private void-tags
|
||||
#{"area" "base" "br" "col" "embed" "hr" "img" "input"
|
||||
"link" "meta" "param" "source" "track" "wbr"})
|
||||
|
||||
(defn- esc [s]
|
||||
(-> (str s)
|
||||
(str/replace "&" "&")
|
||||
(str/replace "<" "<")
|
||||
(str/replace ">" ">")
|
||||
(str/replace "\"" """)))
|
||||
|
||||
(defn- render-attrs [m]
|
||||
(let [sb (js/Array.)]
|
||||
(doseq [[k v] m]
|
||||
(when (and (some? v) (not (false? v)))
|
||||
(.push sb " ")
|
||||
(.push sb (name k))
|
||||
(when-not (true? v)
|
||||
(.push sb "=\"")
|
||||
(.push sb (esc (str v)))
|
||||
(.push sb "\""))))
|
||||
(.join sb "")))
|
||||
|
||||
(defn- parse-tag
|
||||
"Parse :div.foo.bar#baz → [\"div\" \"foo bar\" \"baz\"]."
|
||||
[tag]
|
||||
(let [full (name tag)
|
||||
id (second (re-find #"#([^.#]+)" full))
|
||||
no-id (str/replace full #"#[^.#]+" "")
|
||||
parts (str/split no-id #"\.")
|
||||
tag-name (if (seq (first parts)) (first parts) "div")
|
||||
classes (str/join " " (rest parts))]
|
||||
[tag-name (when (seq classes) classes) id]))
|
||||
|
||||
(deftype RawHTML [s])
|
||||
|
||||
(defn raw
|
||||
"Wrap a string to be emitted without HTML escaping."
|
||||
[s]
|
||||
(RawHTML. s))
|
||||
|
||||
(defn html
|
||||
"Convert a hiccup form to an HTML string."
|
||||
[form]
|
||||
(cond
|
||||
(nil? form) ""
|
||||
(instance? RawHTML form) (.-s form)
|
||||
(string? form) (esc form)
|
||||
(number? form) (str form)
|
||||
(seq? form) (apply str (map html form))
|
||||
(vector? form)
|
||||
(let [[tag & rest] form
|
||||
[tag-name tag-classes tag-id] (parse-tag tag)
|
||||
[attrs children] (if (map? (first rest))
|
||||
[(first rest) (next rest)]
|
||||
[nil rest])
|
||||
attrs (cond-> (or attrs {})
|
||||
tag-id (assoc :id tag-id)
|
||||
tag-classes (update :class #(str tag-classes (when % (str " " %)))))
|
||||
inner (apply str (map html children))]
|
||||
(if (contains? void-tags tag-name)
|
||||
(str "<" tag-name (render-attrs attrs) " />")
|
||||
(str "<" tag-name (render-attrs attrs) ">" inner "</" tag-name ">")))
|
||||
:else (esc (str form))))
|
||||
Reference in New Issue
Block a user