In this new post, I show you how to create a Blazor component for Quill. With Quill you can add to your application a nice and easy to use web editor.
As a point of interest, I use interaction with JavaScript. You can easily create re-usable custom controls for your Blazor applications even if they contain assets such as JavaScript.
First, you can download the source code from GitHub or download my Nuget package.
Then, for more documentation, example and components about Blazor, here same links in this blog:
- Getting Started With C# And Blazor
- Setting Up A Blazor WebAssembly Application
- Working With Blazor’s Component Model
- Secure Blazor WebAssembly With IdentityServer4
- Blazor Using HttpClient With Authentication
- InputSelect component for enumerations in Blazor
- Use LocalStorage with Blazor WebAssembly
- Modal Dialog component for Blazor
- Create Tooltip component for Blazor
- Consume ASP.NET Core Razor components from Razor class libraries | Microsoft Docs
Table of contents
What is Quill?
Quill is a free, open source WYSIWYG editor built for the modern web. With its modular architecture and expressive API, it is completely customizable to fit any need.
Features
So, Blazor Quill Component is a custom reusable control that allows us to easily consume Quill and place multiple instances of it on a single page in our Blazor application. Also, it will allow us to implement the following features:
- Exports editor contents in Text, HTML, and Quill’s native Delta format
- Allows initial content to be set declaratively in the control or programmatically
- Provides a read only mode, suitable for displaying Quill’s native Delta format
- Allows custom toolbars to be defined
- Allows custom themes
- Has an inline editor mode that also allows custom toolbars
Create the component
So, I have to create a new Razor Class Library. In Visual Studio 2019, Add a new project and select this option from the list and then follow the wizard. My project is called PSC.Blazor.Components.Quill but fell free to use the name you like.
Now, the component has only 3 files:
BlazorQuill.js
under wwwrootQuillInterop.cs
QuillEditor.razor
Before anything else, add in _Imports.razor
the following line to use JSInterop
:
@using Microsoft.JSInterop
Add BlazorQuill.js
So, under the wwwroot we can add static files like JavaScript, images, CSS and so on. Under this folder, I’m going to create the BlazorQuill.js
. This file contains QuillFunctions
function to invoke changes to the QuillJs
. The code for this file is the following
(function () {
window.QuillFunctions = {
createQuill: function (
quillElement, toolBar, readOnly,
placeholder, theme, debugLevel) {
var options = {
debug: debugLevel,
modules: {
toolbar: toolBar
},
placeholder: placeholder,
readOnly: readOnly,
theme: theme
};
new Quill(quillElement, options);
},
getQuillContent: function (quillElement) {
return JSON.stringify(quillElement.__quill.getContents());
},
getQuillText: function (quillElement) {
return quillElement.__quill.getText();
},
getQuillHTML: function (quillElement) {
return quillElement.__quill.root.innerHTML;
},
loadQuillContent: function (quillElement, quillContent) {
content = JSON.parse(quillContent);
return quillElement.__quill.setContents(content, 'api');
},
enableQuillEditor: function (quillElement, mode) {
quillElement.__quill.enable(mode);
}
};
})();
The functions are:
createQuill
: this function is responsible to applyQuillJS
to the elements in the page.getQuillContent
returns the content in theQuillJS
in Delta formatgetQuillText
: this returns only the text from the editorgetQuillHTML
returns the HTML from the editorloadQuillContent
: this function imports a document in Delta format in the editorenableQuillEditor
QuillInterop
So, how does our application know how to call the JavaScript? I have to create something that sits in the middle between JavaScript and Blazor. QuillInterop
is the connection between them based on JSInterop
.
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System.Threading.Tasks;
namespace PSC.Blazor.Components.Quill
{
public static class QuillInterop
{
private const string strCreateQuill = "QuillFunctions.createQuill";
private const string strGetText = "QuillFunctions.getQuillText";
private const string strGetHTML = "QuillFunctions.getQuillHTML";
private const string strGetContent = "QuillFunctions.getQuillContent";
private const string strLoadQuillContent = "QuillFunctions.loadQuillContent";
private const string strEnableQuillEditor = "QuillFunctions.enableQuillEditor";
internal static ValueTask<object> CreateQuill(
IJSRuntime jsRuntime,
ElementReference quillElement,
ElementReference toolbar,
bool readOnly,
string placeholder,
string theme,
string debugLevel)
{
return jsRuntime.InvokeAsync<object>(
strCreateQuill,
quillElement, toolbar, readOnly,
placeholder, theme, debugLevel);
}
internal static ValueTask<string> GetText(
IJSRuntime jsRuntime,
ElementReference quillElement)
{
return jsRuntime.InvokeAsync<string>(
strGetText,
quillElement);
}
internal static ValueTask<string> GetHTML(
IJSRuntime jsRuntime,
ElementReference quillElement)
{
return jsRuntime.InvokeAsync<string>(
strGetHTML,
quillElement);
}
internal static ValueTask<string> GetContent(
IJSRuntime jsRuntime,
ElementReference quillElement)
{
return jsRuntime.InvokeAsync<string>(
strGetContent,
quillElement);
}
internal static ValueTask<object> LoadQuillContent(
IJSRuntime jsRuntime,
ElementReference quillElement,
string Content)
{
return jsRuntime.InvokeAsync<object>(
strLoadQuillContent,
quillElement, Content);
}
internal static ValueTask<object> EnableQuillEditor(
IJSRuntime jsRuntime,
ElementReference quillElement,
bool mode)
{
return jsRuntime.InvokeAsync<object>(
strEnableQuillEditor,
quillElement, mode);
}
}
}
QuillEditor.razor
Now, the last (but not least) part of the component, the Razor component. So, here is the magic happens. I defined 2 div
for the toolbar and the QuillJs
element. Then, in the code I define some parameters and the function to expose like GetText()
or GetContent()
. Those functions are invoking the QuillInterop
and get data from or set QuillJs
via Javascript. EditorContent
and ToolbarContent
are the tags I can use in the page of my application to pass the text I want to display and the toolbar I want to use.
@inject IJSRuntime JSRuntime
<div @ref="@ToolBar">
@ToolbarContent
</div>
<div @ref="@QuillElement">
@EditorContent
</div>
@code {
[Parameter] public RenderFragment EditorContent { get; set; }
[Parameter] public RenderFragment ToolbarContent { get; set; }
[Parameter] public bool ReadOnly { get; set; } = false;
[Parameter] public string Placeholder { get; set; } = "Insert text here...";
[Parameter] public string Theme { get; set; } = "snow";
[Parameter] public string DebugLevel { get; set; } = "info";
private ElementReference QuillElement;
private ElementReference ToolBar;
protected override async Task
OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await QuillInterop.CreateQuill(
JSRuntime,
QuillElement,
ToolBar,
ReadOnly,
Placeholder,
Theme,
DebugLevel);
}
}
public async Task<string> GetText()
{
return await QuillInterop.GetText(
JSRuntime, QuillElement);
}
public async Task<string> GetHTML()
{
return await QuillInterop.GetHTML(
JSRuntime, QuillElement);
}
public async Task<string> GetContent()
{
return await QuillInterop.GetContent(
JSRuntime, QuillElement);
}
public async Task LoadContent(string Content)
{
var QuillDelta =
await QuillInterop.LoadQuillContent(
JSRuntime, QuillElement, Content);
}
public async Task EnableEditor(bool mode)
{
var QuillDelta =
await QuillInterop.EnableQuillEditor(
JSRuntime, QuillElement, mode);
}
}
Use the component in an example
Now, I have the component and it is time to test it. In Visual Studio, add a new Blazor WebApplication project in the solution. Then, add the component as dependencies clicking the right-click on Dependencies in the project and then Add project Reference. So, in the _Imports.razor
add the reference to this component adding the following line
@using PSC.Blazor.Components.Quill
Now, the tricky part. We have to add CSS
and JavaScript
in the project. Under wwwroot, you have index.html
. Open it. I want to use the themes for QuillJs
. I have to add the references for those in the head
of the page and there are coming from a CDN. You can save them locally and change the href
. Now, the same for the QuillJs
Javascript
. To add the JavaScript for my component, I have to add a _content
reference.
Here you have the full index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>PSC.BlazorQuillExample</title>
<base href="/" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
<link href="PSC.BlazorQuillExample.styles.css" rel="stylesheet" />
<!-- Quill stylesheet -->
<link href="//cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<link href="//cdn.quilljs.com/1.3.6/quill.bubble.css" rel="stylesheet">
</head>
<body>
<div id="app">Loading...</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
<!-- Quill library -->
<script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
<script src="_content/PSC.Blazor.Components.Quill/BlazorQuill.js"></script>
</body>
</html>
Change the Index.razor
Now, if you replace all the content in the Index.razor
with the following code
@page "/"
@using QuillControl
<QuillEditor @ref="@QuillHtml">
<ToolbarContent>
</ToolbarContent>
<EditorContent>
<h1>Hello World!</h1>
</EditorContent>
</QuillEditor>
@code {
}
The result is only a simple text like in the following image.
Ok, it is working but we want more. at least the toolbar. So, in the QuillEditor
add a ToolbarContent
tag and you can see all the options for customizing the Toolbar in the documentation. Then, I want to add some custom text in the editor for the user as placeholder. For that, there is the EditorContent
tag that accepts HTML code.
<QuillEditor @ref="@QuillHtml">
<ToolbarContent>
<select class="ql-header">
<option selected=""></option>
<option value="1"></option>
<option value="2"></option>
<option value="3"></option>
<option value="4"></option>
<option value="5"></option>
</select>
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<button class="ql-underline"></button>
<button class="ql-strike"></button>
</span>
<span class="ql-formats">
<select class="ql-color"></select>
<select class="ql-background"></select>
</span>
<span class="ql-formats">
<button class="ql-list" value="ordered"></button>
<button class="ql-list" value="bullet"></button>
</span>
<span class="ql-formats">
<button class="ql-link"></button>
</span>
</ToolbarContent>
<EditorContent>
<h4>This Toolbar works with HTML</h4>
<a href="https://puresourcecode.com/">
PureSourceCode
</a>
</EditorContent>
</QuillEditor>
Programmatically retrieving content
In the @code
section of the page, add the following code:
QuillEditor QuillHtml;
string QuillHTMLContent;
public async void GetHTML()
{
QuillHTMLContent = await this.QuillHtml.GetHTML();
StateHasChanged();
}
and in the markup section I have the following code
<button class="btn btn-primary"
@onclick="GetHTML">
Get HTML
</button>
<div style="height: 5px; width: 100%;"></div>
<div style="width: 100%; background-color: #dcdcdc;">
@((MarkupString)QuillHTMLContent)
@QuillHTMLContent
</div>
So, when I run the application, I can click the Get HTML button to display the contents of the editor as HTML on the page as well as display the raw HTML. This happens because the code invokes the QuillInterop
and it invokes the JavaScript that replies to the QuillInterop
the HTML code and the QuillInterop
passes the value to the UI.
Native content
While it is possible to store and retrieve HTML content from the editor control, HTML is unable to capture all formatting that the editor control supports, for example centering text.
To support all the options, you will need to export and load content in the Quill control’s native Delta format.
Add the following markup code:
<QuillEditor @ref="@QuillNative"
Placeholder="Enter non HTML format like centering...">
<ToolbarContent>
<span class="ql-formats">
<select class="ql-font">
<option selected=""></option>
<option value="serif"></option>
<option value="monospace"></option>
</select>
<select class="ql-size">
<option value="small"></option>
<option selected=""></option>
<option value="large"></option>
<option value="huge"></option>
</select>
</span>
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<button class="ql-underline"></button>
<button class="ql-strike"></button>
</span>
<span class="ql-formats">
<select class="ql-color"></select>
<select class="ql-background"></select>
</span>
<span class="ql-formats">
<button class="ql-list" value="ordered"></button>
<button class="ql-list" value="bullet"></button>
<button class="ql-indent" value="-1"></button>
<button class="ql-indent" value="+1"></button>
<select class="ql-align">
<option selected=""></option>
<option value="center"></option>
<option value="right"></option>
<option value="justify"></option>
</select>
</span>
<span class="ql-formats">
<button class="ql-link"></button>
</span>
<span class="ql-formats">
<button class="ql-image"></button>
</span>
</ToolbarContent>
</QuillEditor>
<button class="btn btn-primary"
@onclick="GetContent">
Get Content
</button>
<button class="btn btn-primary"
@onclick="LoadContent">
Load Content
</button>
<div>
@QuillContent
</div>
and in the @code
section add the following code
QuillEditor QuillNative;
string QuillContent;
public async void GetContent()
{
QuillContent = await this.QuillNative.GetContent();
StateHasChanged();
}
public async void LoadContent()
{
await this.QuillNative.LoadContent(QuillContent);
StateHasChanged();
}
When I run the application I see that there is custom placeholder text that was set using the Placeholder property. Also, I see that there are toolbar options such as centering and indenting that are not supported by HTML.
Display read-only and Inline editing
HTML content can be displayed on any page when using the Blazor (MarkupString) tag, however, the Quill native Delta format requires the Quill editor to display.
When you want the content displayed in a read only format, you can set the ReadOnly property on the control, and set the theme to bubble Add the following markup code:
<QuillEditor @ref="@QuillReadOnly"
ReadOnly="true"
Theme="bubble"
DebugLevel="log">
<ToolbarContent>
<select class="ql-header">
<option selected=""></option>
<option value="1"></option>
<option value="2"></option>
<option value="3"></option>
<option value="4"></option>
<option value="5"></option>
</select>
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<button class="ql-underline"></button>
<button class="ql-strike"></button>
</span>
<span class="ql-formats">
<select class="ql-color"></select>
<select class="ql-background"></select>
</span>
<span class="ql-formats">
<button class="ql-list" value="ordered"></button>
<button class="ql-list" value="bullet"></button>
</span>
<span class="ql-formats">
<button class="ql-link"></button>
</span>
</ToolbarContent>
<EditorContent>
@((MarkupString)@QuillReadOnlyContent)
</EditorContent>
</QuillEditor>
<button class="btn btn-info"
@onclick="ToggleQuillEditor">
Toggle Editor
</button>
Add the following code to the @code
section:
QuillEditor QuillReadOnly;
string QuillReadOnlyContent = @"<span><b>Read Only</b> <u>Content</u></span>";
bool mode = false;
async Task ToggleQuillEditor()
{
mode = !mode;
await this.QuillReadOnly.EnableEditor(mode);
}
Now, when I run the application, I see that the content (this can be Text, HTML, or Quill native Delta content), is displayed in read only mode. If I click the Toggle Editor button, the content becomes editable. Clicking on content, brings up the formatting toolbar defined in the markup for the control.
Fix for images
The last thing is a quick fix for the images. If you want to add big images in the editor, you will receive an error from Blazor. For that, I added in the component Quill Blot Formatter.
A QuillJs
module to format document blots. Heavily inspired by quill-image-resize-module. Out of the box supports resizing and realigning images and iframe videos, but can be easily extended using BlotSpec
and Action
.
So, if you want to add images in the editor, you have to add in the index.html
this line
<script src="_content/PSC.Blazor.Components.Quill/quill-blot-formatter.min.js"></script>
Bonus content: get your Blazor GitHub project to properly show C#
In GitHub, many Blazor projects will display the language as JavaScript when it should be C#. Here is how to fix that. So, add this line to your .gitattributes file:
*.razor linguist-language=C#
One thought on “Create a Blazor component for Quill”