How do I change slate editor value using Javascript (DOM manipulation)

2.9k Views Asked by At

I want to change the text in the slate editor using DOM manipulation via Javascript.

Dynamically change the values filled into editable components in a particular webpage. The website has a combination of simple input elements and slate editor. The values input into these fields are saved once the focus gets off them.

I have managed to change the text of normal input elements and on blur the data a gets saved. I am not sure how to achieve the same behaviour on slate editor elements.

I have tried to dispatch events on the slate editor component using dispatchEvent, but it did not work.

<div
  data-slate-editor="true"
  data-key="18"
  contenteditable="true"
  class="
    uta_c_editor__slate-editor uta_c_editor__slate-editor--dictation-disabled
  "
  autocorrect="on"
  spellcheck="false"
  role="textbox"
  data-gramm="false"
  style="
    outline: none;
    white-space: pre-wrap;
    overflow-wrap: break-word;
    -webkit-user-modify: read-write-plaintext-only;
  "
>
  <div data-slate-object="block" data-key="19" style="position: relative">
    <span data-slate-object="text" data-key="20"
      ><span data-slate-leaf="true" data-offset-key="20:0"
        ><span data-slate-string="true">Testing in progress.</span></span
      ></span
    >
  </div>
</div>

Tried dispatching the below event on this element.

let changeEvent = new Event('change', { 
'bubbles': true, 
'detail': {
    value: "Testing Slate Js"
 } });

targetSlateElement.dispatchEvent(changeEvent)

Tried to update its textContent like below:

targetSlateElement.textContent = "Testing Slate JS"

Above method did change its content value but on blur it it resets the content to older content.

2

There are 2 best solutions below

0
On

It's awful, and deprecated, but here's one way to do it:

let selection = window.getSelection();
selection.removeAllRanges();
// remove any current selections
// and then select all children of the slate editor
for (let child of slateElement.children) {
    let range = document.createRange();
    range.selectNode(child);
    selection.addRange(range);
}
// finally use document.execCommand to put our desired replacement text in.
document.execCommand('insertText', false, 'Replacement Text');

(Note that if you're selecting a specific child that you want to change the text of, not the entire slate editor, you only need to select that one node)

As I mentioned, this is deprecated, it uses document.execCommand, which for now works in all major browsers but there's no saying when it will stop.

0
On

One approach could be writing the text directly to the internal memoizedProps property of the corresponding component. This is also how the BTTV browser plugin works for Twitch.

For an example on how the DOM from a Slate editor looks like you can check it out on their own example page.

To get the editor node you can start with document.querySelector('div[role="textbox"]');.

Then you need to find the node which holds the proper data in memoizedProps. You can use the following helper functions from the BTTV plugin:

function getChatInput(element = null) {
    let chatInput;
    try {
        chatInput = searchReactParents(
            getReactInstance(element),
            (n) => n.memoizedProps
                      && n.memoizedProps.componentType != null
                      && n.memoizedProps.value != null
        );
    } catch (_) {}
    return chatInput;
}

function getReactInstance(element) {
    for (const key in element) {
        if (key.startsWith('__reactInternalInstance$')) {
            return element[key];
        }
    }
    return null;
}

function searchReactParents(node, predicate, maxDepth = 15, depth = 0) {
    try {
        if (predicate(node)) {
            return node;
        }
    } catch (_) {}
    if (!node || depth > maxDepth) {
        return null;
    }
    const {return: parent} = node;
    if (parent) {
        return searchReactParents(parent, predicate, maxDepth, depth + 1);
    }
    return null;
}

When you found the correct node, then you can set the text like in the example below (it uses some lines from another function from the BTTV plugin):

var textbox = document.querySelector('div[role="textbox"]');
var textboxInput = getChatInput(textbox);
if (textboxInput == null) {
    return;
}
var text = 'example text set in slate editor';
textboxInput.memoizedProps.value = text;
textboxInput.memoizedProps.setInputValue(text);
textboxInput.memoizedProps.onValueUpdate(text);