O'Reilly logo

Enyo: Up and Running by Roy Sutton

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Layout

In this chapter we’ll explore how to enhance the appearance of Enyo apps by using various layout strategies to place controls where we want them. By combining the knowledge gained in the previous chapters with the layout tools in this chapter, you’ll have most of the knowledge you need to create compelling apps using Enyo. We’ll explore each of the layout tools using examples you can run in your browser.

As with visual controls, Enyo provides both core layout strategies and an optional library called Layout. The core strategies provide the “simpler” approach to layout while the Layout library provides some more advanced features. The Onyx library also provides a layout tool in the form of the Drawer component.

Responsive Design

Before we begin talking about layout strategies we should discuss responsive design. Responsive design means that an app or web page changes its appearance (or functionality) depending upon the device or display size it is used on. It’s important to consider how your app will look on different displays when designing a cross-platform app. Responsive web design is a topic that probably deserves a book of its own. You are encouraged to research the tools and techniques on the Web. Many of those same tools are used both within Enyo and by Enyo app developers. In particular, CSS media queries are often used in Enyo apps. We’ll discuss the tools that Enyo makes available for designing responsive apps, but you may need to supplement these tools in certain circumstances.

Core Layout Features

Enyo provides two useful mechanisms for layout in the core: scrollers and repeaters. The Scroller kind implements a section of the display that is scrollable by the user while the Repeater kind is useful for making repeating rows of items.

Scrollers

One of the bigger challenges in a mobile app is presenting a scrolling area of information that would otherwise be too big to fit. While many solutions exist, their cross-platform performance varies greatly. The Enyo team has spent a considerable amount of time analyzing performance issues and bugs across various browsers to produce the Scroller component.

Scrollers require very little configuration but do have some settings you can control. The vertical and horizontal properties default to automatically allow scrolling if the content of the scroller exceeds its size. Setting either to hidden disables scrolling in that direction while setting either to scroll causes scroll thumbs to appear (if enabled) even if content otherwise fits. The touch property controls whether desktop browsers will also use a touch-based scrolling strategy (instead of thumb scrollers).

For more information on scrollers, visit the scroller documentation page.

Repeaters

Another challenge is to display a list of repeating rows of information. The Repeater component is designed to allow for the easy creation of small lists (up to 100 or so items) of consistently formatted data. A repeater works by sending an event each time it needs data for a row. The function that subscribes to this event fills in the data required by that row as it is rendered. The following sample shows a repeater that lists the numbers 0 through 99:

enyo.kind({
    name: "RepeaterSample",
    kind: "Scroller",
    components: [{
        kind: "Repeater",
        count: 100,
        components: [{ name: "text"}],
        onSetupItem: "setupItem",
        ontap: "tapped"
    }],
    setupItem: function(inSender, inEvent) {
        var item = inEvent.item;
        item.$.text.setContent("This is row " + inEvent.index);
        return(true);
    },
    tapped: function(inSender, inEvent) {
        enyo.log(inEvent.index);
    }
});

new RepeaterSample().renderInto(document.body);
A Repeater control

Tip

Try it out: jsFiddle.

You’ll notice that we placed the Repeater into a Scroller. As the contents would (likely) be too large to fit onto your screen, we needed the scroller to allow all the content to be viewable. The components block is the template for each row and can hold practically any component, though it is important to note that fittables cannot be used inside a repeater.

Also of note is the fact that each time we respond to the onSetupItem event, we reference the component(s) in the components block directly off the item passed in through the event. The repeater takes care of instantiating new versions of the components for each row. If you need to update a specific row in a repeater, you should call the renderRow() function and pass in the index of that row.

Note

To redraw the whole list, set a new value for the count property. Unlike most other properties, calling setCount() with the same value as before will trigger a new redraw. This is to handle the case of data sets that may contain the same number of items even though the data within those items has changed.

Layout Library Features

The modular Layout library includes several methods for arranging data within our apps. Three of the kinds we’ll discuss are Fittable, List, and Panels. Visit the Enyo wiki online to find out more information on the Layout library and the kinds we didn’t cover here.

Fittable

One area of layout that Enyo makes easier is designing elements that fill the size of a given space. Enyo provides two layout kinds, FittableColumnsLayout and FittableRowsLayout, to accomplish this. Fittable layouts allow for a set of components to be arranged such that one component expands to fill the space available while the others retain their fixed size. FittableColumnsLayout arranges components horizontally while FittableRowsLayout arranges them vertically. To specify the child component that will expand to fit the space available, set the fit published property to true.

To apply the fittable style to controls, set the layoutKind property. To make it easier to use, the Layout library includes two controls with the layout already applied: FittableColumns and FittableRows. Fittables can be arranged within each other, as the following code sample shows:

enyo.kind({
    name: "Columns",
    kind: "FittableColumns",
    components: [
        { content: "Fixed width", classes: "dont" },
        { content: "This expands", fit: true, classes: "do" },
        { content: "Another fixed width", classes: "dont" }
    ]
});

enyo.kind({
    name: "FittableSample",
    layoutKind: "FittableRowsLayout",
    components: [
        { content: "Fixed height", classes: "dont" },
        { kind: "Columns", fit: true, classes: "do" },
        { content: "Another fixed height", classes: "dont" }
    ]
});

new FittableSample().renderInto(document.body);

Tip

Try it out: jsFiddle.

An example of fittable layout

In the previous sample, we used both styles of applying a fittable layout, using a layoutKind for the row layout and using the FittableColumns for the column layout. We applied a simple CSS style that added colored borders to the expanding regions. If you resize the browser window, you’ll see that the area in the middle will expand while the areas above and to the sides have fixed heights and widths, respectively.

Note

Fittables only relayout their child controls in response to a resize event. If you need to relayout the controls because of changes in the sizes of components, call the resized() function on the fittable component.

While fittables provide an easy solution to creating specific layouts, they should not be overused. Reflows are performed in JavaScript and too many nested fittables can affect app performance.

Lists

Earlier we covered repeaters, which allowed us to display a small number of repeating items. The List component serves a similar purpose but allows for a practically unlimited number of items. Lists include a built-in scroller and support the concept of selected items (including multiple selected items). Lists use a flyweight pattern to reduce the number of DOM elements that get created and, therefore, speed up performance on mobile browsers.

All this performance doesn’t come without downsides, though. Because list items are rendered on the fly it is difficult to have interactive components within them. It is recommended that simple controls and images be used within lists:

enyo.kind({
    name: "ListSample",
    kind: "List",
    count: 10000,
    handlers: {
        onSetupItem: "setupItem",
        tap: "tapped"
    },
    components: [{ name: "text" }],
    setupItem: function(inSender, inEvent) {
        this.$.text.setContent("This is row " + inEvent.index);
        return(true);
    },
    tapped: function(inSender, inEvent) {
        enyo.log(inEvent.index);
    }
});

new ListSample().renderInto(document.body);

Tip

Try it out: jsFiddle.

In both this example and the Repeater example we knew the number of items we wanted to display so we set the count property when creating them. Often, you won’t know how many items to display while writing your app. In that case, leave the count property undefined and call setCount() once you have received the data. Once set, the List will render itself.

In order to make a List row interactive, you must first use the prepareRow function. Then, a call to performOnRow can be used to act on the row. Finally, lockRow should be called to return the row to its non-interactive state. Let’s modify the tap handler we used to see how we might modify add an interactive element to a row:

enyo.kind({
    name: "ListSample",
    kind: "List",
    count: 1000,
    items: [],
    handlers: {
        onSetupItem: "setupItem"
    },
    components: [
        { name: "text", kind: "Input", ontap: "tapped",
           onchange: "changed", onblur: "blur" }
    ],
    create: function() {
        this.inherited(arguments);
        for(var i = 0; i < this.count; i++) {
            this.items[i] = "This is row " + i;
        }
    },
    setupItem: function(inSender, inEvent) {
        this.$.text.setValue(this.items[inEvent.index]);
        return(true);
    },
    tapped: function(inSender, inEvent) {
        this.prepareRow(inEvent.index);
        this.$.text.setValue(this.items[inEvent.index]);
        this.$.text.focus();
        return(true);
    },
    changed: function(inSender, inEvent) {
        this.items[inEvent.index] = inSender.getValue();
    },
    blur: function(inSender, inEvent) {
        this.lockRow();
    }
});

new ListSample().renderInto(document.body);

Tip

Try it out: jsFiddle.

In this version, we detect a user tapping into a row and then lock that row so that we can make the Input editable. If we did not prepare the row, then the input control woud not be properly associated with the row being edited and our changes would not be preserved. We look for the onblur event so we can call lockRow to put the list back into non-interactive mode.

Panels

Panels are one the most flexible layout tools Enyo has to offer. Panels give you the ability to have multiple sections of content that can appear or disappear as needed. You can even control how the panels arrange themselves on the screen by using the arrangerKind property. The various arrangers allow for panels that collapse or fade as moved, or that are arranged into a carousel or even a grid.

Panels introduce the concept of an active panel. Although the various arrangers can present more than one panel on the screen at a time and all such visible panels can be interactive, the active panel is an important concept. You can easily transition the active panel by using the previous and next function, or detect when a user has moved to a new panel (e.g., by swiping) by listening for the onTransitionFinish event.

A quick example of how to use Panels will help explain. In this example, we’ll set up a layout that can have up to three panels, depending on the available width. As the available width shrinks, the number of panels visible will also shrink, until only one remains:

enyo.kind({
    name: "PanelsSample",
    kind: "FittableRows",
    components: [
        { kind: "Panels", fit: true, arrangerKind: "CollapsingArranger",
          classes: "panels-sample", narrowFit: false,
          components: [
              { name: "panel1", style: "background-color: blue" },
              { name: "panel2", style: "background-color: grey" },
              { name: "panel3", style: "background-color: green" }
          ]
        }
    ]
});

new PanelsSample().renderInto(document.body);

In order to achieve the sizing, we’ll use a little CSS and some media queries to size the panels appropriately:

.panels-sample > * {
    width: 200px;
}

@media all and (max-width: 500px) {
    .panels-sample > * {
        min-width: 200px;
        max-width: 100%;
        width: 50%;
    }
}

@media all and (max-width: 300px) {
    .panels-sample > * {
        min-width: 100%;
        max-width: 100%;
    }
}

Tip

Try it out: jsFiddle.

For this sample, we set the narrowFit property to false. By default, the individual panels in a CollapsingArranger panel will fill the available space when the screen size is below 800px. We changed that default so we could use 200px as the minimum width of a panel. The CSS we used detects when the screen gets below 500px and we limit each panel to half the space. Then, when the screen gets below 300px, we cause the panels to take up all the space. The user can still swipe panels left and right to reveal the other panels.

We have only touched on the power of the Panels component. You should check out the Panels documentation for more ideas on how to use them.

Summary

You should now be able to start producing beautiful and high performance apps that run on mobile and desktop platforms. We explored some of the features necessary to design responsive apps that make the best use of a user’s display size. In the next chapters we’ll learn about the tools Enyo developers can use to polish, package, and deploy their applications.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required