Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Meteor how can I create a generic event handler?

I want to create a generic event handler that I can reuse on dom elements so I don't have to write boiler plate over and over again. I thought I had it figured out but I am getting errors.

The problem I am having is that I think the event handlers are bound at a different time than I need. Maybe at document.ready? Where I think I need to attach them with the .live() method? Though I may have no idea what I am talking about here.

Here is what I am trying to do:

Multi page application.

Multiple collections where data needs to be inserted.

Button code to show the insert form.

<button id="btnShowInsert" class="btn btn-success" rel="tooltip" title="add group">
    <i id="btnIcon" class="icon-plus-sign icon-white"></i>
</button>

Template that shows the form based on the page (controller)

{{> groups_insert}}

Here is the form.

<template name="groups_insert">
    {{#if acl_check}}
    {{> alert}}
    < p>
      < form class="form-horizontal well hide" id="insert">
        <fieldset>
          < div class="control-group">
            < label class="control-label" for="name">Name</label>
            < div class="controls">
              < input type="text" class="input-xlarge" id="name" name="name">
            < /div>
          < /div>
          < div class="form-actions well">
            < button id="btnReset" type="reset" class="btn btn-large">Reset</button>
            < button id="btnSubmit" type="button" class="btn btn-primary btn-large">Submit</button>
          < /div>
        < /fieldset>
      < /form>
    < /p>
  {{/if}}
< /template>

Here is the client code to implement the button that shows the form on the page.

Template.groups.events[ Meteor.eventhandler.btn_events('#btnShowInsert') ] =  Meteor.eventhandler.make_btn_show_insert_form_click_handler();

Here is my generic event handler:

var EventHandler = Base.extend({
  btn_events: function(selector) {
    return 'click ' + selector; //, keydown '+selector+', focusout '+selector;
  },

  make_btn_show_insert_form_click_handler: function(){
    //var click = options.click || function () {};
    return function (event) {
      if (event.type === "click") {
        event.stopPropagation();
        event.preventDefault;
        try{
          if ($('#btnIcon').hasClass('icon-plus-sign') ) {
            $('#btnIcon').removeClass('icon-plus-sign');
            $('#btnIcon').addClass('icon-minus-sign');
          } else {
            $('#btnIcon').removeClass('icon-minus-sign');
            $('#btnIcon').addClass('icon-plus-sign');
          }

          $('#insert').slideToggle('slow', 'swing');

        } catch(error) {
          Alert.setAlert('Error', 'Critical Error: ' + error, 'alert-error');
        }
      }
    }
  },

});

Meteor.eventhandler = new EventHandler;

THE ERROR

Uncaught TypeError: Cannot call method 'btn_events' of undefined

BUT, if I define the event handler this way and call it this way it works.

Template.groups.events[ btn_events('#btnShowInsert') ] =  make_btn_show_insert_form_click_handler();

var btn_events = function (selector) {
  return 'click ' + selector; //, keydown '+selector+', focusout '+selector;
};


var make_btn_show_insert_form_click_handler = 
function () {
  //var click = options.click || function () {};
  console.log( Meteor.request.controller );

  return function (event) {
    if (event.type === "click") {
      event.stopPropagation();
      event.preventDefault;
      try{
        if ($('#btnIcon').hasClass('icon-plus-sign') ) {
          $('#btnIcon').removeClass('icon-plus-sign');
          $('#btnIcon').addClass('icon-minus-sign');
        } else {
          $('#btnIcon').removeClass('icon-minus-sign');
          $('#btnIcon').addClass('icon-plus-sign');
        }

        $('#insert').slideToggle('slow', 'swing');

      } catch(error) {
        Alert.setAlert('Error', 'Critical Error: ' + error, 'alert-error');
      }
    }
  }
};

The Problem
I don't want to have to replicate code all over my site in order to implement a nice button that can slideToggle and form on any page. If I could get it abstracted then I should be able to have a Show Form type of button on all pages for any collection that I am rendering that allows data entry. As well, this leads into being able to create one form handler for all forms as well and then tying them to the controller through an action to the model.

Any ideas?

like image 412
Steeve Cannon Avatar asked Aug 02 '12 10:08

Steeve Cannon


3 Answers

You can bind a high-level template to elements created with child templates. Then you only have to do the binding once. For example

HTML:

<template name="settings">
    {{> login_settings }}
    {{> account_settings }}
    {{> data_settings }}
</template>

<template name="login_settings">
  <btn class="slideToggle">Slide me for login!</btn>
</template>

<template name="account_settings">
  <btn class="slideToggle">Slide me for account!</btn>
</template>

<template name="data_settings">
  <btn class="slideToggle">Slide me for data!</btn>
</template>

JavaScript:

Template.settings.events {
  'click .slideToggle': function() {
    var clickedElement = event.target;
    // add/remove CSS classes to clicked element
  }
};

So if you end up creating 10 different template definitions under settings so you still only have to bind the handler to a single template.

like image 150
jrullmann Avatar answered Oct 28 '22 16:10

jrullmann


I feel like you're overcomplicating things. Why not do this?

Template.someTemplate.events({
  'click .button': buttonClicked
});

function buttonClicked(evt) {
  // DRY code to handle a button being clicked
}

This has the right balance of separation: your event handler is defined once, but you can tell each template that you want its buttons to listen to some event. And if that's not good enough, you can further abstract it:

Template.someTemplate.events(genericEvents);

And possibly even merge genericEvents with specific events for that Template if you wanted.

like image 23
Rahul Avatar answered Oct 28 '22 14:10

Rahul


Here is what I ended up doing. The example only shows the generic insert handler.

var EventHandler = Base.extend({

btnClickHandler: function(){
    return function (event) {
      event.preventDefault();
      Meteor.eventhandler[event.currentTarget.id](event);
    }
  },
insert: function(event){
    event.preventDefault();
    var params =  $('#insert-form').toJSON(); 
    try{
      window[Meteor.request.controller.capitalise()]['validateParams'](params);
      var ts = new Date();
      params.client_updated = ts;
      var has_popup = params.has_popup;
      delete params.has_popup;
      window[Meteor.request.controller.capitalise()]['insert'](params, function(error, _id){
        if(error){
          Alert.setAlert('Error', error, 'alert-error', true, has_popup);
        } else {
          Alert.setAlert('Success', 'Record successfully created.', 'alert-success', true, has_popup);
          $("#insert-form").reset();
          Meteor.flush();
        }
      });
    } catch(error) {
      Alert.setAlert('Error', error, 'alert-error', true, params.has_popup);
    }
  }
 });

 Meteor.eventhandler = new EventHandler;

Now, I merely have to create handlebars templates without any significant javascript coding to handle generic events and wire them up as follows.

$(document).on("click", '#print', Meteor.eventhandler.btnClickHandler());
$(document).on("click", '#insert', Meteor.eventhandler.btnClickHandler());
$(document).on("click", '#remove', Meteor.eventhandler.btnClickHandler());
$(document).on("click", '#removeSubField', Meteor.eventhandler.btnClickHandler());
$(document).on("click", '#insertSubField', Meteor.eventhandler.btnClickHandler())
$(document).on("click", '#update', Meteor.eventhandler.btnClickHandler());
$(document).on("click", '#updateSubField', Meteor.eventhandler.btnClickHandler());
$(document).on("click", "#toggleActive", Meteor.eventhandler.btnClickHandler());
$(document).on("click", "#toggleChild", Meteor.eventhandler.btnClickHandler());

Now, I don't have to write any template event maps to handle basic CRUD. I can create any number of handlebars templates as long as the /route corresponds to the collection name. Although I do some tricky conversions from time to time. Basically, the generic event handler wires up the events, based on the route aka request.controller, to a collection and abstracts it through a client/server shared data model for validation and even access control alongside what is existing in Meteor.

It seems to work well and has reduced my code base significantly. I have dozens of collections where I haven't had to write any event maps handlers because basic CRUD is handled but abstracted enough that I can customize validation, security and other sanity checks on the client/server shared data model.

like image 39
Steeve Cannon Avatar answered Oct 28 '22 16:10

Steeve Cannon