Om: lost focus in radio buttons when changing values

746 views
Skip to first unread message

Elyahou Ittah

unread,
Mar 18, 2015, 2:01:51 PM3/18/15
to clojur...@googlegroups.com
I have issue with this simple radio button example.

I want to select the value with the keyboard. But When I am navigating with the keyboard between the radio buttons the focus is lost... Anyone has a idea why ?


(defonce app-state
(atom
{:values ["label1" "label2" "label3"]}))

(defn checked? [value option]
(and (= value option) "checked"))

(defn form-view [data owner]
(reify
om/IInitState
(init-state [_]
{:answers {"test" "label2"}})
om/IRenderState
(render-state [this state]
(apply dom/span nil
(om/build-all
(fn [option]
(reify
om/IRender
(render [this]
(dom/label nil option
(dom/input #js {:checked (checked? (get (:answers state) "test") option)
:type "radio"
:name "test"
:value option
:onChange
#(let [new-value (-> % .-target .-value)]
(when (js/isNaN new-value)
(om/update-state! owner :answers
(fn [xs]
(assoc xs "test" new-value)))))})))))
(:values data))))))



(om/root form-view app-state
{:target (. js/document (getElementById "test"))})

Steve Ashton

unread,
Mar 20, 2015, 9:14:07 AM3/20/15
to clojur...@googlegroups.com
I ran into a similar error with losing focus. In my case it was because I was generating a different react-id in an on-blur method. I ran your code, and didn't see the id change. But (the reason I bring this up), I do see the same behavior in the Chrome DOM explorer with your code as I did with mine. If you expand all three of the nodes down to the 'input' tags, then click on a different radio button, you'll see all the 'label' DOM elements will collapse. This tells me the actual DOM elements in the browser are being destroyed and recreated, which messes up the tab indexing and focus.

I think the problem is that the owner object you are updating in the on-click is actually the parent's owner, not the child's owner. I added the owner attribute to the child component, but then had to also pass in the state from the parent. This correctly enable keyboard navigation, and the initial 'label2' was selected. But you'll notice that the parent div which displays the current selection does NOT update with new selections. I have more experience with Reagent than with Om, so I don't remember off-hand what the correct way is to update parent state (async channels mabye?).

My modified code is below. Hopefully that helps you get to the final solution.

-Steve



(defonce app-state
(atom
{:values ["label1" "label2" "label3"]}))

(defn checked? [value option]
(and (= value option) "checked"))

(defn form-view [data owner]
(reify
om/IInitState
(init-state [_]
{:answers {"test" "label2"}})
om/IRenderState
(render-state [this state]
(dom/div nil
(dom/div nil (get-in state [:answers "test"]) )
(apply dom/span nil
(om/build-all
(fn [option owner]
(reify
om/IRenderState
(render-state [this state]
(println "rendering")
(dom/label nil option
(dom/input #js {:checked (checked? (get (:answers state) "test") option)
:type "radio"
:name "test"
:value option
:onChange
#(let [new-value (-> % .-target .-value)]
(when (js/isNaN new-value)
(om/update-state! owner :answers
(fn [xs]
(assoc xs "test" new-value)))))})))))
(:values data)
{:state state}))))))


(om/root form-view app-state
{:target (. js/document (getElementById "app"))})

Leon Grapenthin

unread,
Mar 23, 2015, 2:48:34 PM3/23/15
to clojur...@googlegroups.com
You can't close over the owner objet of a parent from within a subcomponent. That is because there is no guarantee that the subcomponent re-renders when the parent owner object is replaced.

One solution would be to pass a callback handler to the subcomponent, e. g. have a subcomponent

(fn [{:keys [on-change option]} owner]
(reify
...........
:onChange (f [e] (let [new-value ...
...
(on-change new-value)))))

And call (om/update-state! ) within passed on-change. Notice that using this approach, when the parent owner changes, the passed on-change fn also changes which will lead to your subcomponent being recalculated with it, even if option didn't change. Which is what you want.


A faster workaround is to not create a subcomponent in the first place.

om/IRenderState
(render-state [this state]
(apply dom/span nil
(map
(fn [option]
(dom/label nil option
....
:onChange (fn [e] ;; happily close over owner here...

...

Should suffice.

Elyahou Ittah

unread,
Mar 23, 2015, 3:30:05 PM3/23/15
to clojur...@googlegroups.com
Thank you Leon,

I used your second solution and it works perfectly


--
Note that posts from new members are moderated - please be patient with your first post.
---
You received this message because you are subscribed to a topic in the Google Groups "ClojureScript" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojurescript/mbgxAsP3mdI/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojurescrip...@googlegroups.com.
To post to this group, send email to clojur...@googlegroups.com.
Visit this group at http://groups.google.com/group/clojurescript.

Reply all
Reply to author
Forward
0 new messages