Fork me on GitHub
#re-frame
<
2022-02-23
>
ag21:02:46

It's been a while since I did any front-end stuff. I'm currently struggling with getting contentEditable div to act right - the caret keeps moving around, etc. If I remember it right, there was some trick with storing data in a local state (atom) and then queuing for the change in app-db (so the changes in the field are reflected elsewhere). Can someone please point to an example. Thanks.

p-himik21:02:22

There are a bunch of links and discussions here https://github.com/reagent-project/reagent/issues/79 and here https://github.com/day8/re-frame/issues/39 Also relevant: https://day8.github.io/re-frame/FAQs/laggy-input/ tl;dr: avoid data roundtrips that re-render the component. Instead, use a stateful component that sends out its state on every change and that's re-rendered only if the external state is different from the internal one. In terms of specific code, re-com has a bunch of components that use that technique.

ag21:02:51

I know that article about the laggy-input. But it doesn't solve the problem with the caret. I'm gonna look through the other things you shared. Thank you!

ag21:02:15

also, I think there's no :default-input for contentEditable divs

ag21:02:26

I've gotten very close by storing the position of the caret and then restoring it after the input change, but the code looks a bit convoluted and I decided to abandon this approach. I think it makes sense to solve that by using local state. re-com seems to be doing it that way.

p-himik22:02:17

There shouldn't be any need on storing the caret position. Although I've never actually had to deal with editable divs. Let my try something out...

p-himik22:02:18

How are you handling the change of the text - via :on-input?

ag22:02:54

I've tried different handlers: on-input, on-key-down/up. Then I've found 'react-contenteditable' package on npm, but it still has the same problem. Like something simple like this:

[:> ContentEditable
 {:html text
  :on-change (fn [ev]
               (let [new-text (-> (.. ev -currentTarget -innerHTML))]
                 (rf/dispatch-sync [:set-text new-text])))}]

ag22:02:16

doesn't work properly, caret often moves around

p-himik22:02:42

Hmm, interesting. So it seems that the technique with external+internal model tracking that works with input fields doesn't actually work with editiable divs. Let me try that NPM package you mentioned.

p-himik22:02:17

Aha, a combination of the two seems to work:

(defn editable-div [{:keys [value on-change]}]
  (r/with-let [external-value (r/atom value)
               internal-value (r/atom (or value ""))]
    (when (not= @external-value value)
      (reset! external-value value)
      (reset! internal-value value))
    [:> ContentEditable {:html      @internal-value
                         :on-change (fn [^js event]
                                      (let [txt (.. event -target -value)]
                                        (reset! internal-value txt)
                                        (on-change txt)))}]))

ag22:02:44

Oh. Let me try that. Thank you!

ag22:02:17

Hmm. There's something that I'm missing. It's weird. When it's a single line of text - everything works fine. As soon I add new lines - the caret starts jumping to the end again. But I think I'm on the right track. Thank you for your awesome help and guidance.

👍 2
ag23:02:06

(defn editable-field [data key]
  (let [val (get data key)
        external-val (r/atom val)
        internal-val (r/atom (if (nil? @external-val) "" @external-val))]
    (when (not= @external-val val)
      (reset! external-val val)
      (reset! internal-val val))
    [:div {:class '[p-2]}
     [:h2 {:class '[font-medium mb-2]} (titleize key)]
     [:> ContentEditable
      {:class '[w-full border p-2 bg-white]
       :html @internal-val
       :on-change (fn [^js ev]
                    (let [new-text (-> (.. ev -currentTarget -innerHTML))]
                      (reset! internal-val new-text)
                      (rf/dispatch-sync [::module-types/update-draft
                                         {:key key
                                          :text new-text}])))}]]))

;; and to render it, from parent component

(for [k [:former_title :short_description :description :tips]]
     ^{:key k} [editable-field data k])
Everything seems to be working. Once again, thank you Eugene for saving me hours of struggle!

p-himik23:02:21

NP! Glad you got it working.