Re: How do I properly pass a parameter to a parameterized event handler in a loop?



Sean Dockery wrote:
"RobG" <rgqld@xxxxxxxxxxxxxx> wrote in message news:43b3d46f$0$21393$5a62ac22@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Sean Dockery wrote:

I have the following HTML file that I've been using for testing...

<html>
<head>
<script type="text/javascript">
<!--

Don't use HTML comment delimiters inside script elements, they don't do anything useful and are potentially harmful.


I had always believed that they were a considered a "best practice". Can you give an example of situation where they would be harmful?

HTML comment delimiters were used inside script elements to cater for browsers that didn't know what a script element was and to stop them displaying the element content. Such browsers would be prior to Netscape 2 (Dec 1995), IE 3 (Jun 1996) or HTML 3.2 (Jan 1997).


Anyone still using one of those browsers on the web today has far more to worry about than the display of some script element content. If that is a genuine concern for you, put the script in an external file so that the script element has no content.

Even if someone visited your site with such an old browser, it should not display markup appearing inside the head element anyway.

The delimiters are potentially harmful for two reasons:

1. They are not valid JavaScript (or ECMAScript) and browsers are not required to treat them as comment delimiters (though likely all current browsers do provided they start before any real script and the closing tag is commented out).

2. If you decide to use XHTML, the script element is not portable as the comment delimiters will comment out the script content. XHTML requires that different delimiters be used:

   <URL:http://www.w3.org/TR/xhtml1/#h-4.8>


Again, I think most browsers will ignore them but you can't guarantee that.


In reality, adding comment delimiters does no real harm in current browsers, but the fact that they have survived for at least 5 years (or so) beyond their use-by date hints that their continued use simply perpetuates their futile existence.

Welcome to 2006  :-)


function handleWindowLoad() {
var items = [];
for (var i = 0; i < 11; i++) {
 items[i] = "item" + (i + 1);
}

This array isn't necessary.


The array is necessary in that it is the mechanism by which I will be delivered a number of JavaScript objects. See below.

Ah, a prototype. :-)


var parent = document.getElementById("parent");
for (var i = 0; i < items.length - 1; i++) {
 var child = document.createElement("div");
 child.innerHTML = items[i];
 var currentItem = items[i];
 handler = new Object();
 handler.currentItem = currentItem;
 handler.onclick = function() {
  handleButtonClick.call(handler);
 }
 child.attachEvent('onclick', handler.onclick);

attachEvent is OK for IE and others that copy it's event model, but what about the rest? Add a fork for addEventListener for W3C browsers.


This is just a proof of concept. In production, I'm using the popular "addEvent" function that hides the browser details.

I can guess what that does, but adding the onclick directly as below seems simplest of all.




But in any case it seems simpler to use:

child.onclick = handler.onclick;

[...]


You need to say what is important here. What do you really want to store, a reference to the new div or the text 'item1', 'item2', etc?


Okay. In my application, I have a list (array) of JavaScript objects that are being provided through the DWR remoting library. For the purposes of testing, I could easily create the objects as follows...

var person0 = { id: 1234, firstName: 'John', lastName: 'Doe' };
var person1 = { id: 2345, firstName: 'Jack', lastName: 'Rabbit' };
var people = [person0, person1];

In the callback function, I want to create HTML elements in the DOM for each of the items in the result list such that the elements that allow for addition detail information to be fetched for each person when the user clicks on them. I want the handleButtonClick function in my example to have access to the relevant object which was clicked upon by the user.

So you are trying to create a link between the DOM object and the 'record' in the array. I think the easiest way is to pick a unique identifier for each record (you have an id that might be useful) then use that as the key to the person record.


When you create each div, give it an id that you use as the key to the person's record in the people object. IDs can't start with numbers but can include them. The example below (end of post) does just that.

I've used an array of for the people records then created an index object that maps the element IDs to the people record indexes. That may be useful, but you can also just sift through the records in an object using for..in for up to say, 50 records with no noticeable affect on performance (depending on the system). If you have a lot of records, an index will really help.

The people array could be an object too, I'm not really using the array-ness of it. If the ID is unique, the structure could be:

 var people = {
       '1234' : {firstName: 'John', lastName: 'Doe' },
       '2345' : {firstName: 'Jack', lastName: 'Rabbit'}
     };

Using an array means that if you modify the order of records, delete or add some you'll have to update the index object.

[...]


As I mentioned in the other reply in this thread, I have come to believe that I don't understand closures yet. I had thought that they worked the same as Java's anonymous inner classes, but they do not.

I don't know what Java anonymous inner classes are, and while I can recognise a closure I can't explain them fully. They are powerful and handy but can also cause problems.




As to how to avoid it, there are many schemes but it depends on what you are really trying to do.



I have also read that closures are bad from the point of view that they can often be the cause of memory leaks. Is there a memory-leak-less way to achieve the same thing that as the code above?

Yes, see above. One solution(?) is below:


function handleWindowLoad() { var numOf = 10; var child, txt; var parent = document.getElementById("parent");

   for (var i=0; i<numOf; ++i) {
     child = document.createElement("div");
     txt = 'item' + (i + 1);
     child.appendChild(document.createTextNode(txt));
     child.id = txt;

     child.onclick = handleButtonClick;
     parent.appendChild(child);
   }
 }


function handleButtonClick() { alert(this.id); }


I'm confused by this part here. Can you tell me to which JavaScript object the keyword "this" refers in this function?

When the function is attached to an element using a reference, 'this' will refer to the element whose event called the function. In the case above, 'this' will refer to the div that was clicked on.



I am confused because I thought that the "this" keyword was only value when you did something like this...

function MyObject(id) {
    this.id = id;
}
myObject.prototype.whatever = function() {
    alert(this.id);
}
var myObj = new MyObject(567);
myObj.whatever();

In the above case, 'this' refers to the new object being created.



...and that the "call" method of the Function object was the only way to bind the "this" keyword to a specific object when dealing with generic methods. For example...


function MyObject(id) {
    this.id = id;
}

function whatever() {
    alert(this.id);
}

var myObj = new MyObject(678);
whatever.call(myObject);

Is there always a value for the "this" keyword that is implied somehow?

If it doesn't apply to anything else, it will be a reference to the global object (the window object in most browsers).



Maybe something like this will do the trick (feature detection and doctype omitted for brevity):



<html> <head><title>...</title> <script type="text/javascript">

 // A people array that holds records for each person
 var people = [
       { id: 1234, firstName: 'John', lastName: 'Doe', age: 35 },
       { id: 2345, firstName: 'Jack', lastName: 'Rabbit', age: 12 }
     ];

 // To hold a reference to each people record by id:
 var peopleIndex = {};

 function handleWindowLoad()
 {
   var parent = document.getElementById("parent");
   var d, p, pName;
   for (var i=0, len=people.length; i<len; ++i){

     // Create a div element
     d = document.createElement('div');

     // Build a text string and add it as div content
     pName = people[i].firstName + ' ' + people[i].lastName;
     d.appendChild(document.createTextNode(pName));

     // Use the person ID to give the div element an id
     d.id = 'id-' + people[i].id;

     // Add a cross-reference entry in the index file for faster
     // retrieval of the person record later
     peopleIndex['id-' + people[i].id] = i;

     // Add the onclick
     d.onclick = function (){
                   handleButtonClick(this.id, people, peopleIndex);
                 }

     parent.appendChild(d);
   }
 }

 function handleButtonClick(elId, obj, objIndex)
 {
   var idxNum = objIndex[elId];
   var record = obj[idxNum];
   var txt = 'Record ' + idxNum;
   for (var prop in record){
     txt += '\n' + prop + ' : ' + record[prop];
   }
   alert(txt);
 }

 window.onload = handleWindowLoad;

 </script>
 </head>
 <body>
  <div id="parent"></div>
 </body>
 </html>





--
Rob
.