QxTransformer Wiki

Example project

The information on this page is only informational and does not correspond to the current release (0.3alpha1).


Now it is time to set up your own project. Let’s say it is your job to create an application that allows to view the program of a big conference online. For convenience,we’ll call that application simply ‘conference’. Go to the qxtransformer folder and issue the following commands:

cd ../qxtransformer # go to folder if your are still in the showcase folder
./create-project conference --backend=php

This will create the skeleton of your new project which with a PHP backend. If you want to create an application with a java or perl backend, issue the create-project command with –backend=java or –backend=perl.

The file path structure of your projects then looks like this:

|-- conference/
|-- backend/
| `-- php/
| `-- services/
|-- frontend/
| `-- source/
| |-- class/
| |-- resource/
| |-- script/
| `-- xml/
| `-- Application.qxml
`-- Makefile

In most cases, the shell script sets up the makefile correctly. However, you might have to adapt the path to the qooxdoo installation manually and/or configure the build options. Please consult the qooxdoo documentation for details.

The application will consist of two main parts:

  • a main application window that consists of a toolbar with buttons and a split pane separating a tree with the conference sessions and two panes displaying details on the sessions and the papers.
  • a search window which will allow the user to type in search phrases and to retrieve sessions and papers that contain the phrases

We will implement the main window as a <qx:application> and the search window as a separate class.

Let’s do the main window first.

+------------------------+
| toolbar |
+------+-----------------+
| | |
| tree | session details |
| | |
| +-----------------+
| | |
| | paper details |
| | |
+------+-----------------+

Main application and toolbar

First, we create a file conference/frontend/source/xml/conference.Application.qxml and implement the main application and the tool bar:

<?xml version="1.0" encoding="utf-8"?>
<qx:root xmlns:qx="http://www.qooxdoo.org"
xmlns:qxt="http://www.qxtransformer.sourceforge.net/extension"
qooxdooVersion="0.7">

 
<!-- application -->
 
<qx:application title="Conference Program viewer"
namespace="conference" authors="Christian Boulanger">

 
<qx:verticalBoxLayout width="100%" height="100%">

<!-- toolbar -->
<qx:toolBar width="100%" id="toolBar">

<qx:label width="1*" top="5" left="10">
<b>Conference Program Viewer</b>
</qx:label>

<qx:toolBarButton
id="searchButton"
text="Search Program" icon="icon/22/actions/zoom.png" tooltip="Search the program for papers, sessions and people">

<qx:eventListener type="execute" dispatchMessage="conference-showSearchWindow"/>
</qx:toolBarButton>
 
</qx:toolBar>

The button dispatches a message when clicked that we will handle later.

Split pane and tree

<qx:horizontalSplitPane 
firstSize="300" secondSize="1*"
top="0" left="0" width="100%" height="1*"
border="inset" showKnob="true">

 
<qx:leftPane>

<qx:virtualTree
id="sessionTree"
left="0" top="0" width="100%" height="100%"
backgroundColor="white"

dataBinding="true"
serviceUrl="../backend/php/services/index.php"
serviceName="lsa2007.program"
serviceMethodUpdateClient="getSessionTree"
onAppear="this.updateClient()">


<qx:virtualTreeColumn heading="Sessions" width="1*"/>

This creates the splitpane and a treeVirtual widget in the splitpane’s left pane. Note how we set up databinding for the virtual tree that is triggered when the tree first appears. The data sent by the server looks like this:

[ { treedatamodel :  [ { isBranch : true, parentNodeId : 0, label: "First Day", ... ] } ]

For each node that should be appended to the tree, the server needs to add an object that can be added directly by the TreeDataModel. To distinguish between tree leafs and tree branche, use the additional “isBranch” property, which, if true, adds a Branch instead of a Leaf. You can also attach custom properties this way, which can be retrieved by the getState() method.

In our case of the conference program viewer, the server sends tree branches for the days (Wednesday, Thursday, ...) and time periods (8:30-10:00, 10:00-11:30 etc.), and tree leafs for the actual sessions.

Now we want to add a behavior to the tree. Whenever the user clicks on a branch (day or time period), the tree should load the subnodes (branches or leafs). When the user clicks on the leafs (sessions) , the application should load the pane with details on the session.

We are adding an event listener to implement this behaviour:

        <qx:eventListener type="changeSelection">
var node = event.getData()[0];
if ( node.type == qx.ui.treevirtual.SimpleTreeDataModel.Type.LEAF )
{
// session, load session data
qx.event.message.Bus.dispatch( new qx.event.message.Message("conference-loadSession",node.sessionNo ) );
}
else if ( ! node.bOpened )
{
// folder, load children
this.updateClient( {parentId:node.nodeId,label:node.label} );
}
</qx:eventListener>

</qx:virtualTree>
 
</qx:leftPane>

HTML display panes

The right pane contains a vertical split pane with an upper and bottom pane, which contain labels with details on the sessions and papers in the session.

<qx:rightPane>
 
<qx:verticalSplitPane
firstSize="1*" secondSize="1*"
top="0" left="0" width="100%" height="100%" showKnob="true">


<qx:topPane>

<qx:label
id="sessionLabel" scope="global"
width="100%" height="100%" padding="10"
wrap="true" textOverflow="false" overflow="auto"
border="inset-thin"
dataBinding="true"
serviceUrl="../backend/php/services/index.php"
serviceName="conference.program"
serviceMethodUpdateClient="getSessionDetails">


<div>Click on session on the left to display session details.<br/>Built with <a href="http://www.qooxdoo.org" target="blank">qooxdoo</a> and <a href="http://qxtransformer.sourceforge.net" target="blank">QxTransformer</a>.</div>

<qx:messageSubscriber filter="conference-loadSession">
var sessionNo = message.getData();
this.setUserData("sessionNo",sessionNo);
this.updateClient(sessionNo);
</qx:messageSubscriber>

</qx:label>
</qx:topPane>

You can see here how we can add html code directly to the label - note that it must be written in one long line without line break!

We have added a message subscriber which will update the label whenever a “conference-loadSession” message is dispatched and save the session number in the user data.

The paper details pane is implemented similarly:

        <qx:bottomPane>
<qx:label
id="paperLabel" scope="global"
width="100%" height="100%" padding="10"
wrap="true" textOverflow="false" overflow="auto"
border="inset-thin"
text="Click on paper on to display details here"
dataBinding="true"
serviceUrl="../backend/php/services/index.php"
serviceName="conference.program"
serviceMethodUpdateClient="getPaperDetails">


<qx:messageSubscriber filter="conference-loadPaper">
var paperNo = message.getData();
this.updateClient(paperNo);
</qx:messageSubscriber>
</qx:label>
</qx:bottomPane>
</qx:verticalSplitPane>
</qx:rightPane>
</qx:horizontalSplitPane>
</qx:verticalBoxLayout>

"Loading..." popup

Finally, we’ll do a little popup which appears whenever a server request is started and displays until the request has been answered. This can be easily done by listening to the messages dispatched by the data manager extension.

<qx:popup
height="auto" width="auto" autoHide="false"
onAppear="this.centerToBrowser()">

<qx:atom
border="outset-thin"
padding="10"
label="Loading, please wait..."
backgroundColor="white">


<qx:messageSubscriber filter="datamanager-rpc-*">
<![CDATA[
var status = message.getName();
var timestamp = message.getData()
var queue = this.getUserData("queue") || [];
switch ( status )
{
case "datamanager-rpc-start":
queue.push(timestamp);
break;

case "datamanager-rpc-end":
for (var i=0; i<queue.length; i++)
{
if (queue[i]==timestamp)
{
queue.splice(i,1);
}
}
break;
}
this.setUserData("queue",queue);
if (queue.length) {
this.getParent().show();
} else {
this.getParent().hide();
}
]]>

</qx:messageSubscriber>
</qx:atom>
</qx:popup>

As you can see in this code, the datamanager dispatches “datamanager-rpc-start” and “datamanager-rpc-start” with the timestamp of the corresponding request (There is also a “datamanager-rpc-object” which dispatches the timestamp with a reference to the request object itself as soon as it is available, but we don’t need this here).

Business logic example

We still need to implement a bit of event code that adds the desired behavior to our application:

<!-- search window -->
<qx:messageSubscriber filter="conference-showSearchWindow">
<![CDATA[
if (window.searchWindow) {
searchWindow.getWidget().show();
} else {
searchWindow = new conference.SearchTool;
}
]]>

</qx:messageSubscriber>
 
</qx:application>
</qx:root>

On receiving a “conference-showSearchWindow” message, this subscriber creates an instance of the search window (see below) or displays it in case it already exists. Of course, we could have but this code directly into the button that dipatches the message, but we’ll use this as an example how to move event handling code out of the gui description.

Search window

Let’s move on to the search window.

<?xml version="1.0" encoding="utf-8"?>
<qooxdoo
xmlns:qx="http://www.qooxdoo.org"
xmlns:qxt="http://www.qxtransformer.sourceforge.net/extension"
qooxdooVersion="0.7">

 
<qx:widget namespace="lsa2007" className="SearchTool"
title="Search Papers/Sessions" authors="Christian Boulanger">

 
<qx:window
id="formWindow"
width="800" height="auto"
caption="Search Papers/Sessions"
showMinimize="true" showMaximize="true" showClose="true"
modal="false" display="true" onAppear="this.centerToBrowser()">

That was easy. The windwo should have a tool bar which contains the search options, a text field where the user can type in search text, and a table with the search results.

<qx:verticalBoxLayout width="100%" height="100%" spacing="2">
 
<qx:horizontalBoxLayout width="100%" height="auto">

<!-- search options -->

<qx:toolBar width="100%" height="30"
id="searchOptions">


<qx:atom
icon="icon/16/actions/edit-find.png"
label="Search"
padding="2" horizontalAlign="center"
verticalAlign="middle" />


<qx:radioManager id="searchType" >
<qx:toolBarRadioButton text="All"
checked="true" />

<qx:toolBarRadioButton text="Sessions" />
<qx:toolBarRadioButton text="Papers" />
<qx:toolBarRadioButton text="People" />
</qx:radioManager>

<qx:toolBarSeparator/>

<qx:radioManager id="searchScope">
<qx:toolBarRadioButton text="Titles only" />
<qx:toolBarRadioButton text="Titles and
Abstracts" checked="true"/>

</qx:radioManager>

</qx:toolBar>

</qx:horizontalBoxLayout>

<!-- search bar -->
<qx:horizontalBoxLayout width="100%" height="30" spacing="5">

<qx:textField id="searchText" width="1*"
onAppear="this.setFocused(true)" />


<qx:button
label="Search" icon="icon/16/actions/zoom.png">

<qx:eventListener type="execute"
dispatchMessage="conference-updateSearchResults"/>

</qx:button>

</qx:horizontalBoxLayout>

This piece of code should be pretty much self-explanatory. Note that the <qx:toolBarRadioButton> elements need to be wrapped in a <qx:radioManager>.

The table displaying the search results is a qx.ui.table.Tabe widget which has a specific xml syntax in QxTransformer:

<qx:table 
id="searchResultTable" scope="global"
tableModel="simple"
width="100%" height="200"
border="inset-thin"

dataBinding="true"
serviceUrl="../backend/php/services/index.php"
serviceName="lsa2007.program"
serviceMethodUpdateClient="search">


<qx:tableColumn label="Type" width="10%"/>
<qx:tableColumn label="Text" width="80%"/>
<qx:tableColumn label="Session" width="10%"/>

As you can see, we need to supply a tableModel (”simple”= qx.ui.table.tablemodel.Simple is the only one currently supported) and can specify label and width for the individual columns.

The table has databinding turned on and expects the following data structure from the server on “updateClient()”:

{ 'tabledatamodel' : [ [ "Type Row 1","Text Row 1","Session Row 1" ], [ "Type Row 2","Text Row 2","Session Row 2" ], ... ] }

As you can see, databinding in this case simply replaces the whole content of the table. Support for other (remote) tableModels will be implemented later. In our case, we do not need functionality for updating the server (for example, if a table cell is changed). Such behavior is not yet implemented, but will soon be.

  

Now we want to specify when and how the table should be updated.

    <qx:messageSubscriber filter="conference-updateSearchResults">
this.updateClient({
'searchText' : searchText.getValue(),
'searchType' : searchType.getSelected().getLabel(),
'searchScope': searchScope.getSelected().getLabel()
});
</qx:messageSubscriber>

The server method which does the actual searching needs some information which is supplied by the options in the toolbar and the text fields.

Finally, we need to tell the table what should be done when the user clicks on a table row.

    <!-- event listener "changeSelection" needs modifier since 
it is not dispatched on widget level - this should change, by the way! -->

<qx:eventListener type="changeSelection"
qxt:modifier=".getSelectionModel()">

<![CDATA[
var data = [];
searchResultTable.getSelectionModel().iterateSelection(function(index) {
data.push(searchResultTable.getTableModel().getRowData(index));
});
var sessionNo = data[0][2];
if ( parseInt(sessionNo) )
{
qx.event.message.Bus.dispatch(
new qx.event.message.Message("conference-loadSession", sessionNo )
);
}
]]>

</qx:eventListener>
</qx:table>
</qx:verticalBoxLayout>
</qx:window>
</qx:widget>
</qooxdoo>

What this code does is to get the session number which is in the third column of the row on which the user has clicked, and then tell the label with the session details to update itself with the session number.

  Sign in   Home   Sitemap   Terms   Report Abuse   Print  |  Powered by Google Sites