13

我有一个<li>使用 Meteor.startup 填充 find() 的列表,如下所示。然后我<li>使用 data() 获取这些的所有数据属性并将其放入一个对象中并尝试 return/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 中创建了一个新的谷歌地图(使用 GMaps.js 插件),然后在嵌套的订阅中从集合中获取所有文档,forEaches 结果并获取纬度和经度值,然后继续添加谷歌地图上的标记...

编辑 2

我以这种方式将我的“地图”变量设为全局变量,无需嵌套 .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

4 回答 4

30

流星启动

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

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

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

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

我认为这可能会给您带来一些麻烦。如果您使用 Google 地图等外部 API,它们可能仍会呈现地图。这Template.myTemplate.rendered()意味着Meteor已经完成了使用必要的反应变量渲染模板。因此,要了解您的 Google 地图何时准备就绪,您需要连接到 Google 地图 API。看看这个问题

使用 Meteor.subscribe

您在使用时可能会得到null/的原因是因为这是流星通常将数据渲染到模板中的过程undefinedrendered

你基本上console.log(getMarkers());是在订阅完成之前打电话,这就是你得到null/undefined

Meteor 使用模板和反应数据的这个总结过程:

  1. 构建没有数据和渲染的模板 - 在这个阶段还没有数据
  2. 向服务器请求集合中的数据
  3. 使用新数据重建模板并渲染

因此,如果在过程 1) 很短的时间内您将没有数据,这就是为什么您可能会null在第一次渲染时获得(例如在您的代码中)&。要克服这个问题,您应该使用Meteor.subscribe从服务器下载所有数据时运行的回调:例如

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

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

建议对您的代码进行修改:

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

确保你把你的模板助手放在 aMeteor.isClient而不是 aMeteor.startup因为 Meteor.startup 在你的初始 DOM 准备好之后被触发(初始是第一个但在它被反应变量或路由器改变之前)所以你的模板定义需要在之前运行这个阶段。

于 2013-03-09T21:21:02.917 回答
2

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

Meteor.startup()是一个用于初始化全局变量、响应会话和订阅来自服务器的主要数据的地方。您在那里编写的所有内容都将在客户端启动后立即执行一次。

是meteor提供的Template.myTemplate.rendered()一个特殊的helper,每次对应的数据发生变化时都会运行,主要用于获取模板中包含的属性或操作DOM元素。

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

于 2013-03-09T17:41:13.927 回答
2

非常感谢 Akshat 的详细回答)

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

我以这种方式准备好我的 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>
于 2016-06-02T14:46:08.307 回答
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);
于 2013-03-09T20:59:53.557 回答