Where to keep global reference to DOM node?


#1

Somewhere here, there’s a wrong way to do things, and I’m trying to avoid it – only, so far, I haven’t been able to determine which one it is.

In my layout file, which is wrapped in a package called, let’s say, my-app-layout, I have a top level container element <div class="my-modals"></div> where my app should insert modal boxes (or maybe toast messages etc.).

Opening some modal box template in this container using the Blaze API works beautifully – I call renderWithData with a template, context, parent view, and – the container as the parent DOM node.

Blaze.renderWithData(Template.myModalBox, this, containerNode, template.view);

But how do I get hold of this container node at any random place in my app? Provided that my layout is loaded.

  1. I could just get it with a global jQuery selector: $(".my-modals")[0] – but then I would have this specific class name written all over my code base. (Also, I prefer to have classes pertain to styling only.)

  2. I could store a reference to the DOM node in a global variable, once the layout template is rendered:

    Template.myLayout.hooks({
      rendered: function () {
        myModals = this.firstNode;
      }
    });

In the last case, I would have to check for existence every time I use it: if (typeof myModals !== "undefined") – definitely not nice.

So the question is, how should I reference this node?

Is there any convenient global place where I can save/delete a direct reference to the node after rendering or destroying it?


#2

Perhaps I’m missing something here, but what’s wrong with an id? The global reference would be the id name or document.getElementById(name) for the actual element.


#3

Fundamentally you’re on the right track. The ideal way would of course be to get rid of the need for that global variable completely, maybe by attaching your modals simply to the body element and giving them all a specific class so you can use that for styling instead of needing them all to be placed as children of a certain element.

The way you’re doing it right now it would actually make sense to use the css class name for finding that element. Because you seem to be requiring them to be nested inside that only for style reasons, not any programmatic/logic ones.

But yes, if you want to keep going that way, relying on something global, then you’ve already found what you need. Use a selector, don’t store a reference. Move the selector to a common method so you keep things DRY.

And thinking about this and writing about this, I see why you don’t feel too great about the solution. It’s a minor thing in the overall scheme of things, but something is off nevertheless.

Actually I think the solution really is to just use document.body because that always exists, never has to be checked for null, you don’t need a selector, and with a css class on the modal for styling purposes you’ll be fine from that perspective as well.
(Otherwise maybe making sure the modals template loads first and indeed storing a global reference to it and just assuming it’s always defined, which it then should be, would be almost equally fine.)


Most Useful Meteor APIs
#4

Thanks, @seeekr – good to hear someone else’s reflections.


#5

This is how it’s done in meteoric:ionic… https://github.com/meteoric/meteor-ionic/blob/master/components/ionActionSheet/ionActionSheet.js#L4


#6

Just a thought, and maybe I’ve misunderstood your needs, but to me it looks like you’re reinventing the wheel here a bit. Is there anything stopping you from just using a {{> Template.dynamic template=myTemplateName data=theRelevantData }} in your app layout?

You can make myTemplateName a helper in the parent Template and ask it to return Session.get('modalTemplateName'). You can then set the Session variable anywhere you need to, and it even keeps state over hot code reloads. This way you don’t have to interact with the DOM directly at all, which seems to me to be a nice declarative way of tackling the problem.

Edit: you mentioned multiple templates. You can still use the above solution by putting the Templates in an array (of either strings with the template names, or of objects with names and data) and then use an `{{#each modalTemplates}}`` and put the Template.dynamic within that block.


#7

I use the Blaze API directly in order to get the correct view hierarchy (the opening template should be the view parent of the modal template, even though it’s placed somewhere else in the DOM tree).


#8

@copleykj: Thanks!

I see that Ionic is using a simple top-level jQuery selector: $('.ionic-body').get(0). I’m actually leaning towards this method now – I recently moved the code for opening new modals into a block helper:

<template name="appModalOpen">
  {{> Template.contentBlock}}
</template>
Template.appModalOpen.events({
  click: function (event, template) {
    Blaze.renderWithData(Template[template.data.template], this, $(".app-modals").get(0), template.view);
  }
});
{{‪#appModalOpen‬ template='appMyPageMyModal'}}
  <button>...
{{/appModalOpen}}

#9

I’d also like to hear more discussion of robfallows’ suggestion.


#10

@jononomo: I’ve actually settled on using a jQuery selector for now, like @robfallow suggests – I’m just using a class instead of an ID (this has been discussed endlessly elsewhere).

Check out this hackpad for the full modal pattern:

https://meteor.hackpad.com/A-pattern-for-incorporating-modals-wo8Uhynt02r