0

Am trying to laod tweets into a div after looping them from yahoo placemaker. They are loading on the div but the information shown by them is placemaker's last result.

This is the code..

            function getLocation(user, date, profile_img, text,url) {
            var templates = [];
            templates[0] = '<div><div></div><h2 class="firstHeading">'+user+'</h2><div>'+text+'</div><div><p><a href="' + url + '"target="_blank">'+url+'</a></p></div><p>Date Posted- '+date+'</p></div>';
            templates[1] = '<table width="320" border="0"><tr><td class="user"  colspan="2" rowspan="1">'+user+'</td></tr><tr><td width="45"><a href="'+profile_img+'"><img src="'+profile_img+'" width="55" height="50"/></a></td><td width="186">'+text+'<p><a href="' + url + '"target="_blank">'+url+'</a></p></td></tr></table><hr>';
            templates[2] = '<div><div></div><h2 class="firstHeading">'+user+'</h2><div>'+text+'</div><div><p><a href="' + url + '"target="_blank">'+url+'</a></p></div><p>Date Posted- '+date+'</p></div>';
            templates[3] = '<table width="320" border="0"><tr><td class="user"  colspan="2" rowspan="1">'+user+'</td></tr><tr><td width="45"><a href="'+profile_img+'"><img src="'+profile_img+'" width="55" height="50"/></a></td><td width="186">'+text+'<p><a href="' + url + '"target="_blank">'+url+'</a></p></td></tr></table><hr>';
                        var geocoder = new google.maps.Geocoder();
                Placemaker.getPlaces(text, function (o) {
                    console.log(o);
                    if (!$.isArray(o.match)) {
                        var latitude = o.match.place.centroid.latitude;
                        var longitude = o.match.place.centroid.longitude;
                        var myLatLng = new google.maps.LatLng(latitude, longitude);
                        var marker = new google.maps.Marker({
                            icon: profile_img,
                            title: user,
                            map: map,
                            position: myLatLng
                        });

                      var infowindow = new google.maps.InfoWindow({
                          content: templates[0].replace('user',user).replace('text',text).replace('url',url).replace('date',date)
                      });
                      var $tweet = $(templates[1].replace('%user',user).replace(/%profile_img/g,profile_img).replace('%text',text).replace('%url',url));
                      $('#user-banner').css("visibility","visible");$('#news-banner').css("visibility","visible");
                      $('#news-tweets').css("overflow","scroll").append($tweet);
                      function openInfoWindow() {
                          infowindow.open(map, marker);
                      }
                      google.maps.event.addListener(marker, 'click', openInfoWindow);
                      $tweet.find(".user").on('click', openInfoWindow);
                        bounds.extend(myLatLng);
                    }
                });
            }
anjel
  • 1,355
  • 4
  • 23
  • 35
  • Where is your loop? This sounds like the common "closure inside loop" problem. Have a look at http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example. – Felix Kling Sep 03 '12 at 20:18
  • i dont understand this closures.can u help me figure out how to solve this? – anjel Sep 03 '12 at 21:04
  • I don't see any problem with your code. You have to provide more information (about the input, resulting HTML, etc) and / or a http://jsfiddle.net/ demo. – Felix Kling Sep 03 '12 at 21:11
  • Ok i will try to give some more information tomorrow . thanku – anjel Sep 03 '12 at 21:15

2 Answers2

1

ak,

After some experimentation, I tracked down the problem to Placemaker.js not being able to handle multiple simultaneous requests. It's tempting to think you can overcome the problem with closures to remember loop-generated data but this doesn't work. The fix needs to be within Placemaker and I opted to refactor it as a jQuery plugin. jQuery affords the possibility of returning a promise from the getPlaces method, making it similar to jQuery's native $.ajax(), $.get() etc.

Yahoo! Placemaker.js as a jQuery Plugin

/* ******************************************************************************
 * Yahoo! Placemaker.js factored as a jQuery Plugin
 * ******************************************************************************
 * by Beetroot-Beetroot: http://stackoverflow.com/users/1142252/beetroot-beetroot
 * ******************************************************************************
 * For example of usage, see : http://stackoverflow.com/questions/12253544/
 * ******************************************************************************
 * All rights reserved
 * Please keep this attribution intact
 * ******************************************************************************
 */
(function($){
    // **********************************
    // ***** Start: Private Members *****
    var pluginName = 'Placemaker';
    var config = {
        appID: ''
    }
    // ***** Fin: Private Members *****
    // ********************************

    // *********************************
    // ***** Start: Public Methods *****
    var methods = {
        config: function(obj) {
            $.extend(config, obj);
        },
        getPlaces: function(data) {
            var that = this;//jQuery object
            var def = new $.Deferred();
            if(config.appID === '') {
                def.rejectWith(this, [{message: pluginName + ' plugin application ID is not set'}]);
                return def.promise();
            }
            var query = [
                'select * from geo.placemaker where documentContent="' + data.text + '" and documentType="text/plain"'
            ];
            if(data.locale) {
                query.push('and inputLanguage="' + data.locale + '"');
            }
            query.push('and appid="' + config.appID + '"');
            var url = [
                'http://query.yahooapis.com/v1/public/yql?q=' + encodeURIComponent(query.join(' ')),
                'format=json',
                'env=http%3A%2F%2Fdatatables.org%2Falltables.env',
                'callback=?' // callback=Placemaker.retrieve ????
            ];
            $.ajax({
                url: url.join('&'),
                type: 'GET',
                dataType: 'JSON',
                cache: false
            }).done(function(o) {
            if(o.query && o.query.results && o.query.results.matches) {
                    def.resolveWith(that, [o.query.results.matches, data]);
                }
                else {
                    def.rejectWith(that, [{message:'no locations found'}]);
                }
            }).fail(function(jqXHR, textStatus, errorThrown) {
                def.rejectWith(that, [{message: textStatus}]);
            });
            return def.promise();
        }
    };
    // ***** Fin: Public Methods *****
    // *******************************

    // *****************************
    // ***** Start: Supervisor *****
    $.fn[pluginName] = function( method ) {
        if ( methods[method] ) {
            return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
        } else if ( typeof method === 'object' || !method ) {
            return methods.init.apply( this, arguments );
        } else {
            $.error( 'Method ' + method + ' does not exist in jQuery.' + pluginName );
        }
    };
    // ***** Fin: Supervisor *****
    // ***************************
})( jQuery );

Setting the appID

You can hard-code your appID in your own copy of the plugin or set it like this :

    $().Placemaker('config', {'appID': '..........'});

Note that .Placemaker() needs to be invoked on a jQuery object. For the 'config' method, any selector will do, so an empty jQuery object, $() will suffice.

Using the Plugin

The rest of your code, including a plugin call, will look like this :

$(function() {
    // *** fixed data ***
    var mapOptions = {
        center: new google.maps.LatLng(35.74651, -39.46289),
        zoom: 2,
        mapTypeId: google.maps.MapTypeId.ROADMAP
    };
    var templates = [];
    templates[0] = '<div><h2 class="firstHeading">%user</h2><div>%text</div><div><a href="%url" target="_blank">%url</a></div><div>Date Posted- %date</div></div>';
    templates[1] = '<table width="320" border="0"><tr><td class="user" colspan="2">%user</td></tr><tr><td width="45"><a href="%profile_img"><img src="%profile_img" width="55" height="50"/></a></td><td width="186">%text<p><a href="%url" target="_blank">%url</a></p></td></tr></table><hr/>';
    templates[3] = "https://api.twitter.com/1/statuses/user_timeline.json?include_entities=true&include_rts=false&screen_name=%val&count=10&callback=?";
    $$ = { //cache of jQuery objects
        news_tweets: $("#news-tweets"),
        user_banner: $('#user-banner')
    };

    // *** functions ***
    function news_tweets(value1) {
        $$.news_tweets.empty();
        var bounds = new google.maps.LatLngBounds(); //??
        var map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
        $.getJSON(templates[3].replace('%val', value1), function(data) {
            var d, len = data.length;
            for (var i = 0; i < len; i++) {
                var item = data[i];
                d = {
                    text: item.text,
                    user : item.user.name,
                    date: item.created_at,
                    profile_img: item.user.profile_image_url,
                    url: (item.entities && item.entities.urls && item.entities.urls.length) ? item.entities.urls[0].url : '',
                    locale: null
                }
                d.$tweet = $(templates[1].replace('%user', d.user).replace(/%profile_img/g, d.profile_img).replace('%text', d.text).replace(/%url/g, d.url))
                    .appendTo($$.news_tweets.css("overflow", "scroll"))
                    .find(".user")
                    .Placemaker('getPlaces', d)  //.Placemaker('getPlaces') returns a promise
                    .done(function(o, d) {
                        var m = ($.isArray(o.match) ? o.match[0] : o.match) || {};
                        if(m.place) {
                            var myLatLng = new google.maps.LatLng(m.place.centroid.latitude, m.place.centroid.longitude);
                            var marker = new google.maps.Marker({
                                icon: d.profile_img,
                                title: d.user,
                                map: map,
                                position: myLatLng
                            });
                            var infowindow = new google.maps.InfoWindow({
                                content: templates[0].replace('%user', d.user).replace('%text', d.text).replace(/%url/g, d.url).replace('%date', d.date)
                            });
                            function openInfoWindow() {
                                infowindow.open(map, marker);
                            }
                            google.maps.event.addListener(marker, 'click', openInfoWindow);
                            this.each(function() { //`this` is already a jQuery object. Re-wrapping as `$(this)` is not necessary.
                                $(this).on('click', openInfoWindow).css({'color':'#900', 'text-decoration':'underline', 'cursor':'pointer'});//$(this) is an individual tweet in #news_tweets.
                            });
                            bounds.extend(myLatLng); //??
                        }
                    }).fail(function(err) {
                        console.log(err.message);
                });
            }
        });
    }

    // *** event handlers ***
    $("#newsTypes").on('click', 'img', function() {
        news_tweets($(this).data('type'));
        //user_tweets("euronews");
    });
});

At the heart of this, you will find the following structure :

for(...) {
    var d = {...}; //object map comprising both data and options
    $(htmlString).appendTo(...).find(...).Placemaker('getPlaces', d).done(fn{}).fail(fn{});
}

It is important to note that :

  • the jQuery method chain starts with a standard jQuery object.
  • from .Placemaker('getPlaces', d), methods in the chain return a jQuery promise object.
  • in both the .done() and the .fail() handler, this is equivalent to the original standard jQuery object.
  • the data map passed as the second argument in the expression .Placemaker('getPlaces', d), reappears as the second argument of the .done() handler. This feature allows us to call .Placemaker('getPlaces', d) in a for loop without needing to specifically put the data into a closure or storing it in the DOM with .data(). In this regard, the 'getPlaces' method effectively serves as a closure as well as providing the required asynchronous lookup behaviour.

All this will be judged as thoroughly confusing or totally clever depending on your point of view.

Beetroot-Beetroot
  • 18,022
  • 3
  • 37
  • 44
  • ak, I've just made a couple of edits so be sure to pick up the latest code. – Beetroot-Beetroot Sep 05 '12 at 21:41
  • I said that i wont use your code and i didnt :) Your code was very helpful for me but a bit advanced. I found the solution to my problem today and i fixed it. The problem was google map was being drawn 2 times and I couldn't see the results of the news div. So I had to merge the two arrays together and load the function once. Many thanks for your help though I will call you if I need anything else :) – anjel Sep 06 '12 at 22:33
  • Sorry if i have wasted your time @Beetroot-Beetroot . – anjel Sep 07 '12 at 16:53
  • No worries ak, I'm just intrigued as to how and why your solution works. I can only think that your version of Placemaker.js is drastically different from the one I started off with. Hopefully the code above will be useful to future visitors here. – Beetroot-Beetroot Sep 07 '12 at 23:20
  • Beetroot can you help me with this one http://stackoverflow.com/questions/12446776/request-twitter-api-without-entering-a-loop.!! – anjel Sep 16 '12 at 13:17
  • My starting point would be the answer I gave you above, so I don't think I can be much use to you. – Beetroot-Beetroot Sep 17 '12 at 11:27
0

From what I see your code is written to .replace() the contents of the div's through each iteration. You need to add some indexing to your template array.

Try something like:

var index=0;

var templates = [];
templates[index]={
'0':'<div><div></div><h2....',
'1':'<table width="320".....',
'2':'<div><div></div><h2....',
'3':'<table width="320"....'
}

Then put your Placemaker.getPlaces block in a for loop You update your templates with templates[index]["0"]=whatever. I think this the logic you should be pursuing. Hope this all helps.

cube
  • 1,774
  • 4
  • 23
  • 33
  • what if the function is coming from a loop? can u check this fiddle. http://jsfiddle.net/ang3lo0o/B9es7/ .I tried your suggestion but is not working – anjel Sep 04 '12 at 14:23