Blaze-magic-events: React-/Angular2-style event handler binding for Blaze

Hi everyone!

While working on a project today I had an idea about a different way of binding event handlers to html elements. Partly inspired by angular 2, partly by React (or what I know of it, which is little enough).

And the idea kinda worked out and is neat and I’ll use it in my projects that are based on Blaze, I think, so I thought I’d share this and see what you guys (and gals) think.

EDIT: It’s now on atmosphere as themeteorites:blaze-magic-events! Check it out!

Copying the README here:


blaze-magic-events

A new way of binding event handlers to html elements for Meteor’s Blaze.

note: all code is ES6

Template

<template name="helloworld">
  <button onclick={{sayHi}}>Say Hi!</button>
  <button onclick={{reset}}>reset</button>
  <p></p>
</template>

Event handlers

  Template.helloworld.events({
    sayHi (e, t) {
      t.$('p').html('hi there from sayHi() handler!')
    },
    reset (e, t) {
      t.$('p').html('')
    },
  })

Repo is on github, there’s some stuff hardcoded (like the helloworld template name reference) – EDIT: not any more! – , but it basically works and, as far as I can tell, should be universally usable. (It’s not a package yet – EDIT: now it is, see link above! --, but simply a POC put into an example app.)

Funny how we’re going back to the roots of using good old onclick and friends :smile:

Cheers,
Denis

ps. Delegated event handling is great for many cases, too. I don’t think an idea such as this should replace all uses of the standard Blaze delegated event handling!

pps. Disclaimer: I’ve probably implemented things in such a way that a core Meteor dev would cry seeing it, but I just don’t know the Meteor internals well enough to understand which methods to call, who to ask basically for what I need… but it works and if there’s interest in collaborating and using it I’ll be happy to learn about the proper ways of implementing this and accept patches/PRs once it’s a proper package.

6 Likes

I love this, I haven’t looked at the implementation at all but I wish Blaze had this style of event handling built in. It’s one of my favorite things about React.

4 Likes

Great, man! But cant find how to catch event from child template. Is it possible?

I think this is actually an anti-pattern, because then your template no longer has a well-defined API. The appropriate thing to do would be to pass a reactive variable or a callback into the child template, rather than listening to DOM events directly.

That’s another reason I prefer this pattern.

1 Like

Would be nice! But it seems complicated with current Blaze architecture. Hope next round $$$ for MDG would be partially passed to improve client (server?) side rendering and new Blaze features)

This way could helps on the next step from templates to components. We could drop jQuery this way and works with native event listeners instead of global implementation!

Cant like it again! It is a brilliant!

1 Like

Yes, I also really like that about React (and Angular2).
The code is actually only about 40 lines or so. I’ll be turning this into a proper package soon-ish and will, if I don’t find that for some reason this is a really bad idea, or that something is off about this, submit a PR to Meteor core/Blaze for discussion about what the core devs think about React-style event handler binding for Blaze.

Good to hear you like it! I think it makes sense to think through all the details of all use cases here. What kind of a common situation are you talking about here, what would you like to accomplish with child templates? I’m not sure I understand what exactly you mean, so please explain the details and give a concrete example if you still think it makes sense!
I’m open to enhancing and improving the implementation, and the “specification” of it, i.e. what cases it handles and how exactly!

EDIT: Ideally fork the repo, add the use case you mean as an example within the same app, and post the link. If it makes sense I’ll adjust the implementation to work for that case as well!

<template name="parent">
  {{# each childsList}}
     {{> child this}}
  {{/each}}
</template>
<template name="child">
  <button onclick={{sayHi}}>Say Hi!</button>
</template>

How parent gets event from a child?

So basically you mean that the template hierarchy should be searched all the way up until a matching handler is found. That does seem consistent with how we currently define event handlers in Blaze. Events basically “bubble up” until they hit a handler, or not.
That may or may not indicate bad design, as Sashko alluded to, but I do believe that consistency in the way Blaze works (and everywhere!) is highly important, and so I do agree that this option should be available.

That would even simplify the implementation.

I’ve pushed a new version, but I haven’t had the time yet to figure out how to mess with the parent template’s children templates such that the appropriate helpers would be defined, so it doesn’t work yet. (That’s how the magic is implemented – defining a somewhat magic helper when an event handler is defined in this non-delegated way, i.e. just methodName and not the signature of 'event targetSelector'.) If someone wants to help feel free to send a PR or give instructions! Otherwise I’ll work on that as time permits!

And thanks for the input, @mrzafod!

Yep. It’s somewhat complex. But if you did it it gives you better performance as well.
We experiment this with Flow Components as State Functions and it works pretty well for us.

I have to use Bubble-up pattern because of current Blaze limitations. For example: I have a tepmlate named “path” (it renders a svg path) and children templates, named “control” (pivots for curve shape). This case I have to listen “controls” events with “path” and then modify the curve. There is no way to pass some logic from “parent” to “control” on get it back with Blaze

I am witching for Flow Components. Right now it is the best thing that could be a platform for Blaze Components. As for me Flow Components are bit unclear at some points.

let view = Blaze.getView(this)
  do {
    let handler
    if (_.find(view.template.__eventMaps, map => handler = map[handlerName])) {
      return handler.call(view, e)
    }
 } while ((view = view.parentView))

Nice try. Dont you think it could be slow (note about Blaze is working the same way already) - it could be like “double bubble”

Thanks :wink: Actually this code is not really “bubbling” up, it’s rather just looking for the view that has the event handler we’re looking for and then just stopping, not continuing*. This performance overhead is relatively low I would say (basically traversing 2 small arrays, doing almost no work in the inner loop), and not critical since it’s not called frequently, but only whenever the user interacts with something, because this is only for standard HTML element events, and user’s generally don’t click that quickly.
But regardless of that, it would be possible to optimize this, and the previous version was better in that regard, because it basically knew where it had to look. And there’s no reason we couldn’t keep it that way, my implementation here is just a simplistic one so it handles a very general case (which we won’t need to do, eventually).
The thing that needs to be figured out first, though, is: We need to hook into the compilation process of each template, learn which child templates it has, and then from children to parent, define helpers for all non-delegated (“magic”) events so they are available within the template and its children.

And another question would be whether it should also be possible to do the reverse of “catching events in parent”: Reference a handler in the parent and allow for child templates to optionally implement / override the parent’s handler. Saying it like that seems to make sense from an OOP perspective, but I can’t think of what that should even look like, expressed in Blaze + Spacebars.

ps. Oh and thanks for that clear example. That does make a whole lot of sense and it’s exactly what I wanted to understand! In my mind that’s exactly the use case for doing delegated event handling (like it is in Blaze currently), but then it would of course still be nice to be able to (sometimes) specify the exact handler to be called directly, and not indirectly by (ab)using css selectors.

*: Actually now I wonder if making it bubble wouldn’t be better, but that would go against OOP principles if we’re thinking in terms of components, i.e. method overriding (shadowing). Looking at this from the DOM perspective it should probably bubble, though, if the handler does not return false. I am undecided on this. Both perspectives make sense.

Guess the same. Components should be solid and they shouldn’t bubble up their events. Your solution looks great and it is limited for me just with how Blaze works with contexts and interacts with other parts of an application. I’ll try your way at the current project soon and post my opinions

1 Like

I love it!
That the first thing anyone coming from Angular notices.
Very nice, looking forward for your package

2 Likes

Thanks for your feedback, @Urigo, very encouraging to hear that from you! Love your enthusiasm for and work with Angular-Meteor, looking forward to using Angular2 when it’s (a bit more) ready!

So I wanted to reuse that code in another project and now I’ve turned this into an actual package.

meteor add themeteorites:blaze-magic-events

Enjoy! And mrzafod, just in case you’re also reading, the bubbling is still not implemented. I’m getting more and more interested and comfortable with the thought of going a bit deeper with Blaze, really learning how to hack it properly. So it’s only a matter of time before I get around to it and become capable of implementing that. I have way too many ideas for improving Blaze to not work on it (c:
(Will be taking some inspiration from angular1+2-meteor, too, I think. Would really love to bring some fundamental two-way-binding to Blaze that would help with 80% of use cases for it, make those trivially easy to implement, among other things.)

2 Likes

@seeekr, дружище, это бомба!
Hi again, I’ve been testing your themeteorites:blaze-magic-events as I mentioned a week ago. I found that listen a child’s ebents from the parent is REALY BAD idea. Anyway your package works fine. For now I advice you think about how to pass those event handlers from parent to child as a parametrs, a mean:

{{> myChildComponent onClick={{onClickParent}} onUserClose={{onUserClose}} }}
1 Like

Could you please give an example so I can see what exactly you mean? i.e. the use case and why it’s such a bad idea. (I haven’t found use cases for that either, so far, so mostly out of interest and seeking enhanced understanding.)

I actually do have ideas about that, which would help with using Blaze more easily in other places as well. Will implement and ping you here when that’s done.