summaryrefslogtreecommitdiff
path: root/src/dompa/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'src/dompa/nodes')
-rw-r--r--src/dompa/nodes/shared.cljc95
1 files changed, 95 insertions, 0 deletions
diff --git a/src/dompa/nodes/shared.cljc b/src/dompa/nodes/shared.cljc
new file mode 100644
index 0000000..b876048
--- /dev/null
+++ b/src/dompa/nodes/shared.cljc
@@ -0,0 +1,95 @@
+(ns dompa.nodes.shared)
+
+(def ^:private default-void-nodes
+ #{:!doctype :area :base :br :col :embed :hr :img :input
+ :link :meta :source :track :wbr})
+
+(defn- node-attrs-reducer [attrs k v]
+ (let [attr-name (-> k name)]
+ (if (true? v)
+ (str attrs " " attr-name)
+ (str attrs " " attr-name "=\"" v "\""))))
+
+(defn- node->html-reducer-fn
+ "Returns a reducer function with the initial state of `void-nodes`
+ set and `nodes->html-fn` function for recursive HTML creation."
+ [void-nodes nodes->html-fn]
+ (fn [html node]
+ (cond
+ ; fragment nodes expand their children to replace themselves
+ (and (not (nil? node))
+ (= (:node/name node) :<>))
+ (str html (nodes->html-fn (:node/children node)))
+
+ ; otherwise business as usual
+ (not (nil? node))
+ (let [node-name (-> node :node/name name)
+ node-attrs (reduce-kv node-attrs-reducer "" (-> node :node/attrs))]
+ (cond
+ (= (-> node :node/name) :dompa/text)
+ (str html (-> node :node/value))
+
+ (contains? void-nodes (-> node :node/name))
+ (str html "<" node-name node-attrs">")
+
+ :else
+ (let [value (nodes->html-fn (-> node :node/children))]
+ (str html "<" node-name node-attrs ">" value "</" node-name ">")))))))
+
+(defn traverse
+ "Recursively traverses given tree of `nodes` with a `traverser-fn`
+ that gets a single node passed to it and returns a new updated tree.
+ If the traverses function returns `nil`, the node will be removed.
+ In any other case the node will be replaced. If you wish to keep
+ a node unchanged, just return it as-is."
+ [nodes traverser-fn]
+ (-> (fn [updated-nodes node]
+ (if-let [updated-node (traverser-fn node)]
+ (let [children (traverse (-> updated-node :node/children) traverser-fn)]
+ (conj updated-nodes (assoc updated-node :node/children children)))
+ updated-nodes))
+ (reduce [] nodes)))
+
+(defn ->html
+ "Transform a vector of `nodes` into an HTML string.
+
+ Options:
+ - `void-nodes` - A set of node names that are self-closing, defaults to:
+ - `:!doctype`
+ - `:area`
+ - `:base`
+ - `:br`
+ - `:col`
+ - `:embed`
+ - `:hr`
+ - `:img`
+ - `:input`
+ - `:link`
+ - `:meta`
+ - `:source`
+ - `:track`
+ - `:wbr`
+ "
+ ([nodes]
+ (->html nodes {:void-nodes default-void-nodes}))
+ ([nodes {:keys [void-nodes]}]
+ (-> (node->html-reducer-fn void-nodes ->html)
+ (reduce "" nodes))))
+
+(defmacro defhtml
+ "Creates a new function with `name` that outputs HTML.
+
+ Example usage:
+
+ ```clojure
+ (defhtml about-page [who]
+ ($ :div
+ ($ \"hello \" who)))
+
+ (about-page \"world\")
+ ```
+ "
+ [name & args-and-elements]
+ (let [[args & elements] args-and-elements]
+ `(defn ~name ~args
+ (->html [~@elements]))))