Table of Contents
Warning | |
---|---|
Remote procedure calls work in proxied applications only. They do not work in SOLO applications. |
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.
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
.
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.
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.
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.
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.
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.
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>
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>
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
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
Warning | |
---|---|
Remote procedure calls return native objects, not XML, and cannot be used with XPath. |
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
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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>
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
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
Unlike a dataset, the generated lz.DataElement from the return value is appended as a child of the lz.DataElement dataobject.
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="...">
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.
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.
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.
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
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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.