Om格式化文本输入组件光标跳跃

时间:2016-05-11 06:27:22

标签: validation reactjs clojurescript om

(我对OM和React来说有点新鲜,如果在某处我还没有看到,那就道歉了。)

我正在尝试创建一个文本框,在编辑过程中验证并格式化其输入,类似于autoNumeric JS库。

我的申请是用于验证美元货币(例如$1,234.56形式的字符串)。具体来说,文本框应该阻止用户在文本框中键入无效字符(如字母),并应该动态格式化其内容以包含美元符号($)和标记数千个位置的逗号。该组件应防止删除这些格式字符。 JS toLocaleString函数已经很好地处理了格式化,但是我在输入后遇到了UI和光标跳跃的位置。

我找到了一个相关的react github issue,它描述了在更新app-state后如何防止光标跳转。捕获该线程的解决方案是a JS Bin,但是它是使用React在javascript中编写的。

我正在尝试使用ClojureScript和Om实现一个版本,但我遇到了麻烦。如果我仅使用组件本地状态实现组件,并且没有全局app-state,则游标执行预期的行为并且不会跳转(类似于autoNumeric库)。最终,我想将值传播到全局app-state。但是,当我让组件更新全局app-state时,光标会在每次更改输入字段后跳转。

我尝试过很多变体,包括不使用本地状态(只有全局应用程序状态)以及全局和本地之间的混合。无论何时我包含全局app-state,光标都会跳转。

这是我当前的实现,它尚未更新全局app-state,但没有跳转游标。 (注意,我正在使用om-tools,因此语法可能与vanilla om略有不同。)

(defn raw-number [s]
  (let [s (if (empty? s "0" (str s)))]
    (js/parseFloat (str/replace s #"[^-0-9.]" ""))))

(defn format-currency [s]
  (.toLocaleString (raw-number s) "en-US" #js {:style "currency" :currency "USD"}))

(defcomponentk validated-input
  "Component that prevents the cursor from jumping after updates."
  [data owner opts]
  (will-receive-props [_ new-state]
    (when-let [dom-node (om/get-node owner "validated-input")]
      (let [old-length (-> dom-node .-value count)
            old-idx (.-selectionStart dom-node)
            _ (set! (.-value dom-node) new-state)
            new-len (count new-state)
            new-idx (max 0 (-> new-len (- old-length) (+ old-idx)))]
        (om/update! data :cursor-pos new-idx))))

  (did-update [_ _ prev-state]
    (let [new-idx (:cursor-pos data)
          dom-node (om/get-node owner "validated-input")]
      (set! (.-selectionStart dom-node) new-idx)
      (set! (.-selectionEnd dom-node) new-idx)))

  (render [_]
    (dom/input
     (merge
      opts
      {:type "text"
       :ref "validated-input"}))))


(defcomponentk formatted-input
  "Formats the text using the provided formatter before rendering it."
  [data owner [:opts formatter :as opts]]
  (init-state [_]
    {:text (formatter "")})

  (render [_]
    (let [text (om/get-state owner :text)]
      (validated-input
       text ;; << I recognize this to be shady, passing local state down. but it works to prevent the cursor jumping.
       {:opts
        (merge
         opts
         {:value text
          :on-change (fn [e]
                       (let [new-val (formatter (str (.. e -target -value)))]
                         (om/set-state! owner :text new-val)))})}))))

1 个答案:

答案 0 :(得分:0)

我认为我被绊倒了,因为对本地状态的更改和对全局状态的更改都触发了重新渲染。我最终使用了一种有点hacky的解决方法,使用本地状态的原子来避免重新触发重新渲染。

该解决方案在桌面上运行良好,但由于它依赖于on-clickon-key-down事件,我不确定它是否能在移动设备上正常运行。

无论如何,它都符合我的需要。批评是受欢迎的!

(defn save-position! [owner event]
  (let [found-node (om/get-node owner "validated-input")
        idx-atom (om/get-state owner :idx-atom)
        new-idx (if (nil? found-node) @idx-atom (.-selectionStart found-node))]
    (reset! idx-atom new-idx)))

(defcomponentk formatted-input
  "Formats the text using the provided formatter before rendering it."
  [data owner [:opts formatter :as opts]]
  (init-state [_]
    {:idx-atom (atom 0)})

  (did-update [_ prev-props prev-state]
    (let [dom-node (om/get-node owner "validated-input")

          old-text (:text prev-props)
          new-text (:text data)

          old-len (count old-text)
          new-len (count new-text)

          delta-len (- new-len old-len)
          old-idx @(:idx-atom prev-state)

          new-idx (max 0 (+ delta-len old-idx))]
      (set! (.-selectionStart dom-node) new-idx)
      (set! (.-selectionEnd dom-node) new-idx)))

  (render [_]
    (dom/input
     (merge
      opts
      {:value (formatter (str (:text data)))
       :type "text"
       :ref "validated-input"
       :on-click (fn [dom-node] (save-position! owner dom-node))
       :on-key-down (fn [dom-node] (save-position! owner dom-node))
       :on-change (fn [dom-node]
                    (let [new-val (formatter (str (.. dom-node -target -value)))
                          old-idx @(om/get-state owner :idx-atom)
                          found-node (om/get-node owner "validated-input")]
                      (when (not= new-val (.-value found-node))
                        (set! (.-value found-node) new-val)
                        (set! (.-selectionStart found-node) old-idx)
                        (set! (.-selectionEnd found-node) old-idx))
                      (om/update! data :text new-val)))}))))