Building nested vector from map in Clojure - clojure

I have a set of URLs, and some of the URLs have indirect references (as a vector). Any URL that does not have an indirect reference just has nil. I'm starting out with the following test map:
{"URL 1" nil,
"URL 2" ["indirect 1" "indirect 2"]}
I'm using hiccup to build an HTML report, so I want this output:
[:div "Imports: "
[:ul
[:li "URL 1"]
[:li "URL 2"]
[:ul
[:li "indirect 1"]
[:li "indirect 2"]
[:li "indirect 3"]]]]
I'm running into some problems returning nil when the URL does not have an indirect reference. My current code looks like this:
(defn list-imports
[imports]
(if-not (nil? imports)
[:div "Imports: "
[:ul
(for [direct (keys imports)]
[[:li direct]
(if-let [indirects (get imports direct)]
[:ul
(for [indirect indirects]
[:li indirect])]
[:span])])]]
[:div "Imports: none" [:br] [:br]]))
The problem is, it's returning this...
[:div
"Imports: "
[:ul
([[:li "URL 1"] [:span]]
[[:li "URL 2"] [:ul ([:li "indirect 1"] [:li "indirect 2"])]])]]
I had to add in a [:span] tag as the case for when the indirect imports are nil, which I don't really want there... but otherwise, it puts in nil there.
The other problem is that it ends up enclosed in () and an extra vector, because I'm doing multiple things within the for statement. When I try to convert it with hiccup, I get [:li "URL 1"] is not a valid element name.

This can be a tricky aspect of building hiccup tags. Sometimes it helps to break the problem into smaller pieces.
(defn list-indirects
[indirects]
(when (seq indirects)
[(into [:ul] (mapv (fn [i] [:li i]) indirects))]))
(defn list-imports
[imports]
(if (some? imports)
[:div "Imports: "
(into [:ul]
(for [[url indirects] imports]
(into [:li url] (list-indirects indirects))))]
[:div "Imports: none" [:br] [:br]]))
These functions should give you the desired output.
(list-imports {"URL 1" nil
"URL 2" ["indirect 1" "indirect 2"]})
=>
[:div "Imports: "
[:ul
[:li "URL 1"]
[:li "URL 2"
[:ul
[:li "indirect 1"]
[:li "indirect 2"]]]]]
This output is slightly different from your expected output, but I think it's closer to what you'd actually want i.e. the [:li "URL 2"] tag in your example should contain the :ul of "indirects" to be valid HTML.
Another thing to be wary of, if the order of these items is important, is that maps may not be ordered in the way you expect, especially once you have over a certain number of keys. When you traverse the map to build hiccup, it's possible that "URL 2" could come before "URL 1". You could workaround this by using a vector of tuples, or maybe a sorted map.

Related

Clojure sending data with nested vector to function and use the items

First, I'm sorry if my question is something simple or not clear. I'm almost new to Clojure and I need help.
I want to create a function to generate some HTML link for a menu. This is what I have now and it works fin:
(defn test-function [title-id title-name]
[:span {:onclick #(.scrollIntoView (.getElementById js/document title-id))
:style {:color :red} title-name])
[:li (test-function "title-1-id" "title-1-name")]
[:li (test-function "title-2-id" "title-2-name")]
But as I have many li tags, I want to have something like this to send to the function, and the function can generate the links for me, exactly like what the current code do for me. But I didn't find how to do that yet, and have no idea if I should use vector or something else.
[["title-1-id" "title-1-name"] ["title-2-id" "title-2-name"]]
Thank you in advance.
You can use for, then destructure each element into id and name (I just renamed name to li-name to avoid shadowing function name) and wrap whole for in [:ul ... ]:
[:ul
(for [[li-id li-name] [["id1" "Scroll to red"] ["id2" "Scroll to pink"]]]
[:li {:style {:cursor :pointer
:color :red}
:on-click #(.scrollIntoView (.getElementById js/document li-id))}
li-name])]
[:div
[:div [:img#id1 {:src "" :style {:width 600 :height 1000 :background "red"}}]]
[:div [:img#id2 {:src "" :style {:width 600 :height 1000 :background "pink"}}]]
[:div [:img#id3 {:src "" :style {:width 600 :height 1000 :background "yellow"}}]]]
Note that you even don't need Javascript to scroll to target element- [:a ... ] can point to the object on the same page:
[:ul
(for [[li-id li-name] [["id1" "Scroll to red"] ["id2" "Scroll to pink"]]]
[:a {:href (str "#" li-id)
:style {:color :red
:cursor :pointer
:text-decoration :none}}
[:li li-name]])]
[:div
[:div [:img#id1 {:src "" :style {:width 600 :height 1000 :background "red"}}]]
[:div [:img#id2 {:src "" :style {:width 600 :height 1000 :background "pink"}}]]
[:div [:img#id3 {:src "" :style {:width 600 :height 1000 :background "yellow"}}]]]

How do I access individual fields of a form in Clojure?

I'm building my very first web app, and I am having a hard time accessing individual fields of a form when the user submits the form. Here's what I have:
(defroutes app
(GET "/" [] homepage)
(POST "/city" request display-city)
(route/resources "/")
(route/not-found "Not Found"))
(defn display-city [request]
(html5
[:div {:class "the-city"}
[:h2 "ALL ABOUT YOUR CITY"]
[:ul
[:li "Your city is " (str request) "! That's all"]]]))
;; and here's the hiccup form:
[:form {:action "/city" :method "post"}
(anti-forgery-field)
[:p "Enter your home address"]
[:div
[:label {:for "street-field"} "Street:"]
[:input {:id "street-field"
:type "text"
:name "street"}]]
[:div
[:label {:for "city-field"} "City:"]
[:input {:id "city-field"
:type "text"
:name "city"}]
[:div
[:label {:for "state-field"} "State:"]
[:input {:id "state-field"
:type "text"
:name "state"}]
[:label {:for "zip-field"} "ZIP:"]
[:input {:id "zip-field"
:type "text"
:name "zip"
:size "10"}]]
[:div.button
[:button {:type "submit"} "Submit"]]]])
;; When I run the code above, I can see the entire form that's submitted via (str request), in what looks to be a Clojure map. But I can't figure out how to extract individual "key/vals" (from that address form, I'd like to extract the city), or how to store those results in a way that I can use it. Any ideas?
This is a super basic /city page that I am trying to get running to understand how things work before building bigger things. Thanks!
In your request map, there should be a key :form-params with a map of key/value pairs that were POSTed. Here's how you could get an individual value out:
(get-in request [:form-params :city])
Or you could destructure :form-params map to bind many values at once:
(let [{:keys [city state zip]} (:form-params request)]
(format "%s, %s %s" city state zip))

Interactive list in Reagent

I want to create a list of html elements (which include the results of a query) which are hidden by default but the user can toggle that state. I have tried a couple different ways below as toy examples but can't get either to work.
This code correctly creates three buttons, which were alter the exps state correctly but which do not ever hide content.
(:require [reagent.core :as r] )
(def exps (r/atom [true true true]))
(defn like-component []
[:div
(for [ [i r] (map-indexed vector ["A" "B" "C"])]
[:div
[:button {:on-click #(swap! exps update-in [i] not)}]
(when (nth #exps i)
[:pre (str i r)])])])
(r/render [like-component]
(js/document.getElementById "app"))
On the other hand, the code below will create only one element but it works correctly.
(defn expandable-view [e bool]
(let [expanded (r/atom bool)]
(fn []
[:li
[:div.expandable
[:div.header {:on-click #(swap! expanded not)}
"Click me to expand and collapse"]
(if #expanded
[:div.body (allow-html :pre e)])]])))
(defn like-component []
[:ul
(vec
(for [ e ["A" "B" "C"]]
(expandable-view e true ))) ])
(r/render [like-component]
(js/document.getElementById "app"))
Edit: Possibly related:
https://github.com/reagent-project/reagent/wiki/Beware-Event-Handlers-Returning-False
for is lazy, so reagent can't tell you're dereferencing exps in the first code snippet.
We can workaround it by explicitly dereferencing atoms.
(defn like-component []
(apply str #exps) ;; because #exps is a vector, and reagent
;; treat vectors as hiccup component
;; we can't just put `#exps` here.
[:div
(for [ [i r] (map-indexed vector ["A" "B" "C"])]
[:div
[:button {:on-click #(swap! exps update-in [i] not)}]
(when (nth #exps i)
[:pre (str i r)])])])
Or just wrap the lazy sequence in doall.
(defn like-component []
[:div
(doall (for [ [i r] (map-indexed vector ["A" "B" "C"])]
[:div
[:button {:on-click #(swap! exps update-in [i] not)}]
(when (nth #exps i)
[:pre (str i r)])]))])
FYI, related discussions.
any idea why the second block I posted only creates one element?
Vectors are special citizens for Reagent, they're treated as hiccup/React components.
For a working example
(defn like-component []
[:ul
(doall
(for [ e ["A" "B" "C"]]
[expandable-view e true]))])
Also notice that we are using [expandable-view e true] to properly construct a reagent component.
For more informations, I'd strongly suggest reading Using [] instead of () and Creating Reagent Components.
I achieved this type of behaviour by using bootstrap. I have a list of records in a state atom which are all hash maps. I add a :visible key to each map which is used to set the appropriate bootstrap classes on the record. There is a function which toggles the :visible setting in the has map. The component renders the record with a button which toggles visibility by changing the :visible value in the map, causing the component to re-render.
(defn toggle-visibility [k h]
(let [new-v (if (= "show" (:visible h))
"hidden"
"show")]
(state/set-value-in! [(state/this-page) :host-list k :visible] new-v)))
(defn host-component [k]
(let [host (state/value-in [(state/this-page) :host-list k])]
^{:key k} [:div.panel.panel-default
[:div {:class "panel-heading show"}
[:div {:class (condp = (:status host)
"Active" "text-success"
"Inactive" "text-info"
"Unknown" "text-warning"
:else "text-danger")}
[:button {:type "button" :class "btn btn-default"
:aria-label "Expand"
:on-click #(toggle-visibility k host)}
[:span {:class (str "glyphicon "
(if (= "show" (:visible host))
"glyphicon-minus"
"glyphicon-plus"))}]]
[:strong " IPv4 Address: "] (:ipv4 host)
[:strong " Hostname: "] (:hostname host)
[:div.pull-right (str "Host ID: " (:host-id host))]]]
[:div {:class (str "panel-body " (:visible host))}
[:ul.list-group
[:li.list-group-item
[:strong "Host Status: "] (:status host)]
[:li.list-group-item
[:strong "MAC Address: "] (:mac host)]
[:li.list-group-item
[:strong "IPv6 Address: "] (:ipv6 host)]
[:li.list-group-item
[:strong "Operating System: "] (:os host)]
[:li.list-group-item
[:strong "DHCP Client: "] (:dhcp host)
[:strong " DNS Entry: "] (:dns host)
[:strong " Revers DNS Entry: "] (:reverse-dns host)]
[:li.list-group-item
[:strong "Host Type: "] (:host-type host)]
[:li.list-group-item
[:strong "Network Group: "]
(str (:network-group host) " / " (:subgroup-name host))]
[:li.list-group-item
[:strong "Managed By: "] (:management-group host)]
[:li.list-group-item
[:strong "Creation Date: "] (:created-dt host)]
[:li.list-group-item
[:strong "Last Modified Date: "] (:last-modified-dt host)]
[:li.list-group-item
[:strong "Last Seen Date: "] (:last-seen-dt host)]]]]))
Essentially, letting bootstrap handle the showing/hiding of the content, leaving the code to just toggle the visible/invisible state. The full code is on my github page at theophilusx/Arcis

How to repeat a list of items from a vector in hiccup?

If I have a vector name-lst as ["John" "Mary" "Watson" "James"],
and I want to diplay them as list items, how do I do that using hiccup ?
something like
[:ul
(for [name name-list]
[:li name])]
will return a list of [:li ] in between [:ul ] instead of just repeating.
There must be something better. I am relatively new to hiccup, I searched but couldn't find anything.
Once you feed the data structure to Hiccup you should get the expected result:
(require '[hiccup.core :refer [html]])
(def names
["John" "Mary" "Watson" "James"])
(html [:ul
(for [name names]
[:li name])])
;=> "<ul><li>John</li><li>Mary</li><li>Watson</li><li>James</li></ul>"

Composing templates with Hiccup and Compojure

I'm relatively new to Clojure and Compojure web development. The first issue that I've noticed in the toy example that I'm building is that of HTML templating. I'd like to have support for something like partials in Rails, or the templating framework that Django uses.
Currently I have:
(defn index-page []
(html5
[:head
[:title "Home | Compojure Docs"]
(include-css "/css/bootstrap.min.css")
(include-css "/css/bootstrap-responsive.min.css")]
[:body
[:div {:class "container-fluid"}
[:div {:class "row-fluid"}
[:div {:class "span2 menu"}]
[:div {:class "span10 content"}
[:h1 "Compojure Docs"]
[:ul
[:li
[:a {:href "/getting-started"} "Getting Started"]]
[:li
[:a {:href "/routes-in-detail"} "Routes in Detail"]]
[:li
[:a {:href "/destructuring-syntax"} "Destructuring Syntax"]]
[:li
[:a {:href "/nesting-routes"} "Nesting Routes"]]
[:li
[:a {:href "/api-documentation"} "API Documentation"]]
[:li
[:a {:href "/paas-platforms"} "PaaS Platforms"]]
[:li
[:a {:href "/example-project"} "Example Project"]]
[:li
[:a {:href "/example-project-on-cloudbees"} "Example Project on CloudBees"]]
[:li
[:a {:href "/interactive-development-with-ring"} "Interactive Development with Ring"]]
[:li
[:a {:href "/emacs-indentation"} "Emacs Indentation"]]
[:li
[:a {:href "/sessions"} "Sessions"]]
[:li
[:a {:href "/common-problems"} "Common Problems"]]]
(include-js "/js/jquery-1.9.1.min.js")
(include-js "/js/bootstrap.min.js")]]]]))
(defn routes-in-detail []
(html5
[:head
[:title "Routes in Detail | Compojure Docs"]
(include-css "/css/style.css")]
[:body
[:h1 "Routes in Detail"]]))
Is there a good way for me not to repeat code? I'd like the stuff in the HEAD tag to be in it's own template file or function, and then be able to include it as I go. For instance, I'd like to include it in the 'routes-in-detail' function. I've looked at Enlive, but I'm not sure how to use that with Hiccup. Any thoughts on best practices here would be appreciated.
You can pull parts of the markup out into separate vars:
(def head
[:head
[:title "Home | Compojure Docs"]
(include-css "/css/bootstrap.min.css")
... ])
(defn routes-in-detail []
(html5
head
[:body
... ]))
If you need your snippet/partial to take parameters, you can make it into a function instead, for example:
(defn head [title]
[:head
[:title title]
(include-css "/css/bootstrap.min.css")
... ])
(defn routes-in-detail []
(html5
(head "Routes in detail")
... ))
Sometimes you'll want your "snippet" to consist of multiple top-level elements rather than a single one. In that case you can wrap them in a list - hiccup will expand it inline:
(defn head-contents [title]
(list [:title title]
(include-css "/css/bootstrap.min.css")
... )))
(defn routes-in-detail []
(html5
[:head (head-contents "Routes in detail")]
[:body ... ]))
Once you realize the fact that hiccup markup is made out of plain clojure data structures, you'll find that manipulating/building it with functions is easy and flexible.
There's a new templating library called clabango which is modelled after the Django templating library, it may be what you're looking after: https://github.com/danlarkin/clabango