Search

Tuesday, October 02, 2007

Prototype.js Object Initialization and Bind While Enumerating Arrays

If the title sounds confusing, that is because it is. I couldn't figure out a better way to describe the issue I was running into. Let see if I can better demonstrate. I have a "initialize" function defined in my JavaScript object as such:

initialize: function() {
// siteID contains a list of ids such as "1,2,3,4"
// Convert the id list in siteIDs to a prototype array $A(siteIDs) and
// for each element in the array, run a function passing it the single siteID
$A(siteIDs).each(function(siteID) {
// If an element exists on the page with the id of setCurrentSiteElementPrefix + current siteID
if ($(this.setCurrentSiteElementPrefix + siteID)) {
 // Add an observer to the 'click' event of that element and bind it
 // to the 'getContent' function
 $(this.setCurrentSiteElementPrefix + siteID).observe('click', this.getContent.bind(this));
}
});
}
where "siteIDs" is a comma delimited list of ids ("1,2,3,4") and "setCurrentSiteElementPrefix" is a part of the name of link element with a dynamically generated id (such as setCurrentSiteElementPrefix_1, setCurrentSiteElementPrefix_2). Here is the above loop in pseudo code (just for your kids Doug): Convert the list of siteIDs to a prototype array and for each found array element, run a function passing the individual site id. $A(siteIDs) converts the list of site ids to an array, while .each() executes the inline function for each array element. // $A(siteIDs).each(function(siteID) If an element exists on the page with the id starting with setCurrentSiteElementPrefix (definition not shown here) + current siteID (passed by the inline function above) // if ($(this.setCurrentSiteElementPrefix + siteID)) Add an observer to the "click" event of that element and bind it to the "getContent" function. The "getContent" function has to bound to the "this" scope since the "getContent" function will not otherwise know what "this" means when trying to refer to elements defined in the object. // $(this.setCurrentSiteElementPrefix + siteID).observe('click', this.getContent.bind(this)); Never mind why I am doing this, I will follow up on that with another post. So I was trying to tie an event observer to the 'click' event of each link element by looping through the list of dynamic ids ($A(siteIDs).each(function(siteID)). However, that was giving me some issues with the message "this.getContent is not defined". Why the heck not, the code is inside the "initialize" method of my object?! It turns out while looping over an array as above the "this" scope is not preserved. So the function inside the loop has no idea what "this" means. The simple fix is to surround the function with parenthesis and append ".bind(this)":
initialize: function() {
 // Note the extra parenthesis before 'function(siteID)'
 $A(siteIDs).each((function(siteID) {
  if ($(this.setCurrentSiteElementPrefix + siteID)) {
   $(this.setCurrentSiteElementPrefix + siteID).observe('click', this.getContent.bind(this));
  }
 }).bind(this));
 // Note the ').bind(this)' before the semicolon
}
If you like to learn more on bind, check out Understanding bind and bindAsEventListener in Javascript and Understanding bind and bindAsEventListener in Javascript - Part II
// //]]>