Chapter 32.  States

Table of Contents

1. Overview
2. States vs Procedures
2.1. Attributes of states
2.2. Performance Considerations
2.3. States that are not children of views
2.4. States may contain views or classes
2.5. Classroot not available in <state>
2.6. Dragstate

This chapter discusses the general use of states and the built in states dragstate and resizestate.

1. Overview

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.

2. States vs Procedures

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).

2.1. Attributes of states

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>

2.2. Performance Considerations

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.

2.3. States that are not children of views

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.

2.4. States may contain views or classes

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>

2.5. Classroot not available in <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.

2.6. Dragstate

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>