10/21/2012

Bubbling in Enyo2, Part 2

We looked at a simple example in Part 1. A button component sent an event up the object hierarchy until it was captured by the App kind and resolved. Wouldn't it be great if we could use the same BubbleButton kind in 2 different hierarchies and get 2 different results? Yes it would; so let's do that.

Let's start by revisiting the BubbleButton:

enyo.kind({
  name: 'BubbleButton',
  kind: enyo.Button,
  handlers: {
    ontap: 'tapped'
  },
  tapped: function() {
    this.setAttribute ('disabled', 'true');
    this.bubble('onUpdate');
    return true;
  }
  });

The BubbleButton is an extended type from the base enyo.Button type. This means that all the standard methods and functionality of a button are provided automatically when a new instance is created. This includes the instance getting create, destory, and render methods. I have removed all three explicitly from the button definition for easier reading and less code. They will be inherited when the time comes. The button is still listening for a tap event which is defined in the handlers section.

  handlers: {
    ontap: 'tapped'
  },

The tapped function will run when the button receives the ontap event. The ontap is an event we get for free from the Enyo framework. The ontap event blurs the line between mobile and desktop, and is fired by both touch screen taps and mouse clicks.

One change has been made since last time. The tapped function now includes this line:

this.setAttribute ('disabled', 'true');

The BubbleButton will be assigned the html disabled attribute after being pressed. The vanilla disabled attribute is used so it will only work on browsers that support that property. I can't think of a browser that doesn't support the disabled property but it is worth mentioning. The setAttribute method tells Enyo to immediately update the UI representation of this component. A separate render call is not required.

With this in place, there is a way to verify which one of two ontap functions is being executed. It will be possible to confirm which hierarchy the button is affecting. The active button will turn gray and non-interactive.

The tapped function will cause the onUpdate event to start bubbling up the object hierarchy. The BubbleButton is still fairly generic to this point. This supports the reuse model that is so integral to Enyo. The BubbleButton can be placed into any app with no effort, and provides a simple set of localized functionalty.

The example includes two new kinds: BubbleDiv and BubbleParagraph. The BubbleDiv looks like:

enyo.kind({
  name: 'BubbleDiv',
  kind: enyo.Control,
  tag: 'div',
  handlers: {
    onUpdate: 'updateDiv'
  },
  components: [{
    tag: 'p',
    name: 'divParagraph',
    content: 'Blank Div'
  },
  {
    kind: 'BubbleButton',
    content: 'Press Me in this Div'
  }],
  updateDiv: function () {
    this.$.divParagraph.content = 'Not A Blank Div Anymore';
    this.render();
    return true;
  }
});

The BubbleDiv explicitly identifies its tag as a 'div'. This tells Enyo to render the component as an html div. A div is the default tag for any component without a tag explictly set to something else. This makes setting the tag redundant but is done to provide a visual differential for us to read the 2 new kinds together. There are two components contained within this div object. The first component is a simple paragraph:

{
  tag: 'p',
  name: 'divParagraph',
  content: 'Blank Div'
}

Giving the component a tag attribute will set the html tag, just like the div tag in the BubbleDiv. The name attribute gives us a way to reference the paragraph in code. The name is not the rendered id. The id attribute can be set explicitly but I find that is not a good idea. Enyo ends up needing the id to do its own magic. The reference via its name is maintained in the Enyo framework so we rarely need to worry about the actual id. Finally, the content is what is put into the paragraph. The paragrapgh will render like this:

<p id="app_bubbleDiv_divParagraph">Blank Div</p>

The second component is our BubbleButton component. The button inherits the properties of the regular enyo.button which includes automatic handling of the content property. Setting content in this case is what will appear on the button.

{   kind: 'BubbleButton',
  content: 'Press Me in this Div'
}

The most important part of the BubbleDiv for our purposes is the handler function:

  handlers: {
    onUpdate: 'updateDiv'
  },

  updateDiv: function () {
    this.$.divParagraph.content = 'Not A Blank Div Anymore';
    this.render();
    return true;
  }

The BubbleButton will bubble up the onUpdate event. Since the BubbleDiv contains a BubbleButton it can choose to capture that event. This is accomplished by adding a handler for onUpdate to our BubbleDiv. When an event is captured, the onUpdate handler tells Enyo to call the updateDiv function. The updateDiv function is going to change the contents of the paragraph inside the div.

The function is going to get the paragraph by using the "this.$" shorthand. The shorthand allows access to the current component's contents, including other components. It is possible to retrieve a component from inside another component if you know it's name. In this case, we are looking for 'divParagraph' which is the name we gave the paragraph inside of the BubbleDiv. Using dot notation we grab the divParagraph component inside this component and change it's content ('this.$.divParagraph.content'). Since this modifies the contents of an html property, we will need to re-render this component. The BubbleDiv is re-rendered and the onUpdate event is stopped by returning true.

The BubbleParagraph is very similar to our BubbleDiv:

enyo.kind({
  name: 'BubbleParagraph',  
  kind: enyo.Control,
  handlers: {
    onUpdate: 'updateParagraph'
  },
  components: [{
    tag: 'p',
    name: 'paragraph',
    content: 'Blank Paragraph'
    },
    {
    kind: 'BubbleButton',
    content: 'Press Me in this Paragraph'
  }],
  updateParagraph: function () {
    this.$.paragraph.content = 'Not A Blank Paragraph Anymore';
    this.render();
    return true;
  }
});

The crucial difference between the two kinds is in the updateParagraph function. The updateParagraph looks for a different component, and changes the content to something different than the BubbleDiv. Pushing the button in the BubbleParagraph changes the content to "Not a Blank Paragraph Anymore". The most important feature of both is the BubbleButton. Both kinds use the same BubbbleButton kind. Despite this simularity, the result of pushing the button is different. Use the jsFiddle below to try it out. In summary, two different kinds with two different behaviors using the same button.

The power of Enyo's object and bubbling system have allowed the creation of a new reusable component in a few lines of code. The BubbleButton has been written once and used twice. An Enyo kind definition is very narrative in structure which makes it easy to maintain and understand. An Enyo kind can exist as a targeted event emitter with the bubble functionality. The simple way that a small definition can unlock so much potential is one of my favorite features of the Enyo2 framework.


In Part 3, the example will be expanded to allow the onUpdate event to continue past the direct parent to be handled elsewhere.

Previous parts

Bubbling in Enyo, Part 1