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)])])
....
Related
I have a component:
(defn inner-input [cljs_element activeEl title]
(let [form (atom title)]
(fn [cljs_element activeEl title]
[:input {:type "text"
:onChange #(reset! form (.. % -target -value))
:on-blur #(change-title cljs_element (.. % -target -value))
:style {:display (if (:active (:node cljs_element)) "block" "none")
:width (* (+ 1 (count #form)) 8)
:max-width 730
:min-width 170}
:value #form}])))
It is nested in other component:
(defn card-input [cljs_element activeEl]
(fn [cljs_element activeEl]
(let [title (:title (:node cljs_element))]
[:div
[inner-input cljs_element activeEl title]])))
When i type data to the input in the inner-input component, i need update the local state form. And when the outer component card-input updates i want to reset my form to new title prop from argument. How can i achieve that?
I tried put (reset! form title) between let and fn in the inner-input component but it will not help
You can use reagent/track! to listen to changes to title, and reagent/dispose to stop listening. You can alternatively use add-watch and remove-watch, but track is a more convenient syntax.
(defn inner-input [title]
(reagent/with-let
[form (reagent/atom #title)
watch (reagent/track! (fn [] (reset! form #title)))]
[:label
"Inner input"
[:input {:on-change (fn [e]
(reset! form (.. e -target -value)))
:on-blur (fn [e]
(reset! title (.. e -target -value)))
:value #form}]]
(finally
(reagent/dispose! watch))))
(defn card-input []
(reagent/with-let
[title (reagent/atom "hello")]
[:div
[:label "Title"
[:input {:on-change (fn [e]
(reset! title (.. e -target -value)))
:value #title}]]
[inner-input title]]))
Now if you type in the inner input it will only update the outer when you exit the input box, but changing the outer title will immediately change the inner one. Is that what you wanted?
But if you don't want to pass title as a ratom and have to pass it as a value, then instead you can compare it to the previous value to determine if it changed, and reset form only when it changes.
(when (not= #previous-title title)
(do (reset! previous-title title)
(reset! form title)))
This code can go in render seeing as it is safe to call when form changes... nothing will happen.
This is a snippet from the Reagent project. Looking at complete-all and clear-done, I understand the point is to swap out the modified map. I don't understand how it's being done. The definition of mmap calls for 3 parameters — and complete-all seems to be calling it with two, namely map and #(assoc-in % [1 :done] v). clear-done calls with remove and #(get-in % [1 :done]). I tried using the repl to experiment but couldn't get the requires to work out.
(ns todomvc.core
(:require [reagent.core :as r]))
(defonce todos (r/atom (sorted-map)))
(defonce counter (r/atom 0))
(defn add-todo [text]
(let [id (swap! counter inc)]
(swap! todos assoc id {:id id :title text :done false})))
(defn toggle [id] (swap! todos update-in [id :done] not))
(defn save [id title] (swap! todos assoc-in [id :title] title))
(defn delete [id] (swap! todos dissoc id))
(defn mmap [m f a] (->> m (f a) (into (empty m))))
(defn complete-all [v] (swap! todos mmap map #(assoc-in % [1 :done] v)))
(defn clear-done [] (swap! todos mmap remove #(get-in % [1 :done])))
The existing map is passed as the first argument to the function. When all else fails...
I was following the sample code from http://yogthos.github.io/reagent-forms-example.html and was attempting the use the multi-select option for a list.
(defn select-item [item]
(go
(reset! current-selection item)
(let [response (<! (check-for-response))]
(reset! current-response response)
(reset! past-response response))))
;;batch
(defn item-list []
[:div#items-list
[items-list-header]
[:ul.list-group.items {:field :multi-select :id :pick-a-few}
(if (pos? (count #items))
(doall (for [item #items]
^{:key (item "upc")}
[:li.list-group-item [:a {:class (set-item-class item) :on-click #(select-item item) :href "#"}
(item "description")]]))
[:li [:a "No Items For This Department"]])]])
(defn product-component []
[:div
[item-list]
[product-response]
;[bind-fields item-list items]
;[bind-fields item-list product-response]
])
Does anyone know why I am unable to multi-select? The logic in select-item will change, but I can't seem to see the multi-select in the UI
I've been messing around with bind-fields in my product component with no success.
I'm trying to parse HTML with CSS into Hiccup in a Reagent project. I am using Hickory. When I parse HTML with inline CSS, React throws an exception.
(map
as-hiccup (parse-fragment "<div style='color:red'>test</div>")
)
The above generates [:div {:style color:red} "test"] & Reactjs returns exception from Reactjs:
Violation: The style prop expects a mapping from style properties to values, not a string.
I believe [:div {:style {"color" "red"}} "test"] must be returned instead.
Here is the code view:
(ns main.views.job
(:require [reagent.core :as reagent :refer [atom]]
[hickory.core :refer [as-hiccup parse parse-fragment]]))
(enable-console-print!)
(defn some-view [uid]
[:div
(map as-hiccup (parse-fragment "<div style='color:red'>test</div>"))
])
The whole repo is here and it works. I added the parsing from style tag to a map for React in the core.cljs file:
(ns hickory-stack.core
(:require [clojure.string :as s]
[clojure.walk :as w]
[reagent.core :as reagent :refer [atom]]
[hickory.core :as h]))
(enable-console-print!)
(defn string->tokens
"Takes a string with syles and parses it into properties and value tokens"
[style]
{:pre [(string? style)]
:post [(even? (count %))]}
(->> (s/split style #";")
(mapcat #(s/split % #":"))
(map s/trim)))
(defn tokens->map
"Takes a seq of tokens with the properties (even) and their values (odd)
and returns a map of {properties values}"
[tokens]
{:pre [(even? (count tokens))]
:post [(map? %)]}
(zipmap (keep-indexed #(if (even? %1) %2) tokens)
(keep-indexed #(if (odd? %1) %2) tokens)))
(defn style->map
"Takes an inline style attribute stirng and converts it to a React Style map"
[style]
(tokens->map (string->tokens style)))
(defn hiccup->sablono
"Transforms a style inline attribute into a style map for React"
[coll]
(w/postwalk
(fn [x]
(if (map? x)
(update-in x [:style] style->map)
x))
coll))
;; Test Data
(def good-style "color:red;background:black; font-style: normal ;font-size : 20px")
(def html-fragment
(str "<div style='" good-style "'><div id='a' class='btn' style='font-size:30px;color:white'>test1</div>test2</div>"))
;; Rendering
(defn some-view []
[:div (hiccup->sablono
(first (map h/as-hiccup (h/parse-fragment html-fragment))))])
(reagent/render-component [some-view]
(. js/document (getElementById "app")))
I just started to use Om (a reactjs based library for ClojureScript). I would like to filter a list based on user input. The following works but the solution seems to be to complicated. Is there a better one ?
(ns om-tut.core
(:require-macros [cljs.core.async.macros :refer [go]])
(:require [om.core :as om :include-macros true]
[om.dom :as dom :include-macros true]
[clojure.string :as string]))
(enable-console-print!)
(def app-state (atom {:list ["Lion" "Zebra" "Buffalo" "Antelope"]}))
(defn handle-change [e owner {:keys [text]}]
(om/set-state! owner :data (vec (filter (fn [x] (> (.indexOf x(.. e -target -value)) -1)) (#app_state :list))))
(om/set-state! owner :text (.. e -target -value)))
(defn list-view [app owner]
(reify
om/IInitState
(init-state [_]
{:text nil
:data (:list app)
})
om/IRenderState
(render-state [this state]
(dom/div nil
(apply dom/ul #js {:className "animals"}
(dom/input
#js {:type "text" :ref "animal" :value (:text state)
:onChange #(handle-change % owner state)})
(map (fn [text] (dom/li nil text)) (:data state)))))))
(om/root list-view app-state
{:target (. js/document (getElementById "registry"))})
I think that this is a better solution:
(ns om-tut.core
(:require-macros [cljs.core.async.macros :refer [go]])
(:require [om.core :as om :include-macros true]
[om.dom :as dom :include-macros true]))
(def app-state (atom {:list ["Lion" "Zebra" "Buffalo" "Antelope"]}))
(defn handle-change [e owner {:keys [text]}]
(om/set-state! owner :text (.. e -target -value)))
(defn list-data [alist filter-text]
(filter (fn [x] (if (nil? filter-text) true
(> (.indexOf x filter-text) -1))) alist))
(defn list-view [app owner]
(reify
om/IInitState
(init-state [_]
{:text nil})
om/IRenderState
(render-state [this state]
(dom/div nil
(apply dom/ul #js {:className "animals"}
(dom/input
#js {:type "text" :ref "animal" :value (:text state)
:onChange (fn [event] (handle-change event owner state))})
(map (fn [text] (dom/li nil text)) (list-data (:list app) (:text state))))))))
(om/root list-view app-state
{:target (. js/document (getElementById "animals"))})