Layout UI components

Keep UI component in order

User interface has to be adjustable and adaptive to an environment and a hardware where it can be shown. In that respect existence of mobile phones, various WEB browsers, different operation systems, PCs, laptops, tablets and combinations of all these factors makes life harder. To avoid potential UI layouting problems developers should not locate and size UI components by assigning dedicated (x,y) coordinates and (width,height) values. Otherwise even slight font changing can crash designed UI layout.

Layout managers is well known solution to get adaptive UI. That is exactly the thing that helps developing adjustable UI layout. Layout manager doesn’t trust fixed UI components positions and sizes. It uses rules-based manner to order UI components. Layout manager is defined on the level of an UI component and “knows” two important things:

Technically zbkit layout manager class has to implement the following:

A layout manager can be applied to any Zebra UI component by calling “setLayout(layout)” method. Additionally children UI components can specify extra parameter called constraints that is specific for a particular layout manager. It is can be done either by setting a children component “constraints” field or during insertion of the component to the parent component:

zebkit.require("ui", function(ui) {
    // create panel
    var p = new ui.Panel();
    // set layout manager for the panel
    p.setBorderLayout();
    // add children components
    p.add("center", new ui.Label("Center")); 

    // another way to specify constraints is filling "constraints"
    // field of a children component
    var l = new ui.Label("Top");
    l.constraints = "top";
    p.add(l);
    ...
});

Implementing simple layout manage

Nevertheless zebkit provides rich set of different predefined layout managers, it makes sense for better understanding to start from developing an own Zebra layout manager. It should help to discover how simple the idea is. As an example, let’s develop layout manager that orders components along parent component diagonal:

zebkit.package("ui.demo", function(pkg, Class) {
    // declare layout manager class
    pkg.DiagLayout = Class(zebkit.layout.Layout,[
        // define what preferred size the given "target" component 
        // wants to have. in this case it calculated as sum of 
        // preferred heights and widths of children components
        function calcPreferredSize(target) {
           var psW = 0, psH = 0;

           for(var i=0; i < target.kids.length; i++) {
               var kid = target.kids[i];
               if (kid.isVisible) {
                   var ps = kid.getPreferredSize();
                   psW += ps.width;
                   psH += ps.height;
               }
           }
           return { width:psW, height:psH };
        },

        // define rules how children components of the
        // given "target" have to be ordered
        function doLayout(target) {
           var x = target.getTop(), y = target.getLeft();
           for(var i=0; i < target.kids.length; i++) {
               var kid = target.kids[i];
               if (kid.isVisible) {
                   var ps = kid.getPreferredSize();
                   kid.setBounds(x, y, ps.width, ps.height);
                   x += ps.width;
                   y += ps.height;
               }
           }
        }
    ]);
});

Use just developed diagonal layout manager as follow:

Custom diagonal layout
zebkit.require("ui", "ui.demo", function(ui, demo) {
    var r = new ui.zCanvas("layoutSampleDiag", 200,200).root;
    // set developed above diagonal layout manager
    r.setLayout(new demo.DiagLayout());
    // add children components
    r.add(new ui.Button("One"));
    r.add(new ui.Button("Two"));
    r.add(new ui.Button("Three"));
});

Predefined layout managers

Below you can find snapshots for all supplied by zebkit layout managers. Every snapshots are provided together with “live” application that run the snapshot source code directly on this page.

zebkit.layout.StackLayout

The layout manager places children UI components over each other and stretches its to fill the whole parent component surface.

Paint on top method implementation
zebkit.require("ui", function(ui) {
    var r = new ui.zCanvas("layoutSample1", 300, 300).root;
    r.setBorder("plain");
    // set stack layout manager
    r.setStackLayout();
    // add button
    r.add(new ui.Button("Under transparent"));
    // add partially transparent panel component
    var p = new ui.Panel();
    p.setBackground("rgba(240,240,240,0.7)");
    r.add(p);
});

zebkit.layout.BorderLayout

Border layout manager splits component area into five parts: top, left, right, bottom and center. Children components are placed to one of the part basing on constraints that have been specified for them:

Border layout
zebkit.require("ui", function(ui) {
    var r = new ui.zCanvas("layoutSampleBorder", 600,350).root;
    // set border layout manager
    r.setBorderLayout();

    // add children UI components with different constraints
    r.add("center", new ui.Button("CENTER"));
    r.add("left",   new ui.Button("LEFT"));
    r.add("right",  new ui.Button("RIGHT"));
    r.add("top",    new ui.Button("TOP"));
    r.add("bottom", new ui.Button("BOTTOM"));
});

zebkit.layout.PercentLayout

Percent layout manager orders children components basing on percentage sizes of the components. The percentage sizes are defined as the children components constraints.

Horizontally ordered percent layout manager with stretched vertically components

Percent layout
zebkit.require("ui","layout",function(ui, lay) {
    var r = new ui.zCanvas("percentLayout1", 400, 250).root;
    r.setBorder("plain");

    // set percent layout manager that stretches components 
    // vertically and sizes component horizontally according 
    // to its percentage constraints
    r.setLayout(new lay.PercentLayout());

    // add button that takes 20% of horizontal space
    r.add(20, new ui.Button("20%"));
    // add button that takes 30% of horizontal space
    r.add(30, new ui.Button("30%"));
    // add button that takes 50% of horizontal space
    r.add(50, new ui.Button("50%"));
});

Horizontally ordered percent layout manager with preferred components heights

Percent layout
zebkit.require("ui","layout",function(ui, lay) {
    var r = new ui.zCanvas("percentLayout2", 400, 200).root;
    r.setBorder("plain");

    // set percent layout manager that sizes components vertically 
    // according to its preferred heights and sizes components 
    // horizontally according to its percentage constraints
    r.setLayout(new lay.PercentLayout("horizontal", 2, false));

    // add button that takes 20% of horizontal space
    r.add(20, new ui.Button("20%"));
    // add button that takes 30% of horizontal space
    r.add(30, new ui.Button("30%"));
    // add button that takes 50% of horizontal space
    r.add(50, new ui.Button("50%"));  
});

Vertically ordered percent layout manager with preferred components widths

Percent layout
zebkit.require("ui","layout",function(ui, lay) {
  var r = new ui.zCanvas("percentLayout3", 400,200).root;
  r.setBorder("plain");

  // set percent layout manager that sizes components horizontally 
  // according to its preferred widths and sizes components 
  // vertically according to its percentage constraints
  r.setLayout(new lay.PercentLayout("vertical", 2, false));

  // add button that takes 20% of vertical space
  r.add(20, new ui.Button("20%"));
  // add button that takes 30% of vertical space
  r.add(30, new ui.Button("30%"));
  // add button that takes 50% of vertical space
  r.add(50, new ui.Button("50%"));
});

Vertically ordered percent layout manager with stretched horizontally components

Percent layout
zebkit.require("ui","layout",function(ui, lay) {
    var r = new ui.zCanvas("percentLayout4", 400,200).root;
    r.setBorder("plain");

    // set percent layout manager that stretches components
    // horizontally and sizes components vertically according
    // to its percentage constraints
    r.setLayout(new lay.PercentLayout("vertical", 2, true));

    // add button that takes 20% of vertical space
    r.add(20, new ui.Button("20%"));
    // add button that takes 30% of vertical space
    r.add(30, new ui.Button("30%"));
    // add button that takes 50% of vertical space
    r.add(50, new ui.Button("50%"));
});

zebkit.layout.FlowLayout

Flow layout provides many possibilities to align children components.

Vertically ordered UI components are centered horizontally and vertically:

Flow layout
zebkit.require("ui", function(ui) {
    var r = new ui.zCanvas("flowLayout1", 400, 200).root;
    r.setBorder("plain");

    // set flow layout with vertical components ordering and center 
    // vertical and horizontal alignments
    r.setFlowLayout("center","center","vertical",2);

    // add children components
    r.add(new ui.Button("VCentered"));
    r.add(new ui.Button("VCentered"));
    r.add(new ui.Button("VCentered"));
});

Vertically ordered UI components are aligned top-left:

Flow layout
zebkit.require("ui", function(ui) {
    var r = new ui.zCanvas("flowLayout2", 400,200).root;
    r.setBorder("plain");
    // set flow layout with vertical components
    // ordering, top-left alignment and 2 pixels
    // gap between inserted components
    r.setFlowLayout("left","top","vertical", 2);
    // add children components
    r.add(new ui.Button("Left-Top-Ver"));
    r.add(new ui.Button("Left-Top-Ver"));
    r.add(new ui.Button("Left-Top-Ver"));
});

Vertically ordered UI components are aligned top-right:

Flow layout
zebkit.require("ui", function(ui) {
    var r = new ui.zCanvas("flowLayout3", 400,200).root;
    r.setBorder("plain");
    // set flow layout with vertical components
    // ordering, top-right alignment and 2 pixels
    // gap between inserted components
    r.setFlowLayout("right","top","vertical", 2));
    // add children components
    r.add(new ui.Button("Right-Top-Ver"));
    r.add(new ui.Button("Right-Top-Ver"));
    r.add(new ui.Button("Right-Top-Ver"));
});

Vertically ordered UI components are aligned bottom-right:

Flow layout
zebkit.require("ui", function(ui) {
    var r = new ui.zCanvas("flowLayout4", 200,200).root;
    r.setBorder("plain");
    // set flow layout with vertical components
    // ordering, bottom-right alignment and 2 pixels
    // gap between inserted components
    r.setFlowLayout("right","bottom", "vertical", 2);
    // add children components
    r.add(new ui.Button("Right-Bottom-Ver"));
    r.add(new ui.Button("Right-Bottom-Ver"));
    r.add(new ui.Button("Right-Bottom-Ver"));
});

Horizontally ordered UI components are centered vertically and horizontally:

Flow layout
zebkit.require("ui", function(ui) {
    var r = new ui.zCanvas("flowLayout5", 600,120).root;
    r.setBorder("plain");
    // set flow layout with horizontal components
    // ordering, center-center alignment and 2 pixels
    // gap between inserted components
    r.setFlowLayout("center","center","horizontal", 2);
    // add children components
    r.add(new ui.Button("HCentered"));
    r.add(new ui.Button("HCentered"));
    r.add(new ui.Button("HCentered"));
});

Horizontally ordered UI components are aligned center-left:

Flow layout
zebkit.require("ui", function(ui) {
    var r = new ui.zCanvas("flowLayout6", 600,120).root;
    r.setBorder("plain");
    // set flow layout with horizontal components
    // ordering, center-left alignment and 2 pixels
    // gap between inserted components
    r.setFlowLayout("left","center","horizontal",2);
    // add children components
    r.add(new ui.Button("Left-Center-Hor"));
    r.add(new ui.Button("Left-Center-Hor"));
    r.add(new ui.Button("Left-Center-Hor"));
});

Horizontally ordered UI components are aligned center-right:

Flow layout
zebkit.require("ui", function(ui) {
    var r = new ui.zCanvas("flowLayout7", 600,120).root;
    r.setBorder("plain");
    // set flow layout with horizontal components
    // ordering, center-right alignment and 2 pixels
    // gap between inserted components
    r.setFlowLayout("right","center","horizontal", 2);
    // add children components
    r.add(new ui.Button("Right-Center-Hor"));
    r.add(new ui.Button("Right-Center-Hor"));
    r.add(new ui.Button("Right-Center-Hor"));
});

Horizontally ordered UI components are aligned top-right:

Flow layout
zebkit.require("ui", function(ui) {
    var r = new ui.zCanvas("flowLayout8", 600,120).root;
    r.setBorder("plain");
    // set flow layout with horizontal components
    // ordering, top-right alignment and 2 pixels
    // gap between inserted components
    r.setFlowLayout("right","top","horizontal", 2));
    // add children components
    r.add(new ui.Button("Right-Top-Hor"));
    r.add(new ui.Button("Right-Top-Hor"));
    r.add(new ui.Button("Right-Top-Hor"));
});

Horizontally ordered UI components are aligned top-left:

Flow layout
zebkit.require("ui", function(ui) {
    var r = new ui.zCanvas("flowLayout9", 600,120).root;
    r.setBorder("plain");
    // set flow layout with horizontal components
    // ordering, top-left alignment and 2 pixels
    // gap between inserted components
    r.setFlowLayout("left","top","horizontal",2);

    // add children components
    r.add(new ui.Button("Left-Top-Hor"));
    r.add(new ui.Button("Left-Top-Hor"));
    r.add(new ui.Button("Left-Top-Hor"));
});

zebkit.layout.RasterLayout

Raster layout manager is default Zebra component layout manager. It emulates the standard approach where locations and sizes are precisely specified by calling “setLocation(x,y)”, “setSize(w,h)” or “setBounds(x,y,w,h)” methods. It is strongly recommended to avoid using raster layout manager the way developers define exact values for a component location and size. Rules are better in respect of implementing adaptive UI that doesn’t depend on screen resolutions, font metrics, allocated for an UI application size and so on.

Hardcoded, user defined components locations and sizes:

Raster layout

Raster layout manager also can be less dependent from an environment an UI application can be run:

Size components to its referred size:

Raster layout

Size components to its preferred size and align its:

Raster layout

zebkit.layout.ListLayout

List layout manager orders children component vertically as a list of items.

Children components are stretched horizontally to occupy whole parent container width:

Raster layout

Children components are centered horizontally:

List layout

Children components are aligned left:

List layout

zebkit.layout.GridLayout

Grid layout manager splits a component area to number of virtual cells. Children components are placed into the cells. One cell can be occupied only by one children component. Using “zebkit.layout.Constraints” class developers can control how a children component has to be placed inside the virtual cell. “zebra.layout.Constraints” declares the following fields that declares how a component has to be placed inside a virtual cell:

Field Allowed values Description
ax "left"
"right"
"center"
"stretch"
Horizontal alignment in cell
ay "top"
"bottom"
"center"
"stretch"
Vertical alignment in cell
top,left,
bottom,right
integer value >= 0 Cell top, left, bottom and right paddings

The picture below explains how a component can be aligned inside a virtual cell controlled by grid layout manager:

gridlayout

Default grid layout manager constraints

List layout

1. Custom grid layout manager constraints

List layout

2. Custom grid layout manager constraints

List layout

Layout package API and constants

All zebra layout managers are hosted in “zebra.layout” package. The package provides the core “zebra.layout.Layoutable” class. This class describes a rectangular object that is bound with the given size and location. “zebra.layout.Layoutable” component can contain other layoutable components as its children. The children components are laid outed with a layout manager. Pay attention “zebra.layout” package is completely independent from UI part. Developer can easily use it as basis for layout management, for instance, for WEB based elements. Zebra UI engine just extends the basic “zebra.layout.Layoutable” class with visual and event related stuff.

The package provides number of useful API methods that can be handy to manipulate with component hierarchy:

; zebra.layout.getTopParent(comp); get top parent by the given component. Top is a component whose parent is "null" zebra.layout.toParentOrigin(x,y,c, [p]); translate the given relative location into a parent relative location zebra.layout.toChildOrigin(x,y,c,p); convert the given component location into relative location of the specified children component successor zebra.layout.isAncestorOf(p,k); test if the given kid component is an ancestor of the specified parent component
API method Description
zebra.layout.getDirectChild(p,k) get immediate kid for the given parent and children component
zebra.layout.getDirectAt(x,y,p) get immediate kid located at the given location of the specified parent component

Also it declares number of constants that from time to time have to be used as a layout constraints: [wpcol_2third] [table] Constraints;Description “left”; left constraint or alignment “right”; right constraint or alignment “top”; top constraint or alignment zebra.layout.BOTTOM; bottom constraint or alignment “center”; center constraint or alignment “horizontal”; horizontal constraint or alignment “vertical”; vertical constraint or alignment zebra.layout.STRETCH; stretch constraint zebra.layout.USE_PS_SIZE; use preferred size zebra.layout.TLEFT; top left constraint or alignment zebra.layout.TRIGHT; top right constraint or alignment zebra.layout.BLEFT; bottom left constraint or alignment zebra.layout.BRIGHT; bottom right constraint or alignment [/table][/wpcol_2third][wpcol_1third_end] constraints [/wpcol_1third_end]