Chapter 7. Introduction to Views

Table of Contents

1. Introduction
2. Constraints and Modifiers
2.1. Parent and Immediateparent
2.2. Operations in constraints
3. Layouts and container views

1. Introduction

Views are the basic visual element in OpenLaszlo applications. They allow you to specify how elements will interact with one another. In the OpenLaszlo Basics tutorial we've seen how to specify a top-to-bottom organization of text elements. In this tutorial you'll learn how to do more complex and powerful interactions among visual elements.

Here's the code from the previous tutorial:

Example 7.1. Simple window

<canvas height="300" width="500">
    <window x="20" y="20" width="200" height="250" 
            title="Simple Window" resizable="true">
        <simplelayout axis="y" spacing="10"/>
        <text>Here is some text.</text>
        <text>I could ramble for hours.</text>
        <button>My Button</button>
    </window>
</canvas>

We've added a button (My Button) just to remind us of how the <simplelayout> affected the sibling elements.

That button would look better if it were centered. There is an attribute that we could use to do this, but just to see better how LZX works, let's center the button programatically. This will give you a good look at how constraints and modifiers work, which will help you build more complex views.

2. Constraints and Modifiers

A constraint is an object which takes responsibility for keeping a view property set to a certain value. Constraints are described in full in Chapter 27, Constraints, but here we'll just get a flavor of them because they are a key feature of LZX syntax. In order to show constraints, we'll also introduce the key concepts of "parent" and "immediateparent", which describe where views fit in different kinds of hierarchies.

Depending on the type of constraint, that can be 'the x position of the mouse' or 'the width of the parent view' or whatever. In this case, we will constrain a property to the width of the parent view.

Example 7.2. Constraining button position relative to window

<canvas height="300" width="500">
  <window x="20" y="20" width="200" height="250" 
          title="Simple Window" resizable="true">
    <simplelayout axis="y" spacing="10"/>
    <text>Here is some text.</text>
    <text>I could ramble for hours.</text>
    <button x="${immediateparent.width / 2}" >
      My Button
    </button>
  </window>
</canvas>

What we've said here is "constrain the x position of the button to half the width of its parent". Actually we said "immediateparent". What's that about?

2.1. Parent and Immediateparent

As explained in greater depth in Chapter 26, Views, "parent" is a term that refers to the lexical parent in the code, and "immediateparent" refers to the runtime parent of a view as the code is executed. Often they are the same thing, but it's important to understand when they're not. In particular, you generally need to use "immediateparent" when you create views by instantiating classes.

You can always use immediateparent, instead of parent (you just have to type those extra 9 characters). In this case the immediateparent is the window's content area (the white bit). In short we had to use immediateparent here because <window> is a class, and when you instantiate it and put contents inside of it, you must use the immediateparent reference to get the desired behavior.

2.2. Operations in constraints

You can perform operations on the value an attribute that you constrain to. The modifier in our example is the "divide by two" operator.

Notice that this code does not center the button. Rather, it constrains the position of the button to the middle of the window. The problem is that the position of a view is determined by its top-left corner. In other words, the top-left corner of the button is centered in the window, not the button itself. In order to center the button, we need to move it over by half of its own width. One way to do that would be to just give the button an explicit width, and offset the constraint by half of that.

Example 7.3. Centering the button

<canvas height="300" width="500">
  <window x="20" y="20" width="200" height="250" 
        title="Simple Window" resizable="true">
    <simplelayout axis="y" spacing="10"/>
    <text>Here is some text.</text>
    <text>I could ramble for hours.</text>
    <button width="160" x="${immediateparent.width / 2 - 80}">
      My Button
    </button>
  </window>
</canvas>

That works but it's not a general solution. We want the button to always be in the middle of the window even if it changes width. Here's how we do that:

Example 7.4. A button that stays centered

<canvas height="300" width="500">
  <window x="20" y="20" width="200" height="250" 
          title="Simple Window" resizable="true">
    <simplelayout axis="y" spacing="10"/>
    <text>Here is some text.</text>
    <text>I could ramble for hours.</text>
    <button x="${(immediateparent.width / 2) 
                       - (this.width / 2)}">
      My Button
    </button>
  </window>
</canvas>

This will center the button regardless of its size. Parenthesis have been added to clarify the execution order of the code. To prove that the button will remain centered, we'll add an onclick script to the button that will resize it.

For now, you don't need to understand how this works. Simply observe that when you click the button, its width grows. We'll revisit this topic in the "Scripting" tutorial.

Example 7.5. Growing button

<canvas height="300" width="500">
  <window x="20" y="20" width="200" height="250" 
            title="Simple Window" resizable="true">
    <simplelayout axis="y" spacing="10"/>
    <text>Here is some text.</text>
    <text>I could ramble for hours.</text>
    <button x="${(immediateparent.width / 2) 
                         - (this.width / 2)}"
      onclick="this.setAttribute('width', this.getAttribute('width') + 10);" >
        My Button
      </button>
   </window>
</canvas>

Notice that as the button grows, it still stays centered.

In the above example we are using two methods of referencing properties of a view. For example, we use this.getAttribute('width') and this.width. They are different, and there are specific situations when you can and can't use the this.width syntax. In short, this.width can only be used in constraints.

3. Layouts and container views

What if now we wanted to have a row of buttons going across the top of our window? We can try adding a few buttons at the top:

Example 7.6. Vertically aligned buttons

<canvas height="300" width="500">
  <window x="20" y="20" width="200" height="250" 
          title="Simple Window" resizable="true">
    <simplelayout axis="y" spacing="10"/>
    <button>1</button>
    <button>2</button>
    <button>3</button>
    <text>Here is some text.</text>
    <text>I could ramble for hours.</text>
    <button x="${(immediateparent.width / 2) 
                       - (this.width / 2)}"
            onclick="this.setAttribute('width', this.getAttribute('width') + 10);">
      My Button
    </button>
  </window>
</canvas>

We can't change the <simplelayout> tag, because that would make a mess of the other elements in the window. Instead we can put all three buttons in their own view, and that view will then become a single element in the window.

Then we can apply whatever kind of layout/positioning to the button view we want:

Example 7.7. Grouping buttons in a <view>

<canvas height="300" width="500">
  <window x="20" y="20" width="200" height="250" 
          title="Simple Window" resizable="true">
    <simplelayout axis="y" spacing="10"/>
      <view bgcolor="#ff0000">
      <simplelayout axis="x" spacing="5"/>
      <button>1</button>
      <button>2</button>
      <button>3</button>
    </view>
    <text>Here is some text.</text>
    <text>I could ramble for hours.</text>
    <button x="${(immediateparent.width / 2)
                       - (this.width / 2)}"
            onclick="this.setAttribute('width', this.getAttribute('width') + 10);">
      My Button
    </button>
  </window>
</canvas>

That's better. By using the attributebgcolor (background color) attribute we can see how big the view is; it's only as big as it needs to be. We haven't specified either a height or width, so it is just large enough to accommodate the buttons.

What if we wanted the row to be like the toolbars at the top of your browser—a uniform color, that stretches from end to end?

We would constrain the width of the red view to the width of its parent.

Example 7.8. Constraining a width to parent's width

<canvas height="300" width="500">
  <window x="20" y="20" width="200" height="250" 
            title="Simple Window" resizable="true">
    <simplelayout axis="y" spacing="10"/>
    <view bgcolor="#ff0000"
          width="${parent.width}">
      <simplelayout axis="x" spacing="5"/>
      <button>1</button>
      <button>2</button>
      <button>3</button>
    </view>
    <text>Here is some text.</text>
    <text>I could ramble for hours.</text>
    <button x="${(immediateparent.width / 2) - (this.width / 2)}"
            onclick="this.setAttribute('width', this.getAttribute('width') + 10);">
      My Button
    </button>
  </window>
</canvas>

Now the view stretches across, but if you look at the toolbars you'll notice that they are a little bit taller than the buttons they contain. To make our buttons look the same, we could set an absolute height, but let's make it dependent on one of the buttons:

Example 7.9. Constraining view to child's height

<canvas height="300" width="500">
  <window x="20" y="20" width="200" height="250" 
            title="Simple Window" resizable="true">
    <simplelayout axis="y" spacing="10"/>
    <view bgcolor="#ff0000"
                  width="${parent.width}"
                  height="${this.refButton.height + 8}">
      <simplelayout axis="x" spacing="5"/>
      <button name="refButton">1</button>
      <button>2</button>
      <button>3</button>
    </view>
    <text>Here is some text.</text>
    <text>I could ramble for hours.</text>
    <button x="${(immediateparent.width / 2) - (this.width / 2)}" 
            onclick="this.setAttribute('width', this.getAttribute('width') + 10);">
      My Button 
    </button>
  </window>
</canvas>

In order to be able to reference a particular button, we had to give it a name attribute.

Now the buttons are top-aligned — they should be vertically aligned to the center of the red bar. Remember how earlier we said that there was an attribute to align stuff to the center? We're going to use it now:

Example 7.10. Using the 'valign' attribute

<canvas height="300" width="500">
  <window x="20" y="20" width="200" height="250" 
          title="Simple Window" resizable="true">
    <simplelayout axis="y" spacing="10"/>
      <view bgcolor="#ff0000"
            width="${parent.width}"
            height="${this.refButton.height + 8}">
        <simplelayout axis="x" spacing="5"/>
        <button name="refButton" valign="middle">
          1
        </button>
        <button>2</button>
        <button>3</button>
      </view>
    <text>Here is some text.</text>
    <text>I could ramble for hours.</text>
    <button x="${(immediateparent.width / 2) - (this.width / 2)}" 
            onclick="this.setAttribute('width', this.getAttribute('width') + 10);">
      My Button 
    </button>
  </window>
</canvas>

The valign attribute aligns the view to the center of its parent's height. It can take a value of top, center or bottom. There is an align attribute that aligns views to the x-axis. The only problem with using that here is that we'd have to give each button an align attribute.

The solution is to wrap all of our buttons in a view. Then we could apply a valign attribute to all of them at once.

Example 7.11. Applying 'valign' to container view

<canvas height="300" width="500">
  <window x="20" y="20" width="200" height="250" 
          title="Simple Window" resizable="true">
    <simplelayout axis="y" spacing="10"/>
    <view bgcolor="#ff0000"
          width="${parent.width}"
          height="${this.buttons.refButton.height + 8}">
      <simplelayout axis="x" spacing="5"/>
      <view name="buttons" valign="middle">
        <button name="refButton">1</button>
        <button>2</button>
        <button>3</button>
      </view>
    </view>
    <text>Here is some text.</text>
    <text>I could ramble for hours.</text>
    <button x="${(immediateparent.width / 2) - (this.width / 2)}" 
            onclick="this.setAttribute('width', this.getAttribute('width') + 10);">
      My Button 
    </button>
  </window>
</canvas>

OK, we seem to have lost buttons one and two, but let's come back to that. You'll notice that we gave the new view a name: "buttons". We also had to change the address to the button's height from this.refButton.height to this.buttons.refButton.height. This was necessary because refButton is now inside buttons, which is inside the red view.

Now to get buttons one and two back! In the "buttons" view, there is nothing to tell the OpenLaszlo Runtime that it should space them out. Why not? Because we forgot to transfer the <simplelayout> tag to the buttons view.

Example 7.12. Using <simplelayout> to align buttons

<canvas height="300" width="500">
  <window x="20" y="20" width="200" height="250" 
          title="Simple Window" resizable="true">
    <simplelayout axis="y" spacing="10"/>
      <view bgcolor="#bdbdbd"
            width="${parent.width}"
            height="${this.buttons.refButton.height + 8}">
        <view name="buttons" valign="middle">
          <simplelayout axis="x" spacing="5"/>
          <button name="refButton">1</button>
          <button>2</button>
          <button>3</button>
        </view>
      </view>
    <text>Here is some text.</text>
    <text>I could ramble for hours.</text>
    <button x="${(immediateparent.width / 2) - (this.width / 2)}" 
            onclick="this.setAttribute('width', this.getAttribute('width') + 10);">
      My Button 
    </button>
  </window>
</canvas>

We changed the color of the view to make it look better.