Oracle APEX - TinyMCE Word & Character count #JoelKallmanDay
Introduction
I could say that I started my blog on October 11, 2021, but that wouldn't be true because my first post dates back to November 2020 (published on another platform). However, I consider that I started blogging on #JoelKallmanDay.
Exactly 1 year ago, I wrote an article on the same subject. Why start all over again, you might ask? Well, the Oracle APEX team decided to change the library used to manage rich text fields. By deprecating CKEditor, it was necessary to be able to migrate customers for whom we had implemented a character/word counter.
Let's see how to do that!
TinyMCE: what is this?
If you haven't heard of it, it's the new library delivered with Oracle APEX 23.1 to replace CKEditor5. It has advantages like a simpler API, but some disadvantages like fewer plug-ins and we still don't have access to all the parameters in the initialization code. But we can still do great things with it!
Enabling the wordcount plugin
To activate the wordcount plug-in, go to the attributes of your page item and fill in the JavaScript initialization function with the code below:
function(options){
options.editorOptions.plugins += " wordcount";
if ( Array.isArray( options.editorOptions.toolbar ) ) {
options.editorOptions.toolbar.push( { name: "wordcount", items: [ "wordcount" ] } );
} else {
options.editorOptions.toolbar += " wordcount";
}
return options;
}
Wait, what? That's all we have to do? Yes, but the default function is rather limited since it only adds a button to the toolbar, so the user has to click on it to see the number of characters/words they've typed. Here's what it looks like:
This works and displays the number of characters/words typed in the field, but what if I want these numbers to change as you type?
Displaying live numbers
Activating the plugin will give us access to an API for retrieving counters, as explained in the documentation. We will use two functions in this API
//Accessing the wordcount plugin for the active editor
var wordcount = tinymce.activeEditor.plugins.wordcount;
//Display the number of word for the active editor
console.log(wordcount.body.getWordCount());
//Display the number of character for the active editor
console.log(wordcount.body.getCharacterCount());
We now need to trigger an event when someone types into the editor, and to do this we can use the init_instance_callback function
function(options){
//Enbale the wordcount plugin
options.editorOptions.plugins += " wordcount";
//Declare the init instance callback
options.editorOptions.init_instance_callback = function(editor) {
// Get the wordcount for the active editor (used to update the counters)
let formWrapper = tinymce.activeEditor.container.parentElement.parentElement;
//Function that updates the counters
function writeWordCount() {
// Get the wordcount for the active editor
let wordcount = tinymce.activeEditor.plugins.wordcount;
// Write the updated numbers in the counter
apex.jQuery(formWrapper).parent().find(".tinymce-word-count").text(wordcount.body.getCharacterCount() + " characters | " + wordcount.body.getWordCount() + " words");
}
// During initialization we need to insert the wordcount div and write the counter
apex.jQuery(formWrapper).after('<div class="tinymce-word-count" role="region" aria-label="Word count"></div>');
writeWordCount();
// listen to the input event to update the counters
editor.on("input", apex.util.debounce( writeWordCount, 100 ));
}
return options;
}
We then add the following CSS code to style the counters
.tinymce-word-count {
display: flex;
justify-content: flex-end;
padding: 0.5rem;
border: 1px solid var(--tm-color-toolbar-border,#eee);
border-bottom-left-radius: var(--tm-border-radius,10px);
border-bottom-right-radius: var(--tm-border-radius,10px);
border-top: none;
line-height: 1rem;
font-size: 0.6875rem;
}
Run your page and voilà, it just works 😍
Is it possible to make a more fun counter?
Oh yes, it is!!! We're going to display a fun gauge as well as the word count in the editor, let's see it!
First, we need to update the editor's JavaScript initialization function with the code below
function(options){
//Enbale the wordcount plugin
options.editorOptions.plugins += " wordcount";
//Declare the init instance callback
options.editorOptions.init_instance_callback = function(editor) {
// Get the wordcount for the active editor (used to update the counters)
let formWrapper = tinymce.activeEditor.container.parentElement.parentElement;
//Function that updates the counters
function writeWordCount() {
const wordcount = tinymce.activeEditor.plugins.wordcount, //wordcount for the active editor
maxChar = 100; // character limit
let formWrapper = tinymce.activeEditor.container.parentElement.parentElement,
gauge = formWrapper.parentElement.getElementsByClassName("character-gauge")[0],
wordCounter = formWrapper.parentElement.getElementsByClassName("word-number")[0],
chars = wordcount.body.getCharacterCount(),
words = wordcount.body.getWordCount(),
percentage = Math.round( ( chars / maxChar ) * 100 );
// We want to display minus
if (chars > maxChar) {
chars = ( chars - maxChar ) * -1;
}
// Update the gauge
gauge.style.setProperty('--char_count', "'" + chars + "'");
gauge.style.setProperty('--progress', "" + percentage + "%");
// Update the color
if (percentage >= 75 && percentage < 100) {
gauge.style.setProperty('--gauge-foreground-color', "#ffc36e");
} else if (percentage >= 100) {
gauge.style.setProperty('--gauge-foreground-color', "#ff5151");
}
// Write the numbers of words in the counter
apex.jQuery(wordCounter).text(words + " words in the post");
}
// During initialization we need to insert the wordcount div
apex.jQuery(formWrapper).after('<div class="tinymce-word-count-advanced" role="region" aria-label="Word count"><div class="word-number"></div><div class="character-gauge"></div></div>');
// Write the initial count after initialization
writeWordCount();
// listen to the input event to update the counters
editor.on("input", apex.util.debounce( writeWordCount, 100 ));
}
return options;
}
In this code, we set the character limit to 100, then calculate the percentage based on the current number of characters. We then use this calculation to update a few CSS variables used to style the progression.
Here's the CSS code we need to add to the page
tinymce-word-count-advanced {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0.5rem;
border: 1px solid var(--tm-color-toolbar-border,#eee);
border-bottom-left-radius: var(--tm-border-radius,10px);
border-bottom-right-radius: var(--tm-border-radius,10px);
border-top: none;
line-height: 1rem;
font-size: 0.6875rem;
align-items: center;
}
.character-gauge {
--char_count: "0";
--gauge-background-color: #e6e6e6;
--gauge-foreground-color: #54bdff;
--progress: 48%;
justify-self: end;
display: flex;
justify-content: center;
align-items: center;
font-size: 1rem;
width: 50px;
height: 50px;
border-radius: 50%;
background: radial-gradient(closest-side, white 79%, transparent 80% 100%),
conic-gradient(var(--gauge-foreground-color) var(--progress), var(--gauge-background-color) 0);
}
.character-gauge::after{
content: var(--char_count);
}
Now we can enjoy our wonderful word and character counter!
Conclusion
In this article, we look at how to use the TinyMCE editor wordcount plugin in your Oracle APEX application. It allows you to retrieve the number of words and characters from the active editor and display them as you wish.
I hope you will enjoy reading it and, if you want to test it, you can find all three examples here: https://apex.oracle.com/pls/apex/r/louis/examples/tinymce-word-count
Subscribe to my newsletter
Read articles from Louis Moreaux directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Louis Moreaux
Louis Moreaux
Oracle APEX Developer (Insum) from Paris Contributor of Flows for APEX Passionate about APEX and web development.