CKEditor5 Word & Character count feature #JoelKallmanDay

Introduction

Some time ago, I posted blogs about using some CKEditor5 plugins in an Oracle APEX application and successfully used the image upload feature (part1, part2 and part3) as well as the mention feature (post). This new blog post will focus on using the word & character count feature.

Activating the feature

As usual, you need to activate the plugin by adding code under the JavaScript initialization code of the page element

function(options){
    // Enable word count plugin
    options.editorOptions.extraPlugins.push(
        CKEditor5.wordCount.WordCount
    );

    // Define the container
    options.editorOptions.wordCount = {
        container: document.getElementById( 'word-count' )
    };

    return options;
}

As you can see, we are defining a container here for our word and character count, I have added this container (<div id="word-count"></div>) under the inline help text attribute of the field. There may be a smarter way to put this markup, but I find this one very easy.

Screeshot showing the HTML markup under the inline help text attribute of the rich text editor item

Alright, now if you run this page you should see this, pretty cool right?

Screen recording showing the default word and character counter working in an APEX app

Note that there are two other options that you can use to display or not one of the counters:

  • displayWords: set this option to false to not display the word counter.
  • displayCharacters : set this option to false to not display the character counter. CKEditor will then modify the markup according to this.

Reacting to text input

Another interesting feature is the ability to react to user input using the onUpdate attribute. This can allow us to implement a gauge that will react to user input. Let's see how to do it.

First, we need to update the HTML markup under the inline help text of the rich text editor element with

<div class="demo-update">
    <div class="demo-update__controls">
        <span class="demo-update__words"></span>
        <svg class="demo-update__chart" viewbox="0 0 40 40" width="40" height="40" xmlns="http://www.w3.org/2000/svg">
            <circle stroke="hsl(0, 0%, 93%)" stroke-width="3" fill="none" cx="20" cy="20" r="17" />
            <circle class="demo-update__chart__circle" stroke="hsl(202, 92%, 59%)" stroke-width="3" stroke-dasharray="134,534" stroke-linecap="round" fill="none" cx="20" cy="20" r="17" />
            <text class="demo-update__chart__characters" x="50%" y="50%" dominant-baseline="central" text-anchor="middle"></text>
        </svg>
    </div>
</div>

Then, at the page level, add this CSS code under the inline CSS attribute

.demo-update {
    border: 1px solid var(--ck-color-base-border);
    border-radius: var(--ck-border-radius);
    padding: 1em;
}

.demo-update__controls {
    display: flex;
    flex-direction: row;
    align-items: center;
}

.demo-update__chart__circle {
    transform: rotate(-90deg);
    transform-origin: center;
}

.demo-update__chart__characters {
    font-size: 13px;
    font-weight: bold;
}

.demo-update__words {
    flex-grow: 1;
    opacity: .5;
}

.demo-update__limit-close .demo-update__chart__circle {
    stroke: hsl( 30, 100%, 52% );
}

.demo-update__limit-exceeded .ck.ck-editor__editable_inline {
    background: hsl( 0, 100%, 97% );
}

.demo-update__limit-exceeded .demo-update__chart__circle {
    stroke: hsl( 0, 100%, 52% );
}

.demo-update__limit-exceeded .demo-update__chart__characters {
    fill: hsl( 0, 100%, 52% );
}

And finally, update the element initialization code to update the counter elements with the onUpdate attribute:

function(options){
    // Enable word count plugin
    options.editorOptions.extraPlugins.push(
        CKEditor5.wordCount.WordCount
    );

    // Define the container
    options.editorOptions.wordCount = {
        onUpdate: function(stats) {
            const maxCharacters = 150;
            const container = document.querySelector( '.demo-update' );
            const progressCircle = document.querySelector( '.demo-update__chart__circle' );
            const charactersBox = document.querySelector( '.demo-update__chart__characters' );
            const wordsBox = document.querySelector( '.demo-update__words' );
            const circleCircumference = Math.floor( 2 * Math.PI * progressCircle.getAttribute( 'r' ) );
            const sendButton = document.querySelector( '#save-word-count' );
            const charactersProgress = stats.characters / maxCharacters * circleCircumference;
            const isLimitExceeded = stats.characters > maxCharacters;
            const isCloseToLimit = !isLimitExceeded && stats.characters > maxCharacters * .8;
            const circleDashArray = Math.min( charactersProgress, circleCircumference );

            // Set the stroke of the circle to show how many characters were typed.
            progressCircle.setAttribute( 'stroke-dasharray', `${ circleDashArray },${ circleCircumference }` );

            // Display the number of characters in the progress chart. When the limit is exceeded,
            // display how many characters should be removed.
            if ( isLimitExceeded ) {
                charactersBox.textContent = `-${ stats.characters - maxCharacters }`;
            } else {
                charactersBox.textContent = stats.characters;
            }

            wordsBox.textContent = `Words in the post: ${ stats.words }`;
            // If the content length is close to the character limit, add a CSS class to warn the user.
            container.classList.toggle( 'demo-update__limit-close', isCloseToLimit );
            // If the character limit is exceeded, add a CSS class that makes the content's background red.
            container.classList.toggle( 'demo-update__limit-exceeded', isLimitExceeded );
            // If the character limit is exceeded, disable the send button.
            sendButton.toggleAttribute( 'disabled', isLimitExceeded );
        }
    };

    return options;
}

This time you should see something like this

Animated screenshot showing the circular character counter reacting to the user type id

Conclusion

In this article, we saw how to enable the word and character count feature of CKEditor5 and how to enhance it to change its appearance. I find it useful to display it to the user instead of having an error message after sending a page. As usual, you can find a working example here.