views:

43

answers:

1

Hi Folks,

I'll start with an example snippet:

self.addwidget({
    box:    ns.box({                        
                text:       'Foobar',
                fromTop:    ~~(Math.random()*window.innerHeight),
                fromLeft:   ~~(Math.random()*window.innerWidth),
                toTop:      240,
                toLeft:     40,
                css:        'foobar',
                easing:     'easeOutCirc',
                duration:   2000,
                events:     {
                    mousedown:      function(e){
                        e.target.position = {x: e.pageX, y: e.pageY};
                    },
                    mouseup:        function(e){
                        // "this" should reference be "box"                                     
                    }
                }
    }),
    delay:  3000
});

Short description:
ns.box() takes an object as argument and creates a new jQuery object. It uses jQuery.extend() to merge the events property object with the jQuery constructor $('<elem/>', {});
After that, ns.box() returns a new object which contains some methods.
--

What I want to archieve is to have access to those propertys / methods within the event handlers. Of course I cannot access box.somemethod at this point because I cannot reference the outer box property at this point. So I tried to change the scope from the event handlers with jQuery.proxy(), to this, but with no success.
this.somemethod is unreferenced even when proxied. I also tried to proxy all objects top->down, no success either.

Is it even possible in a construct like this one, to access the propertys from the returned object of ns.box(), within the event handlers ?

+4  A: 

You can achieve this with a scoping function, like this:

self.addwidget((function() {
    var box = ns.box({                        
                text:       'Foobar',
                fromTop:    ~~(Math.random()*window.innerHeight),
                fromLeft:   ~~(Math.random()*window.innerWidth),
                toTop:      240,
                toLeft:     40,
                css:        'foobar',
                easing:     'easeOutCirc',
                duration:   2000,
                events:     {
                    mousedown:      function(e){
                        e.target.position = {x: e.pageX, y: e.pageY};
                    },
                    mouseup:        function(e){
                        // You can access `box` here                         
                    }
                }
    });

    return {
        box:   box,
        delay: 3000
    };
})());

By assigning the box to a var inside our scoping function, we create a symbol that the event handlers (which are closures) close over and thus have access to. We call the function immediately, having it return the object. References held by closures are enduring, and so...

I use this pattern all the time because I don't like having anonymous functions (your event handlers are anonymous, for instance); more here. If I were doing the above, I'd make those named instead, like this:

self.addwidget((function() {
    var box = ns.box({                        
                text:       'Foobar',
                fromTop:    ~~(Math.random()*window.innerHeight),
                fromLeft:   ~~(Math.random()*window.innerWidth),
                toTop:      240,
                toLeft:     40,
                css:        'foobar',
                easing:     'easeOutCirc',
                duration:   2000,
                events:     {
                    mousedown:      boxMousedown,
                    mouseup:        boxMouseup
                }
    });

    function boxMousedown(e){
        e.target.position = {x: e.pageX, y: e.pageY};
    }

    function boxMouseup(e){
        // You can access `box` here                         
    }

    return {
        box:   box,
        delay: 3000
    };
})());

Those functions now have names that can show up in call stacks and error messages, but they're completely private, not cluttering up the global namespace.

T.J. Crowder
Indeed, nice suggestion T.J. Using a closure to `box`, I should have figured that for myself.
jAndy
@T.J. Crowder: what does `~~()` do?
Robusto
@Robusto: See the link in Yi Jiang's comment on the question. I didn't know either until I read it. :-)
T.J. Crowder
@T.J.: even if this solution is great, I'd still prefer to access the `box` over `this` within the handlers. That only has application logic reasons. Can you think of a way to accomplish that? Even possible?
jAndy
@jAndy: Sure, it's easy enough: In my second example (the one using named functions), change `mousedown: boxMousedown` to `mousedown: function(event) { return boxMousedown.call(box, event) }`. It introduces an extra function call, but that's unlikely to be a big performance issue on mousedown and mouseup. The only other way would be if the code behind `ns.box` provided support for it explicitly.
T.J. Crowder
@T.J.: Doh I expressed myself wrong. I'm well aware of `.call()` and `.apply()`. I meant to have `this` referencing to `box` in my original code. Some way, to change the scope there so I could just call `this.some_method_in_box()`. Normally, `this` references the `DOM node`. I thought `.proxy()` could do this, but it seems not to work within an object literal. Anyways, good answer again.
jAndy
@jAndy: Thanks. `proxy` will work here; all it's doing is what I was doing above with `call`, but in a tidier way. So `mousedown: jQuery.proxy(boxMousedown, box)` would do it. (You don't have to use a named function if you don't want to, it just makes life easier.) All of this is made possible by the scoping closure, I don't think you can do it without it, because you don't have a way to get a that box reference.
T.J. Crowder