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.