Enyo Part 3: Onyx

In the previous two parts (Part 1 and Part 2) we talked about Enyo and how you can create and use your own set of components. Enyo allows you to write modular code an create reusable components.

In fact a large number of reusable components and useful widgets are available for Enyo, and you can just use them rather than reinvent the wheel. This library of components is called Onyx, and it will be the subject of this post. Onyx extends and improves the library of components available for Enyo by adding additional features and styling. Enyo also offers a library of components for layout.

While Enyo as a whole is device and browser independent, the Onyx component set is designed with mobiles and tablets in mind. This makes sense when you consider that it is the framework for developing applications for WebOS, an operating system designed for mobiles and tablets. Of course it is possible to create your own set of widgets that are desktop-optimized.

In the previous two parts we created basic components / applications, so out first order of business is to update them to use the Onyx component set. Let’s start with what we built in the first part, which was just a simple search widget that included a text entry box, a search button and clear button (which was optional). Here is the code we had at the end of the first part:

enyo.kind({
  name: "SearchComponent",
  kind: enyo.Control,
  published: {
    showClearButton: true
  },
  create: function () {
    this.inherited(arguments);
    if (this.showClearButton) {
      this.createComponents(
        [
          {tag: "button", content: "Clear"}
        ],
        {owner: this}
      );
    }
  },
  components: [
    {tag: "input"},
    {tag: "button", content: "Search"}
  ]
});
enyo.kind({
  name: "App",
  fit: true,
  components: [
    {kind: "SearchComponent", showClearButton: true}
  ]
});

As before you can just put this in the App.js in the source folder file and load up debug.html in a browser to see it in action.

Above in the code you can see how we are using plain HTML tags as components in our “SearchComponent”, let’s replace these with Onyx component. This much is quite simple, we need to replace { tag: "input" } with { kind: "onyx.Input" }. The first uses the HTML input tag, while the second uses the Onyx component for input that provides additional features.

Similarly, the plain button too needs to be replaced with an an Onyx button. So { tag: "button", content: "Search" } for example, becomes { kind:"onyx.Button", content: "Search" }. Similarly you can update the “Clear” button.

Another thing we need to do for onyx components is to add the “onyx” class to our application. This is as simple as adding a “classes” property to our “App” and setting that to “onyx”. Here is what the code looks like at this point:

 enyo.kind({
  name: "SearchComponent",
  kind: enyo.Control,
  published: {
    showClearButton: true
  },
  create: function () {
    this.inherited(arguments);
    if (this.showClearButton) {
      this.createComponents(
        [
          {kind: "onyx.Button", content: "Clear"}
        ],
        {owner: this}
      );
    }
  },
  components: [
    {kind: "onyx.Input"},
    {kind: "onyx.Button", content: "Search"}
  ]
});
enyo.kind({
  name: "App",
  fit: true,
  classes: "onyx",
  components: [
    {kind: "SearchComponent", showClearButton: true}
  ]
});

Of course this still doesn’t look that great. So let’s take advantage of some more Onyx features. First we will add a placeholder for the search box. This is as simple as adding a “placeholder” attribute while declaring it:

{ kind: "onyx.Input", placeholder: "Search..." }

OK, this is an improvement, but we can make it look much better if we wrap this with an InputDecorator, a component designed to improve the look and functionality of text inputs. If you want to have stylish input areas that have background images or icons, this is the way to go. We will simply use it for the extra style.

{ kind: "onyx.InputDecorator", components: [
    { kind: "onyx.Input", placeholder: "Search..." }
]}

Again, an improvement, but with Onyx we can do much better. Currently the components are just not laid out that well. What we’ll do next is combined these components into a toolbar component. Here is what the components section of the SearchComponent should look like after that:

components: [{
      kind: "onyx.Toolbar", 
      name: "toolbar", 
      components: [
        {
          kind: "onyx.InputDecorator", components: 
          [{kind: "onyx.Input", placeholder: "Search..."}]
        },
        {kind: "onyx.Button", content: "Search"}
       ]
    }]

This looks a lot better, but you will notice now that the “Clear” button is misplaced. This is because the layout has changed since we added the toolbar, so it is now being added to the wrong place. You can correct that by changing the this.createComponents code to this.$.toolbar.createComponents. As you will see above, we gave the toolbar a name of “toolbar” just so we could access it this way. In fact it is a good idea to give a name to all your important components.

We will now revise our second application.

Before we do that though, we will make one change. In our previous tutorial we were introducing components, so it made sense to make most of the previous application a component, and then add that to an application. In this example that adds needless complexity, so the first change we will make it to remove the app that included the RotateImageComponent and rename the RotateImageComponent to “App”. This effectively makes the component itself the app, this will make discussing layouts much simpler. Here is what the code is like after that step:

enyo.kind({
  name: "App",
  kind: enyo.Control,
  published: {
    rotation: 0,
    skewX: 0,
    skewY: 0
  },
  create: function () {
    this.inherited(arguments);
    this.updateTransforms();
  },
  rotateC: function () {
    this.setRotation(this.rotation + 1);
  },
  rotateA: function () {
    this.setRotation(this.rotation - 1);
  },
  skewXInc: function () {
    this.setSkewX(this.skewX + 1);
  },
  skewXDex: function () {
    this.setSkewX(this.skewX - 1);
  },
  skewYInc: function () {
    this.setSkewY(this.skewY + 1);
  },
  skewYDec: function () {
    this.setSkewY(this.skewY - 1);
  },
  rotationChanged: function () {
    this.updateTransforms();
  },
  skewXChanged: function () {
    this.updateTransforms();
  },
  skewYChanged: function () {
    this.updateTransforms();
  },
  updateTransforms: function() {
    this.$.logo.applyStyle("transform", "rotate(" + this.rotation + "deg) skewX(" + this.skewX + "deg) skewY(" + this.skewY + "deg)");
    this.$.logo.applyStyle("-moz-transform", "rotate(" + this.rotation + "deg) skewX(" + this.skewX + "deg) skewY(" + this.skewY + "deg)");
    this.$.logo.applyStyle("-o-transform", "rotate(" + this.rotation + "deg) skewX(" + this.skewX + "deg) skewY(" + this.skewY + "deg)");
    this.$.logo.applyStyle("-webkit-transform", "rotate(" + this.rotation + "deg) skewX(" + this.skewX + "deg) skewY(" + this.skewY + "deg)");
    this.$.logo.applyStyle("-ms-transform", "rotate(" + this.rotation + "deg) skewX(" + this.skewX + "deg) skewY(" + this.skewY + "deg)");
  } ,
  components: [
    { tag: "img", name: "logo", src: "assets/enyo-logo.png" },
    { tag: "button", content: "Rotate +", onclick: "rotateC" },
    { tag: "button", content: "Rotate -", onclick: "rotateA" },
    { tag: "button", content: "Skew X +", onclick: "skewXInc" },
    { tag: "button", content: "Skew X -", onclick: "skewXDex" },
    { tag: "button", content: "Skew Y +", onclick: "skewYInc" },
    { tag: "button", content: "Skew Y -", onclick: "skewYDec" }
  ]
});

If you run this, you will see it works like before.

Now the first immediate change we can make is to convert all the normal buttons to Onyx buttons. We can also replace the “img” tag with an enyo.Image component. The advantage of this is that it disables the ability to drag the image, which is important for touch interfaces. We will also add the classes: "onyx" bit like before. Here is what we get:

enyo.kind({
  name: "App",
  kind: enyo.Control,
  classes: "onyx",
  components: [
    { kind: "enyo.Image", name: "logo", src: "assets/enyo-logo.png" },
    { kind: "onyx.Button", content: "Rotate +", onclick: "rotateC" },
    { kind: "onyx.Button", content: "Rotate -", onclick: "rotateA" },
    { kind: "onyx.Button", content: "Skew X +", onclick: "skewXInc" },
    { kind: "onyx.Button", content: "Skew X -", onclick: "skewXDex" },
    { kind: "onyx.Button", content: "Skew Y +", onclick: "skewYInc" },
    { kind: "onyx.Button", content: "Skew Y -", onclick: "skewYDec" }
  ]
});

You may have noticed we left out major parts of the code; we won’t repeat the various functions for manipulating the image again and again since they are not the focus here. This code will look the same, even if it doesn’t work. You can always include all those functions from the code further above and the app will work like before.

Another change we can immediately make is to combine all the buttons into one toolbar:

enyo.kind({
  name: "App",
  kind: enyo.Control,
  classes: "onyx",
  components: [
    { kind: "enyo.Image", name: "logo", src: "assets/enyo-logo.png", fit: true },
    { kind: "onyx.Toolbar", components: [
      { kind: "onyx.Button", content: "Rotate +", onclick: "rotateC" },
      { kind: "onyx.Button", content: "Rotate -", onclick: "rotateA" },
      { kind: "onyx.Button", content: "Skew X +", onclick: "skewXInc" },
      { kind: "onyx.Button", content: "Skew X -", onclick: "skewXDex" },
      { kind: "onyx.Button", content: "Skew Y +", onclick: "skewYInc" },
      { kind: "onyx.Button", content: "Skew Y -", onclick: "skewYDec" }
    ]}
  ]
});

Next we will apply a proper layout to this app.

Enyo provides a number of ways to define the layout of applications, two important components for layouts are FittableRows and FittableColumns. These two combined can be used to create complex flexible layouts for applications.

FittableRows allows you to place content in rows; on one of these rows you can set the fit property to true which will give it a variable height that will expand as the page is resized.

FittableColumns allows you to place content in columns. Like with FittableRows one of these columns can have fit set to true and that column will expand to take up available space.

These can be used together to create application UIs. For example, a standard UI for an application would have a row with a menu, another row with the toolbars, yet another row for content and sidebars which would be set to fit in available space, and a last final row with the status bar. The content row could have columns for panels and one column for the content, which could be set to fit in available space.

There are two ways to use these layouts. First it is possible to use the FittableRows and FittableColumns components directly, children of the FittableRows component will be placed in rows, and likewise children of FittableColumns components will be laid in columns. The other option is to set the layoutKind property of a control to either FittableColumnsLayout or FittableRowsLayout.

In the first case we would replace kind: enyo.Control in our application with kind: enyo.FittableRows or kind: "FittableRows".

In the second case we would add the layoutKind property and set to enyo.FittableRowsLayout or simply "FittableRowsLayout". We will use this method in our application. We will also set fit:true for the image so it takes up the rest of the space. This will make it stretch the image though, which is generally not desirable, a better approach would be place the image inside another component that has fit: true set on it. Here is what we are going for:

enyo.kind({
  name: "App",
  kind: enyo.Control,
  layoutKind: enyo.FittableRowsLayout,
  classes: "onyx",
  components: [
    { fit: true, style: "text-align: center;", components: [
      { kind: "enyo.Image", name: "logo", src: "assets/enyo-logo.png" }
      ], 
    },
    { kind: "onyx.Toolbar", components: [
      { kind: "onyx.Button", content: "Rotate +", onclick: "rotateC" },
      { kind: "onyx.Button", content: "Rotate -", onclick: "rotateA" },
      { kind: "onyx.Button", content: "Skew X +", onclick: "skewXInc" },
      { kind: "onyx.Button", content: "Skew X -", onclick: "skewXDex" },
      { kind: "onyx.Button", content: "Skew Y +", onclick: "skewYInc" },
      { kind: "onyx.Button", content: "Skew Y -", onclick: "skewYDec" }
    ]}
  ]
});

As you can see in the above code we have added a parent element for the image, and centered the image inside the parent by applying the text-align: center; style. The parent element has no tag or kind defined for it, in this case it defaults to a div which works for us.

Now, this looks a lot better, but we will make one final adjustment, see if you get what we’ve done in the following code:

enyo.kind({
  name: "App",
  kind: enyo.Control,
  layoutKind: enyo.FittableRowsLayout,
  classes: "onyx",
  components: [
    { fit: true, style: "text-align: center;", components: [
      { kind: "enyo.Image", name: "logo", src: "assets/enyo-logo.png" }
      ]
    },
    { kind: "onyx.Toolbar", components: [
      { content: "Rotate"},
      { components: [
        { kind: "onyx.Button", content: "+", onclick: "rotateC" },
        { kind: "onyx.Button", content: "-", onclick: "rotateA" }]
      },
      { content: "Skew X"},
      { components: [
        { kind: "onyx.Button", content: "+", onclick: "skewXInc" },
        { kind: "onyx.Button", content: "-", onclick: "skewXDex" }]
      },
      { content: "Skew Y"},
      { components: [
        { kind: "onyx.Button", content: "+", onclick: "skewYInc" },
        { kind: "onyx.Button", content: "-", onclick: "skewYDec" }]
      }
    ]}
  ]
});

In the above code, we have grouped together the increment and decrement buttons for rotation and X and Y skewing, and made it into a label instead. Hopefully you’ll agree this looks a bit better.

Onyx and Enyo include a number of components like the ones described above. You have Panels, Arrangers, Drawers, Scrollers and Lists for layout and Buttons, Menus, Pickers, Popups controls for applications. You can combine these, extend them or simply create your own set of widgets to create powerful applications with Enyo.

If you are interested in learning more beyond this point—as you should be—you should check out the extensive documentation available for Enyo and try some of the above code in their Sampler app.

Leave a Comment

Your email address will not be published. Required fields are marked *