Chapter 27. Constraints

Table of Contents

1. Constraints defined
2. Syntax for creating and removing constraints
2.1. Creating Constraints in Tags
2.2. Creating constraints from script
2.3. Delegates and constraints
2.4. States: a quick look
3. Constraints in action
3.1. Modeling real system constraints
4. Events and Dependencies
5. Multiple Dependencies
6. Circular Constraints
7. Performance Considerations
7.1. Declaring When Constraints are to be evaluated
7.2. When to use Layouts instead of Constraints
7.3. When to use events and methods instead of constraints
8. Constraints and Functions

1. Constraints defined

In LZX, a constraint is an attribute whose value is a function of one or more other attribute's value(s).

Constraints help you quickly create dependencies without writing a lot of code. You can specify that certain views are the same size or positioned relative to one another. Specific elements can appear only under certain conditions; and those conditions may be declared with a simple expression. Constraints may be used for non-visual attributes as well.

2. Syntax for creating and removing constraints

Constraints may be created in either of two ways:

  • in a tag, using the ${} syntax to tie the value of one attribute to that of another

  • in script, using the applyConstraint() method

  • in script, using delegates

These are described in turn below.

There is no method for removing constraints. Therefore if you have a constraint that you wish to sometimes be in force and other times not be in force, you should use <state> s.

2.1. Creating Constraints in Tags

The syntax for coding a constraint is:

$when{expression}

where:

  • $ is the token indicating a constraint

  • when is an optional compiler directive: immediately, once, or always. $always{expression} can be abbreviated to ${expression}

  • { and } are tokens delimiting the expression to be evaluated

  • expression is a JavaScript expression

2.2. Creating constraints from script

The following example uses the applyConstraint() method inherited from LzNode in order to create a constraint at runtime. You'll see that the setConstraint method has the same effect as the simple expression y=${m.y}

Example 27.1. Runtime constraints with applyConstraint

<canvas>
  <view x="250" width="20" height="20" bgcolor="red"
        y="${m.y}"/>

  <view x="300" width="20" height="20" bgcolor="blue">  
    <handler name="oninit">
      var f = function () {   
        this.setAttribute("y", m.y);       
      }
      var d = [m, "y"];
      this.applyConstraint("y", f, d);
    </handler>
  </view>

  <window id="m" x="10" title="Drag me" width="160" height="20"/>  
</canvas>


f is a callback function that is required for the applyConstraint() method. d is an array consisting of a pointer to a reference node, and the attribute to bind to.

2.3. Delegates and constraints

Laszlo applications can use something called a delegate in script to associate a method or any global function with an event. When the event is triggered, the method is called.

The above constraint expression could also be written in script as:

<handler name="oninit">
  var width_del = new LzDelegate(this, "updateMyWidth");
  width_del.register(parent, "onwidth");
</handler>
  
<method name="updateMyWidth">
  this.setWidth(parent.width);
</method>

You can read more about delegates in Chapter 30, Delegates.

2.4. States: a quick look

States are a convenient way to set and remove constraints at runtime.

You create states using the <state> tag, for example:

Example 27.2. states and constraints

<state name="flexistate">
    <attribute name="width" value="${parent.width}" />
</state>

<state name="boringstate" apply="false">
    <attribute name="width" value="50" />
</state>

States are further explained in Chapter 32, States .

3. Constraints in action

Whenever the value of an attribute changes, its on event is generated. Because a constraint is an attribute whose value is dependent upon the values of one or more other attribute(s), the value of the constraint is recalculated whenever it receives the on event for the attributes on which it depends.

Consider

 
<view name="someView" 
   width="${someAttribute + someOtherAttribute}" 
   /> 

Then the value of someView.width would be recomputed whenever an onsomeAttribute or onsomeOtherAttribute event occurred.

So for example

<view name="beatles" width="${this.paul.width + 28}">
   <view name="paul" onclick="clickhandler()">
      <!-- clickhandler method here to increase paul's width based on user clicking mouse -->
   </view>
</view>

The width of beatles will increase or decrease as a function of paul's width; the expression this.paul.width + 28 is a constraint.

This is a trivial example, but it serves to make the point that in declaring the structure of your objects in LZX you also declare the rules by which they will relate to each other. Constraints are a fundamental concept in LZX programming, and learning to "think in LZX" is a mostly a matter of learning to properly model your system's behavior in terms of the constraints on its constituent parts.

3.1. Modeling real system constraints

You can think about constraints as modeling real system interrelationships. A constraint expression can establish that two objects are connected.

In the example below, the visibility of the blue square is connected to the value of the checkbox. When you toggle the checkbox, the visibility of the blue square automatically switches as well.

Example 27.3. An element appears when a checkbox is set

<canvas height="120">
  <checkbox id="cb" text="Show Blue Square"/>
  <view visible="${cb.value}"
        width="30" height="30" bgcolor="blue"/>
  <simplelayout/>
</canvas>

In the example below, the position of the blue square is connected to the position of the mouse. If you move the mouse inside the canvas, the blue square will move as the mouse moves, floating relative to the position of the mouse. In this case, the constraint expression includes a method call, but it works in the same way as the simple attribute expression in Example 27.3, “An element appears when a checkbox is set”.

Example 27.4. Constrain to Mouse Position

<canvas height="120">
  <view bgcolor="blue" width="40" height="40"
        x="${parent.getMouse('x')}"
        y="${parent.getMouse('y')}"/>
</canvas>

The constraint expression may be almost any Javascript expression. For example, you could change the y value in example Example 27.3, “An element appears when a checkbox is set” to y="${immediateparent.getMouse('y')+10}" and cause the blue square to float below the mouse. If you centered the blue square at the mouse position by setting the y value with y="${immediateparent.getMouse('y') - this.width/2}" — if the width of the blue square changes, the y value with be updated to position the blue square relative to the new width and the mouse position.

See Constraints and Functions, below, for limitations on the types of expression you can use in constraints.

4. Events and Dependencies

Every constraint has one or more dependencies. For example, the constraint expression:

width="${parent.width}"

creates a dependency on the parent's width attribute. As explained in Chapter 29, Methods, Events, Handlers, and Attributes, whenever an attribute is set by calling setAttribute('whatever', 4) an onwhatever event is sent. Following the above example for the width attribute, an onwidth event is sent whether the attribute has been set with setAttribute, setWidth() or by another constraint.

When you use a constraint expression, a function is automatically generated to set the value of the attribute. That function is then called whenever an event is triggered on which the constraint has a dependency.

5. Multiple Dependencies

Constraints can have dependencies on multiple attributes. In the code below, the width attribute is dependent on the immediateparent's width attribute and the parent's bordersize attribute. When either attribute is set the width updates. Immediateparent.width is used instead of parent.width, because immediateparent, in this case, is the interior view of the window, while parent is the whole window.

width="${immediateparent.width - parent.bordersize * 2}"

Experiment with the working example below to see multiple dependencies in action. Resizing the window will resize the blue bar. You can also move the slider to adjust the border size.

Example 27.5. Multiple Dependencies

<canvas height="300" width="700">
  <window width="400" height="200" resizable="true">
    <attribute name="bordersize" value="${editor.bordersize.value}"/>
    <view y="20" name="editor" layout="axis:x; spacing:10; inset:10">
      <text y="-4" resize="true">bordersize:</text>
      <slider name="bordersize" value="10"/>
    </view>

    <view bgcolor="0xFFC834" y="80"
          width="${immediateparent.width}" height="10" />
    <view x="${parent.bordersize}" y="80"
             width="${immediateparent.width - parent.bordersize * 2}"
             height="10" bgcolor="blue" />
    
  </window>
</canvas>

6. Circular Constraints

A constraint loop exists when the value of an attribute A depends on B, and B depends on A. It's actually a very natural thing to do in LZX. For example, in the Laszlo application below, the y values of the red and blue squares are constrained to each other.

Example 27.6. Circular Constraints

<canvas height="120">
  <view name="redview" bgcolor="red"
        onmousedown="gragger.apply()" onmouseup="gragger.remove()"
        width="40" height="40" y="${blueview.y}">
    <dragstate name="gragger"/>
  </view>
  <view name="blueview" bgcolor="blue"
        onmousedown="gragger.apply()" onmouseup="gragger.remove()"
        width="40" height="40" y="${redview.y}">
    <dragstate name="gragger"/>
  </view>
  <simplelayout spacing="40" axis="x"/>
</canvas>

How does it work? When you drag the red view to change its on y position, it sends an ony event. The blue view is listening for the red view's ony event and responds by updating its own y position. The red view is listening for the blue view's ony event; however, it doesn't respond to that event because an event handler only gets called one time on each event.

7. Performance Considerations

Constraints are an effective way to write user interface code and in some cases to manage non-visual application state. As with any code, you will want to understand the performance implications, as well as when it is best to use constraints as opposed to alternate methods of achieving similar effects.

7.1. Declaring When Constraints are to be evaluated

Often you will want to use constraint expressions to create the initial state of your application, but you don't need attributes to update dynamically while the application is in use. In this case, you can improve startup time performance by declaring when the expression should be evaluated.

When you write a constraint using the ${...} syntax (as in the example below), when="always" is the default.

<view width="${parent.width}" height="10" bgcolor="yellow"/>

To optimize this expression for the case when the dependency will not change, you will want to specify when="once":

<view width="$once{parent.width}" height="10" bgcolor="yellow"/>

When declaring class attributes, the attribute is typically declared using an <attribute> tag, where the when value may be declared explicitly:

<class name="myclass">
  <attribute name="width" value="${parent.width}" when="once"/>
</class>

In this case if when is not specified, the value is immediately, which means that it initializes the attribute to the value of the expression when the enclosing element is defined. The value must be a constant expression and cannot depend on any other objects.

7.2. When to use Layouts instead of Constraints

Constraints can be very useful for positioning and determining the size of views. However, if you have a large number of views updating based on the same information, it is better to use a <layout> instead of writing a complex constraint system. There are quite a few layouts available in the Openlaszlo platform, and furthermore you can write your own if none of these meet your needs. See Chapter 17, Layout and Design for more on this subject.

7.3. When to use events and methods instead of constraints

Generally, if you have a lot of different things that need to be updated based on a single dependency, it is optimal to write a setter for the dependent attribute and in that setter take appropriate actions.

8. Constraints and Functions

Take care when using JavaScript functions inside of constraints.

In general, there are only a limited number of LZX functions that have dependency functions and can be expected to correctly participate in a constraint. No ECMAScript functions have dependency functions, so none of them will work properly in a constraint.

More precisely, ECMAScript functions will work properly if they are stateless (they depend only on the values of their arguments), so, for example, value="Math.max(parent.width, parent.height)" will do the right thing. (Any stateless function works.) However, many apparently simple functions do not work within constrains.

Consider

     <param value="${escape(canvas.options.serialize())}"/>

in which the value of a parameter is constrained to that returned by a serializing function. Upon inspection, we see that he dependencies of

     value="escape(canvas.options.serialize())"

will be calculated as:

  • the dependencies of escape (which includes the value of canvas.options.serialize())

  • the dependencies of serialize

In this case, value gets evaluated too early (i.e. before the dataset has any data) and that's why you don't see any data. Instead, you would use getValue()which acts as a getter for <param>, instead of using a constraint.

In evaluating dependencies in constraints, the OpenLaszlo compiler only look at the 'tail' of a chain, so if you have an expression a.b.c, only c will be depended on. For a function, the function itself computes its dependencies, based on its arguments. In the case of escape, it doesn't have a dependency function, nor does serialize, so in this particular example, there will be no dependencies at all.

EXCEPT that the constraint mechanism only looks at a tail, so for example NEITHER value="parent.selected.width" NOR value="Math.max(parent.width, parent.selected.width" will work if parent.selected changes value (it's fine if parent.selected stays the same but parent.selected.width changes).

The bottom line is that the constraint calculator is not really designed to be used in functions. It works for many situations, but not for anything complex.