In OpenLaszlo context menus are attached to a single view. If an object on the screen is composed from multiple views the context menu must be attach to each of these views. This was a problem for me because I had screen objects composed from over 20 views each, and assigning a context menu to each of those views was going to make my code both ugly and bug prone.
My solution was to create classes that automatically inherit their context menu from their parent. These classes are shown below.
I used a timer because
views are initialised from the bottom up, so children are inited before their parents
I use ‘initstage=”defer”‘ extensively so I need to wait until something happens.
For views …
<class name="view_cm">
<handler name="oninit">
this.focusDelegate = new LzDelegate(this,
"cm_callback");
LzTimer.addTimer(this.focusDelegate, 100);
</handler>
<method name="cm_callback">
if (parent.isinited)
{
var cm = parent.getContextMenu();
if (cm != null)
{
this.setContextMenu(parent.getContextMenu());
return;
}
}
this.focusDelegate = new LzDelegate(this,
"cm_callback");
LzTimer.addTimer(this.focusDelegate, 100);
</method>
</class>
For text …
<class name="text_cm" extends="text">
<handler name="oninit">
this.focusDelegate = new LzDelegate(this,
"cm_callback");
LzTimer.addTimer(this.focusDelegate, 100);
</handler>
<method name="cm_callback">
if (parent.isinited)
{
var cm = parent.getContextMenu();
if (cm != null)
{
this.setContextMenu(parent.getContextMenu());
return;
}
}
this.focusDelegate = new LzDelegate(this,
"cm_callback");
LzTimer.addTimer(this.focusDelegate, 100);
</method>
</class>
The last class is used for views containing images. If you try and attach a context menu to a view with no background or movieclip, a warning is issued. I like my code warning free, so this class waits for the onload event before trying to set the context menu.
<class name="view_cm_load">
<handler name="onload">
this.focusDelegate = new LzDelegate(this,
"cm_callback");
LzTimer.addTimer(this.focusDelegate, 100);
</handler>
<method name="cm_callback">
if (parent.isinited &&
this.getAttribute('resource') != null)
{
var cm = parent.getContextMenu();
if (cm != null)
{
this.setContextMenu(parent.getContextMenu());
return;
}
}
this.focusDelegate = new LzDelegate(this,
"cm_callback");
LzTimer.addTimer(this.focusDelegate, 100);
</method>
</class>
Using these classes is as simple a changing view, class (extends) and text declarations as required e.g. from “<view … />” to “<view_cm … />”. The rest of the code is untouched.
The above classes are something I dreamed up to solve a particular problem I had. I would be interested in any improvements or alternative approaches.
RESOURCES
OpenLaszlo: building context menus
Tags: OpenLaszlo
THE PROBLEM
I had a serious performance problem with an OpenLaszlo application. The application had a large number of views, most of which were ‘hidden’ at any one time. The views comprised a number of subviews, including an image loaded at runtime. I wanted all views instantiated at startup for quick navigation by the user.
The problem was that the load time ranged from 20 to 30 seconds for even small datasets. That was pretty unacceptable.
WHAT DIDN’T WORK
As I mentioned above, I wanted all the views instantiated at startup for quick navigation by the user. I found this out the hard way. One of my first attempts to fix this problem used a state wrapped around each set of views. By toggling the state on and off as required, I was able to instantiate only the minimal number of views I needed. Load time was super fast.
The problem with this solution was that as the user navigated around the application, the lag in instantiating the newly visible views was very noticeable. Worse, because the state mechanism destroyed views that were no longer necessary, this problem never went away. The views had to be instanitated anew every time.
THE SOLUTION
The runtime loading of images was deferred until the image was required (previously the image had been loaded automatically through the ondata handler). The image view has a load_image() method to manually load the image. There is a small noticeable delay when the image loads, but it is pretty quick and only happens the first time the view is displayed.
<class name="myclass">
<attribute name="imageurl" type="string" />
<method name="load_image">
var image = this.datapath.xpathQuery("text()");
if (image == null) return;
var imageurl="images/" + image;
if (this.getAttribute('imageurl') == imageurl)
return;
this.setSource(imageurl);
this.setAttribute('imageurl', imageurl);
</method>
...
</class>
For the views I set initstage to ‘defer’ or ‘late’ to delay instantiation. The ‘onvisible’ handler calls completeInstantiation() on views that become visible and requests the image be loaded.
<view onvisible="${...}">
<handler name="onvisible" args="v">
if (!v) return;
if (!this.myview.isinited)
this.myview.completeInstantiation();
this.myview.load_image();
</handler>
<myclass name="myview" initstage="defer" />
</view>
These changes sped up application loading by a factor of 6, without materially affecting the user experience.
RESOURCES
Check out http://www.vidog.com/blog/my-top-las…formance-tips/ if you haven’t already.
FOLLOW UPS
OpenLaszlo: background loading of images
OpenLaszlo: initstage … defer vs late
Tags: OpenLaszlo
OpenLaszlo Project Blog » Laszlo Mail tooltip contributed to OpenLaszlo is an old post announcing the availability of tooltip code for OpenLaszlo (go there for a link to the code). I am using this code and it is easy to use and visually elegant.
I needed dynamic content for my tooltips. The display text for the tooltip changed depending on the data retrieved from the database. My solution was to create an ondata handler to set the text attribute of the tooltip. The initial text value was set to “Loading …”. I have never seen the initial value displayed in a tooltip, but I have left it there just in case.
<view clickable="true" datapath="xxx">
<handler name="ondata" args="p">
if (!p) return;
this.tooltip.setAttribute('text', this.datapath.xpathQuery('text()'));
</handler>
<tooltip name="tooltip" text="Loading ..." />
</view>
Improvements and suggestions welcome.
UPDATE: I just found that the tooltip code is part of the incubator for OL 3.3.3. No need to go get the code from anywhere!
Tags: OpenLaszlo
The OpenLaszlo documentation states that the persistent connection functionality should not be deployed for production purposes, except for low-volume non-critical applications.
This may be in part due to the model of holding an open connection between a server and a client over the Internet. Such solutions typically do not scale well (requiring back end servers to handle many thousands of open connections) and have difficulty recovering from broken connections.
I implemented a persistent connection to push data from the server out to clients. I found that messages over ~2K caused the client loader to die, so no further messages, asynchronous or not, could be received. I also found that race coniditions arose between client operations and the server data push, disrupting user experience and causing errors.
In the end I removed the persistent connection functionality and replaced it with a polling loop. A polling loop is simple, reliable, and flexible.
Polling
The core of the polling loop executes as an idle event handler. The handler:
- Checks if the loader is doing any work. If the loader is working then we consider the system busy, so to avoid race conditions and access/update collisions the handler gracefully exits.
- Checks to see if (in my case) 2 seconds has passed since the last update. To track the last update time I use the “update” attribute.
- If both these conditions are OK, then we place a request for the next batch of updates and refresh our update timer.
<attribute name="update" type="number" value="0" />
<handler name="onidle" reference="LzIdle">
if (LzLoadQueue.openConx > 0) return;
var now = (new Date()).getTime();
var then = this.getAttribute('update');
if ((now - then) > 2000)
{
var d = canvas.datasets.???;
var p = new LzParam();
p.addValue(???, ???, true);
d.setQueryString(p);
d.doRequest();
this.setAttribute('update', now);
}
</handler>
The response
Processing the response is no different from processing any other response, except in my case I disable updates for the duration of processing by setting the update counter to a very large value, before refreshing the update timer at the end of processing to “schedule” the next update.
<datapointer xpath="values:/*">
<handler name="ondata">
canvas.setAttribute('update', Infinity);
... process here ...
var now = (new Date()).getTime();
canvas.setAttribute('update', now);
</handler>
</datapointer>
Management
To refresh the timer and defer the next update (as above):
var now = (new Date()).getTime();
canvas.setAttribute('update', now);
To force an update as soon as possible:
canvas.setAttribute('update', 0)
To prevent updates altogether (until update is re-set):
canvas.setAttribute('update', Infinity);
Resources
For a persistent connection tutorial, have a look at http://jackhung.tripod.com/lpsagent/quoteAgent/main.html.
Tags: OpenLaszlo