Chapter 33. Extending Classes

Table of Contents

1. Overview
2. Overriding methods, handlers and attributes
2.1. Handlers cannot be overridden
3. Classroot
3.1. Classroot not available in <state>
4. Declared text
5. Font inheritance
6. Instantiating classes through script
6.1. Be careful of mixing replication and classes declared procedurally
7. Class Names and Tag Names
7.1. Mapping Class and Tag Names
8. Using LZX classes
8.1. Basic approach to implementing classes
8.2. Inherited views
8.3. Placement
8.4. Construction and initialization
9. Glossary

This section assumes you're familiar with basic LZX concepts such as views, methods, and attributes. Familiarity with objected-oriented programming (OOP) concepts is helpful, but not required. For a primer on classes in LZX see Chapter 28, Classes.

1. Overview

Inheritance allows you to create custom classes from other predefined classes. The advantage is that much of the logic doesn't have to be rewritten every time you want to create a class that does something similar, but in a slightly different way.

A subclass is a class derived from another class. The class from which it is derived is referred to as its superclass. A subclass is said to inherit methods and attributes from its superclass. Visual elements of a superclass, such as <view> , are also inherited by each subclass. In LZX, you use the extends attribute to the <class> tag to create subclasses.

<class name="myclass" extends="mysuperclass">
   ...
</class>

Any class that is that you create without using the extends attribute is assumed to be a subclass of <view>. All methods and properties from <view> are inherited by the subclass. These two declarations are equivalent:

<class name="myclass"/>
<class name="myclass" extends="view"/>

You can only extend one class per class declaration, though the inheritance chain can be arbitrarily deep. Multiple inheritance is not supported; that is to say you cannot create a new class that extends more than one existing classes. Methods, handlers, attributes, and views inside a class are inherited down through each level.

Example 33.1. Inheritance chain

<canvas debug="true" height="125">
  <debug x="80" y="5" width="170" height="112"/>

  <class name="top">
    <attribute name="myfoo" value="bar" type="string"/>
  </class>
  
  <class name="middle" extends="top">
    <method name="doit">
      Debug.write("myfoo is " + this.myfoo);
    </method>
  </class>
  
  <class name="bottom" extends="middle">
    <button text="clickme" onclick="parent.doit()"/>
  </class>
  
  <bottom/>
</canvas>

Class definitions can include default values for attributes. For example, a class that extends view can have default width and height attributes:

<class name="myClass" width="50" height="50"/> 1
<myClass/> 2
<myClass width="100"/> 3
1

Defines a class and tag named myClass

2

Creates an instance of myClass with height and width of 50

3

Creates an instance of myClass with height 50 and width 100

2. Overriding methods, handlers and attributes

A subclass can override, that is, provide a different implementation from, a superclass method, but not for a handler. The overriding method can specify a different set of arguments from the superclass's method. For example, if your superclass's method has two arguments such as method(arg1, arg2), you can choose to have a third argument for your subclass such as method(arg1, arg2, arg3).

LZX does not support overloading methods. That means that you cannot have different methods, within one class, that have the same name. The runtime only "cares about" the name of the calling method. You will receive a compilation warning if you define two or more methods with the same name in a class definition.

You can, however, have more than one handler for an event, as described in Chapter 29, Methods, Events, Handlers, and Attributes

You can modify inherited attributes by defining a new value using the <attribute> tag or by declaring it as an attribute in the <class> declaration itself.

Example 33.2. Inheriting Properties

<canvas height="100">
  <!-- create a simplelayout so embedded views are laid out  -->
  <!-- on top of each other.                                 -->
  <class name="class1" layout="class: simplelayout; spacing: 2">
    <attribute name="label" value="class1 label" type="string"/>
    <text>from class1</text>
    <button text="${classroot.label}"/>
  </class>

  <!-- overrides class1's label; inherits text and button -->
  <class name="class2" extends="class1" label="class2 label">
    <text>from class2</text>
  </class>

  <!-- inherit class1's text and button; inherit class2's text -->
  <class name="class3" extends="class2">
    <text>from class3</text>
  </class>
  <class3/>
</canvas>

You can use the super keyword to invoke a superclass's method. The super keyword is useful in instances where you want to extend the superclass's method without rewriting the same logic. A method can only use super to call the method that it overrides. That is, a subclass's myfunc() can only call super.myfunc(), super.myotherfunc().

Example 33.3. The super keyword

<canvas debug="true" height="140">
  <debug x="60"/>

  <class name="foo">
    <method name="talk">
      Debug.write("hello");
    </method>
    <button text="click" onclick="parent.talk()"/>
  </class>

  <class name="bar" extends="foo">
    <method name="talk">
      super.talk();
      Debug.write("goodbye");
    </method>
  </class>

  <bar/>
</canvas>

2.1. Handlers cannot be overridden

In order to override the behavior of an event handler, you would have the handler call a method, and then override the method in the subclass or instance. For example, let's say that you wanted to write a handler for the onclick event that you could override in an instance. In your class definition, you would use this syntax:

    <handler name="onclick" method="handleClick">

Then in the instance you would define handleClick().

Remember, you can call the original click code in the new method by using the super method:

     <method name="handleClick">
         super.handleClick();
         Debug.write('new click');
     </method>

3. Classroot

The classroot property is a convenient short-hand that refers to the root node of a class instance. It's often used by a deeply nested view or method that needs access to something near the root of the class. Though you can equivalently use parent, parent.parent, parent.parent.parent, etc. (depending on how deep the view that contains the reference is nested), using classroot is usually more readable.

Example 33.4. Classroot example

<canvas debug="true" height="160">
  <debug x="155" y="10"/>
    
  <!-- class deep -->
  <class name="deep">
    <attribute name="mytext" value="hello, world" type="string"/>
    
    <view bgcolor="red" width="150" height="150">
      <view bgcolor="green" width="75%" height="75%">
        <button text="clickme" width="75%" height="75%">
          <!-- classroot is a convenient way to access mytext -->
          <handler name="onclick">
            Debug.write("classroot.mytext: " + classroot.mytext);
            Debug.write("parent.parent.parent.mytext: " + 
                        parent.parent.parent.mytext);
          </handler>
        </button>
      </view>
    </view>
  </class>
  
  <!-- instance of class deep -->
  <deep/>
</canvas>

Be careful when using classroot from the root of the class. If there is no surrounding class, classroot will be undefined. Use the this keyword in code attached to the root of the class. On the other hand, if an instance of a class appears inside another class, the classroot for the instance will be the root of the surrounding class. Use this feature as a short-cut to refer to the root of outer classes.

Example 33.5. Referring to outer class's root using classroot

<canvas debug="true" height="200">
  <debug height="175"/>
  
  <class name="foo">
    <method name="doit">
      Debug.write("foo: this is [" + this + "]");
      Debug.write("foo: classroot is [" + classroot + "]");
      Debug.write("foo: classroot.classroot is [" + classroot.classroot + "]");
    </method>
  </class>
  
  <!-- boo contains a foo -->
  <class name="boo">
    <foo name="myfoo"/>
  </class>
  
  <!-- goo contains a boo -->
  <class name="goo">
    <boo name="myboo"/>
    <handler name="oninit">
      myboo.myfoo.doit();
      Debug.write("-----");
      Debug.write("goo: this is [" + this + "]");
      // warning will be displayed -- there is no classroot
      Debug.write("goo: classroot is [" + classroot + "]");
    </handler>
  </class>
  
  <!-- Make an instance of goo -->
  <goo name="mygoo"/>
</canvas>

At the risk of belaboring the topic, here's one more example that demostrates that top level instances of a class have no defined classroot. Notice that even though "bar" is a child of "foo", it does not have a defined classroot, because it is a toplevel instance of the bar class.

Example 33.6. classroots and toplevel instances

<canvas debug="true">
<class name="myclass" height="40" bgcolor="blue">
  <attribute name="button_label" type="text" value="button"/>
  <handler name="onclick">
    this.handleclick()
  </handler>

  <method name="handleclick">
    Debug.write("this = " + this + "and classroot is" + classroot);
  </method>
  <button name="b1" height="30" text="${parent.button_label}"
onclick="Debug.write('classroot of this button is: ' + classroot); parent.handleclick()" />
</class>

<class name="another_class" extends="myclass"/>

<!-- foo and bar views are top-level instances, so their classroots are null.
== The buttons are children of the foo and bar views, respectively, so ==
== their classroots are defined. -->

<myclass id="foo">
  <another_class id="bar" bgcolor="red" button_label ="another button" y="25">
     <method name="handleclick">
       Debug.write("this = " + this + "and classroot is " + classroot);
     </method>
 </another_class>
</myclass>
</canvas>

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

4. Declared text

The <text> and <inputtext> tags are unique among the built in classes in that they can hold textual content:

<canvas height="50" layout="y">
  <inputtext>plain text</inputtext>
  <text><i>styled</i> text</text>
</canvas>

The text may contain character text, as well as certain XHTML markup tags. The inputtext tag may contain character text.

Classes that extend the text and inputtext classes may contain XHTML and plain text, respectively. The text content is available as the text attribute of such a class.

Example 33.7. Extending text classes

<canvas height="50" layout="axis: y">
  <class name="mytext" extends="text"/>
  <class name="myinputtext" extends="inputtext"/>
  <myinputtext>plain text</myinputtext>
  <mytext><i>styled</i> text</mytext>
</canvas>

A user-defined class can also handle text content by defining an attribute named text with a value of text (for plain text) or html (for XHTML text), like <attribute name="text" type="text">. There is another text type available called string which allows you set a text string as an attribute, but does not allow text content.

Consider these two classes, where the first is defined using type="string" and the second with type="text":

<class name="stringText">
  <attribute name="text" type="string"/>
</class>

<class name="textText">
  <attribute name="text" type="text"/>
</class>

Both classes can be used with a text attribute:

<stringText text="some text"/>
<textText text="some text"/>

Only textText can be used with text content. The use of stringText below is invalid, and will produce a compilation warning.

<stringText>some text</stringText> <!-- This is invalid -->
<textText>some text</textText>     <!-- This is valid -->

Using the html type declares that the class can accept html text content, or set through the text attribute. The text may contain XHTML tags such as <b> and <a> , as in the following program:

Example 33.8. Text type: html

<canvas height="50">
  <class name="htmlText">
    <attribute name="text" type="html"/>
    <text resize="true" text="${parent.text}"/>
  </class>
  
  <simplelayout/>
  <htmlText>
    <b>bold</b> text declared here with 
    <a href="http://www.openlaszlo.org"><i>anchor</i></a>
  </htmlText>
  <htmlText text="&lt;b&gt;bold&lt;/b&gt; text set here"/>
</canvas>

Note that XHTML markup within a class that is declared with type="text", instead of type="html", is invalid:

<textText>some text</textText> <!-- valid -->
<textText>some <i>fancy</i> text</textText> <!-- invalid -->
<htmlText>some text</htmlText> <!-- valid -->
<htmlText>some <i>fancy</i> text</htmlText> <!-- valid -->

Any text content with a class that is declared without a text attribute whose type is text or html is invalid:

<class name="noText"/>
<noText>some text</noText> <!-- invalid -->
<noText>some <i>fancy</i> text</noText> <!-- invalid -->

The attribute name must be text. It is an error to use text and html as the type of any other attribute:

<class name="invalidClass">
 <attribute name="label" type="text"/> <!-- invalid -->
</class>

5. Font inheritance

Classes and views enclosed in classes inherit their font and font properties (such as fontstyle and fontsize) from their superclass. Also, any class instance will inherit its font from its enclosing view. A font can be overridden at any point in a view hierarchy and any subview from there on will inherit that font.

Example 33.9. Inheriting fonts

<canvas height="50">
  <font src="helmetr.ttf" name="Helvetica"/>
  <font src="helmetb.ttf" name="Helvetica" style="bold"/>
  
  <class name="foo">
    <!-- view overrides inherited fontstyle to plain -->
    <view fontstyle="plain" bgcolor="yellow">
      <!-- text overrides inherited fontsize to 12 -->
      <text fontsize="12">hello</text>
    </view>
  </class>
  
  <class name="bar" extends="foo" layout="axis: y">
    <text>goodbye</text>
  </class>
  
  <bar font="Helvetica" fontstyle="bold" fontsize="12"/>
</canvas>

6. Instantiating classes through script

In general, instantiation of objects happen using tags. For instance, assuming <class name="myclass"> is declared, you can create an instance of that class by writing <myclass/>. However, there may be times when you will need to instantiate an object using script. The script instantiation syntax for classes looks like:

var myobject = new myclass(parent, attributes, children, instcall)

where:

  • parent is where your object will be placed in the node hierarchy. If it doesn't matter, then you can pass null. If you are creating a subclass of view and the parent is null, the canvas will be the parent of this object.

  • attributes is a hash of attribute values that get passed into the object. For example, if you wanted to instantiate a new view with a different bgcolor, width, and height, you could pass in {bgcolor: 0xff0000, width: 50, height: 50}.

  • children is the array of child views this object encapsulates. The OpenLaszlo Runtime instantiator is responsible for passing in the children of this object based on the LZX hierarchy. You will generally set this to null.

  • instcall is a boolean value that determines when this object will be instantiated. If false, the instantiation of this object will be immediate, otherwise, its instantiation will be synchronized with the rest of the view system. See Appendix A, Understanding Instantiation.

All these parameters are optional. Not setting any of these arguments (e.g., new myclass()) is equivalent to new myclass(null, null, null, 0).

The following example shows you how to instantiate a new object through script and add it to another view.

Example 33.10. Script instantiation

<canvas height="120">
  <class name="mybox">
    <view bgcolor="${parent.bgcolor}" width="50" height="50"/>
  </class>
  
  <view name="redbox" bgcolor="red" width="100" height="100"/>
  
  <!-- Create new mybox with cyan bgcolor and place it in canvas.redbox. -->
  <button x="110" text="add cyan"
            onclick="if (canvas.redbox['cyan'] == null) 
                       new mybox(canvas.redbox, { name: 'cyan', bgcolor: 0x00ffff })"/>

  <!-- Remove cyan view from redbox. -->
  <button x="110" y="30" text="remove cyan"
            onclick="if (canvas.redbox['cyan'] != null) canvas.redbox.cyan.destroy()"/>

</canvas>

6.1. Be careful of mixing replication and classes declared procedurally

Views that you create procedurally are not the same as "clones" created by data replication. In fact, data replication overrides procedurally created views. For example:

  1. Declare a view.

  2. Add subviews to it (procedurally), and alter its properties.

  3. Set a datapath on the view (from step 1) that would make it replicate.

Changes made in step 2 will be ignored after replication.

7. Class Names and Tag Names

All classes are defined in the global namespace. If you wanted to instantiate a class dynamically, you can write new global[classString](...). Using the previous example, mybox can be instantiated like new global['mybox'](canvas.redbox, { bgcolor: 0x00ffff }).

7.1. Mapping Class and Tag Names

Be aware that names for tag classes are not the same as their JavaScript counterpart; rather, the JavaScript name is lz.[tagname]. For example, to access a <view> in script, you would use lz.view. Thus, to instantiate a view through script, you would write new lz.view(...) (not new view(...)).

[Note] Note

In earlier versions of OpenLaszlo, there was an idosyncratic mapping between class names and tag names, and classes in the LFC were handled differently than user devfined classes. For example, the tagname <view> mapped to JavaScript class LzView. With OpenLaszlo 4.0, the lz.[tagname] mapping is universal, and the old mappings are deprecated.

8. Using LZX classes

8.1. Basic approach to implementing classes

An approach you can take to writing classes is just to declare the class tags and have an instance of that class on the canvas. This will give you the framework to see what it looks like while you're building up your class, for example:

<canvas>
  <class name="myclass">
    ...
  </class>
  
  <myclass/>
</canvas>

Optionally, you can sketch out a class by writing a view first and then transforming it into a class. The drawbacks of this approach are that you can't use the classroot keyword and attributes can't be declared.

8.2. Inherited views

Top-level views inherited from a superclass are placed in the top-level of a subclass. The inherited views from the superclass will be placed first in order. This can be verified by examining the subviews array.

Example 33.11. Inheriting views

<canvas debug="true">
  <debug y="215" width="435"/>
  
  <class name="one">
    <view name="r" bgcolor="red" width="200" height="200"/>
  </class>
  
  <class name="two" extends="one">
    <view name="g" bgcolor="green" width="100" height="100"/>
  </class>
  
  <class name="three" extends="two">
    <view name="t" bgcolor="teal" width="50" height="50"/>
    <view name="y" bgcolor="yellow" width="25" height="25"/>
  </class>
  
  <three id="mysubclass" oninit="Debug.write('subviews: ' + this.subviews)"/>
</canvas>

Views declared in an instance of a class will be placed in the top-level of the class unless otherwise declared with the defaultplacement attribute. Those views will be placed with a later order in the subviews array. The defaultplacement attribute tells a class where declared views should be placed in the hierarchy of the class and is explained in more detail in the next section.

Example 33.12. Inherited view order

<canvas debug="true" height="250">
  <debug y="115" width="330"/>
  <class name="foo">
    <view name="r" bgcolor="red" width="100" height="100"/>
  </class>
  
  <foo name="myfoo" oninit="Debug.write('subviews: ' + this.subviews)">
    <view name="y" bgcolor="yellow" width="50" height="50"/>
  </foo>
</canvas>

Notice how view y follows view r in its subviews array. If a <simplelayout> is placed in view myfoo, they will be displayed in order of r followed by y.

Example 33.13. Inherited view order with simplelayout

<canvas debug="true" height="250">
  <debug x="75" y="115" width="330"/>
  
  <class name="foo">
    <view name="r" bgcolor="red" width="100" height="100"/>
  </class>
  
  <foo name="myfoo" oninit="Debug.write('subviews: ' + this.subviews)">
    <simplelayout/>
    <view name="y" bgcolor="yellow" width="50" height="50"/>
  </foo>
</canvas>

8.3. Placement

The internal structure of a class is generally not visible to its hierarchical children. By default, instances which appear inside a class are made children of the top level instance of the class. This is generally not desirable for container classes. For example:

Example 33.14. Undesired placement

<canvas height="50">
  <class name="myframe" extends="view">
    <attribute name="bgcolor" value="red"/>
    <view x="5" y="5" width="${parent.width-10}"
               height="${parent.height-10}"
               bgcolor="#FFFFCC"/>
  </class>
  
  <!-- make an instance of myframe with text inside it-->
  <myframe width="220" height="20">
    <text>This is some text</text>
  </myframe>
</canvas>

This behavior can be changed using the defaultplacement attribute or the determinePlacement() method. Using defaultplacement is simple — this is a class attribute that identifies by name the subview where a child should be attached. The child will be attached to the first subview with a matching name. If none is found, the child is placed as a subview in the top-level node of the class, as would have happened if no defaultplacement had been specified.

Be aware that the defaultplacement attribute should always be declared in an attribute with type="string".

Example 33.15. Placing a child in desired subview

<canvas height="50">
  <class name="myframe" extends="view">
    <attribute name="bgcolor" value="red"/>
    
    <!-- child views of class instances will be placed in the first view
            called insideview -->
    <attribute name="defaultplacement" value="insideview" type="string"/>
    
    <view x="5" y="5" width="${parent.width-10}" name="insideview"
                height="${parent.height-10}" 
                bgcolor="#FFFFCC"/>
  </class>
  <!-- make an instance of myframe with text inside it-->
  <myframe width="220" height="50">
    <text>This is some text</text>
  </myframe>
</canvas>

Elements declared in a class are not considered for placement, but children in subclasses or class instances will be.

Example 33.16. Defaultplacement

<canvas height="150">
  <class name="myframe" extends="view">
      <attribute name="bgcolor" value="red"/>

      <attribute name="defaultplacement" value="'insideview'"/>

      <view x="5" y="5" width="${parent.width-10}" name="insideview"
              height="${parent.height-10}" 
              bgcolor="#FFFFCC"/>

      <!-- this view is not affected by defaultplacement -->
      <!-- because it's declared in the class.           --> 
      <view x="5" y="${parent.height}" name="anotherview" 
            width="${parent.width-10}" height="10"
            bgcolor="blue"/>
  </class>

  <class name="subframe" extends="myframe">
    <!-- the layout and text will be placed in insideview of myframe -->
    <simplelayout axis="y"/>
    <text bgcolor="teal">subframe text</text>
  </class>


  <myframe width="220" height="50">
    <!-- this will be placed in insideview -->
    <text>This is some text</text>
  </myframe>

  <subframe width="220" height="50" y="70">
    <text bgcolor="green">More subframe text</text>
  </subframe>
</canvas>

8.3.1. Placing layouts

A layout declared as an attribute will be considered for placement. This is often the desired behavior because it makes it easy for subclasses and class instances to modify the layout for views inside the default placement. To override this behavior, a layout attribute can be set with a non-existing placement (e.g., placement: null). The placement attribute tells an element's container where it should go within the container's internal hierarchy. If the container has a defaultplacement, the placement value has precedence. Alternatively, you can make sure that a layout isn't handled by defaultplacement by declaring it as a tag element in the class.

Example 33.17. Layout placement

<canvas>
  <!-- the layout attribute will be placed in the red view -->
  <class name="myplacement" defaultplacement="'red'" layout="axis: x; spacing: 5">
    <!-- this layout element applies to views inside of class -->
    <simplelayout spacing="10"/>
    <view name="red" bgcolor="red" width="150" height="150"/>
    <view name="yellow" bgcolor="yellow" width="150" height="150"/>
  </class>

  <myplacement>
    <!-- placement overrides defaultplacement -->
    <view name="blue" bgcolor="blue" width="50" height="50" placement="yellow"/>

    <!-- green and teal will be placed in red -->
    <view name="green" width="50" height="50" bgcolor="green"/>
    <view name="teal" width="50" height="50" bgcolor="teal"/>
  </myplacement>

</canvas>

8.3.2. ImmediateParent

A child placed using defaultplacement or placement will often have two parent types. The parent is the reference to the node that was passed as the child's ancestor in the constructor, e.g., new childClass(parent, args). If the child was created by declaring it in a tag, the parent will be its lexical parent. (Its lexical parent is the tag that encloses it.)

The immediateparent refers to the node where the child is actually placed at runtime. Its value will be the same as parent if no value is assigned to the placement attribute.

Example 33.18. Parent vs. immediateparent

<canvas debug="true" height="200">    
  <debug x="155"/>

  <class name="container" defaultplacement="'red'">
    <view name="red" bgcolor="red" width="150" height="150"/>
  </class>

  <!-- yellow's parent is top and its immediateparent   -->
  <!-- is red, since that's where it's actually placed. -->
  <container name="top">
    <view name="yellow" bgcolor="yellow" width="50" height="50">
      <handler name="oninit">
        Debug.write('parent: ', this.parent);
        Debug.write('immediateparent: ', this.immediateparent);
      </handler>
    </view>
  </container>
</canvas>

8.3.3. Obtaining a reference to the defaultPlacement node

There may be instances a class needs a reference to the default placement node. A good trick is to search the subnodes of the class until it's found.

Example 33.19. Obtaining a reference to the defaultPlacement node

<canvas debug="true" height="200">
  <debug x="155"/>

  <class name="container" defaultplacement="'red'">
    <attribute name="contentview" value="null" type="expression"/>

    <method name="init">
      super.init();

      // get a reference to the content node
      if ( this.contentview == null ) {
        if ( this.defaultplacement != null ){
          this.contentview = this.searchSubnodes( "name" , this.defaultplacement );
        } else {
          this.contentview = this;
        }
      }
      
      Debug.write("content view", this.contentview);
    </method>

    <view name="green" bgcolor="green" width="100" height="100">
      <view name="yellow" bgcolor="yellow" width="50%" height="50%">
        <view name="red" bgcolor="red" width="50%" height="50%"/>
      </view>
    </view>
  </class>

  <container name="top"/>
</canvas>

8.3.4. Overriding placement behavior using determinePlacement()

A node calls its determinePlacement() method to determine the immediateparent of a child. This method will only be called for subnodes which have a placement attribute, or for all subnodes if this node has a non-null defaultplacement. The placement attribute of a subnode overrides a parent's defaultplacement. This method looks for a subnode with the name given in the placement parameter, and returns that node. If no such named node exists, it returns this. The code essentially looks like:

<!-- subnode: the child node to place         -->
<!-- placement: the node to place the subnode -->
<!-- args: init args of the child subnode     -->
<method name="determinePlacement" args="subnode, placement, args">
  // ignore placement if set
  if ( args.ignoreplacement ){
      return this;
  }

  if ( placement == null ){
    var p = null;
  } else {
    var p = this.searchSubnodes( "name" , placement );
  }

  return p == null ? this : p;
</method>

A subclass might implement this method to cause the placement parameter to have a different behavior or additional effects. For instance, a subnode could have parent and immediateparent be the same.

Example 33.20. Overriding determinePlacement

<canvas debug="true" height="200">
  <debug x="155"/>

  <class name="container" defaultplacement="'red'">
    <!-- setting subnode's parent to be the same as immediateparent -->
    <method name="determinePlacement" args="subnode, place, args">
      var p = super.determinePlacement(subnode, place, args);
      subnode.parent = p;
      return p;
    </method>

    <view name="blue" bgcolor="blue" width="100" height="100">
      <view name="red" bgcolor="red" width="150" height="150"/>
    </view>
  </class>

  <container name="top">
    <view name="yellow" bgcolor="yellow" width="50" height="50">
      <handler name="oninit">
        Debug.write('parent: ', this.parent);
        Debug.write('immediateparent: ', this.immediateparent);
      </handler>
    </view>
  </container>
</canvas>

8.4. Construction and initialization

When writing complex classes, a deep understanding of how classes are constructed and initialized is essential. Several steps are involved before a class instance is fully initialized. Describing this process is out of the scope of this chapter, but important methods and events that are involved, and the sequence in which they are invoked, will be discussed here. See Appendix A, Understanding Instantiation for a more in-depth discussion.

The construct() method is called as early as possible in constructing the view system. It is invoked before any arguments have been applied. This is the method to override in lieu of writing a class constructor for your LZX class. If you do override the construct method, be sure to invoke, the superclass method, or results will be extremely unpredictable. Among other things, the construct method is the method where the class being constructed places itself in its container by calling its parent's determinePlacement() method. If the superclass construct method isn't called, the class instance may not be placed correctly.

Alternatively, you can use the onconstruct event, which is sent out by the instantiator after construct() has been called. The onconstruct happens right at the end of the instantiation process, but before any subnodes have been created or references resolved.

Keep in mind that construction happens top-down (parent to subviews), whereas initialization happens bottom-up (subviews to ancestor).

Example 33.21. Construction and initialization

<canvas debug="true" height="180">
  <debug height="160"/>

  <class name="container">
    <!-- Don't forget to call super.construct(parent,args)!! -->
    <method name="construct" args="parent,args">
      Debug.write("container construct", parent, args);
      super.construct(parent, args);
    </method>

    <!-- The onconstruct event -->
    <handler name="onconstruct" args="v">
      Debug.write("container onconstruct", v);
    </handler>

    <method name="init">
      Debug.write("container init");
    </method>

    <handler name="oninit">
      Debug.write("container oninit");
    </handler>
  </class>

  <container>
    <view name="outside" oninit="Debug.write('outside oninit')">
      <view name="inside" oninit="Debug.write('inside oninit')"/>
    </view>        
  </container>
</canvas>

Following instantiation (i.e., after the onconstruct is sent) and if there are child nodes, the createChildren() method is called. This method takes an array of child objects as a parameter. Each child object has three properties:

name

The class name of the child to instantiate

attrs

The attributes to initialize the child with, e.g. name, height

children

An array of child objects. This can be undefined if none exist.

Example 33.22. createChildren()

<canvas debug="true" height="180">
  <debug height="160"/>

  <class name="container">
    <handler name="onconstruct" args="v">
      Debug.write("container onconstruct", v);
    </handler>

    <method name="createChildren" args="c">
      Debug.write("container createChildren", c);
      Debug.write("    c[0].name:", c[0].name);
      Debug.write("    c[0].attrs:", c[0].attrs);
      Debug.write("    c[0].children:", c[0].children);
      super.createChildren(c);
    </method>
  </class>

  <container>
   <view name="outside">
     <view name="inside"/>
   </view>
  </container>
</canvas>

In summary, you can expect the basic timing order of method and event calls to look like:

  1. The construct() method is called.

  2. The determinePlacement() method is called. (The parent's method is called so parent knows where to place this object.)

  3. The onconstruct event is sent.

  4. The createChildren() is called. (Each child is instantiated and initialized)

  5. The init() is called.

  6. The oninit is called.

9. Glossary

classroot

a short-hand that refers to the root node of the instance of a class.

extends

the keyword used in a class declaration to create a subclass.

inheritance

the concept of classes automatically containing the variables and methods defined in their superclass.

instantiation

the action that creates an instance of a class or object.

overloading

using one identifier to refer to multiple functions.

overriding

to provide a different method implementation in the subclass from its superclass.

subclass

the class that derives from another class.

super

keyword operator that allows a subclass to invoke it's superclass's method.

superclass

the class that a subclass derives its attributes and methods from.