[Solved] Pass helper's result as html string to a js param

#1

Hi guys,

I am kinda new in Meteor development, but I found myself in a tricky situation.

In general, how can I get (evaluate?) a content from a helper (like accounts-ui: {{> loginbuttons}}) to pass it to a JS function as generated HTML? Is this even possible?

I am not certainly sure about it, so, any explanation or suggested educational material would be appreciated.


The exact case is that I would like to use pcel:loading and show up the accounts-ui’s login form. This packages is based on PleaseWait.JS which has a string ‘member’ containing the loading message in plain Html.

#2

If I understand correctly you want to insert an HTML element into the DOM from a helper as a string?

If so, and if you’re using blaze, you can just use the triple brace format. For example:

Template.example.helpers({
  returnsHTMLString () {
    return '<div>test</div>'
  }
<template name="example">
  {{{returnsHTMLString}}}
</template>
#3

Welcome @drb33r!

You can get the rendered html of a template by using Blaze.toHTML or Blaze.toHTMLWithData

Because you’re not passing data into the loginbuttons component, you can just use toHTML

const loginButtonsHtml = Blaze.toHTML('loginbuttons');

Small note that loginbuttons is a Template and not a helper, the distinction might make searching easier.
Also note that Blaze.toHTML doesn’t work for helpers, only templates

#4

Hi, @coagmano

Thanks, this looks exactly what I am looking for. I will try it on Monday.

It was clear to me that Blaze does the rendering. But I did not realized that I can ask for a specific template to be rendered anytime in my js code, even with parametric data.

As I mentioned, I am new in this topic, and I have to put all pieces of this new puzzle together. It sounds a bit odd to ask for a rendered part of my UI after years of C++ development. :smiley:

Thanks a lot!

2 Likes
#5

I tried your suggestion and just a loginButtons string appeared. :slight_smile:

Okay, maybe not ‘loginButtons’ but Template.loginButtons as parameter. In that case nothing visible appeared. But, a new <div> was inserted into the document:

<div id="login-buttons" class="login-buttons-dropdown-align-">
// several empty lines here
</div>

Close, but no cigar.

Do you have any further suggestion?

Response to the small note about templates and helpers, I read the following in the accounts-ui doc
This is why I thought that loginButtons is a helper and not a template:

Then simply add the {{> loginButtons}} helper to an HTML file.

#6

Hmmm…

I just checked my project and we’re using {{> atForm }} to load the login form and not using {{> loginButtons }} anywhere.
But we’re just using accounts-password without any Oauth providers so that might be why?

Using Blaze.toHTML(Template.atForm) rendered a email and password field, is that what you are after?


Okay I added loginButtons to the page and realised that the dropdown only renders when the dropdown link is clicked.
Looking at the template code this is controlled by a session variable Accounts._loginButtonsSession, so you could set it to active and then grab the html?

Accounts._loginButtonsSession.set('dropdownVisible', true)
Blaze.toHTML(Template.loginButtons)

The rest of the code is in here if you wanted to poke around:

#7

I don’t even need the dropdown, I would use only one login service. Yesterday I started to dig into the source of the account-ui, but got lost easily.
Today, it goes better, I think that the ‘template evaluation’ stops at the following point:

<template name="_loginButtonsLoggedOut">
  {{#if services}}              {{! OK }}
    {{#if configurationLoaded}} {{! OK, because the #else case is not displayed }}
      {{#if dropdown}}          {{! FALSE, only one service, no password }}
        {{> _loginButtonsLoggedOutDropdown}}
      {{else}}
        {{#with singleService}} {{! THIS IS THE  POINT }}
          <div class="login-buttons-with-only-one-button"> {{! I should see this div }}

So, what does the #with singleService?

It calls the getLoginServices() and returns the first item from the array. The called function does the following:
var services = Package['accounts-oauth'] ? Accounts.oauth.serviceNames() : [];
I tried in the console and Accounts.oauth.serviceNames() returned the correct service name.

The #with is like an if statement, so, I don’t really see, what goes wrong here.


Maybe useful to mention that I am trying to use scholtzm:accounts-steam package.

#8

I’m pretty rusty on Meteor OAuth but I’m pretty sure extra services are stored in a collection and so it might be that the code is calling toHTML before the data is available?

I poked around in the accounts API and found this Accounts.loginServicesConfigured()
Which tells you if the internal subscription has received data and is ready, so you could use that in an autorun:

Tracker.autorun(() => {
  var ready = Accounts.loginServicesConfigured();
  if (!ready) return;

  var html = Blaze.toHTML(Template.loginButtons)
  // do something with html
});
#9

Finally, I managed to display the login button! Thanks a lot! :vulcan_salute:
I learned couple of things, very very basic things, so I write them here mostly for newbies like me.

rendered != onRendered
Template.templateName.rendered != Template.templateName.onRendered
(I am curious about the differences, but had no time to research it. Yet.)

Need to wait for internal subscriptions

That’s true.
I had to wait until the services got ready before rendering my Template.splash using Accounts.loginServicesConfigured();

How to render after data is ready
The following thread and the suggestions cleared my mind a bit:
Good techniques for waiting till data is absolutely finished loading by @brucejo
Now, I understand that dividing templates into several parts and applying a #if helperGuard makes possible dynamic re-rendering my template. (Is that you, Reactive thingy? :thinking: )

Template rendering with Blaze
When everything is ideal, Blaze can do the magic, even into a variable. Just call toHTML()!

Don’t be lazy to read the package sources and API docs!
#BestAdvice

This is how my code looks now:

splash.js

let welcomeMsg = '<p class="loading-message">Welcome</p>';

Template.splash.rendered = function () {
    this.splash = window.pleaseWait({
      logo: '/img/d2logo.png',
      backgroundColor: '#7f8c8d',
      loadingHtml: welcomeMsg + Blaze.toHTML(Template.loginButtons)
    });
};

Template.splashContainer.helpers({
  steamIsReady : () => {
    return Accounts.loginServicesConfigured();
  }
});

Template.splash.events({
  'click #login-button': function() {
    Meteor.loginWithSteam();
  }
});

splash.html

<template name="splashContainer">
  {{#if steamIsReady}}
    {{> splash}}
  {{/if}}
</template>

Don’t ask me, why I decided to use pcel:loading to create a fancy, centered, animated splash screen for my Steam login button. It is a loading page, not a splash screen, for f’s sake! But it was a nice learning experience… (Even writing this post, I found ways to write my code more cleaner.)

Finally, the button is visible, now the 'click #login-button' event went missing. But that’s another story…

(Oh, and sorry, I should’ve asked it on StackOverflow.)

#10

Thanks for sharing!

My experience with StackOverflow is that folks there don’t let you explore your way to a solution like we did here. They just see a question without an obvious answer and move on (or downvote).

So there’s no need to apologise! This is the right place to work through questions like this :slight_smile:

As for the events, Blaze will only listen to events that happen within the DOM scope of the current template (and children).
I’m guessing that pleaseWait adds an element to the body? which would mean that the event handler won’t listen for any events within it.
If that’s the case, the solution then is to use a plain old addEventListener in rendered/onRendered

  this.splash = window.pleaseWait({
      logo: '/img/d2logo.png',
      backgroundColor: '#7f8c8d',
      loadingHtml: welcomeMsg + Blaze.toHTML(Template.loginButtons)
    });
  document.querySelector('#login-button')
    .addEventListener('click', () => Meteor.loginWithSteam() );
#11

('#login-buttons`’) :face_with_monocle: :expressionless:

I almost couldn’t find out what’s wrong with the event timeline and what’s the dark magic here…
Anyway, thanks a lot! :slight_smile:

1 Like