Use scripting to add the text counters
When someone’s working on the HTML code, we don’t want them to worry about what the scripting code does. They shouldn’t need to know or worry about the scripting when working on the HTML.
From the HTML code we remove the counters, and have the scripting code add them instead. I’ve commented them out below to indicate that it’s been removed, but you can delete them entirely from your code.
<!--<p>
<textarea oninput="countChars(this)"></textarea>
<span>0</span>
</p>
<p>
<textarea oninput="countChars(this)"></textarea>
<span>0</span>
</p>
<p>
<textarea oninput="countChars(this)"></textarea>
<span>0</span>
</p>-->
<p><textarea oninput="countChars(this)"></textarea></p>
<p><textarea oninput="countChars(this)"></textarea></p>
<p><textarea oninput="countChars(this)"></textarea></p>
The character counter cannot be added from the countChars function, because we need the counter to be seen before we interact with the textarea. We also cannot access the textarea from script that runs before it.
That means that we need to either use an onload event to add the counter, or use some separate code after the textarea to add the counter.
Back in the olden days we would have used an onload event to add the counter because scripts only worked from the head section of HTML and not the body. That’s a problem we now don’t have to worry about, and much better results are now achieved by placing the script at the end of the body, after all of the HTML content instead.
One of the counters can be added by placing this code in a separate script section below the textareas:
<script>
var textarea = document.getElementsByTagName("textarea")[0];
textarea.parentNode.innerHTML += "<span>0</span>/50";
</script>
Right now it’s only the first textarea that gets the counter added to it. We need it to be added to all of the textareas.
As we have multiple counters, we need to loop through each of the textarea elements that are found and add a counter to each one of them.
Add counter to all textareas
We can get all of the textareas using document.getElementsByTagName("textarea") and loop through each of them usign a for loop.
// the following code works, but can be improved
var textareas = document.getElementsByTagName("textarea");
for (var i = 0; i < textareas.length; i += 1) {
textareas[i].parentNode.innerHTML += "<span>0</span>/50";
}
But surely, there’s a better way to do that today? The index variable of a for loop is an unnecessary complication that’s not needed these days.
Other code structures exist such as for…of to access the elements, but that doesn’t work in Internet Explorer.
// the following for...of isn't supported by Internet Explorer
var textareas = document.getElementsByTagName("textarea");
for (var textarea of textareas) {
textareas[i].parentNode.innerHTML += "<span>0</span>/50";
}
- Problem: For loops, for…of and for…in become problematic.
- Solution: Prefer forEach iteration over for, for…in or for…of loops.
Arrays do have the Array.from method to create an array from other arraylike objects, but that’s not supported by Internet Explorer.
// the following Array.from isn't supported by Internet Explorer
var textareas = document.getElementsByTagName("textarea");
Array.from(textareas).forEach(function (textarea) {
textarea.parentNode.innerHTML += "<span>0</span>/50";
});
What about getting a nodeList from querySelectorAll, and using the nodeList forEach method?
// the following textareas.forEach isn't supported by Internet Explorer
var textareas = document.querySelectorAll("textarea");
textareas.forEach(function (textarea) {
textarea.parentNode.innerHTML += "<span>0</span>/50";
});
Nope, that’s not supported by Internet Explorer either.
If we didn’t have to care about Internet Explorer this would be much easier, but IE is still being used by 7% of browser visitors.
A technique that does with with IE is to use Array.prototype.forEach.call, but that’s quite a handful to use, and feels too much like a hack to get things working.
// the following works, but Array.prototype.forEach.call is complicated
var textareas = document.querySelectorAll("textarea");
Array.prototype.forEach.call(textareas, function (textarea) {
textarea.parentNode.innerHTML += "<span>0</span>/50";
});
Because of issues such as the above, polyfills can be used instead. A polyfill is some code that helps to fill in missing features from a browser. In this case, it’s nodeList support for the forEach method.
- Problem: Is a handy feature not supported by some browsers?
- Solution: Polyfills help you to write easier-to-understand code.
Here’s the polyfill that lets browsers use forEach on list of nodes:
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = Array.prototype.forEach;
}
With that polyfill in place, we can now use the much easier forEach method with textareas.forEach instead.
var textareas = document.querySelectorAll("textarea");
textareas.forEach(function (textarea) {
textarea.parentNode.innerHTML += "<span>0</span>/50";
});
Instead of using getElementsByTagName which gives us a live HTMLCollection object on which we’d want to use a for loop, or for…of, we can use querySelectorAll instead, along with forEach method to iterate over each element.
Using querySelectorAll also acts as protection against future change as getElementsByTagName only works with elements, whereas querySelectorAll also works with unique identifiers and class names.
Here’s the updated scripting code that uses querySelectorAll and the forEach method.
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = Array.prototype.forEach;
}
var textareas = document.querySelectorAll("textarea");
textareas.forEach(function (textarea) {
textarea.parentNode.innerHTML += "<span>0</span>/50";
});
That fixes some of the problems, but has added other ones to the list.
countChars function assumes only one textarea
the span counter is meaningless when scripting doesn’t work
- inline attribute event handler doesn’t belong in HTML code
- counter assumes a limit of 50 characters
- multiple sections of scripting code, some above and some below
- relies on the parentNode having nothing after textarea
- innerHTML means using HTML code inside of JavaScript
While working with the character count, not all textareas require the same 50 char limit. We’ll do something about that in my next post.