Hello,
here is my first try at a PHP class which can generate QooxDoo
JavaScript from QxBuilder XML files. It is still very basic and
incomplete, but you can see where I am going.
Suggestions are very much appreciated. Here are two example conversions:
<?xml version="1.0" encoding="utf-8"?>
<qx:widgets xmlns:qx="http://qooxdoo.sourceforge.net">
<qx:splitPane id="bg_horizonatlSplitpane"
orientation="horizontal"
top="0" left="0"
width="100%" height="100%"
oneTouchExpandable="false"
dividerLocation="100"
minimumDividerLocation="10"
firstWidget="bg_treepane"
useFirstChildAs="firstWidget"
useSecondChildAs="secondWidget">
<qx:label html="Left Pane"/>
<qx:splitPane id="bg_verticalSplitpane"
orientation="vertical"
top="0" left="0"
width="100%" height="100%"
oneTouchExpandable="false"
dividerLocation="100"
minimumDividerLocation="10"
useFirstChildAs="firstWidget"
useSecondChildAs="secondWidget">
<qx:label html="Top Pane"/>
<qx:label html="Bottom Pane"/>
</qx:splitPane>
</qx:splitPane>
</qx:widgets>
becomes
var qx_1 = bg_horizonatlSplitpane = new QxSplitPane("horizontal");
var qx_2 = bg_treepane = new QxLabel("Left Pane");
window.application.add(qx_2);
var qx_3 = bg_verticalSplitpane = new QxSplitPane("vertical");
window.application.add(qx_3);
var qx_4 = new QxLabel("Top Pane");
window.application.add(qx_4);
var qx_5 = new QxLabel("Bottom Pane");
window.application.add(qx_5);
qx_3.set({top:0,left:0,width:"100%",height:"100%",oneTouchExpandable:false,dividerLocation:100,minimumDividerLocation:10,firstWidget:qx_4,secondWidget:qx_5});
qx_1.set({top:0,left:0,width:"100%",height:"100%",oneTouchExpandable:false,dividerLocation:100,minimumDividerLocation:10,firstWidget:qx_2,secondWidget:qx_3});
window.application.add(qx_1);
and
<?xml version="1.0" encoding="utf-8"?>
<qx:widgets xmlns:qx="http://qooxdoo.sourceforge.net">
<qx:menuBar id="app_menubar" width="100%">
<qx:menuBarButton height="16" label="File"
useFirstChildAs="menu">
<qx:menu>
<qx:menuButton label="Open"/>
<qx:menuButton label="Save"/>
<qx:menuButton label="Close"/>
</qx:menu>
</qx:menuBarButton>
<qx:menuBarButton height="16" label="Edit"
useFirstChildAs="menu">
<qx:menu>
<qx:menuButton label="Copy"/>
<qx:menuButton label="Cut"/>
<qx:menuButton label="Paste"/>
</qx:menu>
</qx:menuBarButton>
</qx:menuBar>
</qx:widgets>
becomes
var qx_1 = app_menubar = new QxMenuBar();
var qx_2 = new QxMenuBarButton("File");
var qx_3 = new QxMenu();
window.application.add(qx_3);
var qx_4 = new QxMenuButton("Open");
var qx_5 = new QxMenuButton("Save");
var qx_6 = new QxMenuButton("Close");
qx_3.add(qx_4,qx_5,qx_6);
qx_2.set({height:16,menu:qx_3});
var qx_7 = new QxMenuBarButton("Edit");
var qx_8 = new QxMenu();
window.application.add(qx_8);
var qx_9 = new QxMenuButton("Copy");
var qx_10 = new QxMenuButton("Cut");
var qx_11 = new QxMenuButton("Paste");
qx_8.add(qx_9,qx_10,qx_11);
qx_7.set({height:16,menu:qx_8});
qx_1.add(qx_2,qx_7);
qx_1.set({width:"100%"});
window.application.add(qx_1);
Judge which one is nice to code ;-)
Since the QooxDoo API does not always model a parent-child
relationship ( because many times, child widgets are attached
by properties or methods rather than the generic "add" method),
there needs to be quite a bit of ugly hacking - still unfinished.
I will update the PHP QxBuilder while I am working on my app. Anyone
like to contribute?
Greetings,
Christian
<?php
/**
* Server-Side QxBuilder Class
*
* @author Christian Boulanger
* @version 0.1
* @todo -implement QxEventListener delegate
*
* Careful, this class behaves differently from the Javascript QxBuilder
*
* Additional parse instructions via attributes:
* forceAdd= "true" forces adding of previously created widgets
* to parent widget
* args="attr1[,attr2[,..]]" specifies which attributes to provide as
* arguments to the constructor function.
* this is needed because some QxWidget
* require constructor arguments to be correctly
* built.
* useFirstChildAs="attr1" Use the first/second child node as an attribute
argument
* useSecondChildAs="attr2 and don't add it to the parent. useful to keep a
* logical parent-child order when the qooxdoo API
doesn't
* work that way (for example, in menus). property
* will be set to the parent after the child
widget has been
* created.
*
*
* useage:
* $qxbuilder = new QxBuilder ( $xml ); // or use QxBuilder ( $path,
"file" );
* $this->output = $qxbuilder->build();
*
**/
// dependencies
require_once "XML/XPath.php";
// constants
define ( "QX_BUILDER_EVAL_ATTR" ,
"color,backgroundColor,border,menu,manager,group" );
class QxBuilder
{
/**
* debug mode
**/
var $debug = false;
/**
* prefix for anonymous variable counter
**/
var $varprefix = "qx_";
/**
* counter for anonymous javascript variables
**/
var $varcounter = 0;
/**
* constructor arguments for common QxWidget Types
* needs to be updated from the QooxDoo API.
* you can manually specify the constructor arguments by
* providing an "args" attribute with the names of attributes
* to use as arguments, for example args="html,icon"
* var @array
**/
var $qxConstructorArgs = array (
QxLabel => "html",
QxSplitPane => "orientation",
QxMenuBarButton => "label",
QxMenuButton => "label,icon"
);
/**
* parent objects
* @var array
**/
var $_parent = array();
/**
* constructor
* @param mixed $xml xml string or path to xml file
* @param string type default string
**/
function QxBuilder ( $xml, $type="string" )
{
switch ( $type ){
case "file":
$this->xml = file_get_contents ( $xml );
break;
default:
$this->xml = $xml;
}
}
/**
* builds and returns javascript
* @return string javascript code
**/
function build ()
{
$xmldoc = new XML_XPath();
$xmldoc->load($this->xml);
$nodes = $xmldoc->evaluate('./*');
$parent = "window.application";
return $this->_buildNode ( $nodes, $parent );
}
/**
* remembers javascript widget objects created on the client
**/
function _registerWidget ( $name )
{
if ( $this->_checkIfCreated ( $name ) ) return;
$_SESSION['qx_widgets_created'][] = $name;
}
/**
* checks if widget has been created
**/
function _checkIfCreated ( $name )
{
return ( @array_search( $name, $_SESSION['qx_widgets_created']
) !== false );
}
/**
* checks if argument / value need quotations marks (strings)
* or not (numbers, objects)
* @param mixed $arg
* @return bool true if string
**/
function _isString ( $arg )
{
return ! ( is_numeric ( $arg ) or
$arg == "false" or $arg == "true" or
substr ( $arg, 0, 2 ) == "Qx" or
$this->_checkIfCreated( $arg ) );
}
/**
* gets current anonymous variable
**/
function _getCurrentVar()
{
return $this->varprefix . $this->varcounter;
}
/**
* gets current anonymous variable and increase count
* register widget as created
**/
function _getNewVar()
{
$id = $this->varprefix . ++$this->varcounter;
$this->_registerWidget($id);
return $id;
}
/**
* gets next anonymous variable without increasing count
**/
function _getNextVar($offset=1)
{
$id = $this->varprefix . ( $this->varcounter + $offset );
return $id;
}
/**
* converts a qooxdooXML node into javascript
* @param PEAR_XML_RESULTSET Object $nodes
* @param string $parent name of parent object
**/
function _buildNode ( &$nodes, $parent )
{
$output = "";
$objectsToAdd = array();
$nodes->rewind();
while($nodes->next()){
$nodeName = $nodes->nodeName();
if ( $nodeName == "#comment" ) continue;
$className = "Qx" . strtoupper ( substr (
$nodeName,0,1 ) ) . substr ( $nodeName, 1);
$varName = $this->_getNewVar();
$attributes = $nodes->getAttributes();
$constrArgs = either ( $attributes['args'],
$this->qxConstructorArgs[$className],
"" );
$args = "";
// use first/second child as attribute argument (see class doc)
if ( $attr = $attributes['useFirstChildAs'] ) {
$firstChild = $this->_getNextVar();
$attributes[$attr] = $firstChild;
$this->_parent[$firstChild] = "window.application";
unset ( $attributes['useFirstChildAs'] );
}
if ( $attr = $attributes['useSecondChildAs'] ) {
$secondChild = $this->_getNextVar(2);
$attributes[$attr] = $secondChild;
$this->_parent[$secondChild] = "window.application";
unset ( $attributes['useSecondChildAs'] );
}
// constructor arguments
if ( count ( $constrArgs = explode ( ",", $constrArgs )
) ) {
foreach ( $constrArgs as $i => $key ) {
$value = $attributes[$key];
if ( ! empty ( $value ) ) {
if ( $this->_isString ( $value ) ) {
$constrArgs [$i] = '"' . $value . '"';
} else {
$constrArgs [$i] = $value;
}
unset ( $attributes[$key] );
} else {
unset ( $constrArgs[$i] );
}
}
$args = implode (",", $constrArgs );
}
if ( $this->debug ) {
$output .= "$parent.debug ('$varName :
$className(" . addslashes($args) . ")');\n";
}
switch ( $className ) {
case "QxScript":
$code = preg_replace (
"/(<!--)|(-->)|(<\/?qx:[^>]*>)/","", $nodes->toString() );
$output .= $code . "\n";
break;
case "QxEventListener":
// todo: delegates
$code = preg_replace (
"/(<!--)|(-->)|(<\/?qx:[^>]*>)/","", $nodes->toString() );
$event = either ( $attributes['args'],
"event" );
$type = $attributes['type'];
$output .=
"$parent.addEventListener('$type',function($event){\n$code\n});\n";
break;
default:
// create instance
if ( $id = $attributes['id'] ) {
// pass to global variable
$instance = "var $varName = $id
= new $className($args);\n";
$this->_registerWidget($id);
} else {
$instance = "var $varName = new
$className($args);\n";
}
$output .= $instance;
// add to parent
if ( $this->_parent[$varName] ) {
$output .= $this->_parent[$varName] .
".add($varName);\n";
} else {
$objectsToAdd[] = $varName;
}
// add children
if ( !PEAR::isError ( $children = $nodes->childNodes() ) )
{
$output .= $this->_buildNode (
$children, $varName);
}
// add attributes
$attSet = array();
$eventListeners = "";
if ( count ( $attributes ) ){
foreach ( $attributes as $key
=> $value ){
// key types
if ( $key == "id" or
$key == "args" ) {
continue;
} elseif ( $key ==
"forceAdd" ) {
if ( $value ==
"true" and count ( $objectsToAdd ) ) {
$output .= "$parent.add(" . implode (",",
$objectsToAdd ) . ");\n";
$objectsToAdd = array();
}
continue;
} elseif ( ereg(
"on[A-Z]", substr ($key,0,2) ) ) {
$type =
strtolower ( substr ( $key,2 ) );
$eventListeners
.= "$varName.addEventListener('$type',function(event)\{$value});\n";
continue;
} elseif ( strstr(
QX_BUILDER_EVAL_ATTR, $key ) ) {
$attSet[] =
"$key:$value";
continue;
}
// value type
if ( $this->_isString (
$value ) ) {
$attSet[] =
"$key:\"$value\"";
} else {
$attSet[] =
"$key:$value";
}
}
if ( $this->debug ) {
foreach ( $attSet as
$attr ) {
$output .=
"$varName.debug('$attr')\n";
$output .=
"$varName.set(\{$attr});\n";
}
} elseif ( count ( $attSet ) ) {
$output .=
"$varName.set({" . implode ( ",",$attSet ) . "});\n" ;
}
if ( $eventListeners ) $output
.= $eventListeners;
}
break;
}
}
if ( count ( $objectsToAdd ) ) {
$output .= "$parent.add(" . implode (",", $objectsToAdd
) . ");\n";
$objectsToAdd = array();
}
return $output;
}
}
if ( ! function_exists ( "either" ) ){
/**
* avoids if statements such as if($a) $c=$a; else $c=$b;
*
* @argument mixed
* @argument mixed ...
* @return first non-false argument, otherwise false
*/
function &either()
{
$arg_list = func_get_args();
foreach($arg_list as $i => $arg)
if ( $arg ) return $arg_list[$i];
return false;
}
}
?>