3

I want to traverse a vector tree that represents hiccup data structures:

[:div {:class "special"} [:btn-grp '("Hello" "Hi")]] 

Then I want to dispatch on the keyword of the vector, if a multimethod has been defined for the keyword, then it would return another set of vectors, which will replace the original tag.

For example, the above structure will be transformed to:

[:div {:class "special"} [:div [:button "Hello"] [:button "Hi"]]]

The custom multimethod will receive the list ("hello" "hi") as parameters. It will then return the div containing the buttons.

How do I write a function that traverses the vector and dispatches on the keyword with everything else in the form as parameter, and then replaces the current form with the returned form ?

Matt Fenwick
  • 48,199
  • 22
  • 128
  • 192
murtaza52
  • 46,887
  • 28
  • 84
  • 120

1 Answers1

1
(ns customtags
  (:require [clojure.walk :as walk]))

(def customtags (atom {}))

(defn add-custom-tag [tag f]
  (swap! customtags assoc tag f))

(defn try-transform [[tag & params :as coll]]
  (if-let [f (get @customtags tag)]
    (apply f params)
    coll))

(defmacro defcustomtag [tag params & body]
  `(add-custom-tag ~tag (fn ~params ~@body)))

(defn apply-custom-tags [coll]
  (walk/prewalk
    (fn [x]
      (if (vector? x)
        (try-transform x)
        x)) coll))

Using it:

(require '[customtags :as ct])
(ct/defcustomtag :btn-grp [& coll] (into [:div] (map (fn [x] [:button x]) coll)))
(ct/defcustomtag :button [name] [:input {:type "button" :id name}])

(def data [:div {:class "special"} [:btn-grp "Hello" "Hi"]])

(ct/apply-custom-tags data)
[:div {:class "special"} [:div [:input {:type "button", :id "Hello"}] [:input {:type "button", :id "Hi"}]]]
DanLebrero
  • 8,545
  • 1
  • 29
  • 30
  • dAni, Thanks for your reply. However why do you say there is no value in representing the whole tree as a datastructure. Hiccup brings value to the table precisely because lets you manipulate the HTML tree as a vector tree, wouldnt it help if custom tags can also be presented likewise? I am asking this in the spirit of learning, so please let me know of your opinion. – murtaza52 May 15 '12 at 13:16
  • `customtags` is just an awkward multimethod, and `defcustomtag` is `defmulti`. Just define a multimethod that dispatches on the first element of a vector, and a `:default` method to leave it alone. Then use `prewalk` to do the walking. – amalloy May 23 '12 at 07:26
  • I tried the multimethod approach first but I found that writing the defmethod functions was more cumbersome than the macro approach as you need the whole vector for the :default case, which means that each defmethod has to deal with the whole vector. Of course you could write a macro to write the defmethod that will handle this, but I don't think that will be more clean or straightforward than the proposed code. – DanLebrero May 23 '12 at 14:28