ClojureScript change display none to visible - clojurescript

I want to modify the visibility of a table when a button is clicked, utilizing clojurescript/javascript interop.
I've tried
{:on-click #(-> js/document
(.getElementById "db-search-result-tables")
(.-style)
(.-display "block"))}
This is the div tag I'm calling it on.
[:div {:style {
:display "none"}
:id "db-search-result-tables"
:class "db-search-results-table"}
[table-to-display]
I've also tried
(-> js/document
(.getElementById "db-search-result-tables")
(.-style)
(.-display)
(set! ""))
but it only displays the table momentarily, and then sets display to none again.

EDIT: This solution doesn't assume any library, based on the reading that the problem statement didn't explicitly mention any library/framework, just JS interop, modifying the DOM directly a la jQuery. Don't use this answer if you use any library or any React wrapper such as reagent.
Maybe it would be easier to create a helper function, say toggle that hides/shows the display of a given element by its ID?
(ns myproject.core)
(defn ^:export toggle [elem-id]
(let [elem (js/document.getElementById elem-id)
style (.-style elem)
display (.-display style)
new-display (if (= "none" display) "block" "none")]
(set! (.-display style) new-display)))
We find the element by its id, use a var to get the current style, get the display out of the style and compute the new value for the display attribute, then we set! it back into the display.
I used the ^:export metadata tag so that the function could be called directly from the document, like this:
<div>
<button onClick="myproject.core.toggle('details')">Toggle details</button>
</div>
<div id="details" style="display: none">
Some details here. Some details here. Some details here. Some details here.
</div>

This is a solution specific to re-frame. I'd suggest utilising the app-db to store the state, with a handler to change the state and a sub to retrieve the current value. Re-frame's README is a great resource for learning about this setup: https://github.com/Day8/re-frame
Direct changes to the DOM will be overridden by re-frame when it sees fit (which is why your original code was being reverted to the original component definition).
Set up subs / handlers
You could create a handler like this:
(re-frame.core/reg-event-fx
:handlers/enable-search-results
(fn [{:keys [db]} _]
{:db (assoc db :show-search-results? true})
And a sub to retrieve the value:
(re-frame.core/reg-sub
:subs/show-search-results?
(fn [db]
(:show-search-results? db))
Update code to use subs / handlers
Now, update your search button to dispatch to the handler:
[:button
{:on-click #(re-frame.core/dispatch [:handlers/enable-search-results])}
"Click to show search results"]
And update your search results div to be visible / hidden based on the sub:
(let [show-search-results? #(re-frame.core/subscribe [:subs/show-search-results?])]
[:div {:style {:display (if show-search-results? "visible" "none"}
:id "db-search-result-tables"
:class "db-search-results-table"}
[table-to-display]])
Alternatively:
(let [show-search-results? #(re-frame.core/subscribe [:subs/show-search-results?])]
(when show-search-results?
[:div {:id "db-search-result-tables"
:class "db-search-results-table"}
[table-to-display]]))
Because the app-db state is persistent, this is exactly where "mutations" like this can be controlled safely.

Related

Reagent turning span into editable field on click

Creating a figwheel/cljs/clj application, working on the front end when the user hits a button I want to turn a span into an editable field on click using reagent, How would I go about this?
I've been reading about focus switching, and reagent/dom-node but given I have the innerHTML aka "real values" displayed on the web-page, how would I setup on-click="" to essentially move the cursor to the component outside the innerhtml (the span) and make it editable and the cursor start flashing.
Thanks guys
Not sure if you guys want a code snippet but
got a widget that pretty much says the following:
{:type :plain-icon-button
:label [:i.ti-pencil-alt]
:tooltip "Click here to edit"
:attr {:tab-index -1}
:on-click (fn [] (prn "What do i do here :( ")}
thanks again guys
One simple solution is to have both fields present, using the CSS property display: none; to toggle them so only 1 is visible at a time. In Hiccup:
(:require
[garden.core :as garden]
[goog.style :as style]
[:div
[:button#mybtn {:display :initial}]
[:textfield#mytf {:display :none }]]
and then toggle the styles using {:onclick hide-btn}
(defn install-css [css-def]
(let [css-str (garden/css css-def)]
(style/installStyles css-str)
css-str))
(defn hide-btn []
(install-css [:#mybtn {:display :none }] )
(install-css [:#mytf {:display :initial}] ))
You can also experiment with the CSS visible property.
Create a local reagent atom and then render either the clickable div or the input element based on the state of that ratom.
(defn click-me
[]
(let [clicked (reagent/atom false)]
(fn []
(if #clicked
[:input {:type "text"
:auto-focus true
:on-blur #(reset! clicked false)}]
[:div {:on-click #(reset! clicked true)}
"Click me."]))))
You'll probably want to add an on-click to the :input, which will submit the data and then set clicked to false again.

Clojurescript reagent/as-element pass props?

I am using material-ui framework wrapped for clojurescript (reagent-material-ui) with reagent for a small desktop app .I was wondering, when I use the reagent/as-element function to construct a React element can I pass props and style in it, and if so, how?
Looks like ragent-material-ui is using cljsjs/material-ui "0.19.0-0". The applicable material-ui style docs are here.
I would give something like this a shot based off the documentation.
;; Using inline styles
(ns my.project
(:require [reagent-material-ui.core :as ui]
[reagent.core :as reagent]))
(reagent/as-element [ui/IconMenu
{:iconButtonElement
(el [ui/IconButton {:touch true} [icon "more_vert"]])
:style {}} ;; Your inline styles here
[ui/MenuItem "Delete"]])
;; Using component specific overrides
;; (depending on the type of element you are styling this will differ)
(reagent/as-element [ui/IconMenu
{:iconButtonElement
(el [ui/IconButton {:touch true} [icon "more_vert"]])
:listStyle {}} ;; Your root element overrides here
[ui/MenuItem "Delete"]])

Why Radium doesn't work with Reagent (Clojurescript)?

I was trying to get working FormidableLabs/radium · GitHub with reagent-project/reagent · GitHub, but I've come to a dead end.
I was able to get it working partially by "hacking" reagent function create-class like this (It's almost the same as original, I just added js/Radium wrapper).
(ns myproject.components.radium
(:require [reagent.core :as r]
[reagent.impl.component :as c]
[reagent.impl.util :as util]
[reagent.interop :refer-macros [.' .!]]))
(defn create-class
[body]
(assert (map? body))
(let [
spec (c/cljsify body)
res (js/Radium (.' js/React createClass spec))
;res (.' js/React createClass spec)
f (fn [& args]
(r/as-element (apply vector res args)))]
(util/cache-react-class f res)
(util/cache-react-class res res)
f))
Then I made function for component like this
(defn radium []
(create-class
{:reagent-render
(fn []
[:button {:style
[{:backgroundColor "red"
:width 500
:height 100
"#media (min-width: 200px)" {:backgroundColor "blue"}
":hover" {:backgroundColor "green"}}
{:height 200}]}
"Heres something"])}))
And I use it somewhere in some other reagent render function like: [radium/radium]
So, merging vector of styles together works well (That's Radium feature).
Media query works also, but only on first render, it doesn't react dynamically when I change screen size.
:hover :focus :active doesn't work at all
I was digging in Radium code to found out what's wrong.
Good sign was that Radium properly assigns onMouseEnter onMouseLeave props to the component and sets :hover state of component to true.
This gets properly fired: https://github.com/FormidableLabs/radium/blob/master/modules/resolve-styles.js#L412
The problem is that render function, which is supposed re-render component based on new state (changed by Radium) is not fired at all.
This render function:
https://github.com/FormidableLabs/radium/blob/master/modules/enhancer.js#L22
Whereas when I run JS Radium examples (no Clojurescript and Reagent), this render function gets fired on every onMouseEnter onMouseLeave. With reagent not at all.
Does Reagent somehow block re-rendering when component state changes?
I've translated the basic button Radium example to be used with Reagent:
(def Radium js/Radium)
(def styles {:base {:color "#fff"
":hover" {:background "#0A8DFF"}}
:primary {:background "#0074D9"}
:warning {:background "#FF4136"}})
(defn button
[data]
(let [kind (keyword (:kind data))]
[:button
{:style (clj->js [(:base styles)
(kind styles)])}
(:children data)]))
(def btn (Radium. (reagent/reactify-component button)))
(def rbtn (reagent/adapt-react-class btn))
(defn hello-world
[]
[:div
[rbtn {:kind :primary} "Hello Primary"]
[rbtn {:kind :warning} "Hello Warning"]])
The key thing is that I converted button reagent component to a React component (using reactify-component), then passed it through Radium and then converted it back to something which I consume in reagent (using adapt-react-class).
In my example, hover works.
Hope this helps.
I've placed the working version on GitHub.

How to handle onsubmit with Domina

I've been trying to write an equivalent for the following in cljs using the Domina library:
HTML:
<form id="my-form">
<input type="text />
<input type="submit" />
</form>
Javascript (jQuery):
$("#my-form").submit(function() {
console.log("submit suppressed");
return false;
});
A solution not to my satisfaction (without Domina library which is very verbose):
(set! (.-onsubmit (.getElementById js/document "my-form")) #(do
(.log js/console "submit suppressed")
false))
I've tried many solutions which all failed - all somewhat similar to:
(listen! (by-id "my-form") :submit #(do
(log "submit suppressed")
false))
It is on purpose that I don't use the click event, because I also want the function to be executed when the form is submitted by code or keyboard.
When you return false from within a jQuery event handler, the library will automatically call e.preventDefault and e.stopPropagation (for more info see here). Calling this functions is the right way to control the behaviour of the event.
Domina wraps js/Event in something that ClojureScript understands. So you can call preventDefault and stopPropagation directly, without using the Javascript interop. You can read more about this neat trick here: https://github.com/levand/domina#event-objects.
This trick is probably what is causing returning false to not work at all. The way this trick is implemented (take a look at the code here) is by wrapping your function with another function that always return true.
So your code should be more like this:
(listen! (by-id "my-form") :submit
(fn [e]
(do
(log "submit suppressed")
(prevent-default e)
(stop-propagation e))))

clojurescript: touch events and Domina

I'm having trouble getting the 'touch' or 'changedTouches' list out of the touchstart event in Domina.
Here's my :require stuff:
(ns myproj
(:require-macros [hiccups.core :as h])
(:require [domina :as dom]
[hiccups.runtime :as hiccupsrt]
[domina.events :as ev]
[cljs.reader :refer [read-string]]
[wsosc :as wo]
[clojure.browser.repl :as repl]
))
And here's my touchstart event handler:
(defn touchstart [evt]
; store event in an atom for repl access
(swap! de (fn [x] evt))
; print something to html to show a result (no console on the phone)
(dom/set-text! (dom/by-id "result") (str "blah" evt))
; hopefully someday extract touch coordinates here.
(let [rct (.getBoundingClientRect (dom/by-id "osccanvas"))
;touchlist1 (get evt "changedTouches")
;touchlist2 (.changedTouches evt)
;touchlist3 (.-changedTouches evt)
;kies (keys evt)]
wat (:type evt) ; this works
;wat (ev/raw-event evt) ; this works
;touchlist (.-changedTouches evt)]
;touch (.item touchlist 1)]
]
(dom/set-text! (dom/by-id "result") (str "touchstart touch:" wat))))
'de' is an atom that I'm trying to use for debug. I'm able to get the :type from the event but that's about it. Pretty much none of the other commented things work, except for ev/raw-event. raw-event returns an object that is fairly incrutable from the repl, at least for me. If I swap! de with the raw-event it looks like this:
ClojureScript:myproj>#de
#<[object Object]>
I have no idea how extract information from this, it seems pretty unresponsive to things like (keys x) or (.keys x), etc.
What is also strange is that I can call (:type evt) in the above function, but if I assign evt to de I can't do the same thing with the 'de' atom at the repl, ie (:type #de).
Ok after much frustration I finally got things to work. It turns out there are a number of layers at work which I was not really aware of (and didn't WANT to be aware of!). The main thing is that there was no touch information in the domina touch event object - that gets stripped out even before domina gets the event. Its like this:
original browser event -> google closure library -> domina library -> my code
And the google closure (not clojure, its javascript) library actually strips out the touch information, so its not available in the event object that I get. Thanks, google. However, the original event is still accessible, its just two layers down. The code looks like this:
(defn touchstart [evt]
(let [wat (ev/raw-event evt)
touches (.-changedTouches (.getBrowserEvent wat))
touch (.item touches 0)
]
(domousedown (.-clientX touch) (.-clientY touch))
))
So I use Domina's raw-event function to get the google closure version of the event ('wat'). But that doesn't have the touch info either. I have to go one more level with getBrowserEvent, and then I can call the changedTouches method as documented here:
https://developer.mozilla.org/en-US/docs/DOM/TouchEvent
And the last piece of the puzzle was detecting whether a touchscreen is present in the first place, so I can set up the right event functions for that. This non-clojure hack does the job:
(if (js* "'ontouchstart' in window")
<set up touch events>
<set up non-touch events>)
I tried various permutations of clojure syntax here but nothing seemed to work for me. Open to suggestions on that.