Clojure/compojure: How do you process user input from a webform? - clojure

I am making a Clojure web-app to process numeric input data. I have set up the main page and am working on having the inputed data processed and displayed after submission via a button.
key bits of code with comments:
(defn process-grades
"Takes user input from home's form-to function and processes it into the final grades list"
[weights grades]
(->> grades
(map (partial percentify-vector weights)) ;; <- The functions being called here are omitted but defined in the same name space.
(mapv #(apply + %))))
(defn home [& [weights grades error]]
(html
[:head
[:title "Home | Clojuregrade"]]
[:body
[:h1 "Welcome to Clojuregrade"]
[:p error]
[:hr]
(form-to [:post "/"]
[:h3 "Enter the weights for each of the grades below. Of course, all of the numbers should add up to 100%. Be sure to include the brackets"
[:br]
(text-area {:cols 30 :placeholder "[40 10 50] <- adds up to 100%"} "weights" weights)]
[:h3 "Enter ALL of the grades for EACH STUDENT in your class.
Make sure that each of the grades is ordered such that the grade corresponds
to its matching weight above."
[:br]
(text-area {:rows 15 :cols 30 :placeholder
"[89 78 63]
[78 91 60]
[87 65 79]
... " } "grades" grades)]
(submit-button "process"))]))
(defn processed [weights grades]
(cond
(empty? weights)
(home weights grades "You forgot to add the weights!")
(empty? grades)
(home weights grades "You forgot to add the grades!")
:else
(do
(html
[:h2 "These are your final grades."]
[:hr]
[:p "test"])))) ;; <- I would like to call process-grades here. "test" renders fine.
(defroutes app
(GET "/" []
{:status 200
:headers {"Content-Type" "text/html"}
:body (home)})
(POST "/" [weights grades] (processed weights grades))
(ANY "*" []
(route/not-found (slurp (io/resource "404.html")))))
;; ...

My Assumption was right. I was taking your code and put it into a barebone luminus project. The html your code produces looks like this:
<h3>
Enter the weights for each of the grades below. Of…
<br></br>
<textarea id="weights" placeholder="[40 10 50] <- adds up to 100%" name="weights" cols="30"></textarea>
</h3>
<h3>
Enter ALL of the grades for EACH STUDENT in your c…
<br></br>
<textarea id="grades" rows="15" placeholder="bla" name="grades" cols="30"></textarea>
</h3>
And here comes the error:
<form method="POST" action="/">
<input type="submit" value="process"></input>
</form>
The form element has to wrap the input elements, otherwise it wont submit them as part of the request.
So if you take your hiccup code and change it to this:
(defn home [ & [weights grades error]]
(html
[:h1 "Welcome to Clojure-grade"]
[:p error]
[:hr]
(form-to [:post "/"]
[:h3 "Enter the weights for each of the grades below. Of course, all of the numbers should add up to 100%. Be sure to include the brackets"
[:br]
(text-area {:cols 30 :placeholder "[40 10 50] <- adds up to 100%"} "weights" weights)
]
[:h3 "Enter ALL of the grades for EACH STUDENT in your class.
Make sure that each of the grades is ordered such that the grade corresponds
to its matching weight above."
[:br]
(text-area {:rows 15 :cols 30 :placeholder "bla"} "grades" grades)]
;...
;
; (Each grade corresponds to one of the weights above,
; so order is important. You can copy and paste directly from your excel file but don't forget
; the brackets!)" } "grades" grades)]
(submit-button "process")))) ;; <- when I hit this in the browser I get a 404, with or without input.
It works. Take precise look at: "(form-to [:post "/"]". I moved it a few lines up so that it wraps the input elements.
Just a side note, spend some time working with the browser developer tools and read at least a small html tutorial. Especially knowing how to use the developert tools will give you a big edge while debugging such problems.

Where are weights and grades being passed into processed? I think you need to pass them as arguments if you want to use them:
(defn processed [weights grades]
;; .. snip
)
;; .. snip
(POST "/" [weights grades] (processed weights grades))

Related

custom body message email triggering in Riemann

I want to trigger a mail with custom body message. I can parse timestamp in my body, but i want to add the string in my body when i try to add string value in body i'm unable to reproduce it. can anyone help me to resolve it. I'm in very critical implementation.
Please find my sample code below,
(let [email (mailer {:host "cccc"
:port 25
:user "111111"
:pass "111111"
:auth "true"
:subject (fn [events] "1DD Monitoring - Response time SLA breach")
:body (fn [events]
(apply str "Hello Team, now the time is" (:**silo** events) "Thank You!"))
:from "xxx#xxxx.com"})]
I'm sending value from logstash to riemann in silo field and i want to print silo field value in body
IMHO the handle function that you define for :body, has a wrong syntax. According to the doc, you must define a function that takes a sequence of events and return a string. For example:
(defn prn-str [& events]
...)
So you have a :**silo** key in your event. But in the :body function, you will have a list of events. (:**silo** events) will be nil.
You can for example get the :**silo** values separated by a comma with:
:body (fn [events]
(str "Hello Team, now the time is "
(clojure.string/join "," (map #(:**silo** %) events))
" Thank You!"))

Custom body message in riemann email

I am trying to create a custom message in the body section of email using riemann.
I couldn't append the field dynamically.
Riemann config:
(let [email (mailer
{:host "XXXXX" :port XX :user "XXX" :pass "XXX" :auth "true"
:subject (fn [events] "Team")
:body (fn [events]
(apply str "Hello Team, now the time is" (:timestamp event) "Thank You!"))
:from "xxx#gmail.com"})]
My output:
Hello Team, now the time is Thank You!
My expected output:
Hello Team, now the time is 12:13:45 Thank You!.
My timestamp not getting appended in the :body.
from the docs:
These formatting functions take a sequence of
events and return a string.
so the question is which event in the sequence would you like to get the timestamp from? If you lookup a keyword in a sequence, as opposed to one of the members of that sequence, you will get back nil as a default:
core> (:asdf '(1 2 3))
nil
and if you apply str that into a couple other strings it will have no effect because str will ignore it. Which is where your output is coming from. Here is an analogous example function:
core> (let [body-fn (fn [events]
(apply str "Hello Team, now the time is"
(:timestamp events)
"Thank You!"))]
(body-fn [{:timestamp 42} {:timestamp 43}]))
"Hello Team, now the time isThank You!"
if we choose the timestamp from the first event:
core> (let [body-fn (fn [events]
(apply str "Hello Team, now the time is"
(:timestamp (first events))
"Thank You!"))]
(body-fn [{:timestamp 42} {:timestamp 43}]))
"Hello Team, now the time is42Thank You!"
When alerting through riemann my personal opinion is to wrap the things being alerted into fixed time windows and use the timestamp from the start of that window, though this is primarily because of personal preference.

In clojure enlive how to create template to add values and keys from a map

I want to create a template with Clojure's Enlive for a "table" like html page, that should have a header and rows. I want to populate this "table" with data that comes from this map. Basicaly I want to populate header with keys, and cells with vals from each of the maps that come from :event-data key.
(def dummy-content
{:title "Events Mashup"
:event-data [{ :event-name "event name 1"
:performer "performer 1"
:date "date 1"
:start-time "start time 1"
:end-time "end time 1"}
{:event-name "event name 2"
:performer "performer 2"
:date "date 2"
:start-time "start time 2"
:end-time "end time 2"}]})
My snippets and template look like this:
(defsnippet header-cell "index.html" [:.psdg-top-cell] [value] [:.psdg-top-cell]
(defsnippet value-cell "index.html" [:.psdg-right] [value] [:.psdg-right]
(deftemplate mshp "index.html" [content]
[:.psdg-top] (append (for [c (keys content)] (header-cell c)))
[:.psdg-right] (append (for [v (vals content)] (value-cell v))))
And index.html has these tags, that are rellevant for this template.
<div id="psdg-top">
<div class="psdg-top-cell" style="width:129px; text-align:left; padding- left:24px;">Summary</div>
<div class="psdg-top-cell">Website.com</div>
</div>
<div class="psdg-right">10 000</div>
When I call the template I get this error:
=> (mshp (:event-data dummy-content))
ClassCastException clojure.lang.PersistentHashMap cannot be cast to java.util.Map$Entry clojure.lang.APersistentMap$ValSeq.first (APersistentMap.java:183)
What am I doing wrong?
The error is occurring when you call (keys content), because (:event-data dummy-content) returns a vector, and keys won't work on a vector. You've got two options - you can either define your header columns elsewhere, or just take them from the first element in the vector, like this: (keys (first content)).
Edit
I've had a go at replicating what you set out in the question, but fixing the errors, and you can find my efforts here: https://www.refheap.com/17659.
If you want something that has a more typical table structure, with the value cells contained in rows, you can try the following. It uses clone-for rather than a combination of append and for, and uses nth-of-type to just return the first "psdg-top-cell" (otherwise there would be twice as many)
(def template
(html-snippet
"<div id=\"psdg-top\">
<div class=\"psdg-top-cell\">Summary</div>
<div class=\"psdg-top-cell\">Website.com</div>
</div>
<div class=\"psdg-right\">10 000</div>"))
(defsnippet header-row template [[:.psdg-top-cell (nth-of-type 1)] ] [headers]
(clone-for [h headers]
(content h)))
(defsnippet value-row template [:.psdg-right] [values]
(clone-for [v values]
(content v)))
(deftemplate mshp template [events]
[:#psdg-top] (content (header-row (map name (keys (first events)))))
[:.psdg-right] (clone-for [e events]
(do->
(wrap :div {:class "psdg-row"})
(content (value-row (vals e))))))

Access a global variable by its name in Clojure

I'm trying my hand at a text adventure in clojure.
This is where I am struggling:
(ns records)
(defrecord Room [fdesc sdesc ldesc exit seen])
(defrecord Item [name location adjective fdesc ldesc sdesc flags action ])
(def bedroom (Room. "A lot of text."
nil
"some text"
'(( "west" hallway wearing-clothes? wear-clothes-f))
false))
(def hallway (Room. "description of room."
nil
"short desc of room."
'(("east" bedroom) ("west" frontdoor))
false))
(def location (ref bedroom))
(defn in?
"Check if sequence contains item."
[item lst]
(some #(= item %) lst))
(defn next-location
"return the location for a entered direction"
[direction ]
(second (first (filter #(in? direction %) (:exit #location)))))
(defn set-new-location
"set location parameter to new location."
[loc]
(dosync (ref-set location loc)))
My problem is with updating the var location.
If I enter (set-new-location hallway) it works correctly. Location is set to the new room and I can access its fields. However, what I need to do is read the next possible exit from the exit field of a room, but when I enter (set-new-direction (next-exit "west")) location says hallway, but it doesn't point to the variable "hallway".
In CL I'd use (symbol-value hallway). How can I do this in Clojure?
EDIT: I really want to use var-per-location because I have sketched out roughly 30 locations, at 20 lines each, making it too unwieldy to put in one map.
You can use #(resolve sym) as a symbol-value workalike; what it actually does is look up the Var named by the symbol sym in the current namespace (might be a Var brought in with use / require :refer) and extract its value. See ns-resolve if you want to control the namespace the Var is looked up in.
You could also not use Var-per-location, but rather store your locations in a map somewhere:
(def locations {:hallway ... :bedroom ...})
(You could also put this map in a Ref for convenience of adding new locations at runtime.)

Parsing values from multiple checkboxes using compojure

I have created small compojure web application, which can display multiple values, fetched from other website, using provided URL. At the moment, this URL is hard coded in one of my functions, and now I would like to add feature for dynamical URL creation, based upon values in text field and checkbox.
This is how my page looks like:
(defn view-layout [& content]
(html [:body content]))
(defn view-input []
(view-layout
[:h2 "Find"]
[:form {:method "post" :action "/"}
( for [category ["Cat1" "Cat2" "Cat3"]]
[:input {:type "checkbox" :id category } category ] )
[:br]
[:input {:type "text" :id "a" :value "insert manga name"}] [:br]
[:input.action {:type "submit" :value "Find"}]
[:a {:href "/downloads"} "Downloads"]]))
(defn view-output []
(view-layout
[:h2 "default images"]
[:form {:method "post" :action "/"}
(for [name (get-content-from-url (create-url))]
[:label name [:br]]
)]))
(defn create-manga-url
[]
"http://www.mysite.net/search/?tfield=&check=000")
Here are the routes:
(defroutes main-routes
(GET "/" []
(view-input))
(GET "/downloads" []
(view-downloads))
(POST "/" []
(view-output) ))
At the moment, I need help with (create-url) function (returns a string), where I would like to fetch all fields, mandatory for my search (one text field and 3 checkboxe's) , and parse values from them, which will be fed into (concatenated) the URL - for checkbox, if checked, the check section will have value 1, instead of 0, or remain 0 if not (check=100, or 010, 011 if two check boxes were selected) . In case of text field, the tfield=userinputtext.
EDIT
I spent a lot of time as a .Net and Java developer, and this part of compojure is a total mystery for me.
This is what I would like to achieve with (create-url) function (pseudo code written in OO style):
(defn create-url [*text_field cbox1 cbox2 cbox3*]
(def url "http://www.mysite.net/search/?")
(def tfield "tfield=")
(def cbox "&check=")
(if (checked? cbox1)
(str cbox "1")
(str cbox "0"))
(if (checked? cbox2)
(str cbox "1")
(str cbox "0"))
(if (checked? cbox3)
(str cbox "1")
(str cbox "0"))
(str tfield (:value text_field))
(str url tbox cbox))
I apologize for how this pseudo code looks like, but this is the part that I would like to learn: How can I scoop data from form, and parse it (in this case i would like to attachh values from form fields into the string)
Can anyone help me with this?
First, you need to add 'name' attributes to your HTML input elements. The 'id' attributes aren't sent to the server on post.
Next, I guess a quick way of doing this similar to your example is:
(POST "/" [a Cat1 Cat2 Cat3] (create-url a [Cat1 Cat2 Cat3]))
and then something like this:
(defn checked? [c]
(and c (= c "on")))
(defn checked->num [c]
(if (checked? c) "1" "0"))
(defn create-url [a cats]
(str "x?tfield=" a "&check="
(apply str (for [c cats] (checked->num c)))))
Or just drop the two helpers:
(defn create-url [a cats]
(str "x?tfield=" a "&check="
(apply str (map #(if (= "on" %) "1" "0") cats))))