centralscru centralscru - 5 months ago 19
jQuery Question

How can I display a character count / word count for Redactor rich text / html editor?

I have just replaced CKEditor (which came with a wealth of mysterious problems relating to AJAX updates to the DOM) with the excellent Redactor. We previously used a CKEditor plugin to give us a character count for the rich text editor. How can I achieve the same thing with Redactor? There's nothing built in and no "plugin" architecture to use.

Answer

I've had a stab at this. I'm sure there will be better ways of achieving the same thing, but I'll post it in case it's a good start for someone else in the same fix.

The following code adds a rich text editor to any textarea fields that have the class "richText", and further adds a character count ONLY if the maxlength attribute is used on the textarea, eg

<textarea class="richText" maxlength="100"></textarea>

Here I've included CSS right in the javascript. In reality I'd take this out into a separate css file.

var initRichText = function () {
    $("textarea.richText").redactor({
        buttons: ['bold', 'italic', '|', 'unorderedlist', 'orderedlist'],
        keyupCallback: function (obj, event) {
            var max = obj.$el.getEditor().next().attr("maxlength");
            if (typeof max !== "undefined") {
                var current = obj.$el.getCode().length;
                var $box = obj.$el.closest(".redactor_box");
                var $indicator = $(".indicator", $box);
                if ($indicator.size() === 0) {
                    $box.append($("<div class='indicator' style='position:absolute;z-index:30;top:7px;right:7px;'><span class='current'>" + current + "</span> of <span class='max'>" + max + "</span> characters remaining</div>"));
                } else {
                    $(".current", $indicator).text(current);
                    $(".max", $indicator).text(max);
                }
                if (current >= max) {
                    $box.css("border", "1px solid #ff0000");
                    $indicator.css("color", "#ff0000");
                } else {
                    $box.css("border", "1px solid #DDDDDD");
                    $indicator.css("color", "#000000");
                }
            }
        }
    });
};
$(function(){
    initRichText();
});

EDIT: as MForMarlon says, redactor.js has changed since we wrote this code. By a very strange coincidence we were working on this TODAY, having not touched it for more than a year! Weird. Anyway, here's the latest version that works with Redactor v9.2.5. Note that we're now using initCallback to apply the character counter when the rich text editor first loads, instead of when the user starts typing. We're also using changeCallback instead of keyupCallback because we've added a "paste as plain text" modal (see redactor.js pastePlainText - but need button to paste html instead), and we want to react to content pasted in via that as well as typed in.

$(".richText").redactor({
    shortcuts: false,
    buttons: ['bold', 'italic', '|', 'unorderedlist', 'orderedlist', '|', 'html', 'pasteAsPlainText'],
    plugins: ['pasteAsPlainText'],
    initCallback: function (event) {
        var max = this.$editor.next().attr("maxlength");
        if (typeof max !== "undefined" && max > 0) {
            var html = this.get();
            var current = html.length;
            var remaining = max - current;
            this.$box.append($("<div class='indicator' style='position:absolute;z-index:30;top:7px;right:7px;'><span class='current'>" + remaining + "</span> of <span class='max'>" + max + "</span> characters remaining</div>"));
        }
    },
    changeCallback: function (event) {
        var max = this.$editor.next().attr("maxlength");
        if (typeof max !== "undefined" && max > 0) {
            var html = this.get();
            var current = html.length;
            var remaining = max - current;
            var $indicator = $(".indicator", this.$box);
            $(".current", $indicator).text(remaining);
            $(".max", $indicator).text(max);
            if (current >= max) {
                this.$box.css("border", "1px solid #ff0000");
                $indicator.css("color", "#ff0000");
            } else {
                this.$box.css("border", "1px solid #DDDDDD");
                $indicator.css("color", "#000000");
            }                
        }
    }
});