1

In my template, I call a server-side method to add an item to a collection. This collection is displayed on the page. Once the new item is rendered, I want to focus it for the user.

Here's the helper that fires the server method.

'click .add-post-button': function(e) {
  var userId = Template.parentData(0)._id;
  Meteor.call('addPost', userId, function(error, result) {
    Session.set('newPostId', result);
  });
}

Once this is finished, the new item appears. I want to focus it for the user. Previously, I tried to do it in the callback above with jQuery, but that didn't work because it had not been added to the DOM by the time the callback ran. Now, I'm trying to use the rendered callback:

Template.postsAdmin.rendered = function() {
  var newPostId = Session.get('newPostId');
  if (newPostId) {
    var $newPostCell = $('#' + newPostId);
    $newPostCell.find('.post').focus();
  }
};

Unfortunately, this doesn't seem to be working either. This code does not run when the new item is added to the view.

How can I run code after the new item has been added to the view in order to focus the new item?

raddevon
  • 3,290
  • 4
  • 39
  • 49
  • Did you check `result`? – Billybobbonnet Jul 02 '15 at 15:14
  • @Billybobbonnet Yes. Result is returning the post ID as expected. – raddevon Jul 02 '15 at 15:15
  • Sorry for asking, but just in case: you checked that your rendered callback is executed *after* the `Session` var is filled? Just to make sure you are using the right `rendered` – Billybobbonnet Jul 02 '15 at 15:19
  • @Billybobbonnet No need to apologize! In fact, the rendered function is *not* firing. I put a breakpoint in it, and I never get there. I'm confused as to why. – raddevon Jul 02 '15 at 15:21
  • I did something similar a while ago and I ended up putting the jquery logic in the callback, as you said you already tried. I too had the same problem of the new item not being in the DOM at first, so I put the jQuery code inside a timeout function with a 200ms delay and that worked. Absolutely not the most elegant way of doing it, but it was fine for me since it was a pet project. – GPicazo Jul 02 '15 at 16:27
  • 1
    have a look at my topic mentioned in the @David Weldon answer. I did the same thing than you, tried a more robust solution, and discovered in his answer a better one. – Billybobbonnet Jul 02 '15 at 18:13

2 Answers2

2

Your original code is really close - you just need to put the focus logic into a reactive context so it will run again with the session variable changes. The shortest path to success is to use a template autorun:

Template.postsAdmin.onRendered(function() {
  this.autorun(function() {
    var newPostId = Session.get('newPostId');
    if (newPostId) {
      var $newPostCell = $('#' + newPostId);
      return $newPostCell.find('.post').focus();
    }
  });
});

However, one problem you may run into is that you'll have a race condition where the new post is added after the autorun fires. A hacky solution is to add a timeout to the above. An improved solution is to add something like your original code to the onRendered callback of the newly added post sub-template:

Template.post.onRendered(function() {
  var newPostId = Session.get('newPostId');
  if (newPostId === this.data._id) {
    // add code here to focus this template instance
  }
});
David Weldon
  • 63,632
  • 11
  • 148
  • 146
  • I am not sure to understand, and I see an opportunity to learn something -> I assumed that the `rendered` function is fired whenever a DOM element changes or is added/removed. Could you confirm that it is not the case? And could you also confirm if when using `template.autorun()`, all it takes to make it run again is changing a Session var inside it, (or a reactive var or reactive dict)? Thanks, as always, for your knowledge, David. – Billybobbonnet Jul 02 '15 at 18:05
  • (1) `onRendered` now fires only once when the template instance is first added to the page. It does not fire again when its child DOM elements change. (2) Yes, you only need a single reactive variable to change inside of a reactive context (an autorun in this case) in order to cause a rerun. – David Weldon Jul 02 '15 at 18:09
  • 1
    Many thanks. I am glad I understood this. Now I will not waste time like I did on this: http://stackoverflow.com/questions/31076359/how-should-i-make-sure-that-a-reactive-dom-element-has-been-loaded?noredirect=1#comment50172431_31076359 – Billybobbonnet Jul 02 '15 at 18:12
  • I just tried this solution. After I click the add post button, the code here runs four times but all before the new element has been added for the new post. So, the jQuery object is empty each time when it tries to select and focus it. – raddevon Jul 02 '15 at 18:26
  • Yeah, this is a classic case of the problems associated with using one template's `onRendered` to modify the appearance of a sub-template. I've modified the answer to offer more ideas. – David Weldon Jul 02 '15 at 18:54
  • Ah, brilliant! I had used `setTimeout` to make it work, but this is much better. I did have to use `this.data._id` instead of `this._id`. Thank you for the help! – raddevon Jul 02 '15 at 19:15
  • I'm glad it worked. :) Good call about `this.data` - I just fixed the answer. – David Weldon Jul 02 '15 at 19:41
  • Hi @DavidWeldon, no offence, I know you've too much of experience and knowledge. Can you please tell me or comment on my answer, why it will not work or In what cases my answer fails? Just want to learn something. – Sasikanth Jul 03 '15 at 04:40
1

One option may be observeChanges? Can you try this?

in the rendered function do following

Template.postsAdmin.rendered = function(){

   var query = Posts.find({}); //your query
   var handle = query.observeChanges({
       added: function (id, user) {
          //code to set focus???
       }
   });
}

This code runs whenever there is a new item addeded to minimongo which matches your query.

Make sure to show the template after you load the data.

docs link http://docs.meteor.com/#/full/observe_changes

Sasikanth
  • 3,045
  • 1
  • 22
  • 45
  • As per your request, here's my feedback: (1) `observeChanges` runs forever unless it's stopped or in an `autorun` - watch out for that (2) The observe isn't specific to this template - `Posts` could have changed somewhere else and this code will run (3) As mentioned in the 2nd half of my question, you still have a race condition where `added` could fire before the DOM element is added to the page. – David Weldon Jul 03 '15 at 05:08
  • thanks for your feedback, I need to go and stop my observers now :) – Sasikanth Jul 03 '15 at 05:20