Monday, April 7, 2014

An EZier Meteor eventhandler

In a previous post, Getting CRUDy with Meteor, I wrote about a way to abstract your event handling so that you didn't have to handle routine events in every single template.   The original solution came from my Meteor application EtherPOS.  The solution was a bit complex, I think partially, because it was so early on in both Meteor's progress and my own Meteor development progress.

Recently, during a massive upgrade of EtherPOS, I further abstracted my event handling into an even simpler structure through the use of nested templates.  This has resulted in more readable code, easier maintainability and easier debugging.

Before considering this, you should make sure you need to abstract your event handling.  It might not be necessary.

If you are doing a lot of inserts, updates and removes across a large number of collections and find your code similar, or find yourself repeating code or you find yourself replicating a lot boiler plate code then you should consider abstracting it into some kind of generic event handler.

If you are doing a lot of very particular and specific things on common events such as handling an insert button then this probably isn't the best solution for you.

The solution is actually quite simple and reduces a lot of your work load.  You will no longer have to write any regularly used event methods for things like cancel, edit, view, insert, update, delete.  I have gone as far as creating events for viewing, inserting, updating and removing child and parent records across collections to provide a nice relational data experience and similar methods for managing child subfield documents.

First, wrap your templates in an event handler template.  Make sure to use corresponding class names for the dom elements you want to handle within the html in the child templates. Here is a simple example.

  {{> eventhandler}}

<template name="eventhandler">
  {{> navbar}}
  {{#if currentUser}}
    {{> home}}
    {{> login}}
  {{> footer}}


 Some caveats!

  1. In this example I use a custom router.  I do not use iron-router.  My routes correspond to a collection name for easier CRUD.  So, means someone is using the Items collection.  The beauty of this in a generic event handler is that I can call methods on the collection through variable names rather than directly.  So the below event handler uses Meteor.request.controller as the collection name, which for example might be "items".
  2. I don't get fancy in terms of form variable collection and just use form to JSON because you can insert the document "as is".  You should always do stripping, validation and transformations before using any doc from a user form.  I have pulled my stripping, validation and transformations out of the below example to make it more readable.
  3. I have stripped out all of my application specific logic just to show the concept of using a generic event handler for all your routine events.
  4. You better be using some kind of client and server side acl.  Use either an existing package or role your own.  It isn't that hard.

So second, setup a generic event map on the event handler template.

Here is the simple abstracted event handler for insert.  I am using the same recipe in my application and it powers insert, update, remove, edit, view, cancel, etc... for 45 collections.  This kind of stuff is a serious time saver for me!  I am sure just based on the insert example you can figure out update, remove, view, edit and anything else you might need.{
  'click #btnInsert': function(event, template){
    // event.stopImmediatePropagation(); // use if you want
    // event.stopPropagation(); // use if you want
    var params =  $('#form').toJSON(); //serializeJSON();
        var params =  $('#form').toJSON(); //serializeJSON();
        window[Meteor.request.controller.capitalise()]['insert'](params, function(error, _id){
            // do something like alert on error
          } else {
            // do something here like alert success message
            console.log('it worked _id: ' + _id);
    } catch(error) {
      //fubar better log this

So, any template in your application that has a button with an id="btnInsert" will fire this event.  You can have as many insert forms as you want and never write another event method again.  The same holds true if you wire up update, remove and any other event you might reuse.

If you want to do something similar with iron-router you should probably look at glob routing and do something like this...

  path: '/:slugs(*)'

Then grab the route parts like /collection/action/_id such as /items/update/aEIUSkhdueI

  var routeParts = this.params.slugs.split('/');

Just use the routeParts for your collection name, action and _id.  Most of the time you don't even need the action or the _id in the route because the action is the event method and the _id is available in this.  So, you really just need the route name.

There may be other ways to do it with iron-router, I just don't have as much experience with iron-router because my project started before iron-router or the predecessor to iron-router existed.

Also, because of propagation another cool thing about a solution like this is that you can still handle the event at the originating template to do template specific things like messaging,  updating the view, drawing on the canvas or whatever else you might need to do.

Ultimately, this solution combined with a few other tricks has allowed me to add whole sections of my application that deal with CRUD of underlying collections without writing a single line of Javascript.  I am able to just drop in some html files and a little sprinkle of some handlebars tags for rendering and the application infrastructure handles views, forms, stripping, validation, transformations, processing and much, much more. 

Enjoy, if you have any questions drop me a line.

No comments:

Post a Comment