Table of Contents
This chapter discusses the general use of states and the built in states dragstate and resizestate.
States are a convenient method of defining multiple complex behaviors in your application using declarative syntax, behaviors that might normally be contained in a script block.
States are written within the object to which the state is applied and can contain any number of children.
All code in a <state>
is written as if it were directly inside the parent of the state when it is applied. This concept sounds
a bit abstract, but in practice seems quite natural, as we hope the following discussion will make clear.
Declarative programming is great for declaring the initial state of an application, but
subsequent transformations may make the originally declared structures barely recognizable.
Laszlo's <state>
construct provides a way to make declarative constructs effectively procedural.
The alternative to using states is to declare the initial state of your program with tags, and then manipulate that state with procedural code, that is, with script. This poses a problem, because some of LZX's most powerful features are difficult to add programmatically.
This is particularly true of animators and constraints. Constraints are central to the whole Laszlo idea, but manipulating them with script can be very tricky. For instance, here's a small program where the blue box follows the mouse:
Example 32.1. following mouse without states
<canvas> <view bgcolor="blue" width="20" height="20"> <attribute name="x" value="${parent.getMouse('x') - 10}"/> <attribute name="y" value="${parent.getMouse('y') - 10}"/> </view> </canvas>
While this is a nice way of expressing this kind of thing (made possible by Laszlo's constraint system), it's rarely useful in a program. Usually, objects only follow the mouse when they're being dragged. What's needed for that is a way to write declarative LZX nodes that could be applied and removed depending on whether the view is to be dragged or not.
The <state>
tag makes this possible. Here's a rewrite of the above program using states to make the blue
box drag only when the mouse is down:
Example 32.2. using <state> to follow mouse
<canvas> <view bgcolor="blue" width="20" height="20" onmousedown="this.setAttribute('mouseIsDown', true )" onmouseup=" this.setAttribute('mouseIsDown', false )"> <attribute name="mouseIsDown" value="false"/> <state apply="${parent.mouseIsDown}"> <attribute name="x" value="${parent.getMouse('x') - 10}"/> <attribute name="y" value="${parent.getMouse('y') - 10}"/> </state> </view> </canvas>
Note that the syntax of the constraints hasn't changed; they're just inside a <state>
tag.
This is useful, but it's also a little strange: the <state>
tag is essentially an invisible node of
hierarchy. You can see this in the fact that the parent
in the apply constraint for the state is
not the same object as the parent
in the drag constraints inside the state.
This example highlights the metaphysical nature of states: they modify the meta-object protocol enough so that
their contents can be stored and applied elsewhere. (This could conceivably be a feature of the system as a whole
as in, for example, an LzNode.setParent(otherLzNode)
, but it's not).
Here's a question: what should happen when you write this?
Example 32.3. attributes of states
<class name="myDragState" extends="state"> <attribute name="dragMin" value="2"/> <attribute name="dragMax" value="20"/> <!-- ... --> </class>
Do the attributes dragMin
and dragMax
pertain to the state itself,
or to the state's container? The former is more powerful: it would allow for developers to extend the special behavior of
states,
say by over-riding the state's apply method. The latter, though, is more consistent with the LZX idiom, where,
as a first-order approximation, a class behaves like a macro.
The answer is, the behavior is the latter: attributes apply to the state's container. Here's a little test program that shows that.
Example 32.4. attributes pertain to container, not parent
<canvas> <class name="constrainedDragState" extends="state"> <attribute name="dragMin" value="100"/> <attribute name="dragMax" value="300"/> <attribute name="mousePos" value="${parent.getMouse( 'x' ) - 10}"/> <attribute name="x" value="${Math.max(Math.min(this.dragMax, this.mousePos), this.dragMin )}"/> </class> <view bgcolor="blue" width="20" height="20" x="100" y="50" onmousedown="myDrag.apply()" onmouseup=" myDrag.remove()"> <attribute name="mouseIsDown" value="false"/> <constrainedDragState name="myDrag"/> </view> </canvas>
Note that the references to dragMin
, dragMax
and such are all qualified by the keyword this
.
Any attribute for which the state has a setter belongs to the state itself,
whereas any attribute that doesn't belongs to the state's apply
target. This makes sense,
because any setter for an attribute which is intended to be applied by the state
(and not kept by the state) can be written in the apply target.
This how it works in LZX. Here's a little program that proves that:
Example 32.5. using setters in <state> attributes
<canvas debug="true"> <class name="testState" extends="state"> <attribute name="countApplies" value="0" setter="this.countApplies = countApplies"/> <method name="apply"> this.setAttribute('countApplies', ++this.countApplies); super.apply(); </method> </class> <button >Try it <handler name="onclick"> ts.apply(); Debug.write("applies:" + ts.countApplies); ts.remove(); </handler> <testState name="ts"/> </button> </canvas>
All states are instantiated. Therefore your application "pays the instantiation cost" of a regular node
of hierarchy for a state, whether or not it is applied. This is because the state is itself an LzNode
,
which must be processed by the LzNode
constructor.
The advantage of limiting the ways in which a developer can modify a state itself is that then a given state can have a limited number of attributes, which are controlled by the runtime. Allowing developers to change the semantics of "state" by attaching attributes and methods to it would make it harder to make this optimization.
Example 32.6. using states to animate transitions
<canvas> <window title="state demo" width="400" height="300"> <state name="max" apply="true"> <animatorgroup duration="1000" process="simultaneous"> <animator attribute="width" to="400"/> <animator attribute="height" to="300"/> <animator attribute="x" to="100"/> <animator attribute="y" to="100"/> </animatorgroup> <text align="center" y="20%">M a x i m i z e d</text> </state> <state name="min" > <animatorgroup duration="1000" process="simultaneous"> <animator attribute="width" to="170"/> <animator attribute="height" to="100"/> <animator attribute="x" to="0"/> <animator attribute="y" to="0"/> </animatorgroup> <text align="center" valign="middle">Minimized</text> </state> <button placement="title_area" align="right" height="16"> Toggle <attribute name="isMax" value="true"/> <handler name="onclick"> if (this.isMax) {parent.max.remove(); parent.min.apply();} else {parent.max.apply(); parent.min.remove();} this.isMax = !this.isMax; </handler> </button> </window> </canvas>
The state element is declared with the apply
, onapply
, and
onremove
attributes, and the <view>
. apply
,
onapply
,
and onremove
control the behavior of the state itself. Any other
attributes and children are applied to the state's parent, when
the state is applied.
If the state is a child of an element that is not a view,
such as a <layout>
or <animator>
, then the view attributes may not
make sense. It is possible to attach an attribute that applies
to one of these other elements to a state, by using
the <attribute>
element.
States can also contain views or classes that are only created when the state is applied.
<state name="showchildren"> <view y="13" x="13" name="children"> <simplelayout/> <treeitem grown="${parent.opened}"> <datapath xpath="*" pooling="true"/> </treeitem> </view> </state>
The <state>
tag does not define classroot. Therefore, its sublasses don't have it either.
If you subclass state you don't get classroot.
<class name="redState" extends="state" />
will not have classroot defined.
You can subclass <state>
to suit whatever purpose is at hand. LZX contains two such subclasses of <state>
to solve common problems.
This example shows how to subclass the <dragstate>
class to create a state functionality that you can apply to
different views.
Example 32.7. Dropshadow done using states
<canvas height="200"> <class extends="dragstate" name="fadeDragger" pooling="true"> <handler name="onapply"> parent.bringToFront(); parent.setAttribute('opacity', .5) </handler> <handler name="onremove"> parent.setAttribute('opacity', 1) </handler> <view name="hShadow" x="10" y="${parent.body.height}" width="${parent.body.width}" height="10" opacity=".2" bgcolor="black"/> <view name="vShadow" x="${parent.body.width}" y="10" width="10" height="${parent.body.height - 10}" opacity=".2" bgcolor="black"/> </class> <view onmousedown="myFadeDragger.apply()" onmouseup="myFadeDragger.remove()"> <fadeDragger name="myFadeDragger"/> <view name="body" width="40" height="40" bgcolor="blue"/> </view> <view x="100" y="100" onmousedown="myFadeDragger.apply()" onmouseup="myFadeDragger.remove()"> <fadeDragger name="myFadeDragger"/> <view name="body" width="60" height="60" bgcolor="red"/> </view> </canvas>
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.