Why does my ui.helper function not work?


#1

hi,

Could you please help me?

I have a helper, and that helper checks if an external system is live. If not, the callback times out, and as such it will fire the error function. So this is also what I see in the log.

We got an error, so Sense is not ready. Check the config

this is the helper;

Template.registerHelper('noSenseConnection', function() {
        console.log('UI HELPER: Check if we have noSenseConnection?');
        Meteor.call('getApps', (error, result) => {
            if (error) {
                console.log('We got an error, so Sense is not ready. Check the config');
                sAlert.error(error.message);
                return true                
            } else{
                return false
                console.log('we have a connection to Sense');
            }
    })   

But why does my spacebars code not work?

<div class="ui main text container">
    {{> sAlert}}
  </div>
  {{#if noSenseConnection}} 
  <div class="ui container">
    <div class="ui warning message">
     ...
      <li> Please check the configuration options below</li>
    </div>
    {{> quickForm collection=senseConfigCollection doc=senseConfig id="updatesenseConfig" type="update"}}
  </div>
  {{else}}
  <div class="ui three column doubling grid">
    <div class="column">
      {{> OEMPartner}}
    </div>
    ....
  {{/if}}
</body>

thank you


#2

BTW, if I just return true from my helper (and comment out all the other stuff) it works…


#3

Anyone please?..


#4

What’s going on here is that when the helper is first called it returns undefined - standard async rules apply - the callback is executed after the enclosing function completes. Also, because Meteor.call is not reactive, this helper is never rerun. When the callback executes, the returned data is given back to the enclosing helper, but is otherwise ignored.

You need to ensure your helper responds reactively, so that when the callback is executed the template is able to react accordingly. Typical patterns will use a reactiveVar or reactiveDict. So, you might do something like this (I named your template “checkConfig”):


Set up your onCreated to define the reactiveVar and make the Meteor.call which will set the reactiveVar appropriately:

Template.checkConfig.onCreated(function() {
  const instance = this;
  instance.connection = new ReactiveVar();
  console.log('UI HELPER: Check if we have noSenseConnection?');
  Meteor.call('getApps', (error, result) => {
    if (error) {
      console.log('We got an error, so Sense is not ready. Check the config');
      instance.connection.set(true);
      sAlert.error(error.message);
    } else {
      console.log('we have a connection to Sense');
      instance.connection.set(false);
    }
  });
});

Modify your helper to just return the reactiveVar:

Template.registerHelper('noSenseConnection', function() {
  const instance = Template.instance();
  return instance.connection.get();
});

BTW, the use of instance is a common pattern from the Meteor Guide for working with template code.


#5

Thank you very much for your help again. This explains everything!

Btw, this also means that a variable assigned to template is not only for this template but basically it can be used for my whole meteor application?


#6

mmm, something is still missing with this approach, I get this error

Exception in template helper: TypeError: Cannot read property 'get' of undefined
    at Object.senseConnection (http://localhost:3000/app/app.js?hash=7121b1157af6cbd9c562694c1d335f7b2d6762f4:536:85)
    at http://localhost:3000/packages/blaze.js?hash=ef41aed769a8945fc99ac4954e8c9ec157a88cea:2994:16

So this code is not working:

Template.body.helpers({
    senseConnection() {
        const instance = Template.instance();
        console.log('the value of instance connection get:', instance.connection.get());
        return instance.connection.get();
    },

So that answers my previous question I think. This approach only works to store vars inside a specific template. What we want here is the template of the parent (body.js), and now we are looking in the checkConfig template I think.


#7

A variable assigned to a template is strictly for that template only. Having said that, when you use a global helper (defined with Template.registerHelper) you get access to the calling template’s context via Template.instance().

This is where React has definite advantages: it gets messy in Blaze when you want access to a parent template’s context (or grandparent’s …). You could consider a global reactiveVar, or export/import a “shared” reactiveVar.


#8

Thank you.

Yeah I must say, in meteor 1.1 I had clean code, and I always knew where I was… My code gets so messy now. What I mean is: I have to import everything and it is not clear for me what do I have to import where. What can be done 1 time in main, and what do I have to do per js file.

What can I only import once. I now import try to import all generic stuff main.js like the todos example app recommends. But I suspect I make an error somewhere

in main.js I try to

  1. Import HTML (works fine), but sometimes I also refer to the template.html in the helper.js (get’s messy)
  2. Import al .js for template helpers (works fine)
  3. Import collections (does not work), I have to do this per .js file?
  4. Import generic javascript like use lodash for everything
import '../imports/ui/generation.js';
import '/imports/ui/UIHelpers';
import '/imports/ui/customer.js';
import '/imports/ui/OEMPartner.js';
import '/imports/ui/steps.js';
import '/imports/ui/router.js';
import '/imports/ui/layout.js';
import '/imports/ui/pages/homeAbout.html';
import '/imports/ui/pages/QMC.html';
import '/imports/ui/pages/securityRules.html';
import '/imports/ui/notFound.html';
import '/imports/ui/nav.html';


import { Template } from 'meteor/templating';
import { Apps, TemplateApps } from '/imports/api/apps.js'
import { Streams } from '/imports/api/streams.js'
import { EngineConfig } from '/imports/api/config.js'

import moment from 'moment';
import lodash from 'lodash';
_ = lodash;

Example .js

import { Template } from 'meteor/templating';
import { Customers } from '../api/customers.js';
import { TemplateApps } from '../api/apps.js';
import './customer.html';

Template.customer.events({
  'click .toggle-checked'() {
    // Set the checked property to the opposite of its current value
    Customers.update(this._id, {
      $set: { checked: ! this.checked },
    });
  },
  'click .delete'() {
    Customers.remove(this._id);
    //todo: remove the stream too
    // updateSenseInfo();
  }
 ... 

#9

Hi,

I tried your suggestion in a different part… but somehow it does not work. I do use await here, thats differerent, can you see the error?

Template.slideContent.onCreated(async function() {
    var instance = this;
    //the header and sub header for which we want to load the slide data/bullets
    var level1 = Template.currentData().slide[0].qText;
    var level2 = Template.currentData().slide[1].qText;
     // and now let's get the slide content: 
    var bullets = await getLevel3(level1, level2); //using the parent, get all items that have this name as parent with a set analysis query

    instance.bullets = new ReactiveVar(bullets); //https://stackoverflow.com/questions/35047101/how-do-i-access-the-data-context-and-the-template-instance-in-each-case-event
    console.log("onCreated get instance.bullets", instance.bullets.get());
    
})

helper

never returns anything, so the instance.bullets.get() return a value in the oncreated, but not in the helper… it does also not work more simpler, without the if, but then I get cannot find get() of undefined

Template.slideContent.helpers({
  bullets: function() {
        if(Template.instance().bullets)
{
    var res = Template.instance().bullets.get();
    console.log('@@@@@@@@@@@@@@@@@ helpder res', res) 
    return res;
}        

#10

Solved by inititializing the array first…

Template.slideContent.onCreated(async function() {
    var instance = this;
    instance.bullets = new ReactiveVar([]); //https://stackoverflow.com/questions/35047101/how-do-i-access-the-data-context-and-the-template-instance-in-each-case-event

    //the header and sub header for which we want to load the slide data/bullets
    var level1 = Template.currentData().slide[0].qText;
    var level2 = Template.currentData().slide[1].qText;
     // and now let's get the slide content: 
    var bullets = await getLevel3(level1, level2); //using the parent, get all items that have this name as parent with a set analysis query
    instance.bullets.set(bullets);    
    console.log("onCreated get instance.bullets", instance.bullets.get());    
})

#11

It’s probably because you are delaying the creation of the ReactiveVar until after the async getLevel3 function completes.

Try:

Template.slideContent.onCreated(async function () {
  var instance = this;
  instance.bullets = new ReactiveVar();
  //the header and sub header for which we want to load the slide data/bullets
  var level1 = Template.currentData().slide[0].qText;
  var level2 = Template.currentData().slide[1].qText;
  // and now let's get the slide content: 
  var bullets = await getLevel3(level1, level2);
  instance.bullets.set(bullets);
  console.log("onCreated get instance.bullets", instance.bullets.get());
});

Template.slideContent.helpers({
  bullets() {
    var res = Template.instance().bullets.get();
    console.log('@@@@@@@@@@@@@@@@@ helpder res', res)
    return res;
  }
});