【问题标题】:How to execute helper function after DOM is ready in meteor在流星中准备好DOM后如何执行辅助函数
【发布时间】:2013-02-25 02:35:49
【问题描述】:

我有一个<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 中创建了一个新的谷歌地图(使用 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;
}

【问题讨论】:

    标签: javascript meteor


    【解决方案1】:

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

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

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

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

    【讨论】:

    • 非常感谢您的回答,但我想现在我的问题比以前更多:) 1)我的 lists.find() 确实有效,我的
    • 已填充...(完全正确因为它们在 .rendered 内) 2)我听从了你的建议:我在 .rendered 内移动了 getMarkers 的所有代码。现在我得到 2 个“null”返回和 6 个坐标变量的控制台日志......你能发布一些您的答案中的代码,这将有很大帮助..
  • 还有一件事,根据 docs.meteor,Meteor.startup “在客户端上,只要 DOM 准备好并且您的 .html 文件中的任何 模板都已准备好,该功能就会运行放到屏幕上。”所以与你所说的相反..
  • 【解决方案2】:

    最好的方法是将代码放入 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);
    

    【讨论】:

    • 谢谢,这确实有效,尽管它似乎在性能方面可能会很昂贵,因为在加载所有内容之前模板会重新渲染很多次..(许多项目的长无序列表.. )
    【解决方案3】:

    Meteor.startup

    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。有一个look at this question

    Using Meteor.subscribe

    您在使用rendered 时可能会收到null/undefined 的原因是因为这是meteor 通常将数据渲染到模板中的过程

    你基本上是在订阅完成之前调用console.log(getMarkers());,这就是你得到null/undefined的原因

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

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

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

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

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

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

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

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

    【讨论】:

    • 谢谢,现在我得到 6 个坐标的 console.logs 是有道理的。因为我有 6 个
    • 。 '将有 50 个或更多项目的列表..)
  • 不过,这应该不会很麻烦,获取坐标并不是那么昂贵的操作,您可能需要数千个,但是您的地图会有太多的针脚:P
  • 实际上,与其扫描 DOM 以获取数据属性,不如直接从我的 Collection 中获取纬度和经度。但我认为这是一个不同的问题:) 为您回答的问题:所以毕竟,我是把它放在 subscribe 回调中还是放在 .rendered 中?:)
  • 如果您的意思是 getMarkers() 并且您将使用集合而不是 DOM 使用订阅,如果您担心这一点(例如移动设备),它也会减少负担
  • 我更改了我的代码: Meteor.subscribe('AllMessages', function() { var coordinates = {}; coordinates = $('li.message').data(); 控制台。日志(坐标);返回坐标;});但现在我在控制台中只得到一个空值......
  • 【解决方案4】:

    非常感谢 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>
    

    【讨论】:

    • 谢谢!结合之前的回复,您提供的 promise/resolve 解决方案解决了我在完成订阅之前初始化 dataTable 时遇到的问题。
    • 仅供参考,我认为这个解决方案对反应不友好。它在第一次加载时工作,但如果图像或新闻被被动更新(添加或删除),插件将不会更新。您将需要在 onRendered 的子模板级别添加砌体复习
    猜你喜欢
    相关资源
    最近更新 更多
    热门标签