ClojureScript - Cropper zoom on button click - clojurescript

I would like to zoom-in or zoom-out the image before cropping. I am using cropperjs. I am trying this by the following code.
(def Cropper (oget js/window "Cropper"))
(defn zoom-in [cropper-inst]
(when cropper-inst
(ocall cropper-inst "zoom" 0.1)))
(defn zoom-out [cropper-inst]
(when cropper-inst
(ocall cropper-inst "zoom" "-0.1")))
(defn mount-listeners [cropper]
(let [zoom-in-el (ocall js/window "document.getElementById" "zoom-in-el")
zoom-out-el (ocall js/window "document.getElementById" "zoom-out-el")
cropper-el (-> cropper .-target)]
(ocall zoom-in-el "addEventListener" "click" (zoom-in cropper-el))))
(defn mount-cropper [wrap]
(when wrap
(when-let [image (oget wrap "firstChild")]
(let [cropper (Cropper. image #js{:aspectRatio 1
:viewMode 0
:guides false
:rotatable false
:zoomable true
:ready mount-listeners})]
cropper))))
I mount the cropper on render. But, I cannot call the zoom method. It is showing an error saying "Oops, Missing expected object key 'zoom'".
When I call the zoom as (ocall cropper-inst "cropper" "zoom" 0.1), the error message is "Oops, Expected a function on key path 'cropper', got instead".
The event object looks like the one in the below image:
But, crpper-el is a HTML tag as <img src="https://static.pexels.com/photos/248797/pexels-photo-248797.jpeg" class="spots_ui_components_cropper--cropped-image27970 spots_ui_components_cropper--cropped-image cropper-hidden">
Any idea?

I finally got it worked. I used (.-cropper (js-this)) and added a # before the zoom-in method call.
Here is the correct code:
(def Cropper (oget js/window "Cropper"))
(defn zoom-in [cropper-inst]
(when cropper-inst
(ocall cropper-inst "zoom" 0.1)))
(defn zoom-out [cropper-inst]
(when cropper-inst
(ocall cropper-inst "zoom" "-0.1")))
(defn mount-listeners [cropper]
(let [zoom-in-el (ocall js/window "document.getElementById" "zoom-in-el")
zoom-out-el (ocall js/window "document.getElementById" "zoom-out-el")
cropper-this (.-cropper (js-this))]
(ocall zoom-in-el "addEventListener" "click" #(zoom-in cropper-this))
(ocall zoom-out-el "addEventListener" "click" #(zoom-out cropper-this))))
(defn mount-cropper [wrap]
(when wrap
(when-let [image (oget wrap "firstChild")]
(let [cropper (Cropper. image #js{:aspectRatio 1
:viewMode 0
:guides false
:rotatable false
:zoomable true
:ready mount-listeners})]

Related

Problem opening files with the FileReader API

This has been bugging me for days. I have a web app that lets the user open documents from their local machine. I'm using the FileReader API for the first time.
It works correctly except for one use case.
Open a document file.
Programmatically create a new document, overwriting the existing one.
Open the same file as above.
When this sequence is executed, the second attempt fails silently (except that the file is not loaded).
Here is an example Reagent program (created from the figwheel-main template) that illustrates the problem.
(ns a-bad-button.core
(:require [reagent.core :as r]))
(def app-state-ratom (r/atom nil))
(defn new-doc []
{:doc-text "Some MINIMAL text to play with."})
(defn add-new-button
[aps]
(fn [aps]
[:input.tree-demo--button
{:type "button"
:value "New"
:on-click #(reset! aps (new-doc))}]))
(defn load-doc-data!
[aps file-data]
(swap! aps assoc :doc-text file-data))
(defn handle-file-open-selection
[aps evt]
(let [js-file-reader (js/FileReader.)]
(set! (.-onload js-file-reader)
(fn [evt] (load-doc-data! aps (-> evt .-target .-result))))
(.readAsText js-file-reader (aget (.-files (.-target evt)) 0))))
(defn add-open-button
[aps]
(fn [aps]
[:div
[:input {:type "file" :id "file-open-id"
:style {:display "none"}
:on-change #(handle-file-open-selection aps %)}]
[:input {:type "button"
:value "Open"
:on-click #(.click (.getElementById js/document "file-open-id"))}]]))
(defn a-bad-button
[aps]
(fn [aps]
[:div
[:h4 "A Bad Button"]
[:p#doc-text-p (or (:doc-text #aps) "Loaded text will go here.")]
[add-new-button aps]
[add-open-button aps]]))
(defn mount! [el]
(reset! app-state-ratom (new-doc))
(r/render-component [a-bad-button app-state-ratom] el))
(defn mount-app-element []
(when-let [el (.getElementById js/document "app")]
(mount! el)))
(mount-app-element)
(defn ^:after-load on-reload []
(mount-app-element))
With println debugging messages, it appears that execution reaches the :on-click handler in the add-open-button function, but the handler, handle-file-open-selection, is never reached or executed.
The failure occurs on Safari, Opera, Brave, and Vivaldi browsers. Files open as expected on Firefox.
Has anyone seen this before and fixed it?
Similar questions:
Filereader - upload same file again not working
FileReader onload not getting fired when selecting same file in Chrome
Basically, the problem is that onChange will not trigger when selecting the same file. One workaround is to set the value of the file input before the file browser opens to something like "", to always trigger an onChange event. In your case, it could look like changing your handle-file-open-selection function to:
(defn handle-file-open-selection
[aps evt]
(let [js-file-reader (js/FileReader.)]
(set! (.-onload js-file-reader)
(fn [evt]
(load-doc-data! aps (-> evt .-target .-result))))
(.readAsText js-file-reader (aget (.-files (.-target evt)) 0))
;; add this
(set! (.-value (.getElementById js/document "file-open-id")) "")
))

How to override onload methods in ClojureScript?

I am trying to override onload function of document and Image in ClojureScript. I think that set! should be possible to do it, but i am not getting any success. Relevant code is as follows :
(defn load-image [img-path]
(let [img (js/Image.)]
(do (set! (.-src img) img-path)
img)))
(defn add-img-canvas [img-path width height]
(let [img (load-image img-path)]
(set! (.-onload img)
(fn [] ;; This function is never called.
(let [canvas (get-scaled-canvas img width height)]
(do (pr-str canvas)
(swap! game-state :canvas canvas)))))))
(defn hello-world []
(let [count (atom 1)]
(fn []
[:div
[:h1 (:text #game-state)]
[:div (do (swap! count inc) (str "count is " #count))]
[:canvas (:canvas #game-state)]])))
(reagent/render-component [hello-world]
(. js/document (getElementById "app")))
(set! (.-onload js/document)
(fn [] ;; This function is also never called.
(add-img-canvas (:img-src game-state) 100 130)))
;;(. js/document onload)
Anonymous functions in add-img-canvas is not getting called. What am i doing wrong ?
I think it may be down to the difference between document.onload vs window.onload. The latter does work as expected.
See this for more details between the two.

touch left / touch right events in reagent

I'm using reagent to develop a site. It's unclear to me how to work with touch events in reagent, actually, in general. I read this post
clojurescript: touch events and Domina, but do i need to use domina to handle on-touch-start events with reagent ? Would anyone have a snippet of code to detect -- given a dom element -- if a user has swiped a % left or right ?
Reagent uses React's virtual DOM and synthetic events. Touch events are available in React but you have to call (.initializeTouchEvents js/React true) before rendering any component (i.e. before calling reagent.core/render-component).
Edit: the code below won't work because touch events are more complex than mouse events. See this MDN page for some (JS) examples. React seems to implement the same touch API.
For the swipe percentage, do something like the following (untested):
(defn swipe-element []
(let [swipe-state (atom {:width 0, :start-x 0, :current-x 0})]
(fn []
[:div {:on-touch-start
(fn [e]
(reset! swipe-state {:width (.. e -target -offsetWidth)
:start-x (.. e -target -pageX)
:current-x (.. e -target -pageX)}))
:on-touch-move
(fn [e]
(swap! swipe-state assoc :current-x (.. e -target -pageX)))}
(let [{:keys [width start-x current-x]} #swipe-state
direction (if (> current-x start-x) "right" "left")]
(str "Swipe " direction ": "
(* (/ (.abs js/Math (- start-x current-x)) width) 100) "%")))))

Is there any way to make an onClick handler in Om without using anonymous function?

I want to make a click handler function for an Om component. The docs and Stack Overflow examples I've found always declare anonymous functions like this
(defn main-view [_ owner]
(reify
om/IRender
(render [_]
(let [xs (items)]
(dom/div nil
(om/build sub-view {:title "View A"})
(om/build sub-view {:title "View B"})
(dom/button
#js {:onClick
(fn [e] (om/transact! xs #(assoc % 1 {:text "zebra"})))}
"Switch To Zebra!"))))))
I think it's cleaner to declare click functions outside the jsx/template area, within the component, the way it's commonly done in regular React. Is there a way do this in Om within the component? I tried this, but it doesn't work because onClick is undefined:
(defn my-component []
(reify
om/IRender
(render [this]
; Using Sablono syntax
(html [:h1 "Here is a heading" {:on-click 'onClick} ]))
onClick
(onClick [this]
; this part never gets executed when you click
(.log js/console "click"))))
I'd like to avoid defining a separate function outside the component if it's possible.
Your question is sensible and it's about handling scope of data.
It is possible but the problem with this approach in most cases you will need local scope data from the outside code block (in your case, it's an Om component).
I would explain in code. Let's say you want to move handler function out:
(anything
(let [a 1 b 2]
(on-event (fn my-handler [evt] (log (+ a b (.someAttr evt)))))))
You'll end up with this which is way longer:
(defn local-data->handler [a b]
(fn [evt] (log (+ a b (.someAttr evt)))))
(anything
(let [a 1 b 2]
(on-event (local-data->handler a b))))
in case you just want to move around inside the component definition:
(anything
(let [a 1
b 2
my-handler (fn [evt] (log (+ a b (.someAttr evt))))]
(on-event my-handler)))
Please note: to keep event handler work, ensure your non-anonymous function (created with defn or let) is the same as the anonymous form, especially argument list.
onClick is undefined because you use it as if it's an Om protocol. Please consult Om lifecycle protocols for correct usage.
https://github.com/swannodette/om/wiki/Documentation
Per your requirements, you should move the function definition out of the component.
You should then be able to pass the function's name to the event listener:
(defn foo [] (println "foo"))
(defn my-component [text owner]
(reify
om/IRender
(render [_]
(dom/button
#js { :onClick foo }
"Click Here"))))

No state info with Chrome React.js tools

I have the following ClojureScript code that uses the om library as a wrapper to React.js
(defn list-view [app owner]
(reify
om/IInitState
(init-state [_]
{:filter nil
:selected-domain nil})
om/IWillMount
(will-mount [_]
(th/poll filter-chan (fn[data]
(om/set-state! owner :filter data))))
om/IRenderState
(render-state [this state]
(let [list-data (sort (list-data (:list app) (:filter state)))]
(if (> (count list-data) 0)
(dom/div #js {:className "sidebar-module sidebar-module-inset"}
(dom/div #js {:className "bs-example well"}
(apply dom/ul #js {:className "list-group"}
(map (fn [text] (domain-list-item text (:selected-domain state) owner))
list-data))))
(dom/span nil))))))
This are the helper functions used in the code above
(defn list-data [alist filter-text ]
(filter (fn [x] (cond (empty? filter-text) false
(nil? filter-text) false
(= filter-text "*") true
:else (> (.indexOf (.toLowerCase x) filter-text) -1))) alist))
(defn domain-list-item [text selected owner]
(let [class-name (str "list-group-item" (if (= text selected) " isSelected" ""))]
(dom/li #js {:className class-name}
(dom/a #js
{:href "#"
:onClick (fn [event] (select-domain owner text))} text))))
Everything works as expected. The only thing that bothers me Is that I do not see any state info when I analyze the page with the React.js tools in Chrome.
It seems as if sometimes the state is directly visible in the state area (e.g. for an input element) and sometimes it is hidden inside the __om_state object.
I find that I often have to click on another component and then back on the component I'm interested in to see the state in the state area. Perhaps that's the issue here?