4

I have the following template:

<template name="modalTest">
    {{session "modalTestNumber"}} <button id="modalTestIncrement">Increment</button>
</template>

That session helper simply is a go-between with the Session object. I have that modalTestNumber initialized to 0.

I want this template to be rendered, with all of it's reactivity, into a bootbox modal dialog. I have the following event handler declared for this template:

Template.modalTest.events({
    'click #modalTestIncrement': function(e, t) {
        console.log('click');
        Session.set('modalTestNumber', Session.get('modalTestNumber') + 1);
    }
});

Here are all of the things I have tried, and what they result in:

bootbox.dialog({
    message: Template.modalTest()
});

This renders the template, which appears more or less like 0 Increment (in a button). However, when I change the Session variable from the console, it doesn't change, and the event handler isn't called when I click the button (the console.log doesn't even happen).

message: Meteor.render(Template.modalTest())

message: Meteor.render(function() { return Template.modalTest(); })

These both do exactly the same thing as the Template call by itself.

message: new Handlebars.SafeString(Template.modalTest())

This just renders the modal body as empty. The modal still pops up though.

message: Meteor.render(new Handlebars.SafeString(Template.modalTest()))

Exactly the same as the Template and pure Meteor.render calls; the template is there, but it has no reactivity or event response.

Is it maybe that I'm using this less packaging of bootstrap rather than a standard package?

How can I get this to render in appropriately reactive Meteor style?

Hacking into Bootbox?

I just tried hacked into the bootbox.js file itself to see if I could take over. I changed things so that at the bootbox.dialog({}) layer I would simply pass the name of the Template I wanted rendered:

// in bootbox.js::exports.dialog
console.log(options.message); // I'm passing the template name now, so this yields 'modalTest'

body.find(".bootbox-body").html(Meteor.render(Template[options.message]));

body.find(".bootbox-body").html(Meteor.render(function() { return Template[options.message](); }));

These two different versions (don't worry they're two different attempts, not at the same time) these both render the template non-reactively, just like they did before.

Will hacking into bootbox make any difference?

Thanks in advance!

blaineh
  • 2,263
  • 3
  • 28
  • 46
  • 1
    While not bootbox, [Crater](http://code.subhog.com/crater#overlays) have overlays that are designed to work with Meteor. – Hubert OG Mar 15 '14 at 05:02
  • It depends on the version of Meteor, Bootstrap, and Bootbox you are using. Bootbox 4.0.0 and above is only compatible with Bootstrap 3 and the way to render it differs for Meteor 0.8 and later. – Andrew Mao Apr 11 '14 at 21:46

5 Answers5

5

I am giving an answer working with the current 0.9.3.1 version of Meteor. If you want to render a template and keep reactivity, you have to :

  • Render template in a parent node
  • Have the parent already in the DOM

So this very short function is the answer to do that :

    renderTmp = function (template, data) {
        var node = document.createElement("div");
        document.body.appendChild(node);
        UI.renderWithData(template, data, node);
        return node;
    };

In your case, you would do :

    bootbox.dialog({
        message: renderTmp(Template.modalTest)
    });
Timo Frionnet
  • 474
  • 3
  • 16
Karl.S
  • 2,294
  • 1
  • 26
  • 33
5

Answer for Meteor 1.0+:

Use Blaze.render or Blaze.renderWithData to render the template into the bootbox dialog after the bootbox dialog has been created.

    function openMyDialog(fs){ // this can be tied to an event handler in another template
      <! do some stuff here, like setting the data context !>
      bootbox.dialog({
        title: 'This will populate with content from the "myDialog" template',
        message: "<div id='dialogNode'></div>",
        buttons: {
          do: {
            label: "ok",
            className: "btn btn-primary",
            callback: function() {
              <! take some actions !>
            }
          }
        }
      });
      Blaze.render(Template.myDialog,$("#dialogNode")[0]);
    };

This assumes you have a template defined:

    <template name="myDialog">
      Content for my dialog box
    </template>

Template.myDialog is created for every template you're using.

$("#dialogNode")[0] selects the DOM node you setup in

    message: "<div id='dialogNode'></div>"

Alternatively you can leave message blank and use $(".bootbox-body") to select the parent node.

As you can imagine, this also allows you to change the message section of a bootbox dialog dynamically.

Timo Frionnet
  • 474
  • 3
  • 16
Michel Floyd
  • 18,793
  • 4
  • 24
  • 39
  • Is this really the best way to do it? It works as it should but I wonder why there is no way to directly show the template in the body instead of replacing afterwards. – Jamgreen Oct 22 '15 at 06:39
  • I suppose you could just put the template into a div and toggle its visibility. That would be a non-bootbox approach. It would also force that template to always be rendered even when it was never looked at. – Michel Floyd Oct 22 '15 at 06:53
  • My big problem is that the template `Template.myDialog` contains `{{#autoform ...}{{> afQuickField }}{{/autoform}}`, but I want the submit button next to the other buttons in my modal. Is this possible at all? The submit button has to be inside `{{#autoform}}{{/autoform}}`. – Jamgreen Oct 23 '15 at 06:39
  • Sure, you can put an autoform in a modal. With bootbox you can define a completely custom modal with no default buttons. See http://stackoverflow.com/a/31017212/2805154 – Michel Floyd Oct 23 '15 at 06:46
  • I have a custom `message` using your approach. But the autoform is both started and ended inside this message (modal body), so the buttons are outside the autoform (modal footer). The link you send shows how to replace the message (modal body) – Jamgreen Oct 23 '15 at 06:52
  • I'm not sure how autoform decides to place its buttons. – Michel Floyd Oct 23 '15 at 15:55
0

In order to render Meteor templates programmatically while retaining their reactivity you'll want to use Meteor.render(). They address this issue in their docs under templates.

So for your handlers, etc. to work you'd use:

bootbox.dialog({
    message: Meteor.render(function() { return Template.modalTest(); })
});

This was a major gotcha for me too!

I see that you were really close with the Meteor.render()'s. Let me know if it still doesn't work.

Timo Frionnet
  • 474
  • 3
  • 16
nickell
  • 389
  • 2
  • 14
  • See my edit, I expected that to work too, that's why this is such a confusing bug! – blaineh Mar 15 '14 at 04:08
  • 1
    Hmmm, I copied your code into an existing project of mine (I'm already using bootstrap and bootbox), I put a button at the top of one of my pages that called the `bootbox.dialog()` that I included in my answer and everything worked, even changing the session variable `modalTestNumber`. So there must be something else that's breaking the reactivity. Have you tried abstracting this out of the rest of your logic? Maybe on a new blank page? – nickell Mar 15 '14 at 04:56
  • Just tried moving it to a different page, no change. Is it maybe that I'm using [this less packaging of bootstrap](https://github.com/simison/bootstrap3-less) rather than a standard package? – blaineh Mar 15 '14 at 19:54
  • I don't think so, I'm using the same package and it worked for me. This is strange :/ – nickell Mar 15 '14 at 21:00
0

This works for Meteor 1.1.0.2

Assuming we have a template called changePassword that has two fields named oldPassword and newPassword, here's some code to pop up a dialog box using the template and then get the results.

bootbox.dialog({
    title: 'Change Password',
    message: '<span/>', // Message can't be empty, but we're going to replace the contents
    buttons: {
      success: {
        label: 'Change',
        className: 'btn-primary',
        callback: function(event) {
          var oldPassword = this.find('input[name=oldPassword]').val();
          var newPassword = this.find('input[name=newPassword]').val();
          console.log("Change password from " + oldPassword + " to " + newPassword);
          return false; // Close the dialog
        }
      },
      'Cancel': {
        className: 'btn-default'
      }
    }
  });
  // .bootbox-body is the parent of the span, so we can replace the contents
  // with our template
  // Using UI.renderWithData means we can pass data in to the template too.
  UI.insert(UI.renderWithData(Template.changePassword, {
    name: "Harry"
  }), $('.bootbox-body')[0]);
Timo Frionnet
  • 474
  • 3
  • 16
DenisH
  • 859
  • 8
  • 12
0

Using the latest version of Meteor, here is a simple way to render a doc into a bootbox

let box = bootbox.dialog({title:'',message:''});
box.find('.bootbox-body').remove();
Blaze.renderWithData(template,MyCollection.findOne({_id}),box.find(".modal-body")[0]);

If you want the dialog to be reactive use

let box = bootbox.dialog({title:'',message:''});
box.find('.bootbox-body').remove();
Blaze.renderWithData(template,function() {return MyCollection.findOne({_id})},box.find(".modal-body")[0]);
Timo Frionnet
  • 474
  • 3
  • 16
dpatte
  • 69
  • 1
  • 7