Chapter 30. Delegates

Table of Contents

1. Overview
1.1. lz.Delegate
1.2. Relationship between delegates and tag-generated events
1.3. Example: using lz.Delegate to register an event at runtime
2. Delegates and Memory Management
3. Using a lz.Timer to show the passage of time
4. Using lz.Delegate to determine initialization of the last replicated view
5. Delegates as attributes
6. Delegates in relation to other languages

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.

1. Overview

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.

1.1. lz.Delegate

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

1.2. Relationship between delegates and tag-generated events

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] 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 example below illustrates lz.Delegate in use.

1.3.  Example: using lz.Delegate to register an event at runtime

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.

1.3.1. Flow of control

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>

2. Delegates and Memory Management

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.

3. Using a lz.Timer to show the passage of time

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>

4.  Using lz.Delegate to determine initialization of the last replicated view

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>

5. Delegates as attributes

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.

6. Delegates in relation to other languages

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.