B4J Tutorial ABMaterial - ABMCustomComponent - Quill Rich Text Editor

In ABMaterial version 4.95, @alwaysbusy introduced me to a new method of handling Rich Text Editing - in the form of an ABMCustomComponent called CompQuillEditor (and likely, much to his chagrin)... Anyway, the cat's out of the bag, so here goes my novice attempt to implement it.... (It isn't all that bad... lot's of good!).

NOTE: DO NOT POST ANY QUESTIONS OR COMMENTS HERE! (other than likes) This tutorial is a work in progress ( me learning this ). When completed, I shall remove this restriction... ( by removing this line ).

There are many sections to this learning process, so hang tough and stay with me....

Why Use This (CompQuillEditor )?
There are many advantages as I have found. They shall be exposed in upcoming sections of this tutorial!
Microsoft Word - this is not! BUT, it is all you may need for your ABM app ( very powerful )...
Quill is a free and powerful JavaScript and well adopted RTE. As you shall see...
Check it out at: https://quilljs.com/

A Brief Tickler...

This image shows a CompQuillEditor without a toolbar (read only). Scroll within to view contents.
Note: One can adjust the height of the editor view (more later).


This image is where one can edit the contents. One can add links or images which is embedded into the text content (it's HTML).
Note: A toolbar is exposed where you can format the text.
There are times where I won't allow adding IMAGES (and size of) into the text stream. The toolbar can be controlled to prevent this!


All of my understanding of this was a result of studying the underlying code.
@alwaysbusy is an expert coder and helped me see how all the parts work.

Next sections shall break this all down so you can implement this into your projects (rather complex - for me anyway - not for you JS experts).
( baby steps )...

Last edited:


Licensed User
Longtime User
Storing / Printing Data

From the Official Quill Documentation:

Rich text editors lack a specification to express its own contents.
Until recently, most rich text editors did not even know what was in their own edit areas.
These editors just pass the user HTML, along with the burden of parsing and interpretting
this. At any given time, this interpretation will differ from those of major browser
vendors, leading to different editing experiences for users.

Quill is the first rich text editor to actually understand its own contents.
Key to this is Deltas, the specification describing rich text. Deltas are designed to
be easy to understand and use. We will walk through some of the thinking behind Deltas,
to shed light on why things are the way they are.

All that said, I don't use Deltas. I researched it but still don't understand how to implement.
We shall use the good old HTML....
Feel free to create your own tutorial that teaches us how to use Deltas properly.

The following is an example of HTML text stored in a MySQL table (saved from the editor). Note the use of Quill CSS embedded within the content.
<p class="ql-indent-1">KTSA <strong>showed </strong>their views...</p><ol><li data-list="bullet" class="ql-indent-1"><span class="ql-ui" contenteditable="false"></span>These issues were cited:</li><li data-list="bullet" class="ql-indent-1"><span class="ql-ui" contenteditable="false"></span>rapid gunfire noise</li><li data-list="bullet" class="ql-indent-1"><span class="ql-ui" contenteditable="false"></span>lead contamination</li><li data-list="bullet" class="ql-indent-1"><span class="ql-ui" contenteditable="false"></span>no ro most times</li><li data-list="bullet" class="ql-indent-1"><span class="ql-ui" contenteditable="false"></span>poaching of KTSA members</li></ol><p><br></p>

When we display this content (read only), we shall always use the CompQuillEditor custom component, since it uses the associated CSS file.
If you use an ABMLabel with .TextAsRAWHTML to display this data - the format will be mostly WRONG since ABMLabel does not use the CSS file (hence RAW HTML).

Printing or Saving as PDF File

Printing and saving has been, in the past, a horrible and time consuming coding project - fraught with errors and frustration...
Now my good friends, it COULD NOT be more Simple! One clean function...

The PrintEditor method - supplied by @alwaysbusy
public Sub PrintEditor(id As String)
    Dim Script As String = $"
            var divContents = $("#${id}").html();
            var a = window.open('', '','');
            a.document.write("@media only screen {html {visibility: hidden}}@charset 'UTF-8';.row:after{clear:both}.transparent{background-color:transparent!important}body{margin:0}main{display:block}html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;box-sizing:border-box}*,:after,:before{box-sizing:inherit}.row .ql-editor .ql-color-yellow{color:#ff0}.ql-editor .ql-color-green{color:#008a00}.ql-editor .ql-color-blue{color:#06c}.ql-editor .ql-color-purple{color:#93f}.ql-editor .ql-font-serif{font-family:Georgia,Times New Roman,serif}.ql-editor .ql;
            a.document.write('<body onafterprint="self.close()">');           
            a.document.write('<div style="width:100%;height:100%;page-break-inside:avoid">');
    page.ws.Eval(Script, Null)
End Sub
NOTE: the <style> ... </style> section in the code above is NOT complete! The complete method will be supplied in a project package later....

The style section is essentially the Quill.CSS file. It is required for the browser (Chrome in my case) to render a proper document with a.print().

To use this we call: PrintEditor("agend_out-parent").
The parameter is the parent <div> that contains the content to be printed....
In a new tab, the following is produced:


With the Destination combo, we can direct pages to a selected printer - OR - save the content as a PDF file!
Once printed (or saved) the tab auto closes and returns to your calling app (tab).

What Next?

All that has been presented above is from a certain page of my huge app.

Now, I intend to create an email page that uses CompQuillEditor to format email content to be sent.
This new project will expose all the nuts and bolts I have discovered to use this custom component correctly.
The complete project source shall be shared here.

It is going to take some time to put this together, but I think the wait shall be worth it.

Last edited:


Licensed User
Longtime User
Required Files / CLASSES and Methods

In order to use this custom component, you must add the following into your page in the BuildPage() method...
NOTE: I have made some modifications to some of the files below, so for best results, use the ones I supply...
They are: [ quill.snow.1.41.css" ], [ quill-abm.1.51.js" ]

I don't pretend to know what all the files do... just following instructions...

public Sub BuildPage()
    ' initialize the theme

    ' initialize this page using our theme
    page.InitializeWithTheme(Name, "/ws/" & ABMShared.AppName & "/" & Name, False, ABMShared.SessionMaxInactiveIntervalSeconds, theme)
    ' add all required CSS and JS for new quill editor
' .....

CompQuillEditor Class

Initialize and Build Methods

Please read the comments in each method...

Sub Class_Globals
    Public ABMComp As ABMCustomComponent
    Private myID As String
    Private EvName As String
    Private fPrint As Boolean ' read only (no toolbar) or full editor with toolbar
    Public hgt As String = "300px"  ' max height of edit window (vertical scroll bar included if higher)
    Private allowimg As Boolean = False ' include link and image controls in toolbar
End Sub
'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(InternalPage As ABMPage, ID As String, EventName As String, ForPrint As Boolean, HeightCSS As String, allowimage As Boolean)
' init ABMComp globals with params passed
   EvName = EventName.ToLowerCase
    fPrint = ForPrint
    hgt = HeightCSS
    allowimg = allowimage
    ABMComp.Initialize("ABMComp",  Me, InternalPage, ID, HeightCSS)
    ABMComp.RawBuild = True  ' requires 4.95 or above....

End Sub
Sub ABMComp_Build(InternalPage As ABMPage, internalID As String) As String
'  called automatically when ABMComp is intialized...

' replace all dashes with underscores...
    Dim varID As String = internalID.Replace("-", "_")
    myID = varID
    Return $"<div id='${varID}-parent' class="only-print"><div id='${varID}'></div></div>"$
End Sub
' Is useful to run some initialization script.
Sub ABMComp_FirstRun(InternalPage As ABMPage, internalID As String)

' replace all dashes with underscores...
    Dim varID As String = internalID.Replace("-", "_")
    Dim script As String

'  create the appropriate editor (read only or edit) with additional parameter settings...
    If fPrint Then
        script = $"var editor = new ABMQuill().QuillInitForPrint('#${varID}', '${hgt}');"$
        script = $"var editor = new ABMQuill().QuillInitEdit('#${varID}', '${hgt}', ${allowimg} );"$
    End If    
    InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
    ' flush not needed, it's done in the refresh method in the lib

End Sub

Get / Set and Size Methods

' Set the editor content text with HTML from a file or database table....
' *********************************************************************************************
    Sub SetHTML(InternalPage As ABMPage, html As String)
    html = html.Replace("'", "\'").Replace(Chr(13), Chr(10)).Replace(Chr(10), "<br>")
    Dim script As String = $"var editor = Quills['#${myID}'];
    InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
End Sub

' from quill-abm.1.51.js
    this.SetInitialHTML = function (value) {
        this.quill.clipboard.dangerouslyPasteHTML(0,value.replaceAll('<blockquote>', '<div class="ql-indent-1">').replaceAll('</blockquote>', '</div>'));
' *********************************************************************************************

' Get the HTML from the editor to save in a file or table
' *********************************************************************************************
Sub GetHTML(InternalPage As ABMPage) As String
    Dim script As String = $"var editor = Quills['#${myID}']
    return editor.GetHTML();
    Dim fut As Future = InternalPage.ws.EvalWithResult(script, Array As Object(ABMComp.ID))
    Return fut.Value
End Sub

' from quill-abm.1.51.js
    this.GetHTML = function () {
        return this.quill.container.querySelector('.ql-editor').innerHTML;
' *********************************************************************************************

' Get the Length of HTML string currently in the editor.
' This method is useful to prevent a WebSocket error by trying to send data larger than the sockets data size limit!
' *********************************************************************************************
Sub GetTextLength(InternalPage As ABMPage, len As Int) As String
    Dim script As String = $"var editor = Quills['#${myID}']
    return editor.GetTextLength(${len});
    Dim fut As Future = InternalPage.ws.EvalWithResult(script, Array As Object(ABMComp.ID))

    Return fut.Value
End Sub

' from quill-abm.1.51.js
    this.GetTextLength = function (value) {
        var strtxt = this.quill.container.querySelector('.ql-editor').innerHTML;
        var strlen = strtxt.length;
        console.log(" GetText Length: "+strlen);
        console.log(" Max Length Allowed: "+value);
        return strlen
' *********************************************************************************************

' ---------------------------------------------------------------------------------------------------------
' This method is used to set the MAXIMUM text message size of a websocket.
' It is often used when allowing images in HTML content.
' Use GetTextLength() to ensure editor HTML length is less than the setMaxTextMessage Size... (set or get methods of editor)
Sub SetMaxTextMessage(size As Int)
    Dim jo As JavaObject = ws
    jo = jo.GetFieldJO("session").RunMethod("getPolicy", Null)
    jo.RunMethod("setMaxTextMessageSize", Array(size))
End Sub
' ---------------------------------------------------------------------------------------------------------

Normally, I would not store image data within HTML content - or a database table!
They are usually stored as indiviual files on disk, with a reference to the name and location within a database table.
However and for instance, it may be useful to add a small image (company logo for example) when composing an email to broadcast.

More of this tutorial to follow soon...
Last edited: