3

So, I have this jQuery .each loop, and for the most part its working as intended; there is one issue, but first the loop:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
    <head>
        <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.1.min.js"></script>
        <script type="text/javascript">
            function Pushpin(){}
            Pushpin.prototype.XZX = {
                site: null,
                getHtmlDescription: function () {
                    var html  = '<b id="infoboxTitle" style="position:absolute; top:10px; left:10px; width:220px;">' + this.site.Name + '</b>';
                        html += '<a id="infoboxDescription" style="position:absolute; top:30px; left:10px; width:220px; height:120px;">{0}</a>';

                    var description = 'Headcount: ' + this.site.Headcount + '<br />';
                    description += 'Leases: ' + this.site.LeaseCount + '<br />';

                    html = html.replace('{0}', description);

                    return html;
                }
            };

            var data = [
                    {"Address":{"City":"Atlanta","Country":"USA","County":"","Latitude":33.9882404987503,"Longitude":-84.1629638209203,"Region":"Southeast","State":"GA","StreetAddress":"Atlanta 177","ZipCode":"30096"},"Headcount":0,"ImageBytes":null,"ImageRefPath":"","LeaseCount":1,"Leases":null,"Name":"Atlanta","NextExpire":"\/Date(1495083600000-0500)\/","Number":"1052","PrimaryUse":"Garage","PropertyID":"OMNI","RecID":32839,"RecordID":1004,"RentableSquareFootage":22000,"SiteRecordID":"DEMO_29626","SiteTotalDollars":0,"Status":null,"Type":"LSE"},
                    {"Address":{"City":"Bellevue","Country":"USA","County":"","Latitude":47.6043250620083,"Longitude":-122.14236047437,"Region":"Northwest","State":"WA","StreetAddress":"Seattle 51","ZipCode":"98007"},"Headcount":0,"ImageBytes":null,"ImageRefPath":"","LeaseCount":1,"Leases":null,"Name":"Bellevue","NextExpire":"\/Date(1260424800000-0600)\/","Number":"1078","PrimaryUse":"Tower","PropertyID":"OMNI","RecID":32865,"RecordID":1027,"RentableSquareFootage":7652,"SiteRecordID":"DEMO_275651","SiteTotalDollars":0,"Status":null,"Type":"LSE"}
                ]; 

            var mylist = []; 
             $.each(data, function (i, item) { 
                try {
                    var pin = new Pushpin();  
                    pin.XZX.site = item;
                    mylist.push(pin); 
                } catch (e) { alert (e); } 
             });
            $(document).ready(function() {
                $('#btnAlert').click(function () { 
                    $('#content').html(mylist[$('#index').val()].XZX.getHtmlDescription());
                } );
            });
        </script>
    </head>
    <body >
        <div style="margin-left:auto; margin-right:auto; width:300px;">
            <div style="position:relative; width:250px;">
                <select id="index">
                    <option>0</option>
                    <option>1</option>
                </select> 

                <input type="submit" id="btnAlert"/>
            </div>
            <div id="content" style="position:relative;width:250px;"></div>
        </div>
    </body>
</html>

Also available on jsfiddle: http://jsfiddle.net/M8YS2/

At the end of the loop, mylist[x].site for any x all point to the same instance of my data item, how can I get around this?

0

4 Answers 4

4

The issue is that each pin.XYZ is the same object -- namely Pushpin.prototype.XYZ.

The simple "fix" is to use:

var pin = new Pushpin(...)
pin.XYZ = {
   site: item
   // the following will get tedious fast, consider one of the "property copy"
   // implementations floating about -- including jQuery.extend   
   getHtmlDescription: Pushpin.prototype.XYZ.getHtmlDescription
}

Which will assign a new object to the XYZ property of each new Pushpin object. Of course, this could be designed differently as well :)

At the very least, move XYZ off the Pushpin.prototype object -- this will allow it to treated nicely as an object (the way that this is passed about actually makes it nigh-impossible for a function dangling off an object of a prototype to access instance data of the object to which the prototype applies); the end-code might look something like:

// We .. "wrap" the real Pushpin constructor
// somewhere global after Bing Mapi JS loaded
Pushpin = (function (bingPushpin) {
   return function Pushpin (...) {
       var pin = new bingPushpin(...)
       pin.XYZ = new XYZ()
       // trick of "returning" from ctor
       return pin
   }
})(PushPin)
// ...
var pin = new Pushpin(...)
pin.XYZ.site = item

Happy coding.


Pre-update answer:

This actually isn't a scoping issue -- there are no inadvertent closures created and each expression is strictly evaluated.

I suspect there is another problem, such as unexpected input (data contains a bunch of the same item) or flawed assumption (such that objects are magically cloned) or something unrelated.

Happy coding.


Analysis:

 var mylist = [];
 $.each(data, function (i, item) {
     // creates new object
     var pin = new Pushpin(x, y);
     // property of new object assigned
     // remember that no "duplication" is occurring
     pin.site = item;
     // new object pushed to array
     mylist.push(pin);
 });

Therefor, no pin will be the same but it is possible that item evaluates to the same object each loop. (The only exception to this is if the Pushpin constructor uses return to return an existing object, which would be a fun fine indeed.)

Sign up to request clarification or add additional context in comments.

5 Comments

The Pushpin is from Bing Maps (not sure if it uses return or not). I have verified that item is different each time through the loop.
@Nate Don't worry about the Pushpin ctor then -- it would break everything in terribly wonderful ways. Do you have a minimal test-case showing the behavior? (jsfiddle.net is a good place to post it, as well as in the original post). For the test-case it should be sufficient to use var pin = {} and remove the Bing Maps dependency.
No, I'll try to get one. Would it make a difference if my .each loop is in the callback for a $.getJSON call?
@Nate Not inherently, but the larger context may contain more clues.
OK, I have a test-case that demonstrates the behavior I'm seeing: jsfiddle.net/M8YS2
0

Considering you're within a function I would say yes of course they all point to the same object as you're only passing by reference. After looking around I stumbled upon this - http://my.opera.com/GreyWyvern/blog/show.dml/1725165 - but it doesn't look like there is a straight-forward option for cloning a Javascript object.

Perhaps your best approach would be to write a function that clones an input object and returns it as a new instance?

1 Comment

Pedantic: Objects in JavaScript are not passed by reference (a reference is an underlying implementation technique/detail that is never exposed in the JavaScript language). Rather, JavaScript objects are passed as themselves (not a copy, clone or duplicate). This calling behavior is known as Pass-By-Object or Pass-By-Object-Sharing.
0

After reading MoarCodePlz's answer I thought maybe this could help getting around the 'by reference' issue. Haven't verified it though.

 var mylist = [];
 $.each(data, function (i, item) {
     // Creates the new object as a part of yourlist
     mylist.push(new Pushpin(x, y));
     // property of new object assigned item
     mylist[x].site = item;
 });

2 Comments

This won't fix anything. An assignment never creates a new object (primitive values can be excluded from the discussion for simplicity).
Okay. Thought it was worth a try. :)
0

Do you need to declare the var pin outside of the .each? then set it to new inside the .each.

var pin;
 var mylist = [];
 $.each(data, function (i, item) {
     try {
         pin = new Pushpin(x, y);
         pin.site = item;
         mylist.push(pin);
     } catch (e) { alert (e); }
 });

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.