Table of Contents
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 25, Classes.
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 30.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:
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 26, 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 30.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 30.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>
|
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>
|
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. Members of state subclasses do not define classroot.
Example 30.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 30.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>
|
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 30.6. 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 30.7. 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="<b>bold</b> 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> |
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 30.8. 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>
|
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 30.9. 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>
|
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 }).
Be aware that names for tag classes are not always the same as their JavaScript counterpart. For example, <view> is actually LzView in script, and <node> is actually LzNode. Thus, to instantiate a view through script, you would write new LzView(...) (not new view(...)). Refer to the LZX Reference for details on how LZX tags map to JavaScript.
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.
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 30.10. 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 30.11. 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 30.12. 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>
|
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 30.13. 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 was specified.
Be aware that the defaultplacement attribute should always be declared in an attribute with type="string".
Example 30.14. 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 30.15. 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>
|
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 30.16. 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>
|
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 was placed in. Its value will be the same as parent if no placement was assigned.
Example 30.17. 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>
|
There may be instances a class will need a reference to the default placement node. A good trick is to search the subnodes of the class until it's found.
Example 30.18. 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>
|
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 30.19. 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>
|
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. See Appendix A, Understanding Instantiation.
The construct() method node is called as early as possible in 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 this method is overridden, the superclass method must be called or results will be extremely unpredictable. For instance, this 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() is 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 30.20. 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:
Example 30.21. 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:
a short-hand that refers to the root node of the instance of a class.
the keyword used in a class declaration to create a subclass.
the concept of classes automatically containing the variables and methods defined in their superclass.
the action that creates an instance of a class or object.
using one identifier to refer to multiple functions.
to provide a different method implementation in the subclass from its superclass.
the class that derives from another class.
keyword operator that allows a subclass to invoke it's superclass's method.
the class that a subclass derives its attributes and methods from.
Copyright © 2002-2005 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.