DEV Community

Kagol
Kagol

Posted on

How does Quill convert Delta to DOM? 3/10

The introduction

In the last post, we showed how Quill uses Delta to describe editor content and its variations. We learned that Delta is just a normal JSON structure with only three actions and one attribute, but it is extremely expressive.

So how does Quill apply Delta data and render it into the editor?

How to use setContents

There is an API in Quill called setContents that renders Delta data into the editor. This post will focus on how this API is implemented.

Take the Delta data from the previous post as an example:

const delta = { "ops": [
    { "insert": "Hello " },
    { "insert": "World", "attributes": { "bold": true } },
    { "insert": "\n" } ]
}
Enter fullscreen mode Exit fullscreen mode

Once we have created an instance of Quill using new Quill(), we can call its API.

const quill = new Quill('#editor', {
  theme: 'snow'
});
Enter fullscreen mode Exit fullscreen mode

Let's try calling the setContents method, passing in the Delta data we just had:

quill.setContents(delta);
Enter fullscreen mode Exit fullscreen mode

The expected formatted text appears in the editor:

image

Deep into setContents

By looking at the source of setContents, we call the modify method, passing in a function:

setContents(delta, source = Emitter.sources.API) {
  return modify.call( this, () => {
    delta = new Delta(delta);
    const length = this.getLength();
    const deleted = this.editor.deleteText(0, length);
    const applied = this.editor.applyDelta(delta);
    ... // The non-core code is omitted for ease of reading
    return deleted.compose(applied);
  }, source, );
}
Enter fullscreen mode Exit fullscreen mode

The call method is used to call modify to change its internal this pointer, which refers to the current Quill instance. Since the modify method is not defined in the Quill class, it needs to be done.

Let's look at the anonymous function passed in the modify method instead of the modify method.

This function does three main things:

  1. Delete all the original content in the editor
  2. Apply the incoming Delta data and render it to the editor
  3. Returns the Delta data after combining 1 and 2

Let's focus on step 2, which involves the applyDelta method of the Editor class.

How the applyDelta method works

As you might guess by its name, the purpose of this method is to apply and render the incoming Delta data into the editor.
The implementation, as we can probably guess, is that the ops array in the loop Delta is applied to the editor one by one.
Its source code is 54 lines long and looks something like this:

applyDelta(delta) {
  let consumeNextNewline = false;
  this.scroll.update();
  let scrollLength = this.scroll.length();
  this.scroll.batchStart();
  const normalizedDelta = normalizeDelta(delta);

  normalizedDelta.reduce((index, op) => {
    const length = op.retain || op.delete || op.insert.length || 1;
    let attributes = op.attributes || {};
    // 1.Insert text
    if (op.insert != null) {
      if (typeof op.insert === 'string') {
        // Plain text content
        let text = op.insert;
        ... // For ease of reading, omit non-core code
        this.scroll.insertAt(index, text);
        ... // For ease of reading, omit non-core code
      } else if (typeof op.insert === 'object') {
        // Rich text content
        const key = Object.keys(op.insert)[0];
        // There should only be one key
        if (key == null) return index;
        this.scroll.insertAt(index, key, op.insert[key]);
      }
      scrollLength += length;
    }
    // 2.Formatting the text
    Object.keys(attributes).forEach(name => {
      this.scroll.formatAt(index, length, name, attributes[name]);
    });
    return index + length;
  }, 0);
  ... // For ease of reading, omit non-core code
  this.scroll.batchEnd();
  this.scroll.optimize();
  return this.update(normalizedDelta);
}
Enter fullscreen mode Exit fullscreen mode

As we guessed, this method is to use the Delta reduce method to iterate over the incoming Delta data, separating the logic of content insertion and content deletion. The iteration of content insertion mainly does two things:

  1. To insert plain text or rich text content: insertAt
  2. Formats the text: formatAt

At this point, we have parsed the logic to apply and render the Delta data to the editor.

Summary

Here's a summary:

  1. There's no logic in the setContents method itself, it just calls the modify method, right
  2. The applyDelta method on the Editor object is called in the anonymous function passed to the modify method
  3. The applyDelta method iterates over the incoming Delta data and in turn inserts/formats/deletes the editor content described by the Delta data

About DevUI team

DevUI is a team with both design and engineering perspectives, serving for the DevCloud platform of Huawei Cloud and several internal middle and background systems of Huawei, serving designers and front-end engineers.

Official website: devui.design

Ng component library: ng-devui (Welcome to star🌟)

by Kagol

Top comments (0)