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 events without states
<canvas
width
="100%
">
<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
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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 states to follow mouse
<canvas
width
="100%
">
<view
bgcolor
="blue
" width
="20
" height
="20
" onmousedown
="this.setAttribute('mouseIsDown', true )
" onmouseup
="this.setAttribute('mouseIsDown', false )
">
<attribute
name
="mouseIsDown
" value
="false
"/>
<handler
name
="onmouseIsDown
">
if(mouseIsDown == true){
ds.setAttribute('applied', true);
} else {
ds.setAttribute('applied', false);
}
</handler
>
<state
name
="ds
">
<attribute
name
="x
" value
="${parent.getMouse('x') - 10}
"/>
<attribute
name
="y
" value
="${parent.getMouse('y') - 10}
"/>
</state
>
</view
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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 lz.node.setParent(otherNode)
, 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 to parent
<canvas
width
="100%
">
<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
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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
height
="200
" width
="100%
">
<class
name
="testState
" extends
="state
">
<attribute
name
="countApplies
" type
="number
" value
="0
" setter
="this.countApplies = countApplies
"/>
<handler
name
="onapplied
"><![CDATA[
if(this.applied == true){
this.setAttribute('countApplies', this['countApplies'] >= 0 ? ++this.countApplies : 1);
}
]]>
</handler
>
</class
>
<button
>Try it
<handler
name
="onclick
">
ts.setAttribute('applied', true);
message.addText("\napplies: " + ts.countApplies);
ts.setAttribute('applied', false);
</handler
>
<testState
name
="ts
"/>
</button
>
<text
id
="message
" y
="30
" multiline
="true
"/>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
In states, the "this" keyword refers to the view or node that state is nested inside of. Methods nested inside of a state apply to the view. There is an exception to the above rule: when listening to the onapply or onremove events, the methods apply to the state, and the "this" keyword points to the state.
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 lz.node
,
which must be processed by the lz.node
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 animiate transitions
<canvas
width
="100%
">
<window
title
="state demo
" width
="400
" height
="300
" oninit
="this.max.setAttribute('applied', true)
">
<state
name
="max
">
<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.setAttribute('applied', false); parent.min.setAttribute('applied', true);}
else {parent.max.setAttribute('applied', true); parent.min.setAttribute('applied', false);}
this.isMax = !this.isMax;
</handler
>
</button
>
</window
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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
" width
="100%
">
<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
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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.