TinyMCE has a built in word counter but it does not have a way to count characters. We have a requirement that limits the users to 5000 characters per textarea field. This count is purely to keep the users from writing a book each time they enter data. We only need to count the actual characters that the user sees, all html and hidden characters are ignored.
To do this I created a character count plugin. It ended up pretty straight forward in the end. I tried many different ways of pulling and counting the visible characters but many of them were pretty inaccurate. This version is the closest I could get to the Microsoft Word character counts.
Factoids:
- I started the plugin with a generic base similar to the existing TinyMCE word count plugin.
- I layered on this Stack Overflow answer to decode the raw HTML in a way that gave me the most accurate character count.
- I used this Stack Overflow answer to hide the the path in the status bar of the editor.
- I also added a generic “Over 5000 characters” validation message that I turn on and off based on the validation done in the “on change” event.
- The last piece was to validate the character count in the “on submit” event.
- Thanks to the commenters who found some problems with this code!
TinyMCE Initialization
<pre>
$('textarea.tinymce').tinymce({
script_url: '/scripts/tinymce/4.0.25/tinymce.min.js',
theme: "modern",
plugins: "charactercount",
toolbar: "bold italic | link | bullist numlist ",
menubar: false,
statusbar: true,
setup: function (editor) {
editor.on('change', function(e) {
var count = this.plugins["charactercount"].getCount();
if (count > 5000)
$('#invalidContentHtml').show();
else
$('#invalidContentHtml').hide();
});
},
init_instance_callback: function (editor) {
$('.mce-tinymce').show('fast');
$(editor.getContainer()).find(".mce-path").css("display", "none");
}
});
</pre>
Character Count Plugin:
tinymce.PluginManager.add('charactercount', function (editor) {
var self = this;
function update() {
editor.theme.panel.find('#charactercount').text(['Characters: {0}', self.getCount()]);
}
editor.on('init', function () {
var statusbar = editor.theme.panel && editor.theme.panel.find('#statusbar')[0];
if (statusbar) {
window.setTimeout(function () {
statusbar.insert({
type: 'label',
name: 'charactercount',
text: ['Characters: {0}', self.getCount()],
classes: 'charactercount',
disabled: editor.settings.readonly
}, 0);
editor.on('setcontent beforeaddundo', update);
editor.on('keyup', function (e) {
update();
});
}, 0);
}
});
self.getCount = function () {
var tx = editor.getContent({ format: 'raw' });
var decoded = decodeHtml(tx);
var decodedStripped = decoded.replace(/(<([^>]+)>)/ig, "").trim();
var tc = decodedStripped.length;
return tc;
};
function decodeHtml(html) {
var txt = document.createElement("textarea");
txt.innerHTML = html;
return txt.value;
}
});
On Submit Code:
var charcnt = tinyMCE.editors["{TextAreaId}"].plugins["charactercount"].getCount() <= 5000;
CSS Tweaks:
/* Optional: Adjust the positioning of the character count text. */
label.mce-charactercount {
margin: 2px 0 2px 2px;
padding: 8px;
}
/* Optional: Remove the html path code from the status bar. */
.mce-path {
display: none !important;
}
** Updated on 10/28/15: Minor changes based on comments