Wrapping a jQuery plugin with Ember.js Views

Whenever I'm working with Ember I often find myself wanting the functionality that a jQuery plugin provides. It may not be obvious, but we can wrap jQuery plugins inside of Ember views and use these plugins just like any other Ember view.

Here I'll be wrapping the PickADate jQuery plugin. This plugin allows you to attach to a text input field and whenever it is focused it shows a date popup for you to select. This Ember view will allow us to embed the PickADate view into our templates and bind the value selected to a property just like Ember.TextField does.

So we have our Ember app set up and we have included the PickADate plugin we can start writing our view.

App.PickADate = Ember.View.extend({
  attributes: ['monthsFull', 'monthsShort', 'weekdaysFull', 'weekdaysShort',
  'monthPrev', 'monthNext', 'showMonthsFull', 'showWeekdaysShort', 'today',
  'clear', 'format', 'formatSubmit', 'hiddenSuffix', 'firstDay', 'monthSelector',
  'yearSelector', 'dateMin', 'dateMax', 'datesDisabled', 'disablePicker'],

  events: ['onOpen', 'onClose', 'onSelect', 'onStart'],

  type: 'text',
  tagName: 'input',
  classNames: 'pickadate',
});

We're just extending Ember's view and setting the attributes and events properties to be the properties and events that the PickADate plugin accepts. We set the tagName of our view to be an input tag instead of the default div and set the classNames attribute of our view to be pickadate.

In order to hook our plugin up to our view we will need to implement the didInsertElement function which as implied, is called when the element is inserted.

didInsertElement: function() {
  var options = {};
  var self = this;

  this.get('events').forEach(function(event) {
    if (self[event]) {
      options[event] = self[event];
    }
  });

  this.get('attributes').forEach(function(attr) {
    if (self[attr] !== undefined) {
      options[attr] = self[attr];
    }
  });
},

We set up the options variable to be an empty object because this will be what we eventually pass to PickADate when we call it on our element. We then loop over all of our events and check if we have a callback set for that event and if we do, we assign it to our options object. We do the same thing with attributes but ensure that they're defined.

This next part is slightly odd because we actually implement a callback ourself but then call the callback that we were provided if it exists. We do this so that we can set the value property which is what our valueBinding depends on.

var onSelectCallback = options.onSelect;
options.onSelect = function() {
  Ember.set(self, 'value', this.getDate(true));
  if (onSelectCallback) {
    onSelectCallback.call(this)
  }
}

Again, we're setting our provided onSelect callback to the onSelectCallback variable so we can use it later. We then set the onSelect callback to a function that sets the value property on our view and then calls our callback if one was provided. If we didn't implement this method our valueBinding property on the view would never update and we would have a datepicker that doesn't set a value.

The last piece of our code is calling the pickadate function on our views element. We can access this via the $ method, which returns a jQuery object of this views element.

this.$().pickadate(options);

All we're doing is calling pickadate on our view and passing it the options object that we created. Here's an example of how you can use the PickADate view in your application.

App.NewIssueView = Ember.View.extend({
  datePicker: App.PickADate.extend({
    format: 'dddd, mmmm d, yyyy',
    firstDay: 1,
    clear: false,

    onStart: function() {
      var date = new Date();
      this.setDate(date.getFullYear(), date.getMonth() + 1, date.getDate());
    },
  }),
})

And in our template we set the placeholder and pass the valueBinding.

{{view view.datePicker valueBinding="content.date" placeholder="Date this issue is due"}}

This is just a simple example of wrapping a single jQuery plugin but can be applied to a lot more than a simple date picker. Here is a gist of the full source for the pickadate Ember view.