TL;DR

Polymer Project's paper-dialog does some DOM node-moving magic which breaks the standard Meteor event mapping; the code below is a way of getting around this.

Background

I'm working on a couple of projects using Meteor, the delightful JavaScript-based magically-syncing data between client and api framework. One of the projects really benefits from the syncing of data as it more or less lives or dies from live server updates. Another one, a side project, is just me having some fun.

In the side project I decided that I'd try to play around with the Polymer Project. There's a basic Meteor Atmosphere package out there called, catchily, meteor-polymer but it's mostly just something that enables the downloading of the Polymer bower components and doesn't really bring any integration between the projects.

In general (so far) I've had no major problems using Polymer components within Meteor, even though Polymer uses the 'Shadow DOM' which is still in draft.

Which leads us to...

The shadow DOM problem

Right now, focussed around Polymer's paper-dialog which, surprise, surprise, displays dialogs.

Actually, I think the problem is another component - core-overlay - which paper-dialog extends/uses... but let's not go too deep.

You embed a simple paper-dialog by declaring it in your page's HTML; your DOM looks nice and shiny:

Toggle your dialog with myShinyDialog.toggle() and, voila, this is what we have:

The core-overlay has magically transported it and away, not just somewhere else in the DOM tree, but has spirited it into a Shadow DOM root node from which events never escape. This is a) intentional and b) expected as per Shadow DOM.

My aim

To use paper-dialog and to also use standard Meteor event maps, like the one below:

Template.shadowDomGah.events({
  'click .ui.test.button': function(event, template) {
    console.log('test button clicked woo');
  }
});

The code

It's a template; there's bits missing which are left as an exercise for someone who cares.

Template HTML

<template name="paper_dialog">
  <paper-dialog>
    {{> UI.contentBlock this}}
  </paper-dialog>
  <span class="paper_dialog_event_emitter" style="display: none;">
    {{> UI.contentBlock this}}
  </span>
</template>

Template JavaScript

Template.paper_dialog.rendered = function() {
  var self = this;
  this.dialogNode = $(this.firstNode);
  this.eventEmitter = $(this.lastNode);

  this.dialogNode.on('click', function resentEventToHiddenCopy(event) {
    var sourceNode = event.originalEvent.toElement;
    var nodeClassId = '.' + sourceNode.className.split(' ').join('.');
    self.eventEmitter.
      find(nodeClassId).//TODO(otupman): improve to use the source's hiearchy to find the target node
      trigger('click', event.originalEvent);
  });
};

Usage

In your HTML:

{{# paper_dialog}}
    Test dialog
    <paper-button class="ui test button">Test button</paper-button>
{{/ paper_dialog}}

In your JS:

Template.shadowDomGah.events({
  'click .ui.test.button': function(event, template) {
    console.log('test button clicked woo');
  }
});

The upshot, however, is that one can (appear to) use standard Meteor practices. A different, less hacky way would be to hook into the containing-Template's event map and bind for specific events.

Comments

  1. You'll note it duplicates the DOM tree; that can cause weirdness.
  2. It only works with CSS class name selectors
  3. I haven't used this in anger
  4. Reactiveness might be a problem (though in general it seems okay).