Posts Tagged ‘qooxdoo’

Using Layouts In Qooxdoo – Part 2: VBox Layout

Wednesday, February 4th, 2009

This is the second part of a tutorial series about layout managers, container objects and object hierarchies in Qooxdoo. Read the first part here.

qx.ui.layout.VBox is a basic layout manager that places child widgets on top of each other.

VBox Layout

VBox Layout

Let’s see how VBox lays out components on a simple application1. All the necessary code is below:

   1 /*
   2 #asset(vbox/*)
   3 */
   4 qx.Class.define("vbox.Application",
   5 {
   6     extend: qx.application.Standalone,
   7     members: {
   8         main: function(){
   9             this.base(arguments);
  10 
  11             var main_container = new qx.ui.container.Composite();
  12 
  13             var layout_manager = new qx.ui.layout.VBox();
  14             main_container.setLayout(layout_manager);
  15 
  16             main_container.add(new qx.ui.form.Button("Child Widget 1"));
  17             main_container.add(new qx.ui.form.Button("Child Widget 2"));
  18             main_container.add(new qx.ui.form.Button("Child Widget 3"));
  19 
  20             var application_root = this.getRoot();
  21             application_root.add(main_container);
  22         }
  23     }
  24 });
VBox Example 1

VBox Example 1

I told you VBox is basic; child widgets are stacked vertically in order they have added. We haven’t set any preferences (constraints), so it’s all defaults. Buttons get an optimal width depending on the length of their label. The container main_container applies this width to itself, it conforms its children’s dimensions.

Try giving one of the buttons a longer label. Now the modified button is wider, that was expected. But the others are widened as well. That is because they have conformed (flexed) their container’s width. We will see how this works in detail.

Let’s set the container’s size same as the viewport’s:

   1 /*
   2 #asset(vbox/*)
   3 */
   4 qx.Class.define("vbox.Application",
   5 {
   6     extend: qx.application.Standalone,
   7     members: {
   8         main: function(){
   9             this.base(arguments);
  10 
  11             var main_container = new qx.ui.container.Composite();
  12             main_container.setWidth(qx.bom.Viewport.getWidth());
  13             main_container.setHeight(qx.bom.Viewport.getHeight());
  14 
  15 
  16             var layout_manager = new qx.ui.layout.VBox();
  17             main_container.setLayout(layout_manager);
  18 
  19             main_container.add(new qx.ui.form.Button("Child Widget 1"));
  20             main_container.add(new qx.ui.form.Button("Child Widget 2"));
  21             main_container.add(new qx.ui.form.Button("Child Widget 3"));
  22 
  23             var application_root = this.getRoot();
  24             application_root.add(main_container);
  25         }
  26     }
  27 });
VBox Example 2

VBox Example 2

Notice that only width seems to be adjusted, even though we have set both dimensions. Actually, it worked as we intended to; main_container now fills the entire viewport. But by default VBox won’t flex child widgets vertically. We will start overriding this behaviour in a minute.

Try setting alignY property to “center” or “bottom” on the layout manager:

layout_manager.setAlignY("bottom");

So far we have set properties (constraints) on containers (main_container.width & main_container.height) and on layout managers (layout_manager.alignY). In the next example we will set properties on child widgets:

   1 /*
   2 #asset(vbox/*)
   3 */
   4 qx.Class.define("vbox.Application",
   5 {
   6     extend: qx.application.Standalone,
   7     members: {
   8         main: function(){
   9             this.base(arguments);
  10 
  11             var main_container = new qx.ui.container.Composite();
  12             main_container.setWidth(qx.bom.Viewport.getWidth());
  13             main_container.setHeight(qx.bom.Viewport.getHeight());
  14 
  15             var layout_manager = new qx.ui.layout.VBox();
  16             main_container.setLayout(layout_manager);
  17 
  18             main_container.add(new qx.ui.form.Button("Child Widget 1"), {flex: 1});
  19             main_container.add(new qx.ui.form.Button("Child Widget 2"), {flex: 1});
  20             main_container.add(new qx.ui.form.Button("Child Widget 3"), {flex: 1});
  21 
  22             var application_root = this.getRoot();
  23             application_root.add(main_container);
  24         }
  25     }
  26 });
VBox Example 3

VBox Example 3

We have added a second parameter to the add method of layout manager. This JavaScript object literal is used to set widget specific properties on the layout manager.

Flex property determines how the extra space2 is distributed between children. By default it is set to 0, which means that the widget will not grow vertically. Any other (integer) value for flex will cause growing to occupy the extra space. Growing is proportional to flex value. Try different flex values for different widgets and see the changes.

NEXT PART: HBox Layout


1: If you haven’t created a qx application before, it is explained in detail in the tutorial.

2: Remaining space after initial dimensions of children are computed.

Bookmark and Share

Using Layouts In Qooxdoo – Part 1

Friday, January 30th, 2009

This is the first part of a tutorial series about layout managers, container objects and object hierarchies in Qooxdoo (qx for short from here on). Its target audience is mostly qx programmers coming from an (X)HTML/CSS/DOM background who have possibly used JavaScript libraries such as JQuery or prototype before. Qx is different, it is a JavaScript RIA Framework. If you have used a GUI toolkit such as Swing (Java), wxWidgets or GTK+ you should have no difficulty following this tutorial. In fact you will probably find it boring1. On the other hand if you are still trying to find out how you can attach your <div /> in an existing qx layout; you are in the right place.

The answer is “No”, to the question above. You don’t create DOM elements with qx (unless you are doing something really exotic). Actually you don’t need to concern yourself with the DOM at all. GUI is abstracted to a set of intuitive classes that you should be familiar from today’s desktop operating systems. This abstraction brings two big advantages:

  • Developers using qx don’t need to concern themselves with cross-browser issues.
  • Theming is easy and uniform.

Widgets Are The Building Blocks

A qx user interface is constructed using widgets. A widget is a class that encapsulates appereance, data and behaviour. For instance a TextField would render itself as a box and a caret, make the text you enter inside accessible programmatically and allow you to be notified whenever a the text data is changed. Widgets are customizable, subclassable and of course themable.

But a GUI hardly ever consists of a single widget. Instead there is a hierarchy of widgets. A container is a special kind of widget that has child widgets. A container widget ususally doesn’t have any visual parts itself and just render its children in its screen space. How child widgets are positioned within their container is decided by the container’s layout manager. Having a layout manager means the abstraction of layout strategy for maximum flexibility.

So how do containers, child widgets and layout managers work together? First of all qx positions everything with absolute coordinates (Yes; position:absolute) internally. But that doesn’t mean you need to position anything absolutely on the API level. Speaking in terms of JavaScript libraries; you can take advantage of floating/auto-sizing/liquid layouts. Position and size of all widgets are negotiated through this hierarchy. Theoretically everyone has a say. This means that you can set some constraints on parents and then some others on child widgets and they all work together. The resulting layout is then converted to absolute coordinates for rendering. (Examples in the next part)

Recap

  1. Qx GUI’s are hierarchically constructed from widgets.
  2. Each application has a root container.
  3. Each container has a layout manager and one or more child widgets2.
  4. A child of a container can be either:
    1. Another container.
    2. Or a control widget (such as a form widget).

NEXT PART: VBox Layout


1: You might want to see Qooxdoo – API documentation instead.

2: Although it is possible that a container may have no children, it is not sensible.

Bookmark and Share

Getting A Little Further Than Hello World With Qooxdoo

Friday, October 17th, 2008

I have mentioned about Rich Internet Applications in a previous post. Qooxdoo is an AJAX framework, especially strong on creating desktop-like GUI’s. It allows you to build your interface in an object oriented manner. Like tkinter or GTK, and much more than the others it is like swing.

Qooxdoo is well documented and has a clean API. It comes with a Python program to help with builds. Because it is such a big framework you test on a partially compiled source and when finished this build program generates a single (actually two, it also generates a loader), compact (and somewhat obfuscated) file for performance. I strongly advise you to give it a try. The following is a small introductory tutorial. It aims to go little further than Hello World. This is not a tutorial explaining object oriented programming concepts, I assume you are already fluent in OOP.

Create The Skeleton

Screenshot of finished application

Screenshot of finished application

We’ll create a simple calculator-like application. I am assuming you have downloaded (latest version is 0.8 for today) and extracted the source into a directory. Let’s call it qxtut. The first thing we will do is to create a skeleton of our application with the following command;

./qooxdoo-0.8-sdk/tool/bin/create-application.py --name basicmath

This command has created the following directory structure under ./basicmath;

qxtut/
  qooxdoo-0.8-sdk/
  basicmath/
    source/
    build/
    cache/
    api/
    config.json
    Manifest.json

Well, some of the directories (build & cache) are not there yet. But as we go on they will appear, I just wanted you to see how the application is organized. config.json and Manifest.json files are configuration files for the build tool. We don’t need to change them for this tutorial, but you are welcome to check their contents.

Let us build our source for the first time;

cd basicmath
./generate.py source

Now if you open ./source/index.html in your browser you can see a Hello World application in action. We’ll replace this with our own program. But before we proceed I’d like to point out a few things;

  • When we define our classes, we call a class method define on qx.Class and pass our class’ name (along with its namespace) and its contents as arguments. We don’t define our classes using prototypes, in order to take advantage of Qooxdoo’s object oriented programming features.
  • Qooxdoo supports single inheritance (with mixins). We define our base class, if we have one, using extend key.
  • Instance members are defined inside the members key and class members are defined inside the statics key.
  • construct and destruct are two special functions for initialization and cleanup of the class.
  • Qooxdoo supports [properties](http://en.wikipedia.org/wiki/Property_(programming)) as well, but it is outside of this tutorial’s scope.
  • Finally, “#asset(basicmath/*)” line tells the build program to include assets (images etc) in qxtut/basicmath/source/resource/basicmath directory.

Custom Classes

Let’s start building our application now. Here is a compact version of Application.js;

/* ************************************************************************
#asset(basicmath/*)
************************************************************************ */

qx.Class.define("basicmath.Application", {
    extend: qx.application.Standalone,
    members: {
        main: function()
        {
            this.base(arguments);
            if (qx.core.Variant.isSet("qx.debug", "on")) {
                qx.log.appender.Native;
                qx.log.appender.Console;
            }
            // Our code will come here
        }
    }
});

Now we will create a custom class named Operation. This class will take two operands and perform an operation on them, and then later we will add it the ability to report the result of the operation. Paste this as Operation.js;

/* ************************************************************************
#asset(basicmath/*)
************************************************************************ */

qx.Class.define("basicmath.Operation", {
    extend: qx.ui.container.Composite,
    construct: function() {
        this.base(arguments);
        var layout = new qx.ui.layout.HBox(6);
        this.setLayout(layout);
        this.operand1 = new qx.ui.form.TextField("0");
        this.operator = new qx.ui.form.SelectBox();
        this.operator.add(new qx.ui.form.ListItem("add"));
        this.operator.add(new qx.ui.form.ListItem("subtract"));
        this.operator.add(new qx.ui.form.ListItem("multiply"));
        this.operator.add(new qx.ui.form.ListItem("divide"));
        this.operand2 = new qx.ui.form.TextField("0");
        this.result = new qx.ui.basic.Label("0");
        this.add(this.operand1);
        this.add(this.operator);
        this.add(this.operand2);
        this.add(this.result);
    },
    members: {
        operand1: null,
        operator: null,
        operand2: null,
        result: null
    }
});

The code should be self explanatory. Notice here, we define operand1, operator, operand2 and result as members of the class. Also notice we initialize those members in the constructor and not in the class body1. This is because their respected values (classes) are derived from the non-primitive Object type. Therefore if we have assigned them a non-primitive type (such as the array [1, 2, 3]) in the members section; all instances would point to the same object.

Let us now plug this object in our application. Replace the comment line “// Our code will come here” with the following;

this.getRoot().add(new basicmath.Operation);

Now, when we compile the source and run index.html we should see our widgets in place.

Screenshot of an Operation widget we have just created

Screenshot of an Operation widget we have just created

Simple Behaviour

We want our widget to calculate the operation and show us the result. Let’s update the members section of Operation with the following;

members: {
    operand1: null,
    operator: null,
    operand2: null,
    result: null,
    updateResult: function() {
        var v1 = this.cleanField(this.operand1);
        var v2 = this.cleanField(this.operand2);
        var r;
        switch(this.operator.getValue()) {
            case "add": r = v1+v2; break;
            case "subtract": r = v1-v2; break;
            case "multiply": r = v1*v2; break;
            case "divide": r = v1/v2; break;
        }
        this.result.setContent(String(r));
        this.operand1.setValue(String(v1));
        this.operand2.setValue(String(v2));
    },
    cleanField: function(field) {
        var val = parseInt(field.getValue());
        return isNaN(val) ? 0 : val;
    }
}

We have added two functions here updateResult and cleanField. Now we make use of them, change the construct with the following;

construct: function() {
    this.base(arguments);
    var layout = new qx.ui.layout.HBox(6);
    this.setLayout(layout);
    this.operand1 = new qx.ui.form.TextField("0");
    this.operand1.addListener("input", this.updateResult, this);
    this.operator = new qx.ui.form.SelectBox();
    this.operator.add(new qx.ui.form.ListItem("add"));
    this.operator.add(new qx.ui.form.ListItem("subtract"));
    this.operator.add(new qx.ui.form.ListItem("multiply"));
    this.operator.add(new qx.ui.form.ListItem("divide"));
    this.operator.addListener("changeValue", this.updateResult, this);
    this.operand2 = new qx.ui.form.TextField("0");
    this.operand2.addListener("input", this.updateResult, this);
    this.result = new qx.ui.basic.Label("0");
    this.add(this.operand1);
    this.add(this.operator);
    this.add(this.operand2);
    this.add(this.result);
}

We just added these three listeners to update the result when an operand or the operator changes;

this.operand1.addListener("input", this.updateResult, this);
this.operator.addListener("changeValue", this.updateResult, this);
this.operand2.addListener("input", this.updateResult, this);

The last parameter (this) for addListener (even though it is sometimes unnecessary) set the scope within the listener code (the second parameter). Qooxdoo handles most of the binding automatically, I think this is included for flexibility.

Let’s compile and run again. The result of the operation should update as you change the values now.

Events To Tie All Together

I’ll give you the finished code first and then we can go over the details. Here is Operation.js;

/* ************************************************************************
#asset(basicmath/*)
#asset(qx/icon/Oxygen/*)
************************************************************************ */

qx.Class.define("basicmath.Operation", {
    extend: qx.ui.container.Composite,
    construct: function() {
        this.base(arguments);
        var layout = new qx.ui.layout.HBox(6);
        this.setLayout(layout);
        this.operand1 = new qx.ui.form.TextField("0");
        this.operand1.addListener("input", this.updateResult, this);
        this.operator = new qx.ui.form.SelectBox();
        this.operator.add(new qx.ui.form.ListItem("add"));
        this.operator.add(new qx.ui.form.ListItem("subtract"));
        this.operator.add(new qx.ui.form.ListItem("multiply"));
        this.operator.add(new qx.ui.form.ListItem("divide"));
        this.operator.addListener("changeValue", this.updateResult, this);
        this.operand2 = new qx.ui.form.TextField("0");
        this.operand2.addListener("input", this.updateResult, this);
        this.result = new qx.ui.form.TextField("0");
        this.result.setReadOnly(true);
        var close_button = new qx.ui.form.Button(
            null,
            "qx/icon/Oxygen/16/actions/application-exit.png"
        );
        close_button.addListener("execute", function(e) {
            this.fireDataEvent(
                "changeResult",
                0,
                parseFloat(this.result.getValue()),
                false
            );
            this.destroy();
        }, this);
        this.add(this.operand1);
        this.add(this.operator);
        this.add(this.operand2);
        this.add(new qx.ui.basic.Label("="));
        this.add(this.result);
        this.add(new qx.ui.core.Spacer(8));
        this.add(close_button);
    },
    events: {
        "changeResult": "qx.event.type.Data"
    },
    members: {
        operand1: null,
        operator: null,
        operand2: null,
        result: null,
        updateResult: function() {
            var v1 = this.cleanField(this.operand1);
            var v2 = this.cleanField(this.operand2);
            var r;
            switch(this.operator.getValue()) {
                case "add": r = v1+v2; break;
                case "subtract": r = v1-v2; break;
                case "multiply": r = v1*v2; break;
                case "divide": r = v1/v2; break;
            }
            this.fireDataEvent("changeResult",
                r,
                parseFloat(this.result.getValue()),
                false
            );
            this.result.setValue(String(r));
            this.operand1.setValue(String(v1));
            this.operand2.setValue(String(v2));
        },
        cleanField: function(field) {
            var val = parseInt(field.getValue());
            return isNaN(val) ? 0 : val;
        }
    }
});

And Application.js;

/* ************************************************************************
#asset(basicmath/*)
#asset(qx/icon/Oxygen/*)
************************************************************************ */

qx.Class.define("basicmath.Application", {
    extend : qx.application.Standalone,
    members : {
        main : function()
        {
            this.base(arguments);
            if (qx.core.Variant.isSet("qx.debug", "on")) {
                qx.log.appender.Native;
                qx.log.appender.Console;
            }
            var layout = new qx.ui.container.Composite(
                new qx.ui.layout.VBox(8)
            );
            var layout_footer = new qx.ui.container.Composite(
                new qx.ui.layout.HBox(6)
            );
            var total = new qx.ui.basic.Label("0");
            var add_button = new qx.ui.form.Button(
                "Add New",
                "qx/icon/Oxygen/16/actions/list-add.png"
            );
            add_button.addListener("execute", function(e) {
                var new_operation = new basicmath.Operation();
                layout.addBefore(new_operation, layout_footer);
                new_operation.addListener("changeResult", function(e) {
                    var old_total = parseFloat(total.getContent());
                    var new_total = old_total - e.getOldData() + e.getData();
                    total.setContent(String(new_total));
                }, this);
            }, this);
            layout_footer.add(add_button);
            layout_footer.add(total);
            layout.add(layout_footer);
            add_button.execute();
            this.getRoot().add(layout);
        }
    }
});

Let’s go top-down and begin with the changes in the Application.js;

var layout = new qx.ui.container.Composite(new qx.ui.layout.VBox(8));
this.getRoot().add(layout);

We don’t necessarily need to subclass everytime we need specialized behaviour. Since I had intented to re-use Operation I have it as a seperate class. But for the layout of the application I just instanciated some classes and tweaked them inside Application.main. layout here is the topmost widget, we will put everything else in it. Basically one or more Operation‘s and a footer to dispay the grand total. VBox layout is by the way a layout manager that stacks children vertically, and a HBox stacks horizontally.

var layout = new qx.ui.container.Composite(new qx.ui.layout.VBox(8));
var layout_footer = new qx.ui.container.Composite(new qx.ui.layout.HBox(6));
var total = new qx.ui.basic.Label("0");
var add_button = new qx.ui.form.Button(
    "Add New",
    "qx/icon/Oxygen/16/actions/list-add.png"
);
add_button.addListener("execute", function(e) {
    var new_operation = new basicmath.Operation();
    layout.addBefore(new_operation, layout_footer);
    new_operation.addListener("changeResult", function(e) {
        var old_total = parseFloat(total.getContent());
        var new_total = old_total - e.getOldData() + e.getData();
        total.setContent(String(new_total));
    }, this);
}, this);

We define a label total to hold the grand total of all operations and an add button for new operations. Notice the closures work on 7th line. Although we limit ourselves a little bit to take advantage of OOP, we are still in a dynamic environment. Finally we tie all these components together and finally add the layout to the application root.

add_button.execute();

This instantiates the first Operation for us. We execute the button instead of creating the widget programmatically to avoid the code duplication (see the “execute” listener on the add_button).

Now let’s take a look at the changes in Operation.js. I have replaced the result Label with a TextField (remember to run “generate.py source” each time dependencies change). I wanted to take advantage of the getOldData function on TextField‘s changeValue event. But appereantly it doesn’t supply the old data. But I kept it as a TextField anyway, setting it read-only.

Then I decided that Operation should signal for a result change (maybe this is more politically correct) and added a custom event changeResult on it.

events: {
    "changeResult": "qx.event.type.Data"
}

This event is fired inside Operation.updateResult;

this.fireDataEvent(
    "changeResult",
    r,
    parseFloat(this.result.getValue()),
    false
);

The second parameter is returned from e.getData() and the third is from e.getOldData(). Therefore we can calculate the grand total without iterating all Operations;

var new_total = old_total - e.getOldData() + e.getData();

An important point here is to fire changeResult to correct the grand total before we destroy an Operation;

close_button.addListener("execute", function(e) {
    this.fireDataEvent(
        "changeResult",
        0,
        parseFloat(this.result.getValue()),
        false
    );
    this.destroy();
}, this);

Wrapping Up

Now it should work correctly, if I haven’t made a typo of course. Let us build it with;

./generate.py build

It generates a loader (~150kb) and an application script (~400kb). You don’t need the Qooxdoo source anymore, you can just upload the contents of the build directory and your application would run.

This concludes my a little further than Hello World tutorial. If you find errors or typos, or have any questions please leave a comment here or contact me at muhuk@jabber.org.


1: Here is an explanation in Qooxdoo manual.

Bookmark and Share