Idiomatic way of rendering style info using Clojure Hiccup - css

I need to build style info within hiccup in order to place an element at a location indicated by the variables "top" and "left". My code looks like so:
(html [:div {:style (str "top" top ";left" left)}
"some text"])
This code is pretty ugly. It would be nicer if hiccup automatically rendered the "style" attribute using standard CSS style rules... Then I could write the following:
(html [:div {:style {:top top :left left}}
"some text"])
Is there already a library that does this? Or, do I need to roll my own solution?
Thank you Clojurians for any pointers!

You could write a function that would do that, and it would even be slightly less typing than the map. For example:
(defn style [& info]
{:style (.trim (apply str (map #(let [[kwd val] %]
(str (name kwd) ":" val "; "))
(apply hash-map info))))})
Which would allow you to write it like this...
(html [:div (style :top top :left left) "some text"])
Sample output from the function...
user=> (style :top 32 :left 14)
{:style "top: 32; left: 14;"}

What about this:
(defn style [s]
(str/join ";" (map #(str (name %) ":" ((keyword %) s)) (keys s))))
(style {:padding "20px"
:background "#e68a00"
:color "white"
:font-size "large"
:font-weight "bold"})

Not much into Clojure yet, but a 'transformation' based approach like that of Enlive sounds like the solution for these kind of needs -
https://github.com/cgrand/enlive

Related

Convert input string into Morse Code with Clojure

I am trying to develop a converter that takes an input string and converts it into morse code through a Morse Library map, while also respecting functional programming rules. Sorry for any clarification issues, I am new to Stack Overflow
(ns clojureassignment.core
(:gen-class))
(require '[clojure.string :as str])
;this function is where the converter is developed
(defn morse->ASCI
[x]
(def morse_Library {:A ".-":B "-...":C "-.-.":D "-..":E ".":F "..-.":G "--.":H "...."
:I "..":J ".---"
:K "-.-":L ".-..":M "--" :N "-.":O "---":P ".--.":Q "--.-":R ".-."
:S "...":T "-":U "..-":V "...-":W ".--":X "-..-":Y "-.--":Z "--.."
:0 "-----":1 ".----":2 "..---":3 "...--":4 "....-":5 "....."
:6 "-....":7 "--...":8 "---..":9 "----."})
(let [stringVector (str/upper-case(seq x))] ;divide the string into a sequence of characters
;trying to create iteration of the input where it checks if its value is found in the morse library
(doseq [[stringVector] (morse_Library)]
(if (= stringVector (morse_Library)
(do (println(str (key morse_Library))))
(do (println("characters not found"))))
)))
(print (str/upper-case stringVector))
)
(defn -main
[& args]
(println "ASCII to Morse Converter.")
(println "Make sure to include whitespaces after each ASCII character. Add String")
(def stringInput (read-line))
(println stringInput )
(morse->ASCI stringInput)
)
(-main)
I tried to create a "doseq" iteration where it checks if the value is found in the map.
Good things:
using a map for your morse translation; maps are also functions and
make it easy to write a transformation like this
Not so good things:
don't def inside other def:s - it's used to define namespace
global things and it is not like var or auto or whatever you
know from other languages
don't use keywords as map keys, if you don't plan to use them like
that; Clojure takes pretty much anything as key. So in your case
use chars
don't seq and then upper-case - wrong order - this will give you
the .toString() from a sequence of characters otherwise
don't name things all the same (stringVector)
doseq is for side-effects; you don't want your morse-code function
to do side-effects (you maybe want to print it later); in functional
programming you shift your side-effects to the edges - this way your
code becomes easier to test and reason about
you pretty much never need :gen-class
use the correct way to require in the
namespace
Clojure prefers snake-case over camelCase
(random rant: if you are using tutorialpoint (I assume this from several
things going wrong here and having them seen there) to learn Clojure: do
yourself a favour and look for another resource; as of now they don't
teach idiomatic Clojure!
Make your transformation a pure function:
upper-case the input
make a seq from it (this now is a sequence of chars) - but you don't
actually have to do this, because the next step will take care of it
map the look-up-table with your designated fallback over each char
join the result
This will look something like this:
(def morse {\A ".-" ,,, })
(->> input
(str/upper-case)
(map #(morse % "???"))
(str/join))
;; preparation of morse map
(ns morse
(:require [clojure.string :as str]))
;; I stole morse dictionary from some python code in:
;; https://www.geeksforgeeks.org/morse-code-translator-python/
(def s "{ 'A':'.-', 'B':'-...',
                    'C':'-.-.', 'D':'-..', 'E':'.',
                    'F':'..-.', 'G':'--.', 'H':'....',
                    'I':'..', 'J':'.---', 'K':'-.-',
                    'L':'.-..', 'M':'--', 'N':'-.',
                    'O':'---', 'P':'.--.', 'Q':'--.-',
                    'R':'.-.', 'S':'...', 'T':'-',
                    'U':'..-', 'V':'...-', 'W':'.--',
                    'X':'-..-', 'Y':'-.--', 'Z':'--..',
                    '1':'.----', '2':'..---', '3':'...--',
                    '4':'....-', '5':'.....', '6':'-....',
                    '7':'--...', '8':'---..', '9':'----.',
                    '0':'-----', ', ':'--..--', '.':'.-.-.-',
                    '?':'..--..', '/':'-..-.', '-':'-....-',
                    '(':'-.--.', ')':'-.--.-'}")
;; and transformed it using clojure to a clojure map:
(def m (read-string (str/replace
(str/replace
(str/replace
(str/replace s
"\n" "")
"                    " " ")
":" " ")
"'" "\"")))
;; now `m` contains the string-to-morse map
The actual answer starts here:
;; convert any text string to a morse string:
(defn string-to-morse [s]
(str/join " "
(map #(get m (str/upper-case %)) (str/split s #""))))
;; and this function can transform the output back to text:
(defn morse-to-string [morse-string]
(let [ms (zipmap (vals m) (keys m))]
(str/join (map #(get (zipmap (vals m) (keys m)) % " ")
(str/split morse-string #" ")))))

clojurescript / hiccup - inline css to set dispatch to a hover action

I'm trying to hover a text and change the :display of another text to none, in order to make it disappear. I've used :onMouseOver, but it does not work because it needs this.value I think.
So, any idea for how I can do that? I require inline CSS in this case.
This is what I have:
[:p {:onMouseOver #(rf/dispatch [:events/hover-feedback])} "hello"]
[:p {:style {:display #(rf/subscribe [::subs/hover-feedback])}} "world"]
And in events file:
(rf/reg-event-db
::hover-feedback
(fn [db]
(assoc db :hover-feedback "none")))
In subs file:
(rf/reg-sub ::hover-feedback (fn [db] (get-in db [:hover-feedback]
I think you forgot one colon in the first :p dispatch. Try this:
Views:
[:p {:on-mouse-over #(rf/dispatch [::events/hover-feedback])}
"hello"]
[:p {:style {:display #(rf/subscribe [::subs/hover-feedback])}}
"world"]
Events (without change):
(re-frame/reg-event-db
::hover-feedback
(fn [db]
(assoc db :hover-feedback "none")))
Subs (unnecessary get-in):
(re-frame/reg-sub
::hover-feedback
(fn [db]
(:hover-feedback db)))

Clojurescript Semantic UI React Search custom renderer

I am trying to implement a search in Clojurescript with reagent/re-frame and semantic-ui. Semantic-ui uses a renderer for the suggestions. This renderer defaults to image, price, title, description. As I want to have suggestions on geocoding I want to list addresses. This is the return data I am getting. I basically want to display name, city and postcode in the suggestions.
{:hits
[{:osm_type "W",
:name "Am Pfuhl",
:osm_value "residential",
:city "Berlin",
:postcode "12209",
:state "Berlin",
:osm_key "highway",
:extent [13.322584 52.4205878 13.3258975 52.419743],
:point {:lng 13.3241429, :lat 52.4201622},
:osm_id 103012039,
:country "Deutschland"}
:took 7}
The code I wrote does not show me any results. I tried a lot but I don't know how to look into the component to see if the state of it changes and if it stores results. The subscription does give me back results when I call it directly.
(def search (helper/component "Search"))
(def grid (helper/component "Grid"))
(def grid-row (helper/component "Grid" "Row"))
(defn on-search-change [event props]
(rf/dispatch [:get-geocode (:value (js->clj props :keywordize-keys true))]))
(defn on-result-select [event props]
(rf/dispatch [:geocode-selected]))
(defn get-geocode-results []
#(rf/subscribe [:geocode-results]))
(defn result-renderer [& meh]
(fn [meh]
[:div (str meh)]))
(defn geocode-component []
[:> grid
[:> grid-row
[:> search {:minCharacters 3
:loading (when (:geocode #(rf/subscribe [:loading])) )
:defaultValue "Berlin"
:selectFirstResult true
:onSearchChange on-search-change
:onResultSelect on-result-select
:resultRenderer result-renderer
:results get-geocode-results}]]])
I would very much appreciate some help on:
How do I find out if the component stores the results correctly?
How do I write a renderer that just renders all of the results for debugging?
Thanks and regards! Timo
Edit: solution on https://gist.github.com/TimoKramer/7e93758afb81dcad985fafccc613153a
From the docs it looks like :resultRenderer expects a React Component, and you're giving it a Clojure function. You can convert your hiccup-style components to React components with reagent.core/as-component.
I haven't tested this, but maybe it can be as simple as:
(defn result-renderer [& meh]
(reagent.core/as-component [:div (str meh)]))
I've used a similar strategy with tooltips:
(defn info-icon
([message]
(info-icon {} message))
([options message]
(let [popup (component "Popup")
icon (component "Icon")]
[:> popup
{:trigger (reagent/as-component [:> icon (merge {:name "info"} options)])}
" "
message])))
Where component probably matches your helper/component, and reagent is reagent.core

Clojure recursive function

As a Clojure newbie, I'm bothered with this small problem:
I would like to iterate through a sequence and execute a split, and then a str (concatenation) function over the sequence elements.
Here is my sequence:
(("2.660.784") ("2.944.552") ("44.858.797"))
What I want to get is something like this:
("2660784" "2944552" "44858797")
And this is my attempt of creating recursive solution for my problem:
(defn old
[squence]
(let [size (count squence)]
(loop [counter 1]
(if (<= counter size)
(apply str (clojure.string/split
(first (first squence))
#"\b\.\b"
))
(old (rest squence)))
)))
And of course, this is not a solution because it applies split and str only to one element, but I would like to repeat this for each element in squence. The squence is product of some other function in my project.
I'm definitely missing something so please help me out with this one...
The simplest way to write it is with replace, rather than split/str. And once you've written a function that can do this transformation on a single string, you can use map or for to do it to a sequence of strings. Here I had to destructure a bit, since for whatever reason each element of your sequence is itself another sequence; I just pulled out the first element.
(for [[s] '(("2.660.784") ("2.944.552") ("44.858.797"))]
(clojure.string/replace s #"\b\.\b" ""))
user=> (defn reject-char-from-string
[ch sequence]
(map #(apply str (replace {ch nil} (first %))) sequence))
#'user/reject-char-from-string
user=> (reject-char-from-string \. '(("2.660.784") ("2.944.552") ("44.858.797"))
)
("2660784" "2944552" "44858797")
Tried this?
=> (flatten '(("2.660.784") ("2.944.552") ("44.858.797")))
("2.660.784" "2.944.552" "44.858.797")
Is it as simple as this?
(def data '(("2.660.784") ("2.944.552") ("44.858.797")))
(require '[clojure.string :as string])
(map #(string/replace (first %1) "." "") data)
;=> ("2660784" "2944552" "44858797")

Hunchentoot/cl-who page composition

Hunchentoot/cl-who Page Composition
I'm trying to put together a few pages in hunchentoot as an experiment, and I'm running into an unexpected wall. As an example, I have the following template macro.
(defmacro page-template ((&key title) &body body)
`(with-html-output-to-string
(*standard-output* nil :prologue t :indent t)
(:html :xmlns "http://www.w3.org/1999/xhtml" :xml\:lang "en" :lang "en"
(:head (:meta :http-equiv "Content-Type" :content "text/html;charset=utf-8")
(:title ,(format nil "~#[~A - ~]Test Site" title)))
(:body ,#body))))
Now when I have a pure text page, or one filled with html literals like
(define-easy-handler (test-page :uri "/") ()
(page-template (:title "Splash Page") (:p "Testing testing")))
everything is a-ok. The page outputs properly and I can see te efforts of my code instantly. However, when I have a page which is made up of redundant elements, it's not as simple. For example, lets say I have a page on which for whatever reason I want to display three RSS newsfeeds. This is a sufficiently complex component that I want to abstract it out, so to my minnd, I should be able to do something like
(define-easy-handler (test-feed :uri "/feeds") ()
(page-template (:title "Splash Page")
(publish-newsfeed "http://nf-one.html")
(publish-newsfeed "http://nf-two.html")
(publish-newsfeed "http://nf-three.html")))
(defmacro publish-newsfeed (url &optional (item-limit 5))
(flet ((get-text (s-tree node-path) (car (last (xmls-tools:find-subtree s-tree node-path)))))
(let ((rss-feed (xmls:parse (drakma:http-request url))))
`(:div :class "rss-feed"
(:a :href ,(get-text rss-feed '("channel" "link")) :target "_top" (:h1 ,(get-text rss-feed '("channel" "title"))))
(:ul ,#(mapcar #'(lambda (item)
`(:li (:a :href ,(get-text item '("link")) :target "_top" (:h2 ,(get-text item '("title"))))
(:p :class "date" ,(get-text item '("pubDate")))
(:p ,(get-text item '("description")))))
(let ((items (xmls-tools:find-all-children (xmls-tools:find-subtree rss-feed '("channel")) "item")))
(if (> (length items) item-limit) (subseq items 0 item-limit) items))))))))
But the result of the above is a "Server Error" page. I'm not quire sure why; page-template is a macro so the calls to publish-newsfeed shouldn't be expanded until they're in the context of with-html-output-to-string. Can anyone tell me what I'm doing wrong?
Also, on closer inspection of the various Hunchentoot/cl-who tutorials, none of them seems to do this kind of page composition. Can anyone with some Hunchentoot experience tell me what the correct/canonical way of decomposing a page into components is?
EDIT:
Correct response by Ramarren below; the with-html-output macros work under different evaluation rules. The version of publish-newsfeed that would actually work in this situation is actually
(defun publish-newsfeed (url &optional (item-limit 5))
(flet ((get-text (s-tree node-path) (car (last (xmls-tools:find-subtree s-tree node-path)))))
(let* ((rss-feed (xmls:parse (drakma:http-request url)))
(items (xmls-tools:find-all-children (xmls-tools:find-subtree rss-feed '("channel")) "item"))
(ltd-items (if (> (length items) item-limit) (subseq items 0 item-limit) items)))
(with-html-output
(*standard-output* nil :indent t)
(:div :class "rss-feed"
(:a :href (get-text rss-feed '("channel" "link")) :target "_top" (:h1 (str (get-text rss-feed '("channel" "title")))))
(:ul (dolist (item ltd-items)
(htm (:li (:h2 (:a :href (get-text item '("link")) :target "_top" (str (get-text item '("title")))))
(:p :class "date" (str (get-text item '("pubDate"))))
(:p (str (get-text item '("description")))))))))))))
Note the removal of mapcar for dolist (I'm a Schemer, don't give me too much of a hard time about liking lambdas, but they weren't the right choice here), and the use of htm to escape blocks of html s-exps (h-exps?) that wouldn't otherwise be in context for with-html-output. Finally, I had to wrap text but NOT :href properties in (str ) to get them to expand dynamically.
The macro with-html-output-to-string expands its body using special evaluation rules. In particular, any not recognized forms are left as is, which means macros are not expanded before the html generating code is generated, which means by the time your publish-newsfeed macro is expanded by the standard compiler it is no longer in context ofwith-html-output-to-string. This is clear when expanding the macro manually, especially using slime macroexpansion feature.
To make it work you should make publish-newsfeed a function and use with-html-output inside it with the same stream (either assume `standard-output everywhere or pass the stream explicitly).

Resources