Clojure: partly change an attribute value in Enlive - clojure

I have this test.html file that contains:
<div class="clj-test class1 class2 col-sm-4 class3">content</div>
A want to define a template that changes only a part of an html attr value:
(deftemplate test "public/templates/test.html" []
[:.clj-test] (enlive/set-attr :class (partly-change-attr #"col*" "col-sm-8")))
This would render:
...
<div class="clj-test class1 class2 col-sm-8 class3">content</div>
...
Thanks for your help!

Just found the update-attr fn suggested by Christophe Grand in another thread:
(defn update-attr [attr f & args]
(fn [node] (apply update-in node [:attrs attr] f args)))
Pretty cool! We can use it directly :
(enlive/deftemplate test-template "templates/test.html" []
[:.clj-test] (update-attr :class clojure.string/replace #"col-.*?(?=\s)" "col-sm-8"))
Or build from it a more specific fn:
(defn replace-attr [attr pattern s]
(update-attr attr clojure.string/replace pattern s))
(enlive/deftemplate test-template "templates/test.html" []
[:.clj-test] (replace-attr :class #"col-.*?(?=\s)" "col-sm-8"))

I don't know if this works for you, but if you know the class you are removing exactly you can do this.
(enlive/deftemplate test-template "templates/test.html" []
[:.clj-test] (enlive/remove-class "col-sm-4")
[:.clj-test] (enlive/add-class "col-sm-8"))
In the repl:
(apply str (test-template))
=> "<html><body><div class=\"class1 clj-test class2 col-sm-8 class3\">content</div></body></html>"

Related

Passing variables and metadata

I wrote a short function for debugging:
(defn printvar
"Print information about given variables in `name : value` pairs"
[& vars]
(dorun (map #(println (name %) ":" (eval %)) vars)))
Then I tried to test it:
(defn -main [arg1 arg2]
(def moustache true) (def answer 42) (def ocelots-are "awesome!")
(printvar 'moustache 'answer 'ocelots-are)
(printvar 'arg1 'arg2))
But ran into some really confusing behaviour:
$ lein repl
> (-main "one" "two")
# moustache : true
# answer : 42
# ocelots-are : awesome!
# CompilerException java.lang.RuntimeException: Unable to resolve symbol: arg1 in this context, compiling:(/tmp/form-init4449285856851838419.clj:1:1)
$ lein run "one" "two"
# Exception in thread "main" java.lang.RuntimeException: Unable to resolve symbol: moustache in this context, compiling:(/tmp/form-init4557344131005109247.clj:1:113)
Experimenting a bit more, I discovered this:
(defn -main [arg1 arg2]
(meta #'arg1))
# Exception in thread "main" java.lang.RuntimeException: Unable to resolve var: arg1 in this context, compiling:(dict_compress/core.clj:9:11)
(defn -main [arg1 arg2]
(def arg1 arg1)
(meta #'arg1))
# {:ns #<Namespace dict-compress.core>, :name arg1, :file dict_compress/core.clj, :column 2, :line 10}
Now I'm totally confused.
What exactly are you passing when you do (f 'var) and (f var)?
Why are there different behaviours when run from the REPL versus directly?
What's the difference between a received argument versus a defined variable?
How can I fix my code?
Am I going about debugging the wrong way?
Inside printvar the def'ed vars moustache answer and ocelots-are are correctly printed because def defines them as "globals".
Meaning there is a moustache var that the printvar function can "see".
Think about it this way, this works:
(def moustache 43)
(defn printvar []
(println moustache)
(defn main [arg1]
(printvar))
This doesn't work:
(defn printvar []
(println arg1))
(defn main [arg1]
(printvar))
Which is exactly what you're doing, passing the parameter name to eval does nothing for the parameter scope (printvar won't be able to see it).
A couple of issues with your code:
You shouldn't be defing inside a function, local bindings are defined with let
If you want to eval you need to consider scope of what you're evaling.
Just to elaborate on #Guillermo's comment, here is a macro that does the printing of any variable, locally or globally bound.
(defmacro printvar
([])
([v & more]
`(let [v# ~v]
(println '~v "=" v#)
(when (seq '~more)
(printvar ~#more)))))
With this you can try the sequence :
user> (def glob-var "foo")
#'user/glob-var
user> (defn -main [loc1 loc2]
(printvar glob-var loc1 loc2))
#'user/-main
user> (-main "bar" 42)
glob-var = foo
loc1 = bar
loc2 = 42
nil
user>

What type of parameters should I pass to this method

I'm new to Clojure, and I am using ring.velocity to develop a webapp.
Here is my ring.velocity.core/render method:
(defn render
[tname & kvs]
"Render a template to string with vars:
(render :name \"dennis\" :age 29)
:name and :age are the variables in template. "
(let [kvs (apply hash-map kvs)]
(render-template *velocity-render tname kvs)))
For this simple example, it works fine:
(velocity/render "test.vm" :name "nile")
But sometimes, we can't hard code the key value pairs. A common way:
(defn get-data [] {:key "value"}) ;; define a fn get-data dynamic.
(velocity/render "test.vm" (get-data));; **this go wrong** because in render fn , called (apply hash-map kvs)
Has the error:
No value supplied for key: ....
It looks like it is treated as if it was a single value. I've changed the type to [], {}, and (), but each of these fails.
My question is: What does & kvs in clojure mean? How can I dynamically create it and pass it to method?
ADD A Simple Test
(defn params-test [a & kvls]
(println (apply hash-map kvls)))
(defn get-data []
[:a "A"])
(defn test[]
(params-test (get-data))
Result
No value supplied for key:((:a "A"))
The problem here is that you're trying to create a hash-map from a single list argument instead of list of arguments.
Use
(apply hash-map kvls)
instead of
(hash-map kvls)
In your original question you can try to use apply with partial
(apply (partial velocity/render "test.vm") (get-data))

Best way to update several values in hashmap?

I have a hash map like this:
{:key1 "aaa bbb ccc" :key2 "ddd eee" :key3 "fff ggg" :do-not-split "abcdefg hijk"}
And I'd like to split some of the strings to get vectors:
; expected result
{:key1 ["aaa" "bbb" "ccc"] :key2 ["ddd" "eee"] :key3 ["fff" "ggg"] :do-not-split "abcdefg hijk"}
I use update-in three times now like the following but it seems ugly.
(-> my-hash (update-in [:key1] #(split % #"\s"))
(update-in [:key2] #(split % #"\s"))
(update-in [:key3] #(split % #"\s")))
I hope there's sth like (update-all my-hash [:key1 :key2 :key3] fn)
You can use reduce:
user=> (def my-hash {:key1 "aaa bbb ccc" :key2 "ddd eee" :key3 "fff ggg"})
#'user/my-hash
user=> (defn split-it [s] (clojure.string/split s #"\s"))
#'user/split-it
user=> (reduce #(update-in %1 [%2] split-it) my-hash [:key1 :key2 :key3])
{:key3 ["fff" "ggg"], :key2 ["ddd" "eee"], :key1 ["aaa" "bbb" "ccc"]}
Just map the values based on a function that makes the decision about whether to split or not.
user=> (def x {:key1 "aaa bbb ccc"
:key2 "ddd eee"
:key3 "fff ggg"
:do-not-split "abcdefg hijk"})
#'user/x
user=> (defn split-some [predicate [key value]]
(if (predicate key)
[key (str/split value #" ")]
[key value]))
#'user/split-some
user=> (into {} (map #(split-some #{:key1 :key2 :key3} %) x))
{:do-not-split "abcdefg hijk", :key3 ["fff" "ggg"], :key2 ["ddd" "eee"], :key1 ["aaa" "bbb" "ccc"]}
This is a different way of approaching the problem.
Think about it for a second: if your string were in a list, how would you approach it?
The answer is that you would use map to get a list of vectors:
(map #(split % #"\s") list-of-strings)
If you think harder you would arrive at the conclusion that what you really want is to map a function over the values of a map. Obviously map doesn't work here as it works for sequences only.
However, is there a generic version of map? It turns out there is! It's called fmap and comes from the concept of functors which you can ignore for now. This is how you would use it:
(fmap my-hash #(split % #"\s"))
See how the intent is a lot clearer now?
The only drawback is that fmap isn't a core function but it is available through the algo.generic library.
Of course if including a new library feels like too much at this stage, you can always steel the source code - and attribute to its author - from the library itself in this link:
(into (empty my-hash) (for [[k v] my-hash] [k (your-function-here v)]))

Append to an attribute in Enlive

Is it possible to append a value to an attribute using enlive?
example: I have this
edit
and would like this
edit
I am currently doing this:
(html/defsnippet foo "views/foo.html" [:#main]
[ctxt]
[:a] (html/set-attr :href (str "/item/edit/" (ctxt :id))))
But I would prefer not to embed the URL into my code, by just appending the id to the existing URL
(html/defsnippet foo "views/foo.html" [:#main]
[ctxt]
[:a#href] (html/append (ctxt :id)))
#ddk answer is spot on but you may prefer a more generic way to solve the problem
(defn update-attr [attr f & args]
(fn [node]
(apply update-in node [:attrs attr] f args))))
and then
(update-attr :href str "123")
You could always write your own append-attr in the same vein as set-attr. Here is my attempt
(defn append-attr
[& kvs]
(fn [node]
(let [in-map (apply array-map kvs)
old-attrs (:attrs node {})
new-attrs (into {} (for [[k v] old-attrs]
[k (str v (get in-map k))]))]
(assoc node :attrs new-attrs))))
Which gives the following, when appending "/bar" to href, on enlive's representation of A link
((append-attr :href "/bar")
{:tag :a, :attrs {:href "/foo"}, :content "A link"})
;=> {:tag :a, :attrs {:href "/foo/bar"}, :content "A link"}

traversing a vector tree

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 ?
(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"}]]]