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.
Note | |
---|---|
You can only use constraints with read/write attributes. |
Constraints may be created as follows:
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, and is one of:
when
immediately
: evaluated at load time; it is the default, but should only be
used for constant expressions.
$immediately{expression
}
is the same as
expression
.
once
: evaluated at initialization time; it can refer to other
nodes that precede this one.
Note | |
---|---|
A |
always
: will track changes in the values referred to.
$always{expression
}
can be abbreviated to
${expression
}
.
Note | |
---|---|
An |
{
and }
are tokens delimiting the expression to be
evaluated
is a JavaScript expression
expression
The following example shows the use of constraints in tags:
Example 27.1. Constraints in tags
<canvas
height
="160
" width
="100%
">
<simplelayout
spacing
="5
"/>
<slider
id
="m
" value
="25
" minvalue
="0
" maxvalue
="100
"/>
<text
text
="$immediately{'immediately: ' + (m?m.value:'undefined')}
"/>
<text
text
="$once{'once: ' + (m?m.value:'undefined')}
"/>
<text
text
="$always{'always: ' + (m?m.value:'undefined')}
"/>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
Here's what the different values for when
do:
immediately
: undefined, because the slider does not exist until the program
runs. If you run this example with the debugger on, you will see a warning.
once
: set to the slider value when the program loads.
always
: track changes in the slider value.
The following example uses the applyConstraintMethod()
method inherited from lz.node
in order to create a constraint at runtime. You'll
see that the setAttribute()
method has the same effect as the simple
expression y=${m.y}
Example 27.2. Runtime constraints with applyConstraintMethod
<canvas
width
="100%
">
<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 d = [m, "y"];
this.applyConstraintMethod("myConstraintMethod", d);
</handler
>
<method
name
="myConstraintMethod
" args
="ignore
">
this.setAttribute("y", m.y);
</method
>
</view
>
<window
id
="m
" x
="10
" title
="Drag me
" width
="160
" height
="20
"/>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
myConstraintMethod
is a callback function that is required for the applyConstraintMethod()
method. d
is an
array consisting of a pointer to a reference node, and the attribute to bind to.
OpenLaszlo 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 y_del = new LzDelegate(this, "myConstraintMethod"); y_del.register(m, "ony"); </handler> <method name="myConstraintMethod" args="newValue"> this.setAttribute("y", m.y); </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.3. 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.4. An element appears when a checkbox is set
<canvas
height
="120
" width
="100%
">
<checkbox
id
="cb
" text
="Show Blue Square
"/>
<view
visible
="${cb.value}
" width
="30
" height
="30
" bgcolor
="blue
"/>
<simplelayout
/>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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 the previous example.
Example 27.5. Constrain to mouse position
<canvas
height
="120
" width
="100%
">
<view
bgcolor
="blue
" width
="40
" height
="40
" x
="${parent.getMouse('x')}
" y
="${parent.getMouse('y')}
"/>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
The constraint expression may be almost any Javascript expression. For example, you could
change the y value in the previous example 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.6. Multiple Dependencies
<canvas
height
="300
" width
="100%
">
<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
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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.7. Circular constraints
<canvas
height
="120
" width
="100%
">
<include
href
="lz/slider.lzx
"/>
<view
name
="redview
" bgcolor
="red
" width
="40
" height
="40
" x
="${blueview.x - 300}
">
</view
>
<view
name
="blueview
" bgcolor
="blue
" width
="40
" height
="40
" x
="${redview.x + 300}
">
</view
>
<slider
x
="10
" y
="50
" value
="50
" onvalue
="redview.setAttribute('x', this.value)
" showvalue
="false
" showrange
="false
"/>
<slider
x
="310
" y
="50
" value
="50
" onvalue
="blueview.setAttribute('x', this.value + 300)
" showvalue
="false
" showrange
="false
"/>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
How does it work? When you drag the red view to change its own 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.
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. See Chapter 29, Methods, Events, Handlers, and Attributes 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 the 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 looks 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-2010 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.