Chapter 35. Browser Integration

Table of Contents

1. Using the Developer Console
2. A model of Browser/LZX application interaction
2.1. The "wrapper page"
2.2. Applications Compiled to SWF
2.3. Applications Compiled to DHTML
3. The Lz Class
4. Placing the LZX application in the HTML page
4.1. Placing LZX applications within <div>s
4.2. Detecting which flash player is running
5. Integrating with Frames
5.1. Frame Sets
5.2. Inline Frames
5.3. Popping and reusing browser windows
6. Passing startup Data to Embedded Applications
6.1. Query Parameters and Global Variables
6.2. Application state
7. Supporting Multiple Resolutions
8. Using the browser's "back" button
9. Scaling OpenLaszlo Applications to Browser Dimensions
10. Browser Limitations

OpenLaszlo applications run inside web browsers, and web browsers are desiged to render HTML pages. This means that each compiled OpenLaszlo application has a relationship to the HTML page that delivers it to the browser. Depending on whether the OL application was compiled to SWF or to DHTML, the mechanics of this relationship differ. However, at an abstract level the model is pretty much the same for all runtimes: an OpenLaszlo application resides within a web page that is executed by a browser; that browser has a "JavaScipt" engine. The OpenLaszlo application can make requests for the JavaScript engine to execute, and the surrounding HTML page can include JavaScript code that makes requests of the OpenLaszlo application.

This chapter discusses the way that OpenLaszlo Applications relate to the HTML pages in which they're delivered, and ways in which the OpenLaszlo appliction and browser JavaScript engine can communicate with each other.

1. Using the Developer Console

In this chapter we'll be talking about the structure of HTML pages that contain LZX applications. Keep in mind that if you compile your applications using the Developer Console, HTML pages of the proper structure are generated automatically. Situations may arise where you may need to customized these pages or even generate pages from scratch, but in many cases, the automaticaly generated page is all you need. Simply press the appropriate button, "Server" or "SOLO", and follow the instructions that are then displayed.

2. A model of Browser/LZX application interaction

Before going into the actual mechanics, let's take a closer look at concepts and teminology.

2.1. The "wrapper page"

As we said above, web browsers display HTML pages. This means that OpenLaszlo applications are delivered to browsers by means of HTML pages. Sometimes those HTML pages contain nothing visible to the user other than the LZX application itself. In such cases the HTML page is essentially invisible, and the fact that there's an HTML page there at all doesn't really matter to the developer or to the user of the application. Such HTML pages, whose only function is to present the OpenLaszlo application to the browser, are sometimes called "wrapper pages."

Depending on which target runtime they're compiled for, OpenLaszlo applications have different relationships with the JavaScript engine of the browser in which they run. To a rough approximation, LZX applications compiled to SWF are foreign objects embedded into an HTML page, and you must use a formal protocol to get across this logical barrier between the LZX application and the JavaScript engine. Applications compiled to DHTML become just a part of the HTML page that contains them. From the point of view of the browser, there is no difference between the OpenLaszlo application and the surrounding page.

But although some of the implementation details differ for the different runtime targets, the OpenLaszlo APIs are, wherever possible, consistent, so that you use the same programming model regardless of the target runtime. So, for example, there is a method called swfEmbed()for embedding SWF OL applcations into the HTML page; this generates an HTML <embed> tag. Similarly, there is an dhtmlEmbed() for placing an OL application compiled to DHTML within a web page. In this case there is no <embed> generated or required, but the methods have similar names because they perform analogous functions.

2.2. Applications Compiled to SWF

[Warning]
[SWF]

Applications compiled to SWF are executed by the Flash player and must make use of the LzBrowser service in order to "escape" to the browser's JavaScript environment.

Any attributes that are declared on the canvas are visible to the browser JavaScript. To go "the other way", from the browser "into" the OpenLaszlo application, the setCanvasAttribute() and setCanvasAttribute() methods are used.

This are discussed below.

Alhough OpenLaszlo applications deployed on the Flash runtime are not dependent on the browser or operating system for their look or behavior, there are some important limitations that the browser container places on the application. There are also a number of Flash Player features that your application can use which are not available in applications compiled to DHTML.

OpenLaszlo applications compiled to SWF can be sent from the OpenLaszlo Server to client in any of several forms:

  • as "naked" swf

  • as swf embedded in an html "wrapper" page

  • as xml source

2.2.1. The LzBrowser service

[Warning]
[SWF]

SWF only: The features described in this section only work in applications compiled to SWF. They do not work in applications compiled to other runtimes.

The LzBrowser service provides access to the browser and player environment. It includes methods to load URLs in the browser and check the version of the player. Furthermore, by using the LzBrowser service together with other OpenLaszlo functions described below, you can build applications that pass information and control between the OpenLaszlo application and the JavaScript of the browser.

The LzBrowser is always running.

2.3. Applications Compiled to DHTML

[Warning]
[DHTML]

DHTML only: The features described in this section only work in applications compiled to DHTML. They do not work in applications compiled to other runtimes.

Applications that are compiled for DHTML run within the browser's JavaScript envinronment. They are essentially compiled into the DOM of that page.

Each LZX application has exactly one <canvas> element, which, when compiled to DHTML, becomes an object. When you embed an LZX application compiled to DHTML into an HTML page, it just becomes part of that page's DOM,and you can address it directly. The "foo" object of the LZX application becomes canvas.foo of the DOM. If there is a swf embedded in that (DHTML) Openlaszlo application, you can still use the mechanism for talking to SWF applications to reach it.

For DHTML, you can call canvas JavaScript directly because it is loaded in the browser. That is to say, unlike in the case of communicating with SWFs, you do not need to use setCanvasAttribute() ore setCanvasAttribute(), nor do you need to use the LzBrowser service.

2.3.1. Embedding the LFC for DHTML applications

[Warning]
[DHTML]

DHTML only: The features described in this section only work in applications compiled to DHTML. They do not work in applications compiled to other runtimes.

When you compile an application to SWF, the compiler includes the Laszlo Foundation Class, or LFC in the resulting file. This does not happen for applications compiled to DHTML, therefore the LFC must be downloaded explicitly. To make the LFC available to DHTML applications, include the following call in the page head:

3. The Lz Class

For both applications compiled to SWF and applications compiled to DHTML, the methods used for bidirectional communication with the browser are in the class Lz. Note that the letter "L" in this class is uppper case. This is not a typo!.

Unlike other OpenLaszlo classes which you use to build LZX applications, the Lz is designed to be incorporated into the HTML page in which your LZX application runs. That is to say, your LZX application is designed to be executed in the <body> section of the page, but the Lz must be include in the <lt;head> section of the page.

The Lz class is defined in

lps/includes/source/embednew.js

The source for embednew.js isshipped compressed as

lps/includes/embed-compressed.js. 

The comments/API documentation are in the source file and the class is documented in the Reference Manual.

To have access to the Lz class, your application must include a line like


<script type="text/javascript" src="/legals/lps/includes/embed-compressed.js"></script>

If you generate wrappers using the developer's console, the correct <script> tag will be generated for you automatically.

Remember, if you compile your application using the Developer's Console, the wrapper pages generated include the proper calls to the Lz class. It "just works" and you don't have to do anything special. If you craft your HTML pages "by hand", of course, you will need to make sure that you incorporate the Lz class.

4. Placing the LZX application in the HTML page

When you place a OpenLaszlo application inside an HTML page, that page has to have some way to know where to place the application, and how to handle it. That is, you must inform the browser that the embedded OpenLaszlo application is actually a SWF file, and so forth.

In the <html><head> of an HTML document that embeds an OpenLaszlo application compiled to SWF, add this line:

Example 35.1. The Lz Class

<script src="/embed-compressed.js" language="JavaScript" type="text/javascript"/>

[Warning]
[SWF]

At the location within the <html><body> where the application is to be embeded, add this line:

Example 35.2. Placing the LZX (SWF) application within an HTML page

   <script language="JavaScript" type="text/javascript">
     lz.swfEmbed({url: '?lzt=swf', bgcolor: '#000000', width: '', height: ''});
   </script>

where the url matches the URI that the application is served from, and the other properties match the attributes of the application's canvas.

[Warning]
[DHTML]

At the location within the <html><body> where the application is to be embeded, add this line:

Example 35.3. Placing the LZX (DHTML) application within an HTML page

   <script language="JavaScript" type="text/javascript">
     lz.dhtmlEmbed({url: '?lzt=swf', bgcolor: '#000000', width: '', height: ''});
   </script>

where the url matches the URI that the application is served from, and the other properties match the attributes of the application's canvas.

4.1. Placing LZX applications within <div>s

The optional appenddivid property can be passed in to swfEmbed() or dhtmlEmbed() method to specify the id of a div whose contents should be replaced with the LZX application. If a div with that ID doesn't exist, one will be written into the document where swf/dhtmlEmbed() is called. If appenddivid is not specified, the div written will be based on the id property + 'Container'.

Alternatively, you can generate wrappers by using the various request types on the url you use to browse to your application, as explained in Chapter 48, Understanding Compilation:

  1. Invoke the lzx application with request type "?lzt=html". This generates the "wrapper."

  2. Using your browser's "View source" function, copy the contents of the wrapper and place in a new file with a .html extension.

  3. In the head of the HTML file, you will see the script tag that includes embed-compressed.js Adjust the path if necessary for deployment.

  4. paste to wherever you want the OpenLaszlo application to appear in HTML page

  5. verify by running HTML file in your browser.

4.2. Detecting which flash player is running

The default wrapper page contains logic to detect the version of flash running on the client.

This page that will look something like the following:

Example 35.4. Flash player version-detecting wrapper



Depending on which version is running, you may wish to use some LZX features that run in some, but not all, versions of the Flash Player.

4.2.1. Version detection

OpenLaszlo uses client-side player detection. If the user either a) doesn't have the Flash Player plug-in or b) has an older version, they'll be prompted to download a fresh version. This feature is included in the default HTML wrappers for lzt=html and the SOLO deployment wizard

4.2.2. Features available only in Flash 7 or later

The following features are available only in the Flash 7 or later environments:

  • Support for the html <img> tag

  • Support for context ("right-click") menus

5. Integrating with Frames

Frames are a very handy way to use OpenLaszlo applications within a page. If you are using a OpenLaszlo application for your navigation or as a widget in your page you might consider using frames to format your layout.

5.1. Frame Sets

When laying out a page that will use OpenLaszlo applications in different places, you can use a frameset like this:

Example 35.5. example frameset

<html>
<frameset cols="128,*">
  <frame name="navBar" src="myOpenLaszloNav.lzx?lzt=html" />
  <frame name="contentArea" src="myOpenLaszloContent.lzx?lzt=html" />
</frameset>
</html>

Inside your application use the target parameter of LzBrowser.LoadURL() to load pages in the target frame.

Example 35.6. target in loadURL

LzBrowser.loadURL('http://www.laszlosystems.com','contentArea');

5.2. Inline Frames

Like framesets, OpenLaszlo applications can be embedded within an "inline frame" or <iframe> which can be more flexible for your layout and easier to use. Like regular frames, inline frames can be named for later reference by the OpenLaszlo application.

Example 35.7. iframes and applications

<html>
  <body>
    <h1>Here is a header</h1>
    <div align="center">
      <iframe src="myOpenLaszloApplication.lzx?lzt=html" width="200" height="200" 
              frameborder="0" name="inlineApplication" scrolling="no"/>
    </div>
  </body>
</html>

Bear in mind that the iframe will contain a canvas, and unless you want the browser to provide scrollbars, you should set the width and height equal to that of your canvas plus any margin you may have included.

5.3. Popping and reusing browser windows

The same way you would target content to a frame, you can target the loadUrl to new window.

Example 35.8. new Blank window

<canvas height="27" >
  <button onclick="LzBrowser.loadURL('http://www.laszlosystems.com','_blank');" text="Click Me!"/>
</canvas>

This mechanism can also be used to name the window allowing subsequent loads to occur in the same window

Example 35.9. new Blank window

<canvas height="56" >
  <simplelayout/>
  <button onclick="LzBrowser.loadURL('http://www.openlaszlo.org','newOpenLaszloWindow');" text="OpenLaszlo Homepage"/>
  <button onclick="LzBrowser.loadURL('http://www.mylaszlo.com','newOpenLaszloWindow');" text="myLaszlo.com"/>
</canvas>

6. Passing startup Data to Embedded Applications

Any query parameters that you add to the end of your url to load the application get passed into the application as globals.

Example 35.10. Passing query parameters to LZX application



6.1. Query Parameters and Global Variables

Once the data has been passed in you can simply access it as a global variable from within the OpenLaszlo application.

Example 35.11. Accessing parameters

<canvas debug="true" oninit="Debug.write(globalVar)"/>       

In many cases you will have full control of the HTML content that is embedding your application. In many other cases you won't be able to depend on the existence of those query parameters so it is wise to provide sane defaults for those values:

Example 35.12. Setting defaults

<canvas debug="true" width="200" height="200">
  <script>
    var innerVar = typeof(global.globalVar) != 'undefined' ? global.globalVar : 'bar';
    Debug.write(global.innerVar);
  </script>
</canvas>

The above application is requested without a globalVar query parameter. The debugger therefore displays the default value bar. If this application is requested with the globalVar=foo query parameter, it will display the default value 'bar'. Click here to see the application in a popup window with the query parameter applied.

6.2. Application state

Using query parameters, you can craft your application to start up in a specific state. The default behavior of a tabslider is to have none of the tabelements opened at startup. The following application changes that default to open to the tab specified on the query parameter, and if one isn't provided, open to the first element.

Example 35.13. Restoring state

<canvas width="300" height="250">
  <script>
    var opened = typeof(global.tab) != 'undefined' ? global.tab : 'one';
  </script>
  <tabslider width="150" x="10" y="10"
             height="200"
             spacing="2" slideduration="300" oninit="this[global.opened].setAttribute('selected',true)">
    <tabelement name="one" text="Tabelement One"/>
    <tabelement name="two" text="Tabelement Two"/>
    <tabelement name="three" text="Tabelement Three"/>
  </tabslider>
</canvas>

Click here to see this application in a popup with a different initial state applied. This is useful when creating links from outside the application, or for creating a bookmark for an application. Another thing to do is read browser cookies from JavaScript and pass in the parameters when calling the application.

7. Supporting Multiple Resolutions

7. Supporting Multiple Resolutions

Most web surfers have discovered the merits of high resolution displays. Some, though, still view the web using very low resolutions. OpenLaszlo applications can be written in a way that allows for multiple resolution versions of the same application to coexist using the same codebase.

Begin by creating your application in a library file instead of a canvas, but use $once{} constraints to size the visual elements relative to the canvas.

Example 35.14. Multi-canvas application library

mainApplication.lzx:
<library>
  <tabslider width="$once{canvas.width - 20}" x="10" y="10"
             height="$once{canvas.height - 20}"
             spacing="2" slideduration="300">
    <tabelement name="one" text="Tabelement One"/>
    <tabelement name="two" text="Tabelement Two"/>
    <tabelement name="three" text="Tabelement Three"/>
  </tabslider>
</library>

Then create multiple files, one for each canvas area you wish to support.

Example 35.15. Multi-canvas application files

mainApplication640.lzx:
<canvas width="640" height="480">
  <include href="mainApplication.lzx"/>
</canvas>

mainApplication800.lzx:
<canvas width="800" height="600">
  <include href="mainApplication.lzx"/>
</canvas>

mainApplication1024.lzx:
<canvas width="1024" height="768">
  <include href="mainApplication.lzx"/>
</canvas>

mainApplication1280.lzx:
<canvas width="1280" height="1024">
  <include href="mainApplication.lzx"/>
</canvas>

For real world deployments, you will want to set the size of your canvas to account for screen area occupied by browser controls. Selecting the correct application size requires some JavaScript to pick the right application when the visitor comes to the page. This example keys off the screen width which will help you determine with high accuracy the height of the client as well.

Example 35.16. Selecting the Correct Canvas


<script type="text/javascript">
var screenW = screen.width;
var screenH =0;
switch (true) {
  case (screenW >= 1280):
    screenW = 1280;
    screenH = 1024;
    break;
  case (screenW >= 1024):
    screenW = 1024;
    screenH = 768;
    break;
  case (screenW >= 800):
    screenW = 800;
    screenH = 600;
    break;
  default:
    screenW = 640;
    screenH = 480;
    break;         
}
lzEmbed({url: 'mainApplication'+screenW+'.lzx?lzt=swf', bgcolor: '#394660', width: screenW, height: screenH});        
</script>  

The LzBrowser.loadURL()() method is used to create a URL based on user input. This URL is then communicated to browser, where a JavaScript function is used to unpack it and fill in a form. The wrapper page is shown immediately below.

<canvas width="200" height="400">
    <attribute name="appear" type="number" value="0" />
    <handler name="onappear">
        win.show.doStart();
    </handler>

    <window id="win" title="Create and Account" 
            y="-300"
            width="180" height="300">
        <animator name="show" attribute="y" from="-300" to="50" 
                  start="false" relative="false" duration="500" />
        <animator name="hide" attribute="y" from="50" to="-300" 
                  start="false" relative="false" duration="500" />
        <simplelayout axis="y" spacing="4" />
         
        <state name="question" apply="true">
            <text multiline="true" width="160" align="center">
                It looks like you don't have an account. Would you 
                like to create one now?
            </text>
            <button>Go
                <handler name="onclick">
                    parent.question.remove();   
                    parent.signup.apply();
                </handler>
            </button>
        </state>
        <state name="signup">
            <text>Name:</text>
            <edittext />
            <text>Email:</text>
            <edittext />
            <text>Username:</text>
            <edittext name="uname"/>
            <text>Password:</text>
            <edittext name="pass" />
            <button>Create Account
                <handler name="onclick">
                    var s = "javascript:fillFormValues('"
                          + parent.uname.getText()
                          + "', '" + parent.pass.getText()
                          + "')";
                    LzBrowser.loadURL(s);
                    parent.hide.doStart();
                </handler>
            </button>
        </state>
    </window>    
</canvas>

8. Using the browser's "back" button

The LzHistory service allows you to preserve the state of your application, and then use the "back" button of the browser to move among such states.

The default wrapper page enables the back button and browser-to-LZX communication. Once you've got this, you can call the browser javascript method

        javascript:lzSetCanvasAttribute(attributeName, value)

to set a canvas attribute and send an event. The following example shows the way the LzHistory service works to move among states. To see the program in action, copy this code to your applications directory.

Example 35.17. Communicating with Back button

<canvas width="1400" height="600" debug="true">
  <debug width="500" height="500" fontsize="12" x="450"/>

  <!-- Click on blob START, STATE_2, STATE_3, END, then click browser back button -->

  <!-- a blob you can click on to select -->
  <class name="blob" extends="drawview" width="100" height="100">
    <attribute name="title" type="text"/>
    <attribute name="selected" type="boolean" />
    <text y="20" x="4" fontsize="16" text="${parent.title}"/>
    <handler name="onclick">
      this.setAttribute('selected', true);
      main.selectBlob(this);
    </handler>

    <method name="select" args="val">
      if (val) {
      // draw a border to select
        this.lineStyle = 0x000000;
        this.lineWidth = 4;
        moveTo(0,0);
        lineTo(0,100);
        lineTo(100,100);
        lineTo(100,0);
        lineTo(0,0);
        stroke();
      } else {
          clear();
      }
    </method>

  </class>

  <!-- A view with four states -->
  <view id="main" x="10" y="10"  layout="axis: x; spacing: 10">
    <attribute name="mystate" type="string" value="START"/>

    <blob bgcolor="#ffcccc" id="s1" title="START"/>
    <blob bgcolor="#ccccff" id="s2" title="STATE_2"/>
    <blob bgcolor="#ccffcc" id="s3" title="STATE_3"/>
    <blob bgcolor="#cccccc" id="s4" title="END"/>

    <method name="unselectAll">
      s1.select(false);
      s2.select(false);
      s3.select(false);
      s4.select(false);
    </method>

    <method name="selectBlob" args="blob">
       // save the old state
     LzHistory.next();
      setAttribute('mystate', blob.title);
      unselectAll();
      blob.select(true);
      recordState(blob.title);
    </method>

    <handler name="oninit">
      unselectAll();
      recordState("START");
      selectBlob(s1);
    </handler>

    <handler name="onmystate">
      Debug.write("got onmystate event: ", this.mystate);
      gotoState(this.mystate);
    </handler>

    <method name="recordState" args="newstate">
      this.mystate = newstate;
      LzHistory.save(this,'mystate', this.mystate);
      Debug.write("recordState", newstate)
    </method>

    <method name="gotoState" args="ns">
      unselectAll();
      if (ns == "START") {
          s1.select(true);
      } else if (ns == "STATE_2") {
          s2.select(true);
      } else if (ns == "STATE_3") {
          s3.select(true);
      } else if (ns == "END") {
          s4.select(true);
      }
    </method>
  </view>

</canvas>


9. Scaling OpenLaszlo Applications to Browser Dimensions

You can make the canvas of the OpenLaszlo application either a fixed size, or you can make it expand or contract as the browser window expands or contracts. To make the canvas an absolute size, set its height and width with integer values, for example, width="40" height="40". To have the canvas scale, simply express the height and width as percentages.

10. Browser Limitations

Intercepting keystrokes can pose some challenges. The keys that are intercepted varies from browser to browser, but anything that the browser defines will not be passed to your OpenLaszlo application. Mozilla Firefox seems to do this correctly, but many browsers won't pass alt and ctrl key combinations down to the application. The application below should demonstrate this, [Ctrl-n] in many browsers is the command you use to open a new browser window. In Mozilla Firefox, you can get the debugger to print the right message, but only if your mouse is floating over the application.

<canvas height="130" debug="true">
  <command oninit="this.setKeys(['Control','n'])"
           onselect="Debug.write('the [Ctrl-n] key combination was pressed');" />
</canvas>