Referencing function parameters

I’d like to try to write it with your help. Here’s how far I’ve got, but I don’t understand how typing in the <textarea> is going to trigger my function.

<!doctype html>
<html> <head> <title> Counter </title> 
<script type="text/javascript">  
var maxChar = 251;
var currentNum = 0;
var calcCharacters = document.getElementById('ta');
function calcCharacters2 () {
calculation of the number of characters in <textarea>;
} 
</script>  
</head> <body>
<form>
    <textarea name="ta" rows="6" style="width:340px;"> </textarea>
	<br>
    <input type="text" name="countDisplay" size="3" maxlength="3" value="250"> Characters Remaining
</form>
</body> </html>

First of all, when that script runs, nothing else below it will exist at the time.
Move the script to the end of the body, just before the </body> tag, and the calcCharacters will change from being undefined, to being a reference to the textarea element.

Once you’ve done that, you can use scripting to assign to one of the textarea events the function.

you can use scripting to assign to one of the textarea events the function.

I think i’m close, but having trouble about how to think of your quote

my script so far:

<!doctype html>
<html> <head> <title> Counter </title> 

</head> <body>
<form>
    <textarea name="ta" rows="6" style="width:340px;"> </textarea>
	<br>
    <input type="text" name="countDisplay" size="3" maxlength="3" value="250"> Characters Remaining
</form>
<script type="text/javascript">  

var maxChar = 251;
var currentNum = 0;
var calcCharacters = document.getElementById('ta');
calcCharacters.onkeydown = calcCharacters2;
function calcCharacters2 () {
alert( "I'm gonna count those chars in ta!" );
     //calculation of the number of characters in <textarea>;
} 
 
</script>  
</body> </html>

You are doing that part of the technique correctly.

The problem now is that you are using getElementById to retrieve a reference to the textarea, but the textarea has no id.

It’s best-practice to use a form id, so that you can then use the form elements collection to access the named elements of the form.

For example:


<form id="descriptiveIdentifierForTheFormContents">


var form = document.getElementById('descriptiveIdentifierForTheFormContents');
var textarea = form.elements.ta;

Of course, do not use anything as large as “descriptiveIdentifierForTheFormContents” for the identifier. Just ensure that it appropriately indicates what the form contains. Something like “countableText” or “textCounter” would be appropriate.

was missing the id in the <textarea> which I added and got access to the function.

So, I tested the value of calcCharacters thinking it might be a value, but the alert said calcCharacters is a html object.

How do I need to think about making the calculations in the function. Here’s the work so far:

<!doctype html>
<html> <head> <title> Counter </title> 

</head> <body>
<form>
    <textarea id="ta" name="ta" rows="6" style="width:340px;"> </textarea>
	<br>
    <input type="text" name="countDisplay" size="3" maxlength="3" value="250"> Characters Remaining
</form>
<script type="text/javascript">  

var maxChar = 251;
var currentNum = 0;
var calcCharacters = document.getElementById('ta');
calcCharacters.onkeydown = calcCharacters2;
function calcCharacters2 () {
alert( "I'm gonna count those chars in ta!" + calcCharacters );
     //calculation of the number of characters in <textarea>;
} 
 
</script>  
</body> </html>

Do you want to use the best-practice technique of only an identifier on the form, so that you can then easily gain access to all of the named elements within the form?

I’ve worked on this some more and calcCharacters.value.length counts the number of characters in <textarea> . I have it in a var called x. What’s the notation for displaying the value of x in the input box from the <input>? I’m always interested in the best-practice technique - hope I have violated too many so far

<!doctype html>
<html> <head> <title> Counter </title> 

</head> <body>
<form >
    <textarea  id="ta" name="ta" rows="6" style="width:340px;"> </textarea>
	<br>
    <input id="ta2" type="text" name="countDisplay" size="3" maxlength="3" value="248"> Characters Remaining
</form>
<script type="text/javascript">  

var maxChar = 255;
var currentNum = 0;
var calcCharacters = document.getElementById('ta');
calcCharacters.onkeydown = calcCharacters2;
function calcCharacters2 () {
var x = calcCharacters.value.length;
alert( "I'm gonna count those chars in !" + x);
     //calculation of the number of characters in <textarea>;
} 
 
</script>  
</body> </html>

To display the value, the function need to gain a reference to the target element where it will be displayed.

There are three basic ways that this may be achieved:

[list][]By adding to the textarea element a property that refers to the target element
[
]By using a name for the target element that is similar to the source element, for example, “myInput” for the source and “myInputCounter” for the target
[*]By explicitly referring to the target element by a concrete identifier. Tis is the least flexible technique, resulting in no ability to reuse the function in other situations.
[/list]

So going with the first technique, in the same place where you assign the textarea event, you can also add to that textarea element a property that points to the counter.


calcCharacters.counter = calcCharacters.form.elements.countDisplay;

so that within the event function, you can retrieve that info from the property on the textarea.


var counter = calcCharacters.counter;

One violation is where the function uses calcCharacters. That limits the function to be capable of working only in that one situation, with that one variable name.

Instead of that, the function is already associated with the element that fired the event, so from within the function you have the this kerword which automatically refers to element that fired the event, that being the text area element.


var calcCharacters = this;

I got it working like this: would you modify my script the way you suggested so I can understand your technique?

<!doctype html>
<html> <head> <title> Counter </title> 

</head> <body>
<form >
    <textarea  id="ta" name="ta" rows="6" style="width:340px;"> </textarea>
	<br>
    <input id="ta2" type="text" name="countDisplay" size="3" maxlength="3" value="250"> Characters Remaining
</form>
<script type="text/javascript">  

var maxChar = 250;
var calcCharacters = document.getElementById('ta');
calcCharacters.onkeydown = calcCharacters2;
function calcCharacters2 () {
var x = maxChar - calcCharacters.value.length;
document.getElementById('ta2').value = x;
} 
 
</script>  
</body> </html>

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;
    };
}

And I just realised something.

You don’t even need to set maxChar to a certain value.
Since you already have the max number as the starting value for the counter, you can retrieve that initial value (no matter what it has since changed to) from the defaultValue property.


counter: form.countDisplay,
maxChar: [s][color="red"]250[/color][/s][color="green"]form.countDisplay.defaultValue[/color],

Thanks again for all your help. I followed post #30 through the point where you said, “Here’s the full set of code changes that we’ve gone through here” (right before you made additional improvements). At that point, I ran the script, but couldn’t get it to work. What did I miss?

Your script as I understand it so far:


<!doctype html>
<html> <head> <title> Counter </title> 

</head> <body>
<form id="messageArea">
    <textarea name="ta" rows="6" style="width:340px;"> </textarea>
	<br>
    <input type="text" name="countDisplay" size="3" maxlength="3" value="250"> Characters Remaining
</form>

<script type="text/javascript">  

var form = document.getElementById('messageArea'),
    calcCharacters = form.elements.ta;

calcCharacters.config = {
    maxChar: 250,
    counter: form.countDisplay
};

calcCharacters.config = calcCharacters.onkeyup =  function () {
    var config = this.config,
        x = config.maxChar - this.value.length;
    config.counter.value = x;
}
</body> </html>

You’ve been following things very well. So well though that you also copied an error in one of the pieces.

The post went through multiple rewrites throughout the course of the day before it was posted, and one of those rewrites managed to leave some chaff in the works.
The post has been updated now, but where there was:

form.countDisplay

that should have instead been

form.[color="green"]elements.[/color]countDisplay

It doesn’t change the working of the script, but it’s the more formal and properly correct way to access things.
Another one that is somethiing for which I should be shot for leaving in though is:

calcCharacters.config = calcCharacters.onkeyup =  function () {

which needs to have the first part excised.

[s][color="red"]calcCharacters.config =[/color][/s] calcCharacters.onkeyup = function () {

A last thing that I noticed in your script, which is what’s actually causing the largest problem, is that it needs to end with a </script> closing tag. Otherwise it just won’t work at all.

Thank-you. It works great.

Now back to #30, you said. “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”, but I don’t see exactly how to apply the change you suggested. Can you say again the change you you were referring to?

PS: Should’ve caught that missing <\script> - Please excuse me.

Current script:

<!doctype html>
<html> <head> <title> Counter </title> 

</head> <body>
<form id="messageArea">
    <textarea name="ta" rows="6" style="width:340px;"> </textarea>
	<br>
    <input type="text" name="countDisplay" size="3" maxlength="3" value="250"> Characters Remaining
</form>

<script type="text/javascript">  

var form = document.getElementById('messageArea'),
    calcCharacters = form.elements.ta;

calcCharacters.config = {
    maxChar: 250,
    counter: form.elements.countDisplay
};

calcCharacters.onkeyup =  function () {
    var config = this.config,
        x = config.maxChar - this.value.length;
    config.counter.value = x;
}
</script>
</body> </html>

Sure. It involves a technique called closure, which is one of the things that makes JavaScript different from many other languages. The following article seems to do a good job of describing scopes and closures.
Closure on JavaScript closures

What we’re going to do is to make use of the fact that every function creates a new activation object, which references all the function arguments and variables within the scope of that function. What is returned from this outer function is the inner function, which also knows about the variables from the outer function.


function counterHandler(config) {
    return function () {
        ...
    };
}

This is very useful because functions assigned to events cannot have parameters to receive other variables. Since the returned function retains knowledge of other variables from the outer function, that means that you can call the counterHandler function with your config info, and the returned function (which retains that custom config) can be assigned to an event.


calcCharacters.onkeyup = counterHandler({
    maxChar: 250,
    counter: form.countDisplay
});

All that’s left now is cleaning up, where the contents of the inner function get their config info from the config variable.


function counterHandler(config) {
    return function () {
        var x = config.maxChar - this.value.length;
        config.counter.value = x;
    };
}

That link on closure has the best most concise explanation on scope that I’ve ever read. Plus, it explains closure in a very understandable way thought not able to assemble the changes you sent me.

Here’s the script with the changes I thought you outlined. I obviously don’t have it right. Would make the changes and send them back?


<!doctype html>
<html> <head> <title> Counter </title>

</head> <body>
<form id="messageArea">
   <textarea name="ta" rows="6" style="width:340px;"> </textarea>
       <br>
   <input type="text" name="countDisplay" size="3" maxlength="3" value="5"> Characters Remaining
</form>

<script type="text/javascript">

vfunction counterHandler(config) {
   return function () {
       ...
   };
}

calcCharacters.onkeyup = counterHandler({
   maxChar: 5,
   counter: form.countDisplay
});

function counterHandler(config) {
   return function () {
       var x = config.maxChar - this.value.length;
       config.counter.value = x;
   };
}
</script>
</body> </html>

The code that contained … within it indicates that the code has been elided for the sake of clarity. The full version of that function was given at the end of the post.

The section of your code that starts with “vfunction” should be removed.

Still missing something. What is it please?

<!doctype html>
<html> <head> <title> Counter </title> 

</head> <body>
<form id="messageArea">
    <textarea name="ta" rows="6" style="width:340px;"> </textarea>
	<br>
    <input type="text" name="countDisplay" size="3" maxlength="3" value="5"> Characters Remaining
</form>

<script type="text/javascript">  



calcCharacters.onkeyup = counterHandler({
    maxChar: 5,
    counter: form.countDisplay
});

function counterHandler(config) {
    return function () {
        var x = config.maxChar - this.value.length;
        config.counter.value = x;
    };
}
</script>
</body> </html>

You’re missing the code that declares the form and calcCharacters variables.

Got it! Been up 18 hours. So, please excuse me that I need to wait til tomorrow morning to look and think about with care. I know I’ll have question.

See you tomorrow.

<!doctype html>
<html> <head> <title> Counter </title> 

</head> <body>
<form id="messageArea">
    <textarea name="ta" rows="6" style="width:340px;"> </textarea>
	<br>
    <input type="text" name="countDisplay" size="3" maxlength="3" value="5"> Characters Remaining
</form>

<script type="text/javascript">  

var form = document.getElementById('messageArea'),
    calcCharacters = form.elements.ta;


calcCharacters.onkeyup = counterHandler({
    maxChar: 5,
    counter: form.countDisplay
});

function counterHandler(config) {
    return function () {
        var x = config.maxChar - this.value.length;
        config.counter.value = x;
    };
}
</script>
</body> </html>