13

I have a list of <li>'s which gets populated with a find() using Meteor.startup as you see below. Then I'm getting all the data attributes of these <li>'s using data() and putting it in an object and trying to return/console.log it so I can see if it works. But I'm getting null as a result.

    Meteor.startup(function () {
    Template.messages.lists = function () {
        var allitems = lists.find();
        return allitems;
    };
    var map;
    map = new GMaps({
        div: '#map_canvas',
        lat: -12.043333,
        lng: -77.028333
    });
    var lat = map.getCenter().lat();
    var lng = map.getCenter().lng();
    map.addMarker({
        lat: lat,
        lng: lng,
        draggable: true,
        title: 'Test',
        dragend: function (e) {
            $('#lat').val(this.getPosition().lat());
            $('#lng').val(this.getPosition().lng());
        }
    });
    console.log(getMarkers());
});


function getMarkers() {
    var coordinates = {};
    coordinates = $('li.message').data();
    return coordinates;
}

I tried the same in my console directly and it works - I get an object back - so I'm guessing that the DOM is not ready/populated before this function is executed.

I am having a hard time understanding the difference between things like Meteor.startup and Template.mytemplate.rendered. In this case it seems that none of them works as I want?

What's the right way/place to do stuff with the DOM (traversing,getting attributes,manipulating)?

edit

as the code changed a lot in order to do what I wanted I post the whole thing.

Meteor.startup(function () {
  var map;
  map = new GMaps({
    div: '#map_canvas',
    lat: 50.853642,
    lng: 4.357452
  });
  Meteor.subscribe('AllMessages', function() {
    var allitems = lists.find().fetch();
    console.log(allitems);
    allitems.forEach(function(item) { 
      var lat = item.location.lat; 
      var lng = item.location.lng;
      console.log('latitude is: ' + lat);
      console.log('longitude is: ' + lng);
      map.addMarker({ 
        lat: lat, 
        lng: lng, 
        draggable: true, 
        title: 'Test', 
        dragend: function(e) { 
          $('#lat').val(this.getPosition().lat()); 
          $('#lng').val(this.getPosition().lng()); 
        } 
      }); 
    });
  });
});

The above code creates a new google map (using the GMaps.js plugin) inside Meteor.Startup, and then in a nested Subscribe fetchs all documents from a collection, forEaches the results and gets the latitude and longitude values, then goes on to add markers in the google map...

edit 2

I made my 'map' variable a global one this way no need to nest .subscribe and .startup. :

Meteor.subscribe('AllMessages', function() {
  var allitems = lists.find().fetch();
  console.log(allitems);
  allitems.forEach(function(item) { 
    var lat = item.location.lat; 
    var lng = item.location.lng;
    console.log('latitude is: ' + lat);
    console.log('longitude is: ' + lng);
    map.addMarker({ 
      lat: lat, 
      lng: lng, 
      draggable: true, 
      title: item.description, 
      dragend: function(e) { 
        $('#lat').val(this.getPosition().lat()); 
        $('#lng').val(this.getPosition().lng()); 
      } 
    }); 
  });
});

Meteor.startup(function () {
  map = new GMaps({
    div: '#map_canvas',
    lat: 50.853642,
    lng: 4.357452
  });
});

Template.messages.lists = function () {
  var allitems = lists.find().fetch();
  return allitems;
}
George Katsanos
  • 13,524
  • 16
  • 62
  • 98

4 Answers4

30

Meteor.startup

Meteor.startup() runs only once, its run on the client and server. So when the browser loads and the initial DOM is ready or the server starts. As Sohel Khalifa said you place initialization functions here. Don't define templates in here because the the templates need to be ready before this function can be fired.

Template.myTemplate.onRendered(function() {... })

This is run when meteor has finished and rendered the DOM. Additionally is run each time the HTML changes within the template. So for each item in your list in a subtemplate/a change in an item/update,etc as well as the list you will see the console.log return something if you use it to check. It will return null/undefined when calling for data sometimes (which i'll explain):

Does this mean all the DOM is ready? NO!:

I think this is what might be causing you a bit of trouble. If you use external APIs such as Google maps, they might still render the map. the Template.myTemplate.rendered() means Meteor has finished rendering the template with the reactive variables necessary. So to find out when your Google maps might be ready you need to hook into the Google maps API. Have a look at this question

Using Meteor.subscribe

The reason you might get null/undefined while using rendered is because this is the process meteor usually renders data into templates

You are basically calling console.log(getMarkers()); before the subscription is complete, which is why you get null/undefined

Meteor uses this summarized process with templates & reactive data:

  1. Build Templates with no data & render - NO data yet at this stage
  2. Ask server for data in collections
  3. Rebuild templates with new data & render

So if at process 1) for a very short time you will have no data yet, which is why you might get null (such as in your code) & at the first render. To get past this you should use Meteor.subscribe's callback which is run when all the data is downloaded from the server: e.g

Meteor.subscribe("lists", function() {
    //Callback fired when data received
});

Note: Before you use this you should read the docs on using subscriptions as you need to remove the autopublish package, as well as make a corresponding Meteor.publish function on the server. While this may seem tedious you may end up doing it anyway to give your users their own lists &/or implement some kind of security.

Suggested edits to your code:

You are doing DOM traversing in the right place, Template.mytemplate.onRendered(function().. but you also need to hook into Google Maps' API to capture when their map is finished drawing. You should also use Meteor.subscribe to make sure you get the timing right and not get null/undefined.

Make sure you put your Template helpers in a Meteor.isClient but not in a Meteor.startup because Meteor.startup is fired after your initial DOM is ready (the intitial is the first but before its changed by reactive variables or a router) so your template definitions need to run before this stage.

Community
  • 1
  • 1
Tarang
  • 75,157
  • 39
  • 215
  • 276
  • Thanks, now it makes sense why I get 6 console.logs of coordinates.. because I have 6
  • 's.. So Indeed that's a lot of executiong (considering in my final app I'll have a list of 50 items or more..)
  • – George Katsanos Mar 09 '13 at 21:31
  • It shouldn't really be much trouble though, getting the co-ordinates isn't that expensive an operation you could probably go with thousands but then your map would have too many pins :P – Tarang Mar 09 '13 at 21:49
  • Actually instead of scanning the DOM to get data attributes, I would be better off getting the latitude and longitude directly from my Collection.. but I think that's a different question:) A question for you answer: So after all, do I put it in subscribe callback or in .rendered?:) – George Katsanos Mar 09 '13 at 21:51
  • If you mean the getMarkers() and you're going to use the collection instead of the DOM use subscribe, it would also be less taxing if you're worried about that (such as mobile devices) – Tarang Mar 09 '13 at 21:54
  • I changed my code as such: Meteor.subscribe('AllMessages', function() { var coordinates = {}; coordinates = $('li.message').data(); console.log(coordinates); return coordinates; }); but now I only get one null in the console... – George Katsanos Mar 09 '13 at 21:54
  • But you're still using the DOM there, .data might still not be ready as rendered is called after subscribe – Tarang Mar 09 '13 at 21:55
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/25895/discussion-between-george-katsanos-and-akshat) – George Katsanos Mar 09 '13 at 22:33
  • great answer, I think rendered is onRendered now though – HaveAGuess Mar 31 '15 at 21:51
  • Is this still valid advice two years down the road? Is this still the best way to make sure the DOM has rendered? – Wes Modes Apr 22 '15 at 06:22
  • the subscribe callback doesn't get called when in offline mode using the appcache package so need another solution to know when the collection is ready to be queried. – malhal Aug 01 '15 at 10:07
  • @malcolmhall In offline mode you wont have data, this is why it's not called back. If you're using an alternative offline data source you would have to use the callback it provides. At the moment, meteor doesn't have an official offline database. – Tarang Aug 02 '15 at 00:10
  • Thanks yes I'm using GroundDB, it has a method to check if ready but it doesn't have a callback hopefully the developer will add one, I put in a request. – malhal Aug 03 '15 at 00:34
  • are all image element initialized on template rendered? – Jakob Alexander Eichler Apr 19 '17 at 13:24