Ruby Forum Rails Spinoffs (closed, excessive spam) > trying to figure out how to use bind() to capture values

Posted by bpickens@forumcomm.com (Guest)
on 15.05.2008 23:58
(Received via mailing list)
Hi all,
I am having trouble with my variable assignments always becoming the
last variable inside my loop.

    var populateAvailableContent = Class.create(methodCall, {
      initialize: function($super, name, message) {
        $super(name,'getAllContent','Loading...')
      },
      result: function(ret) {
        var div = $('articles');
        div.innerHTML = null;
        var rowCount = ret.getRowCount();
        for (i=0;i<rowCount;i++) {
          var item = document.createElement('div');
          var id = ret.id[i]
          var headline = ret.headline[i];
          var html = headline + "<hr />";
          item.id = "item";
          item.onclick = function(){fc.selectArticle(id,headline);};
          item.innerHTML = html;
          div.appendChild(item);
        }
      }
    })

In this scenario, id and headline are always becomes the last value in
the loop and that is what is passed in the fc.selectArticle()
function. I've tried a few different possible solutions I've found on
the web, but none seemed to work. I decided to try and give the bind()
function a go around but I can't seem to figure out how to use it in
my situation.

Any help would be greatly appreciated.
Posted by Ken Snyder (Guest)
on 16.05.2008 00:38
(Received via mailing list)
bpickens@forumcomm.com wrote:
> function a go around but I can't seem to figure out how to use it in
> my situation.
>
> Any help would be greatly appreciated.
>   
You can use each() or curry().  In this situation, you don't need a
certain scope so curry() is more suitable than bind().  See example 
below.

- Ken Snyder



// using each() makes each iteration call a function in its own scope
ret.each(function(data, i) {
  var item = document.createElement('div');
  var id = data.id[i]
  var headline = data.headline[i];
  var html = headline + "<hr />";
  // don't forget to use a unique id value
  item.id = "item" + i;
  item.onclick = function(){fc.selectArticle(id, headline);};
  item.innerHTML = html;
  div.appendChild(item);
});

// using curry returns a new function with the current variable values
enclosed in that scope
for (i=0; i<rowCount; i++) {
  var item = document.createElement('div');
  var id = ret.id[i]
  var headline = ret.headline[i];
  var html = headline + "<hr />";
  // don't forget to use a unique id value
  item.id = "item" + i;
  item.onclick = (function(id,
headline){fc.selectArticle(id,headline);}).curry(id, headline);
  item.innerHTML = html;
  div.appendChild(item);
}
Posted by kangax (Guest)
on 16.05.2008 05:57
(Received via mailing list)
If #bind or #curry somehow don't satisfy your needs, there's always a
plain old function wrapping (which simply stores "id" in its own
closure):

...
.onclick = (function(id){
  return function(){ fc.selectArticle(id, ret.headline[i]) }
})(id);
...

- kangax

On May 15, 5:57 pm, "bpick...@forumcomm.com" <slim...@gmail.com>
Posted by T.J. Crowder (Guest)
on 16.05.2008 11:34
(Received via mailing list)
Hi,

Both Ken and kangax have answered the practical question you posed,
but you're probably wondering why your code didn't work. :-)  It's
because in this line of code:

> item.onclick = function(){fc.selectArticle(id,headline);};

...you're assuming that 'id' and 'headline' become fixed at that point
in time, that you're burning their *values* into the closure function
(the onclick handler) at the point the function is defined.  That's
not how closures work.  They work by giving the function enduring
*access* to the things in scope where it's defined.  So for instance:

function chatter()
{
    var closures;
    var i;
    var msg;
    var closure;

    closures = [];
    for (i = 1; i <= 5; ++i)
    {
        msg = "This is message #" + i;
        closures.push(function() { alert(msg); });
    }

    for (i = 0; i < closures.length; ++i)
    {
        closure = closures[i];
        closure();
    }
}

This *doesn't* show five alerts saying "This is message #1", "This is
message #2", etc.  It shows five alerts saying "This is message #5",
because the functions access the 'msg' variable dynamically when
they're *called*, and by the time they're called, msg is "This is
message #5".  This is incredibly powerful, but can be a bit surprising
when you first look at it, especially when the closure lives on after
the function returns and you think that the local vars have
disappeared (they haven't, the closure preserves them).  So this works
the same way and has the same result:

function chatter()
{
    var closures;

    closures = createChatter();
    doChatter(closures);
}
function createChatter()
{
    var closures;
    var i;
    var msg;

    closures = [];
    for (i = 1; i <= 5; ++i)
    {
        msg = "This is message #" + i;
        closures.push(function() { alert(msg); });
    }

    return closures;
}
function doChatter(closures)
{
    var closures;
    var i;
    var closure;

    for (i = 0; i < closures.length; ++i)
    {
        closure = closures[i];
        closure();
    }
}

Note that the 'msg' variable lives even though createChatter() has
returned; each closure keeps a reference to the scope where it was
defined, and so that scope is preserved in memory even when it would
have been released if there were no closures involved.  (The other
local vars to createChatter() also live on; but nothing uses them, so
that's just wasted space.)

More on closures in my blog (which sadly I haven't had time to post to
lately):
http://blog.niftysnippets.org/2008/02/closures-are-not-complicated.html
http://blog.niftysnippets.org/2008/03/closures-by-example.html

This post may also be useful:
http://blog.niftysnippets.org/2008/03/horror-of-implicit-globals.html
...as you have an implicit global variable 'i' in your code which you
almost certainly intended to be local to your "result" function. ;-)

Hope this helps,
--
T.J. Crowder
tj / crowder software / com

On May 15, 10:57 pm, "bpick...@forumcomm.com" <slim...@gmail.com>
Posted by bpickens@forumcomm.com (Guest)
on 16.05.2008 18:13
(Received via mailing list)
Thank you very much guys. Your suggestions worked for me.

@ T.J.
You explanations were very insightful. I think that I understand
javascript a little better now, thank you.
Posted by bpickens@forumcomm.com (Guest)
on 16.05.2008 19:09
(Received via mailing list)
heh, sorry but I've got another related problem where the onclick
assignment doesn't seem to take to the element.

I have a function, and inside this function I have another function
assigned to a var scoped variable name. This function creates and
returns a <a> element with a <img> element appended inside it. The in
the original function append the <a> element inside a <div> element.
Here is some of the code:

    var deletef = function(id) {
      var deleteImg = document.createElement('img');
      deleteImg.id = id;
      deleteImg.src = '';
      deleteImg.alt = 'delete';
      var deleteLink = document.createElement('a');
      deleteLink.id = id;
      deleteLink.href = "javascript:;";
      deleteLink.onClick = (function(id){
        return function(){alert(id + " deleted...");}
      })(id);
      deleteLink.appendChild(deleteImg);
      return deleteLink;
    }

I am then calling the function inside a appendChild function like so:
    div.appendChild(deletef(id));

Does any one see here why the onclick assignment doesn't seem to work?

On May 16, 11:12 am, "bpick...@forumcomm.com" <slim...@gmail.com>
Posted by bpickens@forumcomm.com (Guest)
on 16.05.2008 19:33
(Received via mailing list)
ps, I also tried the curry() method mentioned above by ken to no
avail...

On May 16, 12:08 pm, "bpick...@forumcomm.com" <slim...@gmail.com>
Posted by Ken Snyder (Guest)
on 16.05.2008 21:00
(Received via mailing list)
bpickens@forumcomm.com wrote:
> ...
>       deleteLink.onClick = (function(id){
> ...
>
> Does any one see here why the onclick assignment doesn't seem to work?
>   
Try using "onclick"--all lowercase.  Of course
deleteLink.observe('click', function(id) {...  would be even better :)

- Ken
Posted by bpickens@forumcomm.com (Guest)
on 16.05.2008 21:17
(Received via mailing list)
yes, a coworker helped me to discover the error... ugg. You gotta love
those.  :)
Posted by T.J. Crowder (Guest)
on 17.05.2008 10:36
(Received via mailing list)
It also looks like you're assigning the same ID to both the image and
the link.  IDs have to be unique within the document.
--
T.J. Crowder
tj / crowder software / com

On May 16, 8:16 pm, "bpick...@forumcomm.com" <slim...@gmail.com>