How To Make Front-End Javascript Reactive in Meteor?


#1

This seems like such a basic thing for an end-developer to want to accomplish – add a front end javascript library to a Meteor app, and make sure that this library reactively updates…

Most of the guidance on Google on this issue:

https://www.google.com/search?q=meteor+with+3rd+party+javascript

is horribly out of date, confusing, or both.

It would be enormously helpful if there were some canonical guidance on how to do something like this.

Here is my situation:

In my meteor app, I am using this library:

I have the .js file in this directory.

root > client > lib 

The problem I’m having however is I can’t figure out where to call this library in my code so it reactively updates with changes to the database.

I have a collection of DIVs like this on the page:

 <div class="waveFormJson" id="{{_id}}" </div>

Then, in my template .js file, I call an underscores _.each function to iterate over the collection and run the Waveform javascript on each one… Here is my code:

Template.videoThumbnails.helpers({
  recordings: function () {
  var trackId = Session.get('trackId');
  var data = Recordings.find().fetch();
  
    _.each(data,function(value,key,obj){
      var waveform = new Waveform({
        container: document.getElementById(value._id),
      });
      waveform.update({data: value.waveFormData})
    })
  return Recordings.find( { 'trackId': trackId }, { sort: { position: -1 }});
  },
  
});

Template.videoThumbnails.onRendered(function(){
  var data = Recordings.find().fetch();
  
    _.each(data,function(value,key,obj){
      var waveform = new Waveform({
        container: document.getElementById(value._id),
      });

      waveform.update({data: value.waveFormData})
    })
})

This is basically working, but eveything seems to run twice, and I just feel like I am not doing this the right way…

thanks


#2

I had the same trouble with image slide-shows, as discussed in this thread:
https://forums.meteor.com/t/blaze-how-to-force-a-template-to-rerender-from-scratch/?source_topic_id=10950

I did not manage to make this work smoothly in the same template (fetch data, react on changes, render the slides).

However, it worked once I moved the actual slide-show in a separate sub-template. So I would recommend that you retrieve your data in the parent template (using this.autorun() in onRendered() instead of duplicating the fetches in a helper), and put the actual rendering logic for the waveform in a sub-template.

I’d also recommend to read through this tutorial on template-level subscriptions. There you can also see how to handle the autorun() properly: https://www.discovermeteor.com/blog/template-level-subscriptions/


I want to execute a nested loop in meteor
#3

Making small templates is a good strategy here. Also in general it results in cleaner code with smaller scopes of data.

In general you could prevent yourself from writing code by making smaller templates. If you need a loop in your template helper then maybe you just needed to apply the helper to the single instance.

Template.singleThumbnail.onRendered(function(){
  you can now find your element inside this template with: this.find('element')
});

#4

Someone at MDG really needs to make a tutorial on this subject… this is exactly the kind of thing that should be simple – incorporating a slideshow plugin into meteor. It’s not simple, there are a hundred different potential ways to do it. Rails was so successful because end developers like myself could easily discover canonical ways to achieve certain simple things… a lot of entry-level developers like myself will get discouraged if something as simple as “how do i a use a slideshow plugin” turns out to be a massively complicated and uncertain subject… Please MDG, give us an example to work from!


#5

Lucfranken’s suggestion seems to have helped. I’m hesitant to declare that I have found a solution, but it does seem that by narrowing down my onRender logic into a template which only holds the exact thing I am trying to keep updated reactively is useful.


#6

I am trying to find good documentation on what gets rendered at which moment but it seems very hard to find. I can’t find a clear explanation about the process of re-rendering. Most interesting I found to give you some background on it is:
https://meteorhacks.com/how-blaze-works

Also https://atmospherejs.com/kadira/debug can help you to see how many times things get rendered.

It may get a place here: https://github.com/meteor/guide/blob/gh-pages/outlines.md#meteor-guide-uiux but it seems the real documentation just is not enough. It should be here: http://docs.meteor.com/#/full/blaze_render which mostly talks about how it renders but not when that is exactly triggered and what things you should consider.


#7

I think

Tracker.autorun(()=> {
    //reactive stuff
})

is darn simple.

frankly, you just need to wrap the parts of the package that changes:

  1. identify the blocks of code that has to rerun upon change
  2. replace the variables that change with reactive-vars
  3. wrap the entire blocks that need to rerun within this autorun-function.

I’d be happy to tell you more about how this works, or maybe you can go to the manual on transparent reactive programming, and check out detailed instructions on about 80% of use cases :wink:


#8

Goatic, you mention Tracker.autorun, which makes me want to cry.

I have literally tried using that maybe twenty or thirty times in various parts of my code, and have never found that it does anything. The documentation states that it does this:

Run a function now and rerun it later whenever its dependencies change.

But I have literally never got it to work, even when I use it to enclose database attributes that are clearly changing. It seems simple, but I must be missing something.

Here is my current template:

<template name="waveForm">
    <div id="{{_id}}" style="height:115px;width:100%" ></div>
</template>

and here is my current onRendered function:

Template.waveForm.onRendered(function() {
  var waveform = new Waveform({
    container: document.getElementById(Template.currentData()._id),
  });
  waveform.innerColor = "#333";
  waveform.update({
    data: Template.currentData().waveFormData
  })
  });

This works fine in these cases:

  1. The page is rendered for the first time
  2. A new item is added to the database

However, I do not get any reactive changes when the database is changed … to be clear, when I change the value of this attribute in the database:

waveFormData

The code does not rerender.

And yes, I tried enclosing the code in the onRendered function with a Tracker.autorun block, but I got errors in the console like

Error: There is no current view

…and the code did not reactively update…

And I am now looking again at the meteor documentation for Template.currentData(), which states that it

Inside an onCreated, onRendered, or onDestroyed callback, returns the data context of the template.
Establishes a reactive dependency on the result.

It seems I must be misunderstanding the statement “Establishes a reactive dependency on the result.”


#9

It seems like you have a lot of good approaches and are doing mostly everything right. My guess is you’re coming up against a small issue. I don’t know the specific problem you are having, but maybe this working example will help you :
http://meteorpad.com/pad/stQJF3JDF2tRz7sgs/waveform
It loads the waveform.js library and updates when you change each player’s score data.

I noticed this error “Error: There is no current view” if I used Meteor.autorun instead of this.autorun inside my template.

Ignore the $.getScript loading routine, that’s just to load the library into Meteorpad. Cool waveform library! I didn’t know it existed.


#10

Thanks for the great feedback looshi, it seems to be working very well now, and I suspected the solution was simple, did not require a lot of code, and did not require reactive vars or other things I don’t understand… except it DID require autorun, but what was beyond my grasp was which object I should be running the autorun on. Here is my (hopefully final) code which is more or less what looshi suggested in the meteorpad:

Template.waveForm.onRendered(function() {
  var self = this;

  // this part only needs to happen once:
  var waveform = new Waveform({
    container: document.getElementById(self.data._id),
  });

  // when Template.data changes, re-run this routine :

  self.autorun(function() {
    var data = Template.currentData();
    waveform.innerColor = "#333";
    waveform.update({
      data: data.waveFormData
    })
  });
});

#11

I can understand your hesitation about autoruns etc., since these reactivity things are hard to grasp when you first get in contact with Meteor. I’m still struggling with some of these things in certain cases.

But I also have to say it is absolutely worth it to dig deeper into this, as reactivity is one of the core concepts of Meteor and understanding it will help you a lot.

I think the key point is to understand that all of this is based on two basic concepts: 1. Reactive data sources, and 2. Reactive computations. Or, to put it into simpler words: Something that will trigger a reaction on a change (that’s what a reactive source does), and something that will do the actual work, i.e. respond to that trigger (that’s what a reactive computation is all about).

Tracker.autorun() is such a reactive computation, and database cursors are a good example for a triggering reactive source. The last thing you have to know is that a reactive computation only will be run if its source code contains a reactive source. So, an autorun() will only be executed if it contains something like a find() method. Plus, it is also important to understand that an autorun() will only react on changes if the reactive source has been touched on the very first execution of the autorun(). During this very first run (typically in the onRendered() or onCreated() method), Tracker detects all reactive data sources that it visits during this first run and lets the reactive sources “know” that they have to trigger that particular code again if their content changes somehow. This makes the “magical re-run” actually happen.

As explained in the Meteor docs, there’s only a small number of things in the core that behave as reactive sources: session variables, database cursors, subscription.ready(), Meteor.status, Meteor.user(), Meteor.userId() and Meteor.loggingIn(). Because there is situations where you want something else (like a variable) to behave as a reactive datasource (i.e. to trigger a reactive computation once it changes), this has been extended by packages like “reactive var” or “reactive dict”. A reactive var is a way of “wrapping” a normal variable in a kind of envelope that enables it to behave as a reactive data source.


#12

you can also use Session to make your variables reactive

example:

var x = new ReactiveVar(true)

Tracker.autorun(() => {
console.log('x is now equal to: ' + x.get() )
})

Now, the tracker will rerun whenever i set x to a new value. The same rule applies to Session.set()/get()