A problem applying 'onclick'' event handlers dynamically

I run a web site on which subscribing members can advertise holiday accommodation etc. The amount a member pays depends (in part) on the number and type of their properties. In the ‘Members Area’ (which requires login) a member can see how much their subscription for this year and next year will be. The appropriate year is selected by the member clicking a button. The data is all extracted from a MySQL database.

Hitherto this has always required a page refresh, but I have been trying to introduce a bit of Ajax, so that only the table gets refreshed. It’s all working very well as long as the ‘onclick’ event handlers are hard coded. I have been trying to assign the onclick event handlers dynamically, but always run into a problem that BOTH the event handlers take on the final value of the variable (k+i) (which should be the year, 2012 or 2013, but always ends up as 2014).

	function prepareHandler() {
		if (!document.getElementById) return;
		var inputs = document.getElementsByTagName('input');
		var len = inputs.length;
		var k = 2012;
		for (var i=0; i<len; i++) {
			var trhd = (k+i);
			inputs[i].onclick = function() {setRateId(k+i);};
			alert ("Year = " + (k+i)); // when function runs, shows correct values, 2012, 2013
		}
		alert ("final Year = " + (k+i)); // Always 2014 (which I think is to be expected)
	}

	function setRateId(abcd) {
		alert ("setRateid called; year = " + abcd); // ALWAYS shows 2014 (unless event handlers are hard-coded)
		var url = "subsformlogic.php?rateid=" + abcd;
		return !grabFile(url);

	}

	window.onload = prepareHandler; // disabled when using hard coded event handlers


It’s as though the values set by the line

inputs[i].onclick = function() {setRateId(k+i);};

aren’t being ‘frozen’, but continue to increment. Surely this should not happen ?

If I use hard coded event handlers there’s no problem at all (the PHP variables evaluate to 2102 and 2013 respectively):-

<p><input type="button" value='This Year' onclick='setRateId(<?php echo $thisyear; ?>)' />
&nbsp;&nbsp;
<input type="button"  value='Next Year' onclick='setRateId(<?php echo $nextyear; ?>)' />
</p>

In this case there are only two event handlers required, so hard coding them is no hardship. However I’m looking to make similar changes to other pages, where there will be more.

I think the problem must lie in the function ‘prepareHandler’ as ‘setRateId’ runs OK even though it gets duff input (there’s an error message if the year exceeds 2013).

Have I overlooked something very basic ?

You’re learning that things are different from what you expected.

When a function is assigned, it is not evaluated at the time of assignment. Instead, it’s when the function is executed that it is evaluated. In your case, it uses the global variable of k and i whenever it is clicked on.

For things to work properly as you expect them to, you need to pass those variables in to a function. For example:


function assignRateEvent(year) {
    return function () {
        setRateId(year);
    };
}
...
inputs[i].onclick = assignRateEvent(k + i);

That way the returned function retains knowledge of the year that you want to use it with.

Thank you, Paul, that works perfectly.

When a function is assigned, it is not evaluated at the time of assignment. Instead, it’s when the function is executed that it is evaluated. In your case, it uses the global variable of k and i whenever it is clicked on.

Yes, I was beginning to realise that that was my problem, although I didn’t express it so elegantly !

A good day on Ajax, and thanks to your help, a successful one.

Hello again, Paul,

I awoke this morning wondering why

	var y = (k+i);
	buttons[i].onclick = assignRateEvent(y);

was able to capture and hold the transient value of ‘y’, but the more ‘direct’

	var y = (k+i);
	buttons[i].onclick = setRateId(y);

could not. I think it must be because both functions actually run at the time of the ‘for’ loop ? Clearly running ‘setRateId’ at that point is not going to get us anywhere. (I’ve changed the ‘input’ of my first posting to ‘button’, but this of course plays no part)

I set up another function with ‘getAttribure’ to show the event handlers after they’d been set, but always get a null result (no matter which of the lines above I use). So then I thought I’d try ‘setAttribute’ as follows:

	var y = (k+i);
	buttons[i].setAttribute('onclick', 'setRateId(' + y + ')');

and this does work in Firefox, AND I can ‘see’ the event handlers using ‘getAttribute’. However, I have previously been warned against using ‘setAttribute’ (in a different context) as it isn’t supported everywhere (presumably IE as usual). I’ll soon find out.

The reason why that works is, if y currently equals 2, it’s assigning to the onclick event the following:


function () {
    setRateId(2)
}

So there is no y value involved in that event anymore.

Hello Paul, thanks for your reply.

The reason why that works is, if y currently equals 2, it’s assigning to the onclick event the following:…

Yes, and ‘setAttribute’ seems a good way to do it for FF. However ‘Stack Overflow’ is full of dire warnings about using ‘setAttribute’ in IE:-
http://stackoverflow.com/questions/95731/why-does-an-onclick-property-set-with-setattribute-fail-to-work-in-ie
I haven’t read the whole article yet, so won’t comment further except to say that the author’s first suggestion is to use an anonymous function, which is what I was initially trying to do…maybe it’s adding the arguments that causes the problem.
Thanks for your help.