Friday, September 2, 2011

Interactively Adding Things with jQuery UI and Backbone.js

‹prev | My Chain | next›

Yesterday I was able to get a jQuery UI dialog updating a Backbone.js model. Still outstanding is being able to initiate the dialog (by means other than the Javascript console) and real-time update of my calendar UI.

I think the first thing to try is making a "Day" Backbone View. This view will be responsible for handling clicks to add new events. At some point, this responsibility should probably move down into an "Add Appointment" View or something more specific to adding appointments. For now, I am satisfied to be able to click on a day and open the jQuery UI dialog with the appropriate date chosen. This ought to be a good start:

  $(function() {
    // ...
    window.DayView = Backbone.View.extend({
      events : {
        'click': 'click'
      },
      click: function() {
        console.log(this.el);
      }
    });
  });
Obviously the click() method will have to do more than log the element being clicked, but this is just a tracer bullet for the target that I ultimately hope to hit. To use that View class, I need to instantiate a Day View object for every day on my funky, funky calendar:
Adding the following ought to do the trick:
    $('#calendar td').each(function() {
      new DayView({el: this});
    });
Nothing too fancy there, for every <td> table cell, create a new DayView with that table cell as the reference element. Specifying a reference element ensures not only that Backbone will associate DOM events with the correct HTML element, but also that Backbone will not try to build its own elements to be inserted into the document later.

After reloading the page and clicking the first, second and third days of this month, the Javascript console sees the correct cells receiving the click event:
Safe in the knowledge that my tracer bullets are hitting where I hope, I am ready to replace the console.log() with a jQuery UI dialog-open:

    window.DayView = Backbone.View.extend({
      events : {
        'click': 'click'
      },
      click: function() {
        $('#dialog').
          dialog('open').
          find('.startDate').
          html(this.el.id);
      }
    });
In addition to opening the dialog, I also find the .startDate element and replace its contents with the ID of the clicked cell. Since my calendar cell IDs are ISO 8601 dates, the result is that the dialog has the date set:
If I close that dialog and click another date cell, the previous date (inside the .startDate element) is replaced with the new ID/date. Easy-peasy. The dialog always shows the date for the calendar cell clicked because the ID for all of the calendar cells is the ISO 8601 formatted date for that cell.

My jQuery dialog View is already able to create the calendar appointment (a.k.a event) by reading the form field values. I can get the start date from the .startDate's HTML:

    window.AppView = Backbone.View.extend({
      // ...
      create: function() {
        Events.create({
          title: this.el.find('input.title').val(),
          description: this.el.find('input.description').val(),
          startDate: this.el.find('.startDate').html()});
      }
    });
And it works! I can create lots of appointments now:

The only thing is that I have to reload the page to see those events show up on the calendar. Surely it is possible to do better in Backbone...?

When the page first loads, it runs through each element in the Events collection. For each pre-existing appointment/event, an EventView object is created and rendered:

    Events.fetch({success: function(collection, response) {
        collection.each(function(event) {
          var el = $('#' + event.get("startDate")),
              view = new EventView({model: event, el: el});
          view.render();
        });
      }
    });
Might something similar work for newly created events? Indeed it does.

The Events.create() method actually returns a Backbone model—just like the on used to create a new EventView on page load. Once I have that, I can create a new EventView object and render it immediately:

    window.AppView = Backbone.View.extend({
      // ...
      create: function() {
        var event = Events.create({
          title: this.el.find('input.title').val(),
          description: this.el.find('input.description').val(),
          startDate: this.el.find('.startDate').html()
        });

        var el = $('#' + event.get("startDate")),
            view = new EventView({model: event, el: el});
        view.render();
      }
    });
Nice! Now I can create and destroy calendar events / appointments with impunity and have them persist on page reload.

I still have a ton of cleanup to do. Renaming a few things is definitely in order. A test or two couldn't hurt. Also, clicking on the delete-event icon also results in a click on the day itself. But all-in-all, I am rather pleased with how this came together.


Day #131

No comments:

Post a Comment