35

I have this Meteor project: https://github.com/jfahrenkrug/code_buddy

It's basically a tool with a big textarea and a pre area that lets you enter source code snippets that automatically get pushed to all connected clients.

I'd like to automatically run the highlightSyntax function when the code was changed, but it doesn't really work.

I've tried query.observe, but that didn't work too well: The syntax highlight flashed up once and then disappeared again.

So my question is: How do I run code after the DOM was updated?

Johannes Fahrenkrug
  • 42,912
  • 19
  • 126
  • 165

9 Answers9

33

A hacky way to do it is:

foo.html

<template name="mytemplate">
  <div id="my-magic-div">
    .. stuff goes here ..
    {{add_my_special_behavior}}
  </div>
</template>

foo.js

Template.mytemplate.add_my_special_behavior = function () {
  Meteor.defer(function () {
    // find #my-magic-div in the DOM
    // do stuff to it
  });
  // return nothing
};

The function will get called whenever the template is rendered (or re-rendered), so you can use it as a hook to do whatever special DOM manipulation you want to do. You need to use Meteor.defer (which does the same thing as settimeout(f, 0)) because at the time the template is being rendered, it isn't in the DOM yet.

Keep in mind that you can render a template without inserting it in the DOM! For example, it's totally legal to do this:

console.log(Template.mytemplate())

So when a template is rendered, there is not a 100% guarantee that it is going to end up in the DOM. It's up to the user of the template.

Geoff
  • 4,372
  • 5
  • 25
  • 29
  • I actually solved it by using window.setTimeout because Meteor.setTimeout through an exception when called from query.observe. – Johannes Fahrenkrug Apr 12 '12 at 15:59
  • I´m having some timing problems with that. I´ve got to use a really long timeout of a few sec as there seems so still be a delay before it is rendered. `Template.contextualFeed.feedItems = -> Meteor.defer -> console.log "Template.contextualFeed.feedItems delayed" console.log $("abbr.timeago") # -> logs as [], with a additional delay it finds them $("abbr.timeago").timeago() Feed.find()` – thomasf1 May 01 '12 at 20:51
  • Hmm... maybe this is the case because Feed.find() first needs to grab the data from the Feed model? Maybe even with a roundtrip? Haven´t actually figured that out yet when it fetches data... – thomasf1 May 01 '12 at 20:59
  • 3
    This one still works, thanks! It just needs to be written a little differently now: Template.templateName.helpers({ add_my_special_behavior: function() { ... } }); – Gene Parcellano Jan 06 '15 at 23:50
  • You may want the origin behaviour of Meteor. It's written here: https://github.com/avital/meteor-ui-new-rendered-callback/blob/master/new1/client/each.js – jwall Mar 13 '15 at 09:54
29

Starting with Meteor 0.4.0, Template.myTemplate.rendered provides a callback that

is called once when an instance of Template.myTemplate is rendered into DOM nodes and put into the document for the first time.

More info at http://docs.meteor.com/#template_rendered

Dan Dascalescu
  • 143,271
  • 52
  • 317
  • 404
Pierre De Wilde
  • 399
  • 3
  • 4
  • 5
    Doesn't really work these days. See https://groups.google.com/forum/#!msg/meteor-talk/47Orrrz7kjg/pY-AfbBNqtQJ – foobarbecue May 20 '14 at 06:24
  • 1
    This callback won't be called when the DOM is updated, e.g. when a new element is added to the `{{#each}}` in a template. It will only be called "[once](http://docs.meteor.com/#/full/template_rendered) when an instance of Template.myTemplate is rendered into DOM nodes and put into the document for the first time." – Dan Dascalescu Dec 10 '14 at 02:51
9

As for the current version of Meteor (1.0), we can now use the .afterFlush() function of Tracker.

Tracker.autorun(function(e){
   var data = Router.current().data();
   if(data.key !== undefined){
       //the data is there but dom may not be created yet
     Tracker.afterFlush(function(){
       //dom is now created.
    });
   }
});
Archy Will He 何魏奇
  • 9,589
  • 4
  • 34
  • 50
3

There is no callback after the DOM is updated, however you can force all pending DOM updates with Tracker.flush().

After you call flush(), you know the DOM has been updated and so you can perform any manual DOM changes you need.

Dan Dascalescu
  • 143,271
  • 52
  • 317
  • 404
n1mmy
  • 2,489
  • 1
  • 19
  • 15
  • 1
    It would be great to have this kind of feature. Something like `Template.myView.onFlush(function() {})` or a special event. For example, just now I'm looking for a way to set the scrolltop of an element at each refresh. Is it possible? Thanks – LarZuK Apr 12 '12 at 07:27
  • @LarZuK Speaking as one of the Meteor developers (I guess we are all developers now, but I mean the original team of 4 :)), I think that's a great idea, and I've often wanted something like that, but I'm not sure how it should work. Do you have a proposal for what should happen if I type `console.log(Template.myView())` into the console? Just never call the function in that case? – Geoff Apr 12 '12 at 08:30
  • Another case: suppose I render the template to DOM nodes with `var frag = Meteor.ui.render(Template.myView)`, but then wait a few seconds before actually inserting `frag` into the page. Do you think the new callback should fire at the time the template is rendered into DOM nodes, or at the time those DOM nodes are put onto the screen? (The former is easy, the latter is really difficult..) – Geoff Apr 12 '12 at 08:32
2

This question is quite old, but the two-year-later solution would be to integrate an operational transformation library with Meteor and use Ace or CodeMirror on the client, which does the syntax highlighting automatically. This has the additional benefit of allowing people to edit at the same time.

I've already done the work for you :)

Andrew Mao
  • 35,740
  • 23
  • 143
  • 224
1

In Blaze Components (I am one of authors) you have an API which calls methods when DOM is inserted, moved, or removed. You can see here how to make an reactive variable when DOM changes.

The downside with this approach is that it does not change when DOM element attributes change (like class change). Only when DOM elements themselves are changed. This works for most cases, but if you need the second, I suggest you simply use MutationObserver. In this case you will be able to respond also to outside changes.

Mitar
  • 6,756
  • 5
  • 54
  • 86
0

It seems Template.myTemplate.rendered doesn't work properly or I don't get it...

I need to load TinyMCE inline after a template with all posts are rendered, so I have :

- a Template

<div id="wrapper">     
         {{#each posts}}
             <div class="editable">{{post}}</div>
         {{/each}}        
  </div>

- and a Function

Template.myPosts.rendered = function(){
      console.dir($("div"));
      tinymce.init({
          selector: "div.editable",
          inline: true,
          plugins: [
              "advlist autolink lists link image charmap print preview anchor",
              "searchreplace visualblocks code fullscreen",
              "insertdatetime media table contextmenu paste"
          ],
          toolbar: "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image"
});

}

However the console logs only the <div id="wrapper"> and not the <div class="editable"> divs, which contain my posts. So, it seems Template.myTemplate.rendered callback occures before the template is rendered, right?

EDIT: I put the Template.myTemplate.rendered code inside a setTimeout() and all seems to work, so I'm sure Template.myTemplate.rendered causes the problem.

Todo
  • 657
  • 1
  • 10
  • 24
0

I just found a little hack that seems to be working pretty well:

Template.myTemplate.onRendered(function() {
    this.autorun(function() {
        Meteor.setTimeout(function() {
            // DOM has been updated
        }, 1);
    });
});

I'm not a Meteor expert so it might have some downsides, but I haven't found any for now — except that it's a bit dirty !

Nico Prat
  • 686
  • 2
  • 6
  • 13
-2

I think you might want to pass a callback to

Meteor.startup(callback)

see http://docs.meteor.com/#meteor_startup

  • Thank you, but it's not about knowing when the DOM is ready on page load. I need to know when the DOM is done updating due to changed data coming from the server. – Johannes Fahrenkrug Apr 12 '12 at 05:53