How to handle onsubmit with Domina - clojurescript

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))))

Related

Lit-html select component event handlers

I am using a library called lit to create custom web components and i have tried using the #change and #select event handlers to display another component with no luck. I also can't seem to find the info on the docs.
My code looks like this :
return html`
<div>
<bx-select helper-text="Optional helper text" #change=${this._updateValue} label-text="Select" placeholder="HIV Test 1 Results:">
${this.answers?.map(
(item: any) => html`<bx-select-item
label-text=${item.label}
value=${item.concept}
.selected=${this.initialTestVal == item.concept}
>
${item.label}
</bx-select-item>`)}
</bx-select>
<bx-select helper-text="Optional helper text" label-text="Select" placeholder="HIV Test 2 Results:">
${this.answers?.map(
(item: any) => html`<bx-select-item #change=${this._updateValue}
label-text=${item.label}
value=${item.concept}
.selected=${this.confirmedTestVal == item.concept}
>
${item.label}
</bx-select-item>`)}
</bx-select>
<bx-select helper-text="Optional helper text" label-text="Select" placeholder="HIV Test 3 Results:">
${this.answers?.map(
(item: any) => html`<bx-select-item
label-text=${item.label}
value=${item.concept}
.selected=${this.finalTestVal == item.concept}
>
${item.label}
</bx-select-item>`
)}
</bx-select>
</div>`;
Any help/ advise on this will be appreciated.
Based on the name <bx-select> I'll assume you're using Carbon web components.
Unfortunately it doesn't look like it's listed in the doc but the event name that's fired when you select appears to be bx-select-selected so you'd want to add an event listener with #bx-select-selected.
This can be seen here https://web-components.carbondesignsystem.com/?path=/story/components-select--default when you select an option and see the "Actions" tab below.
You can also see the component's source code to see where the event is dispatched here https://github.com/carbon-design-system/carbon-web-components/blob/c318f69d726a72f006befc7aa46b76b33695d07f/src/components/select/select.ts#L62 and the name is defined here https://github.com/carbon-design-system/carbon-web-components/blob/c318f69d726a72f006befc7aa46b76b33695d07f/src/components/select/select.ts#L387.

ClojureScript change display none to visible

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.

Confused with HTML5 FileReader

My understanding of the API tells me the code should be as follows to output the contents of a text file (from an Input Tag). I am not sure how to use the event object. The more I read something on Mozilla Developer docs. The more confused I get.
<script>
function f(event)
{
alert("Just to check if the function is triggered");
var r = new FileReader() ;
r.onload = function()
{
alert (r.readAsText(document.getElementById['f'].files[0])) ;
}
}
</script>
<input type="file" id="f" onchange="f(event);" />
An change event triggers the function but nothing happens after the first line (alert message) is executed. No error messages either. Can someone help me with where I am going wrong.
You're attaching to the onload event but you're not doing anything that would cause the event to fire. You have to move your readAsText() call outside and not in the handler.
As the MDN documentation says about onload:
The FileReader.onload property contains a event handler executed when the load event is fired, when content read with readAsArrayBuffer, readAsBinaryString, readAsDataURL or readAsText is available.

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.

Checkbox onclick not firing

I'm at my wit's end with this.
Can anyone see anything wrong with this line? The function won't fire by clicking on the checkbox for some reason, but the calling function works fine (if I copy the exact "onclick" attribute over to the label for the checkbox, it works fine).
<input type="checkbox" name="match_35_0" id="match_35_0d" value="d0" onclick="checkSwap(document.page_form.match_35_0d, document.page_form.match_35_0)"></input>
If anyone can see why on earth this wouldn't be working, I would really appreciate it.
Thanks!
EDIT: Since a couple people asked, here's the checkSwap function (all it does is throw an alert so I can see that my onclicks are working before I add any code):
function checkSwap(radioid, groupid) {
alert("radio: " + radioid + " group: " + groupid);}
And here's the whole sample of the table cell that the checkbox in question is in (apologies for the formatting, the code sample doesn't seem to want to accept my newlines):
<td><label onclick="checkSwap(document.page_form.match_34_0d,document.page_form.match_34_0)" for="match_34_0">N</label><input type="checkbox" name="match_34_0" id="match_34_0d" value="d1" onclick="checkSwap(document.page_form.match_34_0d, document.page_form.match_34_0)"></input></td>
EDIT: Alright, canceling out a separate function that was limiting the checkboxgroup to 1 checked box was the issue.
The code that does the limiting was setting an onclick attribute for each checkbox, and that is apparently overriding the tag-set attribute. I'll have to figure out how to hack around it.
This syntax
document.page_form.match_35_0d
actually searches in the form with name of page_form for an input element with name of match_35_0d. But you have actually set it as an id of the checkbox where the onclick is definied.
You could solve your problem with the following call in the onclick:
checkSwap(this, document.page_form.match_35_0)
By the way, a checkbox is not the same as a radiobutton and you're actually not passing the ID's to the function, but instead the whole elements. Rewrite your function as
function checkSwap(checkbox, group) {
var checkboxId = checkbox.id;
for (var i = 0; i < group.length; i++) {
var element = group[i];
var elementId = element.id;
// ...
}
// ...
}
To obtain an element by ID, just use Document#getElementById().
var element = document.getElementById('someId');
If JQuery's ready method is already defined then Chek box onclick event do not work. You can fire the event if you add a Jquery click event inside ready. Not sure if this is IE issue ..?
Incase you already have Jquery's ready function then Onclick attribute of the ckeckbox will not fire. You have to add the click event in Jquery. Only then it works
Like below. I don't know the reason.
$('#cbFinalAttest').click(function (event) {
...
}
this function does fire - checked in firebug
<input type="checkbox" name="match_35_0" id="match_35_0d" value="d0" onclick="alert('55')"></input>
you have to check 'checkSwap'
It would be easier to pass in this to the function, then the parameter would be a reference to the element that called the function. So you can do:
function checkSwap(self){
alert(self.id);
}
Edit: Also, document.page_form.match_35_0.id will get the id, not the way you have it.