Table of Contents
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.
Constraints may be created in either of two ways:
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.
The syntax for coding a constraint is:
$when
{expression
}
where:
$
is the token indicating a constraint
is an optional compiler directive:
when
immediately
, once
, or always
.
$always{
can be abbreviated to
expression
}${
expression
}
{
and }
are tokens delimiting the
expression to be evaluated
is a JavaScript expression
expression
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.
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.
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 .
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.
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.
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.
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>
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.
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.
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
is not specified, the value is when
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.
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.
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.
Copyright © 2002-2007 Laszlo Systems, Inc. All Rights Reserved. Unauthorized use, duplication or distribution is strictly prohibited. This is the proprietary information of Laszlo Systems, Inc. Use is subject to license terms.