bodine
bodine

Reputation: 1823

Why is my 'this' not changing context inside inner jQuery each loop?

The idea is that I want to loop through these objects and build an HTML structure which will be added to the page. I thought it would be cleaner to do it all in the chain, but apparently I'm not understanding something about the context of this as it evolves through inner loops. I've looked a bit at jQuery.proxy() a bit, but I'm not sure I understand how to apply it here. Maybe there is another way altogether of doing what I'm trying to do here...

var obj = [
    {"id":1213854620001,"name":"item 1","URL":"1213897576001.jpg"},
    {"id":1213854619001,"name":"item 2","URL":"1213890384001.jpg"},
    {"id":1213854618001,"name":"item 3","URL":"1213890378001.jpg"},
    {"id":1213854616001,"name":"item 4","URL":"1213897663001.jpg"},
    {"id":1213854615001,"name":"item 5","URL":"1213897554001.jpg"}
];
$(function() {
    if(obj.length) {
        $("<ul/>",{id:"myID"}).append(function(){
            var that = document.createDocumentFragment();
            $.each(obj,function(index,dataObj){
                $("<li/>",{data:{dataID:dataObj.id},text:dataObj.name}) // this === obj[index] === dataObj, shouldn't it be the [object HTMLLIElement]
                    .live("click",function(event) {
                        openVideo($(event.target).data(dataID));
                    })
                    .append(function() {
                        return $("<img/>",{src:dataObj.thumbnailURL})[0];
                    })
                    .appendTo(that);
            });
            return that;
        }).appendTo("body");
    }
});

function openVideo(str) {
    //console.log(str);
}

The implicit question becomes, why is that empty after my loop? and how can I build this HTML structure with nested loops?

Using the suggestions from the comments, and answers, I built this, which seems to work exactly as it should, reads a little cleaner, and lets jQuery do all the javascript (e.g. documentFragment creation, and manipulation, etc):

$(function() {
    if(obj.length) {
        $("<ul/>",{id:"myID"})
        .delegate("li","click",function(){openVideo($(this).data("dataID"));})
        .append(function() {
            var that = $(this);
            $.each(obj,function(index,dataObj) {
                $("<li/>",{data:{dataID:dataObj.id},text:dataObj.name}).each(function() {                   
                    $("<img/>",{src:dataObj.URL}).appendTo(this);
                    that.append(this);
                })
            });
        }).appendTo("body");
    }
});

Upvotes: 1

Views: 2093

Answers (3)

Adam Rackis
Adam Rackis

Reputation: 83366

this changes meaning as you step into new nested functions. It does not change meaning when you call $() to create a new element.

So immediately inside of

$.each(obj, function(index, dataObj) {

this is the current object over which your looping. Once you get here:

.live("click",function(event) {  // <------  inside of nested function 
    openVideo($(event.target).data(dataID));
})

this is the element on which you clicked.

But calling

    $("<li/>",{data:{dataID:dataObj.id},text:dataObj.name}) 

to create a new li element does not set this to the newly created element.


Also, if you want to save the meaning of this even inside of nested functions, the standard way is to save it to a new variable.

$.each(obj, function() {
    var self = this;

Now self can be used instead of this as the item you're currently "on" anywhere in your loop, even in nested function handlers. Or you can pass index and dataObjects to the each function - same effect.

EDIT

As a comment pointed out, you're using live incorrectly. If you're using jQuery 1.7, you'll want:

$(document).on("click", "li", function(event) {
      openVideo($(this).data(dataID));
});

in your document.ready handler. If all li's that will be clicked will be in a certain div, than select on that div instead of document. If you don't want this click handler to apply to all li's, but only some, then decorate all the li's you want this click handler to apply to with a css class, then instead of passing the filter "li" to on, you'd pass li.yourClass

Upvotes: 4

user1046334
user1046334

Reputation:

$.each sets this for the inner function to be the element iterated (and yes, this is one of the complicated things in Javascript, but your comment can be explained by just referencing each behaviour).

Upvotes: 1

Sheridan Bulger
Sheridan Bulger

Reputation: 1214

The thing is, in prototype based languages, which javascript is one, functions are objects, and objects have properties. So when you use the 'this' keyword, it reverts to the scope of the current prototype. Inside of an 'inline function' (which is actually not a concept in javascript since functions are just objects), this refers to the function.

Upvotes: 0

Related Questions