Chapter 45. OpenLaszlo RPC

Table of Contents

1. Overview
2. <rpc>
2.1. Attributes
2.2. Events
2.3. Methods
3. Declaring rpc
4. Proxy
4.1. RPC call return object
4.2. Generating lz.DataElement from return values
4.3. Using dataobject to map return values
5. <remotecall>
5.1. Attributes
5.2. Events
5.3. Methods
5.4. Using remotecall
[Warning] Warning

Remote procedure calls work in proxied applications only. They do not work in SOLO applications.

1. Overview

OpenLaszlo RPC, or just RPC, is the general term used to define the implementation and APIs that invoke remote procedures calls or services over the network. Current RPC implementations are <javarpc>, and <soap>.

A related class, XMLHTTPRequest (also called "AJAX API"), is described in Chapter 36, Data, XML, and XPath.

<javarpc> allows server-side Java methods to be invoked from the application. Java objects can be stored in the OpenLaszlo Server using an HTTP session (HttpSession) or the web application object (SevletContext). See the Java Servlet APIs for more information.

<xmlrpc> implements XML-RPC, a simple spec that allows applications to make remote procedure calls on different systems. Its transport is HTTP and it uses an XML encoded message to invoke remote functions.

<soap> implements the W3C SOAP specification. Like <xmlrpc> SOAP also uses XML to send messages over the network. Though HTTP is commonly used as its transport, it isn't required. The specification is more complex than XML-RPC and supports different styles of operations (rpc or document), overloaded methods, passing header information, and so forth.

The <rpc> tag is the abstract base class for allRPC classes. Instances of RPC classes are referred to as RPC objects. RPC classes include <soap>, <javarpc>, <xmlrpc>, <sessionrpc>, and <webapprpc>. These classes are essentially LZX wrappers of ORL lz.rpc services. lz.rpc is the ORL abstract class for ORL RPC services. This service provides the basic framework to invoke remote functions over the network. <lz.javarpc>, <lz.xmlrpc>, and <lz.soap> are subclasses of <lz.rpc>, and implement JavaRPC, XML-RPC, and SOAP, respectively.

2. <rpc>

The <rpc> tag is the abstract base class for RPC classes. Subclasses must implement the load() method, which is responsible for creating the proxy object. The proxy object contains a set of stub functions that invoke a remote function (or procedure) over the network. It's up to the caller of the stub function to know what parameters need to be passed in by looking at what the backend remote function expects.

If you're calling a JavaRPC function, for example, you will need to look at the associated Java API. If you're calling a SOAP function, you will need to look at the corresponding operation in a WSDL file.

<rpc autoload="[true|false]"
     secure="[true|false]"
     secureport="...">

The implementation of this class can be seen in lps/components/rpc/rpc.lzx.

2.1. Attributes

autoload: (Boolean) if true, calls to load client proxy during init stage. If false, the proxy must be loaded using the load() method. Default is true.

secure: (Boolean) if true, creates a secure HTTPS connection between the client and the OpenLaszlo Server. Also see secureport below. Default is false.

secureport: (Number) valid only when secure attribute is set to true. The secure port to use. There is no client-side default. Most servers use port 443 as the default HTTPS port.

2.1.1. Read-only properties

proxy: (Object) this is the object containing function stubs. It is created by calling load() (which happens during init if autoload is true).

proxy is equivalent to:

  • a class in JavaRPC

  • a service in SOAP

proxy function stubs are equivalent to:

  • class methods in JavaRPC

  • operations in SOAP

  • methods in XML-RPC.

Note that proxy is not defined until the onload event is sent, thus, function stubs cannot be invoked until onload. Each function stub requires two arguments: an array of parameters and delegate. You can unload it (i.e., set to null) by calling the unload() method. Go to the proxy section for details.

2.2. Events

Note: event handler methods must be declared in the body of <rpc>. Attribute event handlers will not work.

onload: this event is triggered when the proxy is returned to the client.

onunload: this event is triggered when the proxy is unloaded from the client.

ondata: this event is triggered when a declared <remotecall> doesn't handle its ondata events. See the <remotecall> section for details.

onerror: this event is triggered if there was a problem loading or unloading the stub, or if a declared <remotecall> didn't handle its onerror event. See the <remotecall> section for details.

2.3. Methods

2.3.1. load()

The load() method is abstract in this class. Each subclass must define this method. load() is responsible for setting up the proxy

load() implementation in javarpc.lzx

<method name="load">

    /* other code here */

    var opts = {
        loadoption: this.loadoption,
        params: this.createargs,
        classname: this.classname,
        oname: this.attributename,
        scope: this.scope
    }

    LzJavaRPCService.loadObject(this._loadDel, opts, this.secure, this.secureport);
</method>

load() implementation in soap.lzx

<method name="load">
    LzSOAPService.loadObject(this._loadDel,
        { wsdl: this.wsdl, service: this.service, port: this.port },
          this.secure, this.secureport);
</method>

load() implementation in xmlrpc.lzx

<method name="load">
    // Since there aren't any prototypes to load for XML-RPC services, we just
    // create proxy using declared calls.
    for (var cn in this.subnodes) {
        /* code to set up this.proxy */
    }

    this._loadDel.execute( 
        { status: 'ok', message: 'ok', 
          stub: this.proxy, stubinfo: null }
    );

</method>

There are several private, undocumented properties in <rpc>. But one that implementers of subclasses should be made aware of is the delegate property called _loadDel (note: an underscore prefix in a component variable indicates that it is private). This delegate must be passed in to any lower-level ORL APIs or must be called at the end of load(), as is done in the xmlrpc.lzx implementation of load() (shown above). In turn, _loadDel calls the _load() method (a private method in <rpc>) that sets up the proxy, registers declared <remotecall> nodes, and finally sends the onload event.

2.3.2. unload()

This method unloads the proxy from the RPC object. Just like load(), this method has a corresponding _unloadDel delegate and _unload handler method. By default, unload() sets the proxy property to null and then an unload event is sent. However, you can override this function in the cases where server-side clean up is required, as is done with JavaRPC objects.

unload() implementation in javarpc.lzx

<method name="unload">
    /* some other code here */

    // clean up server-side code
    LzJavaRPCService.unloadObject(this._unloadDel, 
        {
          classname: this.classname, 
          oname: this.attributename, 
          scope: this.scope
        }, 
        this.secure, this.secureport);
</method>

See lps/components/rpc/javarpc.lzx for more details.

3. Declaring rpc

Since rpc is an abstract class, SOAP, JavaRPC, and XML-RPC declarations will be used for demonstration.

<!-- SOAP -->
<soap name="mySOAP" wsdl="...">
    <handler name="onload">
        Debug.debug("soap proxy loaded: %w", this.proxy);
    </handler>
    <handler name="onunload">
        Debug.debug("soap proxy unloaded: %w", this.proxy);
    </handler>
</soap>

<!-- JavaRPC -->
<javarpc name="myJavaRPC" classname="..." scope="...">
    <handler name="onload">
        Debug.debug("javarpc proxy loaded: %w", this.proxy);
    </handler>
    <handler name="onunload">
        Debug.debug("javarpc proxy unloaded: %w", this.proxy);
    </handler>
</javarpc>

<!-- XML-RPC -->
<xmlrpc name="myXMLRPC" service="...">
    <handler name="onload">
        Debug.debug('xmlrpc proxy loaded: %w", this.proxy);
    </handler>
    <handler name="onunload">
        Debug.debug("xmlrpc proxy unloaded: %w", this.proxy);
    </handler>
</xmlrpc>

4. Proxy

The proxy property is an object that contains function stubs. It's set when the load call returns. Each function represents a client-side stub to a remote function or operation. Each function requires two arguments: an array of parameters and a delegate. The order of parameters in the array should match the parameters the backend operation expects. The delegate is required because RPC calls are asynchronous, that is, there is no way to know when the function call will return from the backend. When the response is received, the delegate calls the appropriate method.

<!-- Assume mySOAP.proxy contains a function stub called someFunction() -->
<soap name="mySOAP" wsdl="...">
    <handler name="onload">
        Debug.debug('soap proxy loaded:');
        Debug.inspect(this.proxy);
    </handler>
    <handler name="onunload">
        Debug.debug("soap proxy unloaded: %w", this.proxy);
    </handler>
</soap>

<button text="clickme">
    <attribute name="mydel" type="expression"
               value="$once{new LzDelegate(this, 'handler')}"

    <handler name="onclick">
        mySOAP.proxy.someFunction([p1, p2, ..., pN], this.myDel);
    </handler>

    <method name="handler" args="retObj">
        Debug.debug("RPC call returned %w", retObj);
    </method>

</button>

4.1. RPC call return object

When a remote call returns, an object is returned into the callback handler that is specified in the delegate passed in the function stub call. The object contains information relevant to the call and will be referred to as the return object. Successful return objects contain return values, which is the actual value returned from the RPC call. The return value can be a simple type, array, or object. A successful return object looks like:

{
  status: 'ok',
  message: 'ok',
  data: DATA,
  opinfo: OPINFO
}

Data is the return value from the RPC call. Opinfo is information specific to the operation. For SOAP, this will contain the operation name, operation style, and SOAP response headers. For JavaRPC, will contain the remote class name, method name, attribute name of where this object was saved, and other values specific to this call. For XML-RPC, opinfo will contain service URL and method name.

A successful call can also return a void return type:

{
  status: 'ok',
  message: 'void', 
  data: lz.rpc.t_void, 
  opinfo: OPINFO 
}

lz.rpc.t_void is an object that represent a void return type.

A bad call returns an error object:

{
  status: 'error', 
  errortype: ERROR_TYPE, 
  message: ERROR_MESSAGE, 
  error: ERROR_OBJECT,
  opinfo: OPINFO
}

Message is a one sentence description of the error. The error property exists only for exception and fault error types. There are four types of errors:

  • fault

  • exception

  • timeout

  • servererror

A fault error type indicates that there was a problem handling the remote function call. An exception error type is only thrown by SOAP calls and indicates the OpenLaszlo Server had a problem handling the remote function call. Both fault and exception return an error object that can be inspected by looking at the error property. A servererror is returned for general OpenLaszlo Server errors like forbidden requests. Timeout is returned when the application hasn't heard from back from the OpenLaszlo Server in a certain amount of time. This currently defaults to 30 seconds and can't be changed.

Example 45.1. Return object

<canvas debug="true" width="100%">

    <soap name="temperature" wsdl="http://developerdays.com/cgi-bin/tempconverter.exe/wsdl/ITempConverter">

        <attribute name="myDel" value="$once{new LzDelegate(this, 'myhandler')}"/>

        <method name="init">
            Debug.debug('soap service loading...');
            super.init();
        </method>

        <handler name="onload">
            Debug.debug('temperature service loaded!');
            Debug.inspect(this.proxy);
        </handler>

        <method name="myhandler" args="returnObject">
            Debug.debug('got returned object:');
            Debug.inspect(returnObject);
        </method>

    </soap>

    <view layout="spacing: 5" x="20" y="20">
        <button text="ok conversion">
            <handler name="onclick">
                Debug.debug('requesting good conversion...');
                temperature.proxy.FtoC([ 100 ], temperature.myDel);
            </handler>
        </button>
        <button text="bad conversion">
            <handler name="onclick">
                Debug.debug('requesting bad conversion...');
                temperature.proxy.FtoC([ 'string' ], temperature.myDel);
            </handler>
        </button>
    </view>

</canvas>

[Warning] Warning

Remote procedure calls return native objects, not XML, and cannot be used with XPath.

4.2. Generating lz.DataElement from return values

Return values can be mapped to datasets or lz.DataElements. This creates a convenient way to bind return values from remote calls zto elements in the OpenLaszlo canvas. Data mapped return values can be generated using the lz.DataElement.valueToElement() method.

Example 45.2. lz.DataElement.valueToElement()

<canvas debug="true" width="100%">

    <simplelayout spacing="5"/>

    <method name="v2e" args="v">
        Debug.debug('Got %w', v);
        var de = LzDataElement.valueToElement(v);
        Debug.debug("%w", de.serialize());
    </method>

    <button text="number">
        <handler name="onclick">
            var num = 5;
            canvas.v2e(num);
        </handler>
    </button>

    <button text="string">
        <handler name="onclick">
            var str = "a string";
            canvas.v2e(str);
        </handler>
    </button>

    <button text="array">
        <handler name="onclick">
            var arr = [1, 2, 3];
            canvas.v2e(arr);
        </handler>
    </button>

    <button text="object">
        <handler name="onclick">
            var obj = { p1: "a string", p2: 5 }
            canvas.v2e(obj);
        </handler>
    </button>

    <button text="complex array">
        <handler name="onclick">
            var arr = [ 1, { p1: "a string", p2: 5 }, [ 1, 2, 3] ];
            canvas.v2e(arr);
        </handler>
    </button>

    <button text="complex object">
        <handler name="onclick">
            var obj = { p1: [1, 2, 3], p2: 5 };
            canvas.v2e(obj);
        </handler>
    </button>

</canvas>

The root node of lz.DataElement.valueToElement() is named <element>. Simple type values are placed as text nodes of the root node. Array items are placed as <item> nodes under the root. Object properties are placed under the root with the name of the property representing the element that wraps its value.

In the example above, the number

5 

maps to

 <element>5</element>

The string

"a string" 

maps to

 <element>a string</element>

the array

[1, 2, 3]

maps to

<element>
    <item>1</item>
    <item>2</item>
    <item>3</item>
</element>

the object

{ p1: "a string", p2: 5 }

maps to

<element>
    <p1>a string</p1>
    <p2>5</p2>
</element>

4.2.1. SOAP calls and lz.DataElements

Note that returned arrays from SOAP calls are treated a little differently. Each item in a SOAP array has a wrapper element, which is remapped back when turned into an lz.DataElement. That is, instead of wrapping array items in <item>, they're wrapped with the original element name specified in the SOAP message.

For example, assume the original SOAP message for an array return value looks like:

<soap:Envelope soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" ...>
   <soap:Body> <n:getTypesResponse xmlns:n="http://arcweb.esri.com/v2">
         <Result href="#id0" />
      </n:getTypesResponse>
      <id0 id="id0" soapenc:root="0" xsi:type="soapenc:Array" soapenc:arrayType="ns5:KeyValue[19]">
         <i href="#id1" />
         <i href="#id2" />
         <i href="#id3" />
         <i href="#id4" />
         <!-- ... -->
      </id0>
      <id1 id="id1" soapenc:root="0" xsi:type="ns5:KeyValue">
         <key xsi:type="xsd:string">A</key>
         <value xsi:type="xsd:string">Countries</value>
      </id1>
      <id2 id="id2" soapenc:root="0" xsi:type="ns5:KeyValue">
         <key xsi:type="xsd:string">B</key>
         <value xsi:type="xsd:string">Large Non-U.S. Cities</value>
      </id2>
      <id3 id="id3" soapenc:root="0" xsi:type="ns5:KeyValue">
         <key xsi:type="xsd:string">C</key>
         <value xsi:type="xsd:string">Large U.S. Cities</value>
      </id3>
      <!-- ... -->

   </soap:Body>
</soap:Envelope>

This example shows that <item> is not used as the wrapper element for array items.

Example 45.3. SOAP array

<canvas debug="true" height="300" width="100%">

    <soap name="geography" wsdl="http://arcweb.esri.com/services/v2/PlaceFinderSample.wsdl">

        <attribute name="myDel" value="$once{new LzDelegate(this, 'handler')}"/>

        <method name="handler" args="retval">
            if (retval.status == 'error') {
                Debug.error('%w', retval.message);
                return;
            }

            Debug.debug('Got %w', retval.data);
            var el = LzDataElement.valueToElement(retval.data);
            Debug.debug("%w", el.serialize());
        </method>

        <handler name="onload">
            Debug.debug('geography soap service stub loaded');
            this.proxy.getTypes([], this.myDel);
        </handler>

    </soap>

</canvas>

The data-mapped return value can then be used with another lz.DataElement for databinding purposes.

Example 45.4. Setting generated return value lz.DataElement in a dataset

<canvas debug="true" height="150" width="100%">

    <dataset name="myDataset"/>

    <simplelayout spacing="5"/>

    <method name="v2e" args="v">
        Debug.debug('setting %w to mydataset', v);
        var de = LzDataElement.valueToElement(v);
        myDataset.setAttribute("childNodes", de.childNodes )
        Debug.debug("%w", myDataset.serialize());
    </method>

    <view datapath="myDataset:/" x="10" layout="inset: 10; spacing: 2">
        <button text="array">
            <handler name="onclick">
                var arr = [1, 2, 3];
                canvas.v2e(arr);
            </handler>
        </button>

        <text datapath="item/text()"/>
    </view>

</canvas>

4.3. Using dataobject to map return values

There is more convenient way to generate data mapped return values. Instead of calling valueToElement() after each call, a dataobject property can be set in the delegate. Dataobject must be a dataset or an lz.DataElement and tells the RPC function to create a data mapped return value and set it on the dataobject. If the dataobject is a dataset, the child nodes of the data mapped return value are set as the child nodes of the dataset. If the dataobject is an lz.DataElement, the root node of data mapped return value is appended as a child of the lz.DataElement dataobject.

Example 45.5. Setting a dataobject to a dataset

<canvas debug="true" width="100%">

    <dataset name="myDataset"/>

    <soap name="geography" wsdl="http://arcweb.esri.com/services/v2/PlaceFinderSample.wsdl">

        <attribute name="myDel" value="$once{new LzDelegate(this, 'handler')}"/>

        <method name="handler" args="ret">
            Debug.debug('ret: %w', ret);
            if (ret.status == 'ok') {
                Debug.debug('myDataset childNodes: %w', myDataset.childNodes);
            }
        </method>

        <handler name="onload">
            Debug.debug('geography soap service stub loaded');

            // Here we set data
            this.myDel.dataobject = myDataset;

            this.proxy.getTypes([], this.myDel);
        </handler>

    </soap>


    <view datapath="myDataset:/" x="10" layout="inset: 10; spacing: 2">
        <view datapath="i">
            <text datapath="key/text()" resize="true"/>
            <text x="20" datapath="value/text()" resize="true"/>
        </view>
    </view>

</canvas>

Note how the passed in lz.DataElement child nodes are the child nodes of the dataset. Here's another example using an lz.DataElement.

Example 45.6. Setting dataobject to an lz.DataElement

<canvas debug="true" height="300" width="100%">

    <dataset name="myDataset">
        <region name="Region-A"/>
        <region name="Region-B"/>
        <region name="Region-C"/>
    </dataset>

    <soap name="placefinder" wsdl="http://arcweb.esri.com/services/v2/PlaceFinderSample.wsdl">

        <attribute name="myDel" value="$once{new LzDelegate(this, 'handler')}"/>

        <method name="handler" args="ret">
            Debug.debug('ret: %w', ret);
            if (ret.status == 'ok') {
                Debug.debug('myDataset childNodes: %w', myDataset.childNodes);
            }
        </method>

        <handler name="onload">
            Debug.debug('placefinder soap service stub loaded');
            myView.setAttribute('visible', true);
            this.proxy.getTypes([], this.myDel);
        </handler>

    </soap>

    <view name="myView" datapath="myDataset:/" x="10" visible="false">

        <simplelayout spacing="2" inset="10"/>

        <view datapath="region" layout="spacing: 5">
            <attribute name="text" value="$path{'@name'}" type="string"/>

            <text name="t" text="${parent.text + ' (click me)'}" resize="true"/>

            <method name="gotData">
                this.setAttribute('clickable', false);
                this.t.setAttribute("text", this.text);
            </method>

            <handler name="onclick">
                if (text == "Region-A") {
                    this.regions.setAttribute("datapath", "element/i[1-6]");
                } else if (text == "Region-B") {
                    this.regions.setAttribute("datapath", "element/i[7-12]");
                } else {
                    this.regions.setAttribute("datapath", "element/i[12-]");
                }
                var myDel = new LzDelegate(this, "gotData");
                myDel.dataobject = this.datapath.p;
                placefinder.proxy.getTypes([], myDel);
            </handler>

            <view name="regions" x="10">
                <text datapath="key/text()" resize="true"/>
                <text x="20" datapath="value/text()" resize="true"/>
            </view>
        </view>
    </view>

</canvas>

Unlike a dataset, the generated lz.DataElement from the return value is appended as a child of the lz.DataElement dataobject.

5. <remotecall>

In the previous section we discussed how to make RPC calls using function stubs defined in the proxy. Though not discouraged, calling function stubs directly will generally result in your code looking very "scripty". The <remotecall> tag allows for a more declarative style approach to using RPC functions.

<remotecall funcname="..."
     name="..."
     dataobject="..."
     remotecontext="...">

5.1. Attributes

The <remotecall> tag has the following attributes:

funcname: (String) the name of the function stub remotecall represents. This is required.

name: (String) the name of the remotecall. Multiple remotecalls can refer to the same function stub, but names must be unique. Default is value of funcname.

dataobject: (lz.dataset|lz.DataElement) if set, the return value will also be represented as a dataset or as a child of the lz.DataElement. Default is null.

remotecontext: (Object) this attribute is used when the remotecall isn't declared inside of an RPC object. This attribute should be set to the RPC context the remotecall should run from. Default is null.

5.2. Events

ondata: this event is triggered when the remotecall successfully returns data. If ondata is not handled in the remotecall, ondata cascades up to its parent. If the parent doesn't handle it, it then cascades up to the remotecontext, if defined. If none of these objects handle the ondata event, the returned data is ignored. The ondata event sends two arguments to its event handler. The first is the actual return value, the second is information on the operation that returned the value.

onerror: this event is triggered when the remotecall returns an error. If an onerror is not handled in the remotecall, onerror cascades up to its parent. If the parent doesn't handle it, it then cascades up to the remotecontext, if defined. If none of these objects handle the onerror event, the error is displayed in the debugger. The onerror event sends three arguments to its event handler: the error message, an error object (which can be null or undefined depending on the error type), and information on the operation that originated the failed return.

5.3. Methods

5.3.1. invoke(params, delegate)

params: (Array) an array of parameters to pass to the remotecall. If null, the remotecall will use <param> tag values declared inside of it. Default is null.

delegate: (LzDelegate) a delegate to handle the callback. If null, the remotecall will use the default delegate which calls the default handler, whose job is to receive data and raise ondata/onerror events. Default is null.

Call this method to invoke remotecall.

5.4. Using remotecall

Use the invoke() method to use remotecall. If the parameter array (the first argument) is null, declared <param> are used as arguments. A <param> can set either a value attribute or define a getValue() method in its body. The getValue() method is expected to return a value. If both value and getValue() are defined, getValue() always wins. Note that the value attribute is an expression type so strings must be quoted like value="'my string'". To set null, use the value="$once{null}" syntax.

Function stubs can be referenced by two different remotecalls with different parameters. The funcname attribute doesn't have to be unique, but if referenced more than once, remotecalls must explicitly define their name attributes.

Example 45.7. Remotecall

<canvas debug="true" height="300" width="100%">

    <soap name="google" wsdl="http://api.google.com/GoogleSearch.wsdl">

        <handler name="onload">
            Debug.debug('google soap service stub loaded');
        </handler>

        <handler name="onerror" args="error">
            Debug.error("RPC error: %w", error);
        </handler>

        <remotecall name="search" funcname="doGoogleSearch">

            <param value="'2TKUw4ZQFHJ84ByemZK0EXV0Lj+7xGOx'"/>
            <param value="'sweet'"/>
            <param value="1"/>
            <param value="10"/>
            <param value="true"/>
            <param value="''"/>
            <param value="true"/>
            <param value="''"/>
            <param value="''"/>
            <param value="''"/>

            <handler name="ondata" args="value">
                Debug.debug('result is:')
                Debug.inspect(value);
            </handler>

        </remotecall>

        <remotecall name="togglesearch" funcname="doGoogleSearch">

            <param value="'2TKUw4ZQFHJ84ByemZK0EXV0Lj+7xGOx'"/>
            <param>
                <attribute name="toggle" value="0" type="number"/>
                <method name="getValue">
                    var searchTerm;
                    if (toggle % 2 == 0) {
                        searchTerm = 'democrat';
                        toggle = 1;
                    } else {
                        searchTerm = 'republican';
                        toggle = 0;
                    }
                    Debug.debug('search term is %w', searchTerm);
                    return searchTerm;
                </method>
            </param>
            <param value="1"/>
            <param value="10"/>
            <param value="true"/>
            <param value="''"/>
            <param value="true"/>
            <param value="''"/>
            <param value="''"/>
            <param value="''"/>

            <handler name="ondata" args="value">
                Debug.debug('result is:')
                Debug.inspect(value);
            </handler>

        </remotecall>
    </soap>

    <simplelayout spacing="10"/>

    <button x="10" y="10" text="search">
        <handler name="onclick">
            Debug.debug('invoking search...');
            canvas.google.search.invoke();
        </handler>
    </button>

    <button x="10" y="10" text="toggle search">
        <handler name="onclick">
            Debug.debug('invoking togglesearch...');
            canvas.google.togglesearch.invoke();
        </handler>
    </button>

</canvas>

5.4.1. Using dataobject with remotecall

Just like passing in a delegate with a dataobject property as discussed previously with function stubs, a dataobject can be used in a remotecall by setting the dataobject attribute.

Example 45.8. Setting dataobject in remotecall

<canvas debug="true" height="300" width="100%">

    <debug x="225" width="450" height="280"/>

    <dataset name="googleDset"/>

    <soap name="google" wsdl="http://api.google.com/GoogleSearch.wsdl">
        <handler name="onload">
            Debug.debug('google soap service stub loaded');
        </handler>

        <handler name="onerror" args="error">
            Debug.error("RPC error: %w", error);
        </handler>

        <remotecall name="search" funcname="doGoogleSearch" dataobject="googleDset">

            <param value="'2TKUw4ZQFHJ84ByemZK0EXV0Lj+7xGOx'"/>
            <param value="'sweet'"/>
            <param value="1"/>
            <param value="10"/>
            <param value="true"/>
            <param value="''"/>
            <param value="true"/>
            <param value="''"/>
            <param value="''"/>
            <param value="''"/>

            <handler name="ondata" args="value">
                Debug.debug('got result');
                Debug.inspect(value);
            </handler>

        </remotecall>
    </soap>

    <view layout="spacing: 5">
        <button text="search" onclick="google.search.invoke()"/>
        <view bgcolor="yellow" layout="axis: y">
            <view>
                <datapath xpath="googleDset:/resultElements/item" pooling="true"/>
                <text datapath="URL/text()" resize="true"/>
            </view>
        </view>
    </view>
</canvas>

5.4.2. Remotecall remotecontext

In previous examples, a button was used to invoke a remotecall that was declared in an RPC object. The code for a remotecall might have read better if it was placed in the location where it's actually being used. Remotecall can actually be declared anywhere in the node hierarchy, but when doing so, not only must it know the function stub that it will trigger (i.e., the funcname), it also must know which RPC context it will run from. To do so, the remotecontext attribute must be set when declaring a remotecall outside of an RPC object.

Example 45.9. Remotecontext

<canvas debug="true" height="300" width="100%">

    <debug x="100" width="450" height="280"/>

    <soap name="google" wsdl="http://api.google.com/GoogleSearch.wsdl">
        <handler name="onload">
            Debug.debug('google soap service stub loaded');
        </handler>
    </soap>

    <button text="search" onclick="this.doGoogleSearch.invoke()">

        <!-- if name isn't set, name defaults to value of funcname. -->

        <remotecall funcname="doGoogleSearch" remotecontext="$once{canvas.google}">

            <param value="'2TKUw4ZQFHJ84ByemZK0EXV0Lj+7xGOx'"/>
            <param value="'sweet'"/>
            <param value="1"/>
            <param value="10"/>
            <param value="true"/>
            <param value="''"/>
            <param value="true"/>
            <param value="''"/>
            <param value="''"/>
            <param value="''"/>

            <handler name="ondata" args="value">
                Debug.debug('got result');
                Debug.inspect(value);
            </handler>
            <handler name="onerror" args="error">
                Debug.error("RPC error: %w", error);
            </handler>
        </remotecall>
    </button>
</canvas>