DevUI is a team with both design and engineering perspectives, serving huawei Cloud DevCloud platform and several huawei internal middle and background systems, as well as designers and front-end engineers. Design Ng Component library: Ng-devUI
The introduction
This article is based on DevUI rich text editor development practices and Quill source code written.
EditorX is a good, easy to use, powerful rich text editor developed by DevUI. It is based on Quill at the bottom, with many extensions to enhance the editor’s capabilities.
Quill is an API-driven, open source Web rich text editor that supports format and module customization. It currently has over 25K stars on Github.
If you have not been exposed to Quill, it is recommended to go to the Quill website to understand the basic concept of it.
By reading this article, you will learn:
- Know what Quill module is, how to configure Quill module
- Why create a Quill module and how to create a custom Quill module
- How does the Quill module communicate with Quill
- Learn more about Quill’s modularity mechanisms
A preliminary study of Quill module
Anyone who has developed rich text applications with Quill should be familiar with Quill’s modules.
For example, when we need to customize our own toolbar buttons, we configure the toolbar module:
var quill = new Quill('#editor', {
theme: 'snow',
modules: {
toolbar: [['bold', 'italic'], ['link', 'image']]
}
});
Copy the code
The modules parameter is used to configure the module.
The Toolbar parameter is used to configure the toolbar module, passing in a two-dimensional array that represents the grouped toolbar buttons.
The rendered editor will contain four toolbar buttons:
To see the Demo above, poke the configuration toolbar module.
The Quill module is a normal JS class
So what is a Quill module? Why should we understand and use the Quill module?
The Quill module is just a normal JavaScript class with constructors, member variables, and methods.
Here is the approximate source structure of the toolbar module:
Class Toolbar {constructor(quill, options) {// parse the incoming module's Toolbar configuration (i.e., 2-d arrays), } addHandler(format, handler) {this. }... }Copy the code
You can see that the toolbar module is just a normal JS class. With the quill instance and options configuration passed into the constructor, the module class can control and manipulate the editor with the Quill instance.
For example, the toolbar module constructs a toolbar container based on the Options configuration, fills the container with button/drop down elements, and binds the button/drop down processing events. The end result is a rendered toolbar above the body of the editor. You can format elements in the editor via toolbar buttons/dropdown boxes, or insert new elements into the editor.
The Quill module is so powerful that we can use it to extend the power of the editor to achieve what we want.
In addition to the toolbar module, Quill also has some useful modules built in. Let’s take a look.
Quill built-in module
Quill has 6 built-in modules:
- The Clipboard and paste version
- History Operation History
- Keyboard Events
- Syntax highlighting
- The Toolbar Toolbar
- Uploader Uploads a file
Clipboard, History, and Keyboard are required built-in modules for Quill. They are automatically enabled and can be configured but cannot be canceled. Among them:
The Clipboard module handles copy/paste events, matching of HTML element nodes, and HTML-to-Delta transitions.
The History module maintains a stack of actions that record every editor action, such as inserting/deleting content, formatting content, etc., making it easy to undo/redo functions.
The Keyboard module is used to configure Keyboard events and facilitate Keyboard shortcuts.
The Syntax module is used for code Syntax highlighting. It relies on the external library highlight.js, which is closed by default. To use Syntax highlighting, you must install highlight.js and turn it on manually.
Other modules will not be introduced, you can refer to Quill’s module documentation.
Quill module configuration
Just mentioned the Keyboard event module, let’s take another example to deepen the understanding of the Quill module configuration.
The Keyboard module supports a number of Keyboard shortcuts by default, such as:
- The bold shortcut is Ctrl+B;
- The shortcut to hyperlinks is Ctrl+K;
- The shortcut for undo/rollback is Ctrl+Z/Y.
But it does not support the strikeout shortcut. If we want to customize the strikeout shortcut, let’s say Ctrl+Shift+S, we can configure it like this:
modules: { keyboard: { bindings: { strike: { key: 'S', ctrlKey: true, shiftKey: true, handler: function(range, context) { const format = this.quill.getFormat(range); this.quill.format('strike', ! format.strike); } }, } }, toolbar: [['bold', 'italic', 'strike'], ['link', 'image']] }Copy the code
To see the Demo above, please configure the keyboard module.
In the process of developing a rich text editor with Quill, we encountered various modules and created many custom modules, all of which were configured via the Modules parameter.
Next we will attempt to create a custom module to deepen our understanding of Quill modules and module configuration.
Create a custom module
In the previous section, we learned that the Quill module is an ordinary JS class, and there is nothing special about it. The class initializes with an instance of Quill and the module’s options configuration parameter, which then controls and enhances the editor’s functionality.
When the Quill built-in modules don’t meet our needs, we need to create custom modules to implement the functionality we want.
For example, in the Rich text component of EditorX, there is a function to count the current number of words in the editor. This function is implemented through the custom module, and we will show you how to wrap this function into a separate Counter module.
Creating a Quill module involves three steps:
Step 1: Create the module class
Create a new JS file with a normal JavaScript class inside.
class Counter {
constructor(quill, options) {
console.log('quill:', quill);
console.log('options:', options);
}
}
export default Counter;
Copy the code
This is an empty class with nothing but the options configuration information for the Quill instance and module printed in the initialization method.
Step 2: Configure module parameters
modules: {
toolbar: [
['bold', 'italic'],
['link', 'image']
],
counter: true
}
Copy the code
Instead of passing the configuration data, we simply enable the module, only to find that no information is printed.
Step 3: Register the module
To use a module, you need to register the module class with the quill. register method before Quill is initialized (we’ll see how this works later), and since we need to extend a module, we need to start the prefix with modules:
import Quill from 'quill';
import Counter from './counter';
Quill.register('modules/counter', Counter);
Copy the code
At this point we can see that the information has been printed.
Add the logic of the module
At this point we add some logic to the Counter module to count the number of words in the current editor:
constructor(quill, options) { this.container = quill.addContainer('ql-counter'); quill.on(Quill.events.TEXT_CHANGE, () => { const text = quill.getText(); Const char = text.replace(/\s/g, ""); This.container. innerHTML = 'current word count: ${char.length}'; }); }Copy the code
In the Counter module’s initialization method, we call Quill’s addContainer method to add an empty container for the editor to hold the word count module’s contents, and then bind the editor’s content change event so that the words count in real time as we enter content into the editor.
In the Text Change event, we call the getText method of the Quill instance to get the plain Text content in the editor, then use regular expressions to strip out the whitespaces, and finally insert the word count information into the character count container.
The general effect of the display is as follows:
To see the Demo above, please use the custom character statistics module.
Module loading mechanism
After we have a preliminary understanding of Quill module, we will want to know how Quill module works, the following will start from Quill initialization process, through the toolbar module example, Quill module loading mechanism in depth. (this summary involves the analysis of Quill source code, do not understand the place welcome to leave a comment to discuss)
Initialization of the Quill class
When we execute new Quill(), we execute the Constructor method of the Quill class, which is located in the core/quill.js file of the Quill source code.
The approximate source structure of the initialization method is as follows (remove code irrelevant to module loading) :
constructor(container, options = {}) { this.options = expandConfig(container, options); // Extend the configuration data, including adding topic classes... this.theme = new this.options.theme(this, this.options); // 1. Initialize the theme instance using the theme class in Options // 2. Add required module this.keyboard = this.theme.addModule('keyboard'); this.clipboard = this.theme.addModule('clipboard'); this.history = this.theme.addModule('history'); this.theme.init(); // 3. Initialize the theme, which is the core of the module rendering (the actual core is the addModule method called in it), traverses all the configured module classes and renders them to the DOM... }Copy the code
When Quill initializes, it uses the expandConfig method to extend the options passed in, adding elements such as the theme class to initialize the theme. (Default BaseTheme without theme configuration)
The built-in required module is then mounted into the topic instance by calling the addModule method of the topic instance.
Finally, the init method of the theme instance is called to render all modules to the DOM. (More on how this works later)
If the theme is Snow, you will see the toolbar above the editor:
If the bubble theme is used, then when a piece of text is selected, a toolbar float will appear:
Next, we take the toolbar module as an example to introduce the loading and rendering principles of Quill module in detail.
Loading the toolbar module
Using the snow theme as an example, configure the following parameters when initializing the Quill instance:
{
theme: 'snow',
modules: {
toolbar: [['bold', 'italic', 'strike'], ['link', 'image']]
}
}
Copy the code
The this.theme obtained from Quill’s constructor method is an instance of the SnowTheme class. Executing the this.theme.init() method calls the init method of its parent theme, which is located in the core/theme.js file.
Init () {// iterate through Quill options' modules arguments, Object.keys(this.options.modules).foreach (name => {if (this.modules[name] == null) { this.addModule(name); }}); }Copy the code
It will iterate through all the modules in the options.modules parameter, calling the addModule method of BaseTheme in the themes/base.js file.
addModule(name) {
const module = super.addModule(name);
if (name === 'toolbar') {
this.extendToolbar(module);
}
return module;
}
Copy the code
This method will first execute the addModule method of its parent class to initialize all modules. If it is a toolbar module, it will carry out additional processing after the initialization of the toolbar module, mainly to build ICONS and bind hyperlink shortcut keys.
Let’s go back to the BaseTheme addModule method, which is at the heart of module loading.
This method was seen earlier when we introduced Quill’s initialization, and was called when the three built-in required modules were loaded. All module loads actually go through this method, so it’s worth exploring this method, which is located in core/theme.js.
addModule(name) { const ModuleClass = this.quill.constructor.import(`modules/${name}`); // Import the module class. When creating a custom module, you need to register the class with Quill using the quill. register method. To import / / initialization module class this. Modules [name] = new ModuleClass (enclosing quill, enclosing the options. The modules [name] | | {},); return this.modules[name]; }Copy the code
The addModule method first calls the quill. import method to import module classes (only those that have been registered with the quill. register method).
We then initialize the class and mount the example to the modules member variable of the topic class (which now has an instance of the built-in required module).
In the case of a Toolbar module, the addModule method initializes the Toolbar class, which is located in the modules/toolbar.js file.
class Toolbar { constructor(quill, options) { super(quill, options); // Parse the modules.toolbar parameter, If (array.isArray (this.options.container)) {const Container = document.createElement('div'); addControls(container, this.options.container); quill.container.parentNode.insertBefore(container, quill.container); this.container = container; } else { ... } this.container.classList.add('ql-toolbar'); This. controls = []; this.handlers = {}; Object.keys(this.options.handlers).forEach(format => { this.addHandler(format, this.options.handlers[format]); }); Array.from(this.container.querySelectorAll('button, select')).forEach( input => { this.attach(input); }); . }}Copy the code
When the toolbar module is initialized, it first parses the modules.toolbar parameter, calls the addControls method to generate the toolbar buttons and dropdown boxes (the basic idea is to walk through a two-dimensional array and insert them into the toolbar as buttons/dropdown boxes), and binds them to events.
function addControls(container, groups) { if (! Array.isArray(groups[0])) { groups = [groups]; } groups.forEach(controls => { const group = document.createElement('span'); group.classList.add('ql-formats'); controls.forEach(control => { if (typeof control === 'string') { addButton(group, control); } else { const format = Object.keys(control)[0]; const value = control[format]; if (Array.isArray(value)) { addSelect(group, format, value); } else { addButton(group, format, value); }}}); container.appendChild(group); }); }Copy the code
The toolbar module is thus loaded and rendered into the rich text editor, facilitating editor operations.
Now a summary of the module loading process:
- The starting point for module loading is the init method of the Theme class, which loads all modules configured in the option.modules parameter into the Theme class member variable modules and merges them with the built-in required modules.
- The addModule method imports the module class with the import method, and then creates the module instance with the new keyword.
- When a module instance is created, the initialization method of the module is executed, executing the specific logic of the module.
Here is a diagram of a module versus an editor instance:
conclusion
This article introduces the configuration method of Quill module through 2 examples, so that you have an intuitive impression of Quill module.
Then through the character statistics module this simple example to introduce how to develop a custom Quill module, the rich text editor function to extend.
Finally, by analyzing the initialization process of Quill, the loading mechanism of Quill module is gradually introduced, and the loading process of toolbar module is described in detail.
Join us
We are DevUI team, welcome to come here and build elegant and efficient man-machine design/R&D system with us. Recruitment email: [email protected].
The text/DevUI Kagol