Why does Blaze.getData() == null unless I output a document property here?

I am trying to make a list of re-orderable items in Meteor. My items have a info.order property which I change on click events. Why does the example below work until I comment out if the line below // ! ...?

If I comment out that line I get the error Cannot read property '_id' of null when data._id is referenced in the event handler.

This is my javascript:

Widget = new Mongo.Collection('widget');
if (Meteor.isClient) {
    function moveUp (mongo_id) {
        var clicked = Widget.findOne({_id: mongo_id});
	    var above = Widget.findOne({'info.order': clicked.info.order - 1});
	    if (above) {
            Widget.update({_id: clicked._id}, {$inc: {"info.order": -1}});
	        Widget.update({_id: above._id}, {$inc: {"info.order": 1}});
	    }
    }

    Template.widget.helpers({
        // Get list of widget to display and sort by latest first.
        widget: function(data){
            return  Widget.find({}, {sort: {'info.order': 1}});
        },

        display: function(mongo_id, info) {
	        var html = '<div>';
  	        html += '<div>' + info.label + '</div>';
            html += '<div><a href="#" class="btn btn-default js-moveup">Up</a></div>';

	        // ! IF NEXT LINE IS COMMENTED-OUT data == null IN EVENT HANDLER
	        html += '<div>' + info.order + '</div>';
            html += '</div>';
        return html;
        }

    });

    Template.widget.events({
	'click .js-moveup': function(e, tpl){
            e.preventDefault();
            var data = Blaze.getData(e.currentTarget);
            moveUp(data._id);
	}
    });
} // end is MeteorClient

With this template:

<head></head>
<body>
  {{> widget}}
</body>

<template name="widget">
  <div class="container">
    <h1>Widgets</h1>
    {{#each widget}}
    {{{display _id info}}}
    {{/each}}
  </div>
</template>

And this seed data:

Meteor.startup(function () {
    if (Widget.find().count() === 0) {
        [{info :{label: "first", order: 1}},
	     {info: {label: "second", order: 2}},
	     {info: {label: "third", order: 3}}
        ].forEach(function(w){
	Widget.insert(w);
    });
}
});

This is a reposting of http://stackoverflow.com/questions/34354819/blaze-getdatael-returns-null-unless-data-property-is-accessed

I’m not seeing any subscribe/publish related code, so I’m assuming you’re attempting to do this with a local collection? If so, the following will work (and removes a bit of complexity).

Template:

<head></head>
<body>
  {{> widgets}}
</body>

<template name="widgets">
  <div class="container">
    <h1>Widgets</h1>
    {{#each widgets}}
      {{> widget}}
    {{/each}}
  </div>
</template>

<template name="widget">
  <div>
    <div>{{info.label}}</div>
    <div><a href="#" class="btn btn-default js-moveup">Up</a></div>
  </div>
</template>

Helper:

Widget = new Mongo.Collection(null);

if (Meteor.isClient) {

  function moveUp (mongo_id) {
    var clicked = Widget.findOne({_id: mongo_id});
    var above = Widget.findOne({'info.order': clicked.info.order - 1});
    if (above) {
      Widget.update({_id: clicked._id}, {$inc: {"info.order": -1}});
      Widget.update({_id: above._id}, {$inc: {"info.order": 1}});
    }   
  }

  Template.widgets.helpers({
    widgets: function (data){
      return Widget.find({}, {sort: {'info.order': 1}});
    }   
  }); 

  Template.widget.events({
    'click .js-moveup': function (e, tpl) {
      e.preventDefault();
      moveUp(this._id);
    }   
  }); 

} 

Meteor.startup(function () {
  if (Widget.find().count() === 0) {
    var data = [ 
      {info :{label: "first", order: 1}},
      {info: {label: "second", order: 2}},
      {info: {label: "third", order: 3}} 
    ];  
    data.forEach(function (w) {
      Widget.insert(w);
    }); 
  }
});

I would expect it need DOM element which is part of template to map it back to it’s data context.
Not some randomly inserted div. Get some element parent’s which is in Spacebars I would suggest.

Thanks for the help,

I know it will work if I use a template but what puzzled me is that id does work if display includes the info.order value. However, I’ve got an idea what’s going on…

Meteor must compare the output of display() to it’s previous value and only evaluate update the DOM if it has changed (or something similar). If I don’t print out info.order the HTML of each widget is unchanged.

I tested this by replacing info.order with new Date() to add varying content that didn’t reference the model and, sure enough, the widgets more as expected.

So, my take home message is that if you return raw HTML from display Meteor will try to do the write thing but won’t always get it right.