Sure, I’ll even use some fancy colors to help show the changes.
First is to remove the id attributes from the form fields, and to instead use only one unique identifier on the form. That allows you to unclutter the HTML code, and enables you to use the same technique at multiple places on the same page.
You can still have identifiers in the form if you need them, such as when using them for IE6 support of labels that use explicit association, but for scripting to rely on unique identifiers in a form is normally not a good idea as that limits what the script can achieve.
<form [color="green"]id="messageArea"[/color]>
<textarea [s][color="red"]id="ta"[/color][/s] name="ta" rows="6" style="width:340px;"></textarea>
<br>
<input [s][color="red"]id="ta2"[/color][/s] type="text" name="countDisplay" size="3" maxlength="3" value="250"> Characters Remaining
</form>
Using the form identifier as an entry point, we can easily retrieve the references to other elements within the form from there:
[color="green"]var form = document.getElementById('messageArea'),
[/color]calcCharacters = [s][color="red"]document.getElementById('ta')[/color][/s][color="green"]form.elements.ta[/color];
Why that’s helpful is that it’s reducing the number of reasons for failure, and also allows the event’s function to be used on multiple different textareas on the same page, should you desire that later on.
These variables are all currently global variables though (because even though var is used, when it’s done outside of a function they are in the global scope) so we’ll eventually reduce our dependence on them. With functions, they should not rely on global variables. Functions should only use what they receive from their parameters or the this keyword, and in certain situations via their scope.
So, our functions shouldn’t use global variables, which makes them easily to understand and debug, and more capable of being used in different situations without needing to be changed.
The function needs three pieces of info to do its work. It needs the textarea element, a maximum number of characters, and the place where the current number of characters will be shown. The first one can be obtained via the this keyword in the function, but as for the other two, they can be either retrieved from the textarea element, or passed in to the function.
We’ll pass that info in to the function. If the function will be used for several related situations, it can be useful to pass the info in as a single data object called config. Since the function that handles the event only has one parameter passed to it, the event itself, the function needs to get any other information from somewhere else. That can be either from the element that triggered the event, or via closure from when the function was created, or from somewhere else.
We will use the textarea element to hold the config info, which can be stored in an object called config.
var [s][color="red"]maxChar = 250,[/color][/s]
...;
[color="green"]calcCharacters.config = {
maxChar: 250,
counter: form.countDisplay
};[/color]
The function can now retrieve that configuration info from the textarea.
We’ll use the onkeyup event instead of onkeydown. With onkeydown the script doesn’t get the most recent update to the element value. That’s because scripting allows you to return false to the onkeydown event to cancel the character. The onkeyup event is better to use when you want scripting to take effect based on the resulting value.
There’s also no need to separately declare the function either, when it can be applied directly as an anonymous function.
calcCharacters.onkey[s][color="red"]down[/color][/s][color="green"]up[/color] = [s][color="red"]calcCharacters2;[/color][/s] function [s][color="red"]characterCounter[/color][/s]() {
var [color="green"]config = this.config,[/color]
x = [color="green"]config.[/color]maxChar - [color="red"][s]calcCharacters[/s][/color][color="green"]this[/color].value.length;
[s][color="red"]document.getElementById('ta2')[/color][/s][color="green"]config.counter[/color].value = x;
}
Here’s the full set of code changes that we’ve gone through here.
<form [color="green"]id="messageArea"[/color]>
<textarea [s][color="red"]id="ta"[/color][/s] name="ta" rows="6" style="width:340px;"> </textarea>
<br>
<input [s][color="red"]id="ta2"[/color][/s] type="text" name="countDisplay" size="3" maxlength="3" value="250"> Characters Remaining
</form>
<script type="text/javascript">
var [s][color="red"]maxChar = 250,[/color][/s]
[color="green"]form = document.getElementById('messageArea'),[/color]
calcCharacters = [s][color="red"]document.getElementById('ta')[/color][/s][color="green"]form.elements.ta[/color];
[color="green"]calcCharacters.config = {
maxChar: 250,
counter: form.elements.countDisplay
};[/color]
calcCharacters.onkey[s][color="red"]down[/color][/s][color="green"]up[/color] = [s][color="red"]calcCharacters2;[/color][/s] function [s][color="red"]characterCounter[/color][/s]() {
var [color="green"]config = this.config,[/color]
x = [color="green"]config.[/color]maxChar - [color="red"][s]calcCharacters[/s][/color][color="green"]this[/color].value.length;
[s][color="red"]document.getElementById('ta2')[/color][/s][color="green"]config.counter[/color].value = x;
}
</script>
This can then be further improved on, so that the config is not attached to the textarea, but instead is attached to the event function using a closure technique.
[s][color="red"]calcCharacters.onkeydown = counterHandler({
maxChar: 250,
counter: form.countDisplay
});[/color][/s]
calcCharacters.onkeyup = [color="green"]counterHandler({
maxChar: 250,
counter: form.countDisplay
});
function counterHandler(config) {[/color]
...
[color="green"]}[/color]
The way that this technique works is to pass the config to an outer function (that being the counterHandler function), and for that outer function to return an inner function. Scoping ensures that the inner function retain knowledge of the config from the outer function.
function counterHandler(config) {
return function () {
var [s][color="red"]config = this.config,[/color][/s]
x = config.maxChar - this.value.length;
config.counter.value = x;
};
}
You can even use some initial sanity checks, which also acts as a useful reminder about which config values are accepted, and whether they are required or can be given a default value.
function counterHandler(config) {
[color="green"]if (!config || !config.counter) {
return;
}
config.maxChar = config.maxChar || 100;[/color]
return function () {
var x = config.maxChar - this.value.length;
config.counter.value = x;
};
}