As total clojure noob, I am trying to start one small tutorial app, in order to get familiar with compojure. It's a small application which lets user add two numbers, and after clicking on button displays their sum on the other page. I followed instruction from Mark McGranaghan blog. Everything seems ok, until I try to get sum of two numbers I have entered, instead of getting result, I am redirected to the same page (so basically I am stuck on first step of this tutorial). After checking the code, it seems that NumberFormatException is triggered when input parsing takes place (for some reason). In all my tests, I have tried to input all kinds of number format , but with no success. Here is the simplest code version , for which author said should work (I have tried the latest version from github site- same scenario: NFE):
(ns adder.core
(:use compojure.core)
(:use hiccup.core)
(:use hiccup.page-helpers))
(defn view-layout [& content]
(html
(doctype :xhtml-strict)
(xhtml-tag "en"
[:head
[:meta {:http-equiv "Content-type"
:content "text/html; charset=utf-8"}]
[:title "adder"]]
[:body content])))
(defn view-input []
(view-layout
[:h2 "add two numbers"]
[:form {:method "post" :action "/"}
[:input.math {:type "text" :name "a"}] [:span.math " + "]
[:input.math {:type "text" :name "b"}] [:br]
[:input.action {:type "submit" :value "add"}]]))
(defn view-output [a b sum]
(view-layout
[:h2 "two numbers added"]
[:p.math a " + " b " = " sum]
[:a.action {:href "/"} "add more numbers"]))
(defn parse-input [a b] ;; this is the place where problem occures
[(Integer/parseInt a) (Integer/parseInt b)])
(defroutes app
(GET "/" []
(view-input))
(POST "/" [a b]
(let [[a b] (parse-input a b)
sum (+ a b)]
(view-output a b sum)))
Can anyone tell me better way to pars the input values, in order to avoid this exception?I have tried couple of techniques , but nothing worked for me. I am using Leningen v1.7.1 with clojure 1.3 on win 7 machine.
Here is content of my project.clj file:
(defproject adder "0.0.1"
:description "Add two numbers."
:dependencies
[[org.clojure/clojure "1.3.0"]
[org.clojure/clojure-contrib "1.1.0"]
[ring/ring-core "1.0.2"]
[ring/ring-devel "1.0.2"]
[ring/ring-jetty-adapter "1.0.2"]
[compojure "1.0.1"]
[hiccup "0.3.8"]]
:dev-dependencies
[[lein-run "1.0.0"]])
and run.clj script:
(use 'ring.adapter.jetty)
(require 'adder.core)
(let [port (Integer/parseInt (get (System/getenv) "PORT" "8080"))]
(run-jetty #'adder.core/app {:port port}))
Thanks.
You are using compojure 1.0.1, the example in the blog you are following is using compojure 0.4.0.
As of version 0.6.0, Compojure no longer adds default middleware to routes. This means you must explicitly add the wrap-params and wrap-cookies middleware to your routes.
Source: https://github.com/weavejester/compojure
So you need to explicitly add the wrap-params middleware. So the following changes are required...
(ns adder.core
(:use ; change to idiomatic usage of :use
[compojure.core]
[hiccup.core]
[hiccup.page-helpers]
[ring.middleware.params :only [wrap-params]])) ; add middleware for params
(defn view-layout [& content]
(html
(doctype :xhtml-strict)
(xhtml-tag "en"
[:head
[:meta {:http-equiv "Content-type"
:content "text/html; charset=utf-8"}]
[:title "adder"]]
[:body content])))
(defn view-input []
(view-layout
[:h2 "add two numbers"]
[:form {:method "post" :action "/"}
[:input.math {:type "text" :name "a" :id "a"}] [:span.math " + "]
[:input.math {:type "text" :name "b" :id "a"}] [:br]
[:input.action {:type "submit" :value "add"}]]))
(defn view-output [a b sum]
(view-layout
[:h2 "two numbers added"]
[:p.math a " + " b " = " sum]
[:a.action {:href "/"} "add more numbers"]))
(defn parse-input [a b]
[(Integer/parseInt a) (Integer/parseInt b)])
(defroutes main-routes ; needs to be renamed
(GET "/" []
(view-input))
(POST "/" [a b]
(let [[a b] (parse-input a b)
sum (+ a b)]
(view-output a b sum))))
(def app (wrap-params main-routes)) ; wrap the params to allow destructuring to work
Related
Hi guys I'm new at Clojure and ClojureScript...
I'm filling out 2 select element ... What I wonna do is update the second select depending on what option the user choose in the first select.
This is my code:
(ns easy-recipe.components.calculate
(:require [reagent.core :as r :refer [atom]]
[ajax.core :as ajax]
[reagent.session :as session]
[easy-recipe.components.common :refer [input select button]]
[easy-recipe.bulma.core :refer [horizontal-select horizontal-input-has-addons table columns box]]))
(defn handler [request]
(session/put! :categories (get-in request [:body :categories]))
(session/put! :breads (get-in request [:body :breads]))
(session/put! :recipes (get-in request [:body :recipes])))
(defn error-hadler [request]
(let [errors {:server-error (get-in request [:body :erros])}]
errors))
(defn get-categories-breads-recipes []
(ajax/GET "/api/calculate"
{:handler handler
:error-hadler error-hadler}))
(defn select-fun-wrapper [breads]
(fn [e]
(let [category_value (-> e .-target .-value)]
(reset! breads
(filter
#(= category_value (str (% :category_id)))
(session/get :breads))))))
(defn calculate-page []
(get-categories-breads-recipes)
(let [fields (atom {})
categories (atom nil)
breads (atom nil)
recipe (atom nil)]
(fn []
(reset! categories (session/get :categories))
;(reset! breads (filter #(= 1 (% :category_id)) (session/get :breads)))
[:div.container
[box
[horizontal-select "Category"
[select "category" "select" #categories (select-fun-wrapper breads)]]
[horizontal-select "Bread"
[select "bread" "select" #breads #()]]
[horizontal-input-has-addons "Quantity"
[input "quantity" "input" :text "" fields]
[button "quantity-btn" "button" "Add" #() nil]]]
[box
[columns
[table ["Ingredients" "Quantity"] #recipe]
[table ["Product" " "] [["30 Panes" "x"]]]]]])))
As you have noticed the breads reset! is commented... now the "select-fun-wrapper" is working fine, it updates the bread select depening on the category option selected... but if I uncomment that line the "select-fun-wrapper" will stop working (not updating the second select)... I wonna know why does it happend?
I can not leave this line commented because right now I have the problem thar the "Bread" select starts empthy... How could I fill the bread atom without using the reset! function?
Extra code (if it makes it clear):
(ns easy-recipe.bulma.core)
(defn horizontal-select [label select]
[:div.field.is-horizontal
[:div.field-label.is-normal>label.label label]
[:div.field-body>div.field.is-narrow>div.control>div.select.is-fullwidth
select]])
....
(ns easy-recipe.components.common)
(defn select [id class options function]
[:select {:id id :class class :on-change function}
(for [opt options]
^{:key (opt :id)}
[:option {:value (opt :id)} (opt :name)])])
....
I am having some trouble with POST from ajax.
I want to add a user to my database, so I am using POST and the data I want to send is in the form {:id id :pass pass} This is my POST
(defn add-user! [user]
(POST "/add-user!"
{:params user}))
All I want to do is enter information in the form specified above into this POST so I can send it to the database. I know that the argument,to the POST, is in the right form and the queries to the database and my routes are correct but I've made a mistake with the POST and I cannot figure out my mistake.
I am calling add-user! by
(defonce fields (atom {}))
(defn add-user! [user]
(POST "/add-user!"
{:params user}))
(defn content
[]
[:div
[:div
[:p "Enter Name:"
[:input
{:type :text
:name :name
:on-change #(swap! fields assoc :id (-> % .-target .-value))
:value (:id #fields)}]]
[:p "Enter Pass:"
[:input
{:type :text
:name :pass
:on-change #(swap! fields assoc :pass (-> % .-target .-value))
:value (:pass #fields)}]]
[:input
{:type :submit
:on-click #(do
(add-user! #fields))
:value "Enter"}]]
[:div
[:p "Id is " (:id #fields)]
[:p "Pass is " (:pass #fields)]]])
My query to the database in a clj file is
(defn add-user! [user]
(sql/insert! db :users user))
where sql is [clojure.java.jdbc :as sql]
There is not really enough information here to help you debug this fully, but I suspect that you need to modify your POST to:
(defn add-user! [user]
(POST "/add-user!"
{:format :json
:params user}))
If you don't provide :format, cljs-ajax defaults to sending Transit data, which would definitely confuse a server expecting JSON.
:format - specifies the format for the body of the request (Transit, JSON, etc.). Also sets the appropriate Content-Type header. Defaults to :transit if not provided. - JulianBirch/cljs-ajax#getpostput
Happened to me with this code:
(POST "/admin/tests/load"
{:params {:test-id "83"}
:headers {"x-csrf-token" csrf-field}
:handler (fn [r] (do (.log js/console r) (swap! test-state r)))
:format :json
:response-format :json
:error-handler (fn [r] (prn r))})))
"params" always showed up empty "{}". Then I tried:
(POST "/admin/tests/load"
{:params {:test-id "83"}
:headers {"x-csrf-token" csrf-field}} )
and all started working well, even after adding the other options. I know, weird.
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
I am trying to do this in webnoir.
This works:
(defpage [:post "/testurl] {:keys [name phone]}
(html5
(str "name: " name)
(str "phone: " phone)))
Now I want to generate defpages for many modules, each has a list of different fields. And I want to call the defpages from a function. The defpage must accept post for the fields.
Basically I have this: (def fields1 ["Name" "Phone" "Email" "xyz"])
And I would like to pass this to defpage, instead of having to specify the keys manually.
The fields might change in the future and that's why I want my code to pick up the fields and create the defpages dynamically on server startup.
Is it possible?
Thank you for all your help!
You can do this with a macro:
(defmacro defpages [pages]
`(do
~#(map (fn [page]
`(~'defpage [:post ~(str "/" (page :name))]
{:keys ~(into [] (map symbol (page :fields)))}
(~'html5
~#(map (fn [field]
`(str ~(str field ": ")
~(symbol field)))
(page :fields))))) pages)))
(defpages [{:name "testurl"
:fields ["name" "phone"]}
{:name "user"
:fields ["age" "address"]}])
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))))