在ClojureScript中,延迟`:on-click`事件并仅在未触发`:on-double-click`事件时触发

3

有什么简单的方法可以延迟:on-click事件,以便先检测是否触发了:on-double-click事件?

[:div {:on-click (fn [e]
                   ;; listen to double-click event, within 500ms, 
                   ;; if so on-double-click-fn, 
                   ;; if not, on-click-fn
                  )
       :on-double-click (fn [e] 
                          ;; on-click-fn
                         )}]

Thanks!

First attempt:

(defn sleep [timeout]
  (let [maxtime (+ (.getTime (js/Date.)) timeout)]
    (while (< (.getTime (js/Date.)) maxtime))))

[:div {:on-click (fn [e] (sleep 500) (print "single-clicked"))
       :on-double-click (fn [e] (print "double-clicked"))}]

第二次尝试:

(def state (atom {:click-count 0}))

(defn handle-click [e click-fns-map]
  (swap! state update :click-count inc)
  (sleep 500)
  (let [click-count (get @state :click-count)]
    (swap! state assoc :click-count 0)
    (cond
      (= click-count 1) ((:on-single-click click-fns-map) e)
      (> click-count 1) ((:on-double-click click-fns-map) e)))))

[:div 
 {:on-mouse-down 
  (fn [e]
    (handle-click e {:on-single-click #(print "single-click")
                     :on-double-click #(print "double-click")}))}]

 ;;=> "single-click"
 ;;=> "single-click"

编辑:

根据 Taylor Wood 的回答,这里提供了一个抽象层,它包装了HTML元素参数并为您覆盖了:on-click:on-double-click

(defn ensure-single-double-click 
  [{:keys [on-click on-double-click] :as args}]
  (let [waiting? (atom false)]
    (merge 
     args
     {:on-click (fn [e] 
                  (when (compare-and-set! waiting? false true)
                    (js/setTimeout 
                     (fn [] (when @waiting?
                            (on-click %)
                            (reset! waiting? false)))
                     300)))
      :on-double-click (fn [e] 
                         (reset! waiting? false)
                         (on-double-click %))})))

[:a (ensure-single-double-click
      {:style           {:color "blue"} ;; this works
       :on-click        #(print "single-click")
       :on-double-click #(print "double-click")})
    "test"]
2个回答

7
以下是一种解决方法:
(defn slow-link [text single-click-fn double-click-fn]
  (let [waiting? (atom false)]
    [:a {:on-click #(when (compare-and-set! waiting? false true)
                      (js/setTimeout (fn [] (when @waiting?
                                              (single-click-fn %)
                                              (reset! waiting? false)))
                                     500))
         :on-double-click #(do (reset! waiting? false)
                               (double-click-fn %))}
     text]))

[slow-link "Test" #(prn "single-click") #(prn "double-click")]

这将启动一个JS计时器,在500ms后执行给定函数。该函数会检查在超时时间到达时是否仍需要等待另一个点击事件,如果是,则执行 single-click-fn。如果我们没有在等待,那就意味着双击事件已经发生,重置 waiting?false 并调用double-click-fn:on-click处理程序使用compare-and-set,只在还没有处于waiting?状态时采取行动,避免出现三次/四次点击等竞态行为。

1

Taylor Wood的回答接近了,但compare-and-set!不能保护我免受三次(甚至更多次)点击的影响,因为如果在500ms内发生了三次点击,waiting?会再次设置为false并且第二个超时将被安排。 我认为这意味着每个奇数次点击都会技术上安排一个新的超时。

幸运的是,点击事件带有一个名为detail的属性,该属性设置为连续点击的次数。 我在这里找到了它。 以下内容应该解决OP的问题,而不允许三次点击:

:on-click
(fn [e]
 ; This prevents the click handler from running
 ; a second, third, or other time.
 (when (-> e .-detail (= 1))
   (reset! waiting? true))
   ; Wait an appropriate time for the double click
   ; to happen...
   (js/setTimeout
     (fn []
       ; If we are still waiting for the double click
       ; to happen, it didn't happen!
       (when @waiting?
         (single-click-fn %)
         (reset! waiting? false)))
     500)))
:on-double-click #(do (reset! waiting? false)
                      (double-click-fn %))

尽管三次点击听起来很奇特,但它确实有用途:选择一整行文本。因此,我不希望用户错过这个功能。


以下内容是关于如何使得在监听单击事件的元素上实现文本选择的附录。我从谷歌过来就是为了找这个,希望对其他人有所帮助。
遇到的挑战是我的应用要求用户可以在一开始时进行双击操作,但是不需要松开第二次点击,有点像“一点五次点击”。为什么呢?因为我正在监听 span 元素的点击事件,用户可能会双击以选择一个完整单词,但是接着保持第二次点击并拖动鼠标以选择原单词旁边的其他单词。问题在于双击事件处理程序只有在用户释放第二次点击后才会触发,因此等待状态没有及时设置为 false。
我使用了一个 :on-mouse-down 处理程序来解决这个问题:
:on-click
(fn [e]
 ; This prevents the click handler from running
 ; a second, third, or other time.
 (when (-> e .-detail (= 1))
   (reset! waiting? true))
   ; Wait an appropriate time for the double click
   ; to happen...
   (js/setTimeout
     (fn []
       ; If we are still waiting for the double click
       ; to happen, it didn't happen!
       (when @waiting?
         (single-click-fn %)
         (reset! waiting? false)))
     500)))
:on-double-click #(double-click-fn %)
:on-mouse-down #(reset! waiting? false)

请记住,:on-click:on-double-click处理程序仅在释放时触发(并且处理程序按照鼠标按下、单击、双击的顺序触发),这给了:on-mouse-down处理程序一个机会将waiting?设置为false,如果用户尚未释放鼠标,则需要这样做,因为他不会触发:on-double-click事件处理程序。
请注意,现在您甚至不需要在双击处理程序中再将waiting?设置为false,因为鼠标按下处理程序已经在双击处理程序运行时完成了此操作。
最后,在我的特定应用程序中,用户可能希望选择文本而不触发单击处理程序。为了做到这一点,他将点击一个文本片段,并且在不释放鼠标的情况下,拖动光标以选择更多文本。当释放光标时,那不应该触发单击事件。因此,我还必须跟踪用户是否在松开鼠标之前随时进行了选择(一种“半次单击”)。对于这种情况,我必须向组件状态添加一些东西(一个名为selection-made?的布尔原子和一个名为selection-handler的事件处理函数)。此案例依赖于选择的检测,并且由于双击会进行选择,因此不需要再检查事件的详细属性以防止三次或更多次单击。
整个解决方案如下(但请记住,这仅适用于文本元素,因此只是对OP所要求的内容的补充):
(defn component
  []
  (let [waiting? (r/atom false)
        selection-made? (r/atom false)
        selection-handler
        (fn []
          (println "selection-handler running")
          (when (seq (.. js/document getSelection toString))
            (reset! selection-made? true)))]
    (fn []
      [:div
        ; For debugging
        [:pre {} "waiting? " (str @waiting?)]
        [:pre {} "selection-made? " (str @selection-made?)]
        ; Your clickable element
        [:span
          {:on-click
           (fn [e]
            (println "click handler triggered")
            ; Remove the selection handler in any case because
            ; there is a small chance that the selection handler
            ; was triggered without selecting any text (by
            ; holding down the mouse on the text for a little
            ; while without moving it).
            (.removeEventListener js/document "selectionchange" selection-handler)
            (if @selection-made?
              ; If a selection was made, only perform cleanup.
              (reset! selection-made? false)
              ; If no selection was made, treat it as a
              ; simple click for now...
              (do
                (reset! waiting? true)
                ; Wait an appropriate amount of time for the
                ; double click to happen...
                (js/setTimeout
                  (fn []
                    ; If we are still waiting for the double click
                    ; to happen, it didn't happen! The mouse-down
                    ; handler would have set waiting? to false
                    ; by now if it had been clicked a second time.
                    ; (Remember that the mouse down handler runs
                    ; before the click handler since the click handler
                    ; runs only once the mouse is released.
                    (when @waiting?
                      (single-click-fn e)
                      (reset! waiting? false)))
                  500))))
           :on-mouse-down
           (fn [e]
            ; Set this for the click handler in case a double
            ; click is happening.
            (reset! waiting? false)
            ; Only run this if it is a left click, or the event
            ; listener is not removed until a single click on this
            ; segment is performed again, and will listen on
            ; every click everywhere in the window.
            (when (-> e .-button zero?)
              (js/console.log "mouse down handler running")
              (.addEventListener js/document "selectionchange" selection-handler)))
           :on-double-click #(double-click-fn %)}
          some content here]])))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接