Debouncing input events in panels

Has anyone had any success/is it possible to call editDocument() in a debounced “context”, i.e., use something like the UnderscoreJS _.debounce function with, say, an onchange in an <input> and then call editDocument in there? The problem I’m facing is that, of course, doing so won’t call editDocument in the same tick as the UI interaction anymore, meaning it gets impossible:

If it isn’t possible, I’ll convert this to a feature request, which is why I’ll already put a use-case here :wink:.

Use case

I’m currently writing a Markdown plugin (i.e., am extremely close to releasing it, but am now developing a panel for the first update of the plugin). In this panel, there is a textarea with which the Markdown contents of the selected text node can get edited. To avoid the problems described by @Velara in Add modal like support for panels preventing new selections - #3 by pklaschka, the text gets updated on user input, meaning Markdown has to get reparsed everytime. While I have a very performant Markdown parser, it still isn’t optimal that this happens every time a user enters a new character. I would therefore like to debounce the input, as demonstrated in this example of the VueJS docs: Examples | Vue.js to avoid such problems.

I agree that this use case won’t be possible given the limitation Kerri described above. @kerrishotts to confirm

What does debounce do? This was the code:

new Vue({
  el: '#editor',
  data: {
    input: '# hello'
  },
  computed: {
    compiledMarkdown: function () {
      return marked(this.input, { sanitize: true })
    }
  },
  methods: {
    update: _.debounce(function (e) {
      this.input = e.target.value
    }, 300)
  }
})

Is it a way to wait 300 ms between each update? I’m doing something like this manually in my panel but maybe if there was an official method it might help? Also, are you copying to clipboard on every input change?

1 Like

Hi @Velara,

pretty much. Debounce effectively limits the number of updates per second, meaning if you type, say, 5 letters in a second, you can limit the update to only get called, e.g., 2 times per second. Still, it will always do the last update (so that nothing gets lost). For me, I read the textareas input, parse it as Markdown and update the text layer contents accordingly. Since parsing Markdown is a bit costly performance-wise (at least for doing so every time the user presses a key), debouncing would allow to limit parsing to, e.g., once per second, leading to much smoother performance and UX.

Therefore, while I’m not copying to clipboard, I’m still doing a scenegraph change requiring me to use editDocument

I’m interested in the answer as well. I’m trying to use editDocument on a click that triggers a fetch and my UI event gets disconnected from the editDocument.

1 Like

Note that you might be able to use promises to solve this, since editDocument will deal with a Promise return.

1 Like

I have a theory how I could get it to work. I’ll test it later and keep you updated :slightly_smiling_face:

Pseudocode

// Text to apply
let tte <- undefined

textarea.onchange =>
  if !tte
    editDocument async =>
      await delay
      apply tte to text node
  tte <- value;

Update: The idea didn’t work :laughing:. I hadn’t known editDocument (in an asynchronous state) actually blocks the UI elements…

As it looks like this won’t work (as the only way to keep the editing time frame open blocks the UI), I’m now converting this into a feature request, with the use-case described above.

Hey guys. Are there any news on this topic? This is driving me nuts and it’s a big show stopper for my current plugin. no matter what concept I come up with, I end up seeing “session already open” or “[Error: Panel plugin edits must be initiated from a supported UI event]”…

There are a few different cases being discussed here, I think, with slightly different motivations. I’d recommend a different approach for each:

  • Throttling updating of what’s displayed in panel UI (i.e. debouncing the panel update() event/callback) – for nontrivial UI (especially UI that makes network or renditions requests when updating), debouncing is highly recommended to minimize performance impact on XD while your panel is open. You should be able to use normal setTimeout debounce techniques, e.g. _.debounce() from Lodash.

  • Throttling document edits when user is interacting with panel UI – I would not recommend having a goal of debouncing these. If the user is actively interacting with the plugin, it’s best to max out efforts to be as responsive as possible. Real-time visual feedback is a hallmark of XD’s general UX philosophy.

    XD is built for this: its threading architecture will only send your code UI events as fast as you can handle them, so you shouldn’t generally have to do anything special in case your plugin can’t keep up with 60fps updating. And XD generally does a good job of preventing fast, consecutive edits from flooding the Undo stack (see mergeId in the editDocument() API).

  • Waiting for async tasks (e.g. network) before performing an edit – This isn’t really debouncing at all, but some folks on this thread (@Manjila or @simonwidjaja ?) might be more talking about this situation. In these cases, respond to the UI event by synchronously calling editDocument() but return a Promise to it. This is the same as how a menu item command can return a Promise; see the editDocument() docs for details.

1 Like