Table of Contents
This chapter explains the use of delegates, which are a part of the mechanism that different parts of an OpenLaszlo application use to communicate with each other when values of attributes change.
You generally only need to be aware of delegates for objects that you create in script (that is, not with tags). For objects that you create with tags, for example <view>, you can manage communication using event handlers, as described in Chapter 29, Methods, Events, Handlers, and Attributes. When you use handlers, the OpenLaszlo runtime takes care of delegates for you. Delegates are still there, behind the scenes, but the system manages them invisibly. However, it's helpful to understand the relationship between tag and script-based event syntax.
The last section of this chapter explains how delegates compare to similar things in other programming languages.
The purpose of a delegate is to bind an event at runtime to a particular method. Thus to "register an event with a delegate" simply means to bind that event, temporarily, to the method that is invoked by the delegate. The event stays bound to that method until you "unregister" the delegate. This means, then, that when the event happens the method is executed.
Significantly, you can use delegates even if you don't know, at compile time, the name of the event that will invoke the delegate— the name of the event can be passed as a parameter whose value is generated during the running of the application.
In LZX, delegates and events are defined in terms of each other. A delegate is a named object that calls a method, and an event is an object which stores delegates.
When the event is generated, it calls all of its delegates in turn. A delegate can be considered, then, a named method of an instance.
Delegates that you explicitly create are generally used for objects that are created in script (that is, not by tags), although delegates can be associated with tag-generated objects as well.
Delegates are necessary because sometimes you don't know, at compile time, what a particular event should do. For example,
say you have a window
, and you want a certain method to be called every time the window is clicked. In that case, you would use a tag such as
<window onclick="clickhandler()">
. However, there may exist times when the desired action for the event depends on conditions at the time the event is created.
In that situation you would use delegates.
To create and manage delegates, you use the lz.Delegate
class.
The syntax for creating a delegate is:
lz.Delegate(object, method, sender, event)
The 3rd and 4th optional arguments to lz.Delegate are just shorthand:
var del = new lz.Delegate(object, method, sender, event);
is the same as:
var del = new lz.Delegate(object, method); del.register(sender, event);
The lz.Delegate
class is used to create delegates in script.
That is to say, you can statically attach a handler to an event by:
<handler name="eventName" reference="eventSender"> ...what to do... </handler>
but if you don't know eventName
or eventSender
at compile time, you need to use the dynamic technique:
<method name="methodName" args="value=null"> ...what to do... </method> <method name="createDelegate> this.myDel = new lz.Delegate(this, "methodName", eventSender, "eventName"); </method>
Note | |
---|---|
The method referenced by a delegate must accept an argument. Most times this requirement is fulfilled by adding an optional argument to the method. |
An event handler automatically handles all the work of making and registering a delegate for you:
onclick="do something or other;"
expands to:
<handler name="onclick" > do something or other; </name>
Which expands to:
// make a method with a unique name to run the onclick code <method name="$m<unique id>" args="ignore=null"> do something or other; </method> // make a delegate for that method var del = new lz.Delegate(this, $m<unique id>); // register it to receive onclick events del.register(this, 'onclick');
The following example is a simple memory game. When a button is clicked once, nothing special is reported. If a button is clicked a second time, the green status bar reports a message stating this fact. The game can be started over by clicking the 'start over' button.
Notice that for each button, when the onclick
event is fired, the regbutton()
method is invoked on the canvas. The regbutton()
method creates a delegate for
the buttonclickedagain()
method if no such delegate already exists, and then registers
the 'onclick' event of the button that has just been clicked.
If a button that
hasn't been clicked before is clicked, nothing special is reported (although the delegate is created). However,
when a
previously clicked button is clicked,
the delegate calls buttonclickedagain()
which prints
the message stating this fact. This is because buttons that have already been clicked have had their 'onclick' events 'registered
with' (or bound to) the delegate.
Clicking on the 'start over' button unregisters all events from the delegate, and resets the message to nothing special.
Example 30.1. Registering an event at runtime
<canvas
height
="100
" width
="100%
">
<view
width
="80
">
<button
width
="20
" height
="20
" onclick
="canvas.regbutton(this)
" onmousedown
="canvas.reset()
"/>
<button
width
="20
" height
="20
" onclick
="canvas.regbutton(this)
" onmousedown
="canvas.reset()
"/>
<button
width
="20
" height
="20
" onclick
="canvas.regbutton(this)
" onmousedown
="canvas.reset()
"/>
<button
width
="20
" height
="20
" onclick
="canvas.regbutton(this)
" onmousedown
="canvas.reset()
"/>
<button
width
="20
" height
="20
" onclick
="canvas.regbutton(this)
" onmousedown
="canvas.reset()
"/>
<button
width
="20
" height
="20
" onclick
="canvas.regbutton(this)
" onmousedown
="canvas.reset()
"/>
<button
width
="20
" height
="20
" onclick
="canvas.regbutton(this)
" onmousedown
="canvas.reset()
"/>
<button
width
="20
" height
="20
" onclick
="canvas.regbutton(this)
" onmousedown
="canvas.reset()
"/>
<button
width
="20
" height
="20
" onclick
="canvas.regbutton(this)
" onmousedown
="canvas.reset()
"/>
<wrappinglayout
/>
</view
>
<text
id
="statusText
" bgcolor
="green
" resize
="true
"/>
<button
text
="start over
" onclick
="canvas.startover()
"/>
<simplelayout
axis
="y
"/>
<method
name
="regbutton
" args
="b
">
if( typeof this.del == "undefined" ) {
this.del = new lz.Delegate( this, "buttonclickedagain" );
}
this.del.register( b, "onclick" );
</method
>
<method
name
="startover
">
this.del.unregisterAll();
this.reset();
</method
>
<method
name
="reset
">
statusText.setAttribute("text", "Nothing special");
</method
>
<method
name
="buttonclickedagain
" args
="ignore=null
">
statusText.setAttribute("text", "That button was clicked two or more times");
</method
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008, 2010 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
In the past, it was necessary to manually track delegates and manually clean them up when objects were destroyed to prevent memory leaks. This is no longer required - the event system now automatically cleans up delegates as needed.
In the following example, a visual timer is created which shows each passing
second. When the <text>
node is initialized, we call the
updateTimer()
method. updateTimer()
first updates the text showing to indicate
the number of seconds passed, and then increments the secondsPassed attribute.
The first time updateTimer()
is invoked, a new delegate is created which simply
calls updateTimer()
, and a timer is created which in turn calls the newly
created delegate after 1 second. Each subsequent time updateTimer()
is invoked
by the delegate, instead of creating a new timer (which would be inefficient) , lz.Timer.resetTimer()
is invoked.
Example 30.2. Using lz.Timer to create a second timer
<canvas
height
="20
" width
="100%
">
<text
oninit
="updateTimer()
">
<attribute
name
="secondsPassed
" type
="number
" value
="0
"/>
<method
name
="updateTimer
" args
="time=null
">
this.setAttribute('text', this.secondsPassed );
this.secondsPassed++;
if( typeof this.del == "undefined" ) {
this.del = new lz.Delegate( this, "updateTimer" );
lz.Timer.addTimer( this.del, 1000 );
} else {
lz.Timer.resetTimer( this.del, 1000 );
}
</method
>
</text
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008, 2010 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
In the following example, when the last <text>
object has been
replicated and initialized, we set the text in the green <text>
to 'The last
view has been replicated'. To accomplish this task, we first explicitly declare
a datapath on the <text>
to be replicated. Next, we handle
the onclones event, which indicates that the new <text>
nodes
has been cloned, but not initialized. Inside the onclones event, we determine the
last replicated clone, and register the oninit event using lz.Delegate
. When the
last <text>
node has been initialized, the delegate calls the
"replicationComplete" method, where the text of the green <text>
node is updated.
This technique can also be used to detect when the last view has loaded its
resource by simply registering the onload
event instead of the oninit
event.
Example 30.3. Registering for an event when the last view has been replicated
<canvas
height
="150
" width
="100%
">
<dataset
name
="people
">
<people
>
<person
name
="John
"/>
<person
name
="Eric
"/>
<person
name
="Andrew
"/>
<person
name
="chrisk
"/>
<person
name
="Sarah
"/>
<person
name
="Pablo
"/>
<person
name
="Adam
"/>
</people
>
</dataset
>
<view
>
<text
>
<datapath
xpath
="people:/people/person/@name
">
<handler
name
="onclones
">
var lastclone = this.clones[ this.clones.length - 1 ];
if( typeof this.del == "undefined" ) {
this.del = new lz.Delegate( this, "replicationComplete")
} else {
this.del.unregisterAll();
}
this.del.register( lastclone, "oninit");
</handler
>
<method
name
="replicationComplete
" args
="ignore=null
">
lastRepText.setAttribute("text", "The last view has been replicated");
</method
>
</datapath
>
</text
>
<simplelayout
axis
="y
"/>
</view
>
<text
id
="lastRepText
" bgcolor
="green
" resize
="true
">
Replication incomplete
</text
>
<simplelayout
axis
="y
"/>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008, 2010 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
A delegate is an object, and as such it can be an attribute of another object. For example, consider:
Example 30.4. delegate as attribute
<button text="clickme"> <attribute name="mydel" type="expression" value="$once{new lz.Delegate(this, 'handler')}"
The above code creates the delegate mydel
when the button is instantiated. This technique of declaring a delegate has the advantage of being easy to read, and it ensures
that only one delegate is created.
For an example that uses this technique, see Chapter 45, OpenLaszlo RPC.
Here is a brief discussion of delegates in relation to other programming languages with which you may be familiar. You don't need to follow this discussion in order to use delegates in LZX.
A delegate is a 'method pointer'. It's a way of saying: "give me a thing that when I call it will call method m on object o." (As opposed to a function pointer, which doesn't capture the object.) In Java, you use an 'anonymous inner class' to get this effect (i.e., Java creates an instance with a function). In Lisp, you use a closure (i.e., Lisp creates a function with a context). [The Java and Lisp ways are just two sides of the same coin.]
Basically:
// constructor lz.Delegate = function(o, m) { this.o = o; this.m = m; } lz.Delegate.prototype.execute = function () { with (this) { return o[m](); } }; var del = new lz.Delegate(object, 'method'); del.execute();
is the same as:
// constructor function makeDelegate (o, m) { return function () { return o[m](); }; }; var del = makeDelegate(object, 'method'); del();
Here you can see the parallel between allocating an instance that captures the context 'o' and 'm' and has a function (method) to invoke it, and a function that you can invoke that captures the context 'o' and 'm' by being a closure.
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.