如何在流星期间准备好DOM后执行辅助函数

时间:2013-03-09 16:06:53

标签: javascript meteor

我有一个<li>列表,使用Meteor.startup填充find(),如下所示。然后我使用data()获取这些<li>的所有数据属性并将其放入一个对象并尝试返回/ console.log,以便我可以看到它是否有效。但是我得到了null

    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;
}

我在我的控制台中直接尝试了相同的操作 - 我得到了一个对象 - 所以我猜测在执行此函数之前DOM尚未准备好/已填充。

我很难理解Meteor.startup和Template.mytemplate.rendered之类的区别。在这种情况下,似乎没有一个像我想的那样工作?

使用DOM(遍历,获取属性,操作)的正确方法/地点是什么?

修改

因为代码发生了很大的变化,以便按照我想要的方式发布所有内容。

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()); 
        } 
      }); 
    });
  });
});

上面的代码在Meteor.Startup中创建了一个新的Google地图(使用GMaps.js插件),然后在嵌套的Subscribe中获取集合中的所有文档,forEaches结果并获取纬度和经度值,然后去在谷歌地图中添加标记...

编辑2

我将'map'变量设为全局变量,这样就不需要嵌套.subscribe和.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;
}

4 个答案:

答案 0 :(得分:29)

Meteor.startup

Meteor.startup()只运行一次,它在客户端和服务器上运行。因此,当浏览器加载并且初始DOM准备就绪或服务器启动时。正如Sohel Khalifa所说,你在这里放置初始化函数。不要在这里定义模板,因为在激活此函数之前,模板需要准备就绪。

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

这是在meteor完成并渲染DOM时运行的。此外,HTML会在模板中更改每次时间。因此,对于列表中的每个项目,如子项目/更改项目/更新等,以及列表,您将看到console.log返回一些内容,如果您使用它来检查。有时会调用数据时会返回null / undefined(我会解释):

这是否意味着所有DOM都准备好了? !NO

我认为这可能会给你带来一些麻烦。如果您使用外部API(例如Google地图),他们仍可能会渲染地图。 Template.myTemplate.rendered()表示 Meteor 已完成使用必要的反应变量渲染模板。因此,要了解您的Google地图何时准备就绪,您需要加入Google地图API。有一个look at this question

Using Meteor.subscribe

使用null时获得undefined / rendered的原因是因为流星通常会将数据转换为模板

在订阅完成之前,您基本上是在调用console.log(getMarkers());,这就是您获得null / undefined

的原因

Meteor使用这个汇总过程和模板&amp;反应数据:

  1. 构建没有数据的模板&amp;渲染 - 现阶段没有数据
  2. 向服务器询问馆藏数据
  3. 使用新数据重建模板&amp;呈现
  4. 因此,如果在过程1)很短的时间内你还没有数据,这就是为什么你可能得到null(例如在你的代码中)&amp;在第一次渲染。为了解决这个问题,您应该使用Meteor.subscribe的回调函数,该函数在从服务器下载所有数据时运行:例如

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

    注意:在使用之前,您应该阅读有关使用subscriptions的文档,因为您需要删除autopublish包,并在服务器上创建相应的Meteor.publish函数。虽然这可能看起来很乏味,但最终还是可以为用户提供他们自己的列表和/或实现某种安全性。

    建议修改代码:

    您正在正确的位置Template.mytemplate.onRendered(function()..进行DOM遍历,但您还需要连接到Google Maps的API,以便在地图完成绘制时捕获。您还应该使用Meteor.subscribe确保时机正确,而不是null / undefined

    确保将模板帮助器放在Meteor.isClient但不放在Meteor.startup中,因为在初始DOM准备就绪后触发了Meteor.startup(初始化是第一次,但在被反应改变之前)变量或路由器)所以你的模板定义需要在这个阶段之前运行

答案 1 :(得分:2)

结果返回null的原因是,您已将其放在Meteor.startup()中。它实际上是在从服务器加载数据之前运行的。所以lists.find()返回null。

Meteor.startup()是一个初始化全局变量,reative会话和从服务器订阅主数据集的地方。你在那里写的所有东西都会在客户启动后立即执行一次。

Template.myTemplate.rendered()是meteor提供的一个特殊助手,每当相应的数据发生变化时它就会运行,它主要用于获取属性或操纵该模板中包含的DOM元素。

因此,请将您的帮助程序代码放在公共isClient()区域之外。并使用.rendered()帮助程序遍历DOM,获取或操作DOM元素的属性。

答案 2 :(得分:2)

非常感谢Akshat的详细解答)

我有更复杂的使用Meteor.subscribe的情况,我有包含来自DB的图像的模板。所以我需要等待来自两个集合iamges和news(这里的所有其他数据)的数据。

我以这种方式准备好DOM:

imageIsLoaded = new Promise(function(resolve){
    Meteor.subscribe('images',function(){
        resolve()
    });
});

newsIsLoaded = new Promise(function(resolve){
    Meteor.subscribe('news',function(){
        resolve()
    });
});


Template.newsList.onRendered(function(){
    Promise.all([imageIsLoaded, newsIsLoaded]).then(function() {
        // DOM IS READY!!!
        newsServices.masonryInit();
    })
});

模板结构:

<template name="newsList">
         {{#each news}}
            {{> news_item}}
        {{/each}}
</template>

答案 3 :(得分:1)

执行此操作的最佳方法是将代码放入Template.x.rendered()并使用Session反馈变量来跟踪代码是否已运行。例如,你可以像这样:

Template.x.rendered = function () {
  if (Session.get('doneMarkers') == null) {
    // Do your stuff
    if (getMarkers() != null) {
      Session.set('doneMarkers', 'yes');
    }
  }
});

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

如果您想重新运行该部分代码,则只需致电:

Session.set('doneMarkers', null);