Table of Contents
LZX enables the creation of applications that have high design values and fluid, graceful interaction. In this chapter we discuss some basic tools for layout and design. For more advanced topics and exploration of the aesthetics of OpenLaszlo application design, see the Designer's Guide.
By default, all views position themselves at x="0" y="0" in their coordinate space. That means
that two sibling views will sit on top of each other. Aside from explicitly positioning the views so they don't overlap,
the easiest way to separate the siblings is to use a simplelayout
:
Instead of specifying each view's coordinates (either through a
constraint or absolutely), you can declare a layout that decides how
sibling views position themselves relative to one another. The
simplest layout is simplelayout
, which
positions sibling elements beneath (or to the right of) each other:
Example 17.1. Simple Layout
<canvas
height
="80
" width
="100%
">
<view
bgcolor
="yellow
" width
="100
" height
="100
">
<simplelayout
axis
="y
" spacing
="5
"/>
<view
bgcolor
="red
" width
="20
" height
="20
"/>
<view
bgcolor
="blue
" width
="20
" height
="20
"/>
<view
bgcolor
="green
" width
="20
" height
="20
"/>
</view
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
Here's an example that shows how to use layouts to resize views in response to user actions:
Example 17.2. Adding a view to a layout
<canvas
height
="140
" width
="100%
">
<class
name
="coloredRect
" width
="20
" height
="80
" onclick
="this.setAttribute('width', this.width+2)
"/>
<view
x
="10
" y
="10
" name
="container
">
<simplelayout
axis
="x
" spacing
="10
"/>
<coloredRect
bgcolor
="red
"/>
<coloredRect
bgcolor
="aqua
"/>
<coloredRect
bgcolor
="yellow
"/>
<coloredRect
bgcolor
="green
"/>
<coloredRect
bgcolor
="blue
"/>
</view
>
<button
x
="10
" y
="110
">Create A New View
<handler
name
="onclick
">
var newView = new lz.coloredRect(container, { bgcolor : 0x000000 });
</handler
>
</button
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 - 2010 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
Like all layouts, simpleLayout
affects how sibling views relate to one another. This remains true even if views are dynamically instantiated. You can set both the axis of a
simplelayout as well as the distance between the elements. Also note layouts update as the dimensions of the affected views
change.
If you need a specific group of views to be affected by a simplelayout, but want them to be isolated from the rest of the application, encase them in a parent view. You can then specify coordinates on the parent view:
Example 17.3. Isolating views
<canvas
height
="120
" width
="100%
">
<class
name
="coloredRect
" width
="20
" height
="80
"/>
<view
width
="50
" height
="50
" bgcolor
="yellow
"/>
<!-- Group of horizontally stacked views, isolated from app -->
<view
name
="container
" x
="175
" y
="20
">
<simplelayout
axis
="x
" spacing
="10
"/>
<coloredRect
bgcolor
="red
"/>
<coloredRect
bgcolor
="blue
"/>
<coloredRect
bgcolor
="teal
"/>
<coloredRect
bgcolor
="black
"/>
</view
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
There may be times when you would like to select between more than one layout in which to display your view. Options are a good way to do this You then override addSubview in your layout as shown in the code below. To get a better idea of what's going on, run the program with the debugger on.
Example 17.4. Choosing between layouts
<canvas
height
="400
" width
="100%
">
<debug
fontsize
="12
"/>
<class
name
="box
" width
="50
" height
="30
"/>
<view
name
="myview
">
<simplelayout
axis
="x
" spacing
="4
">
<method
name
="addSubview
" args
="sv
">
if ( sv.getOption( 'goesInLayout1' ) ){
super.addSubview( sv );
Debug.debug("adding %w, %w to layout1", sv, sv.name);
}
</method
>
</simplelayout
>
<simplelayout
axis
="y
" spacing
="4
">
<method
name
="addSubview
" args
="sv
">
if ( sv.getOption( 'goesInLayout2' ) ){
super.addSubview( sv );
Debug.debug("adding %w %w to layout2", sv, sv.name);
}
</method
>
</simplelayout
>
<box
bgcolor
="red
" options
="goesInLayout1
" name
="red1
"/>
<box
bgcolor
="red
" options
="goesInLayout1
" name
="red2
"/>
<box
bgcolor
="red
" options
="goesInLayout1
" name
="red3
"/>
<box
bgcolor
="blue
" options
="goesInLayout2
" name
="blue1
"/>
<box
bgcolor
="blue
" options
="goesInLayout2
" name
="blue2
"/>
<box
bgcolor
="blue
" options
="goesInLayout2
" name
="blue3
"/>
<!-- both -->
<box
bgcolor
="yellow
" options
="goesInLayout2;goesInLayout1
" name
="yellow1
"/>
<box
bgcolor
="yellow
" options
="goesInLayout2;goesInLayout1
" name
="yellow2
"/>
<box
bgcolor
="yellow
" options
="goesInLayout2;goesInLayout1
" name
="yellow3
"/>
</view
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007 - 2009 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
In addition to layouts, LZX includes the <hbox>
and <vbox>
tags that allow you to group views horizontally and vertically. Unlike layouts, which are objects that affect the behavior
of their siblings, <hbox>
and <vbox>
are themselves views that constrain the layout of their children. Each <hbox>
or <vbox>
contains a layout, and the tag syntax allows you to set the layout attributes directly on the hbox or vbox.
You can also combine <hbox>
and <vbox>
elements to form tables. The following example illustrates this. (There are more elegant ways to achieve this effect; we
include it here to illustrate the principles.)
Example 17.5. hbox and vbox
<canvas
height
="120
" width
="100%
">
<hbox
spacing
="10
">
<vbox
spacing
="10
" name
="column1
" bgcolor
="red
">
<handler
name
="onclick
">
this.animate('spacing', this.spacing == 10 ? 0 : 10, 1000);
</handler
>
<text
>click
</text
>
<text
>to
</text
>
<text
>animate
</text
>
<text
>spacing
</text
>
</vbox
>
<vbox
spacing
="10
" name
="column2
" bgcolor
="blue
">
<handler
name
="onclick
">
this.animate('spacing', this.spacing == 10 ? 0 : 10, 1000);
</handler
>
<text
>click
</text
>
<text
>to
</text
>
<text
>animate
</text
>
<text
>spacing
</text
>
</vbox
>
<vbox
spacing
="10
" name
="column3
" bgcolor
="yellow
">
<handler
name
="onclick
">
this.animate('spacing', this.spacing == 10 ? 0 : 10, 1000);
</handler
>
<text
>click
</text
>
<text
>to
</text
>
<text
>animate
</text
>
<text
>spacing
</text
>
</vbox
>
</hbox
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
As mentioned previously, layouts act upon sibling elements, but they can control more than just spacing. For example, they can be used to cause views to grow and shrink, as shown below.
A frequent layout problem when designing GUIs is to have an element stretch while its two surrounding siblings maintain a
constant size across the stretch axis. LZX solves this problem with the stableborderlayout
:
Example 17.6. Stableborder Layout
<canvas
height
="140
" width
="100%
">
<class
name
="coloredRect
" height
="80
"/>
<view
x
="10
" y
="10
" name
="container
" width
="200
">
<stableborderlayout
axis
="x
"/>
<coloredRect
bgcolor
="navy
" width
="20
"/>
<coloredRect
bgcolor
="yellow
"/>
<coloredRect
bgcolor
="navy
" width
="20
"/>
</view
>
<button
x
="205
" y
="110
">Increase width of container
<handler
name
="onclick
">
container.setAttribute("width", container.width + 10);
</handler
>
</button
>
<button
x
="10
" y
="110
">Decrease width of container
<handler
name
="onclick
">
container.setAttribute("width", container.width - 10);
</handler
>
</button
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
As the name suggests, this layout can be used to create a border with
a stable dimension (width or
height). stableborderlayout
is very useful for
creating borders, frames and windows. In this example,
stableborderlayout
is combined with some
constraints to create a 5 pixel blue border:
Example 17.7. Creating a border
<canvas
height
="200
" width
="100%
">
<!-- Sliders to control width and height -->
<text
x
="200
" y
="22
">Set the width:
</text
>
<slider
id
="widthSlider
" x
="200
" y
="50
" width
="100
" value
="50
" minvalue
="20
" maxvalue
="100
"/>
<text
x
="200
" y
="92
">Set the height:
</text
>
<slider
id
="heightSlider
" x
="200
" y
="120
" width
="100
" value
="50
" minvalue
="20
" maxvalue
="100
"/>
<!-- Blue border -->
<view
width
="${widthSlider.value}
" height
="${heightSlider.value}
" x
="20
" y
="20
">
<stableborderlayout
axis
="y
"/>
<view
bgcolor
="blue
" height
="5
" width
="${parent.width}
"/>
<view
width
="${parent.width}
">
<stableborderlayout
axis
="x
"/>
<view
bgcolor
="blue
" width
="5
" height
="${parent.height}
"/>
<view
height
="${parent.height}
"/>
<view
bgcolor
="blue
" width
="5
" height
="${parent.height}
"/>
</view
>
<view
bgcolor
="blue
" height
="5
" width
="${parent.width}
"/>
</view
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
If you wanted to use the above code to frame a particular view irrespective of where it was in the view hierarchy, a good way would be to use a floating view:
Example 17.8. Using a floating view
<canvas
width
="100%
">
<!-- A single colored box -->
<class
name
="coloredBox
" width
="50
" height
="50
" bgcolor
="0xff0000
"/>
<!-- Yellow border; invisible at start -->
<view
id
="floater
" width
="50
" height
="50
" visible
="false
">
<!-- Animate to the coordinates of a given view -->
<method
name
="goTo
" args
="whichBox
">
this.setAttribute("visible", true);
this.bringToFront();
var destX = whichBox.getAttributeRelative("x", canvas);
var destY = whichBox.getAttributeRelative("y", canvas);
this.animate("x", destX, 1000, false);
this.animate("y", destY, 1000, false);
</method
>
<stableborderlayout
axis
="y
"/>
<view
bgcolor
="0xFFFF00
" height
="5
" width
="${parent.width}
"/>
<view
width
="${parent.width}
">
<stableborderlayout
axis
="x
"/>
<view
bgcolor
="0xFFFF00
" width
="5
" height
="${parent.height}
"/>
<view
height
="${parent.height}
"/>
<view
bgcolor
="0xFFFF00
" width
="5
" height
="${parent.height}
"/>
</view
>
<view
bgcolor
="0xFFFF00
" height
="5
" width
="${parent.width}
"/>
</view
>
<!-- Controls -->
<view
bgcolor
="yellow
">
<simplelayout
axis
="y
" spacing
="10
"/>
<button
onclick
="floater.goTo(blueBox)
">Go to Blue Box
</button
>
<button
onclick
="floater.goTo(redBox)
">Go to Red Box
</button
>
<button
onclick
="floater.goTo(greenBox)
">Go to Green Box
</button
>
</view
>
<!-- View hierarchy -->
<view
x
="20
" y
="15
">
<coloredBox
id
="blueBox
" bgcolor
="0x0000ff
" x
="200
"/>
<view
x
="5
" y
="40
">
<coloredBox
id
="greenBox
" bgcolor
="0x00ff00
" y
="150
" x
="13
"/>
<view
x
="1
" y
="90
">
<coloredBox
id
="redBox
" bgcolor
="0xff0000
" y
="150
"/>
</view
>
</view
>
</view
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
The <constantlayout>
tag is a way of defining a margin. For example, if all of the children of a container needed to be
indented 10 pixels from the left hand edge of the container:
Example 17.9. Constant Layout
<canvas
height
="140
" width
="100%
">
<class
name
="coloredRect
" width
="60
" height
="15
"/>
<view
x
="10
" y
="10
" name
="container
" bgcolor
="silver
" width
="80
" height
="120
">
<constantlayout
axis
="x
" value
="10
"/>
<simplelayout
axis
="y
" spacing
="10
"/>
<coloredRect
bgcolor
="red
" width
="40
"/>
<coloredRect
bgcolor
="aqua
" width
="50
"/>
<coloredRect
bgcolor
="yellow
"/>
<coloredRect
bgcolor
="green
" width
="20
"/>
</view
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
Here <constantlayout>
and
<simplelayout>
are combined so that the
<constantlayout>
controls the left, whereas
the<simplelayout>
dictates the vertical spacing between the
siblings.
This section
discusses layouts
, which are constructs that allow you to focus on larger design issues by freeing you from the
tedium of positioning views. Layouts are implemented with constraints
, which are described in
Chapter 27, Constraints.
For more on designing the "look and feel" of OpenLaszlo applications, please see
Chapter 3, OpenLaszlo for Designers.
Views can be nested inside one another, and they can reference other parts of the view hierarchy using dot syntax and/or IDs. The view hierarchy helps you define how the various elements of your application relate to one another. It is helpful to break your application into various elements. Consider a photo-viewing application that has a list of images in a right-hand column, and a main stage area for displaying the images to the left. There is also a trash area that we'll cover later. At a very high level, you might break your application up as follows:
Example 17.10. Outline of photo application
<canvas
width
="100%
">
<view
>
<view
name
="stage
" width
="400
" height
="300
" x
="20
" bgcolor
="0xeaeaea
">
<!-- Area for viewing images -->
<text
>Stage
</text
>
<!-- ... -->
</view
>
<view
name
="listOfImages
" width
="200
" height
="300
" x
="500
" bgcolor
="0xffff00
">
<!-- Data-replicated list of images -->
<text
>List of images
</text
>
<!-- ... -->
</view
>
<view
name
="trash
" width
="200
" height
="75
" x
="500
" y
="310
" bgcolor
="0xff0000
">
<!-- Trash for disposing of images -->
<text
>Trash
</text
>
<!-- ... -->
</view
>
</view
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
You can use views with background colors when mocking up applications as shown above, to make them easier to prototype.
If you approach the application in this manner, you've effectively broken it up into manageable pieces already. listOfImages
, stage
and trash
could all be developed independently of one another. listOfImages
would contain the text-list of images that are bound to some data with the descriptive text showing. When the user clicks
one of the images, it would call a showImage()
method on stage
with the URL of the image to show as an argument.
The replicated views inside of listOfImages
would best be positioned using a layout.
The view hierarchy could also be a constraint. Imagine that in the above example, you wanted to be able to drag an item from
the list of image names from listOfImages
to trash
.
Frequently in drag-and-drop environments, the original item that was dragged is left in place (although some indication may
be given that it is in a transient state, such as being faded out), while a copy of it is dragged. If each of the image titles
was draggable, then not only might a dragged item disrupt the arrangement of its siblings, but you wouldn't be able to leave
the original intact, in order to obtain the behavior described above.
To get around this problem, you could use a floating view as the draggable view. A floating view is one that floats on top of the rest of your application, and only appears when dragging:
User clicks and holds on a view.
Floating view made visible, and located over clicked view.
Any visual changes applied to clicked view (e.g. color/alpha transform).
As mouse moves around, the floating view follows the mouse.
Onmouseup the floating view disappears, any actions performed, original clicked view restored.
To position the floating view over the clicked view, you can use the getAttributeRelative()
method of view to obtain the x and y coordinates of the view relative to a view other than its parent. For example, consider
the case below:
Example 17.11. Positioning a "floating view"
<canvas
width
="100%
">
<!-- An item that will get clicked and dragged -->
<class
name
="imageRow
" bgcolor
="0x93A5FF
" width
="$once{parent.width}
" height
="20
">
<attribute
name
="text
" type
="text
"/>
<text
name
="txt
" text
="${parent.text}
" resize
="true
"/>
<handler
name
="onmousedown
">
this.f = new lz.view(canvas, {width: this.width,
height: this.height,
bgcolor: this.bgcolor,
x: this.getAttributeRelative("x", canvas),
y: this.getAttributeRelative("y", canvas),
clickable: true
});
// Make view follow mouse around
this.x_offset = this.getMouse("x");
this.y_offset = this.getMouse("y");
this.startDraggingFloater();
</handler
>
<method
name
="startDraggingFloater
">
this.d = new LzDelegate(this, "adjustFloaterPosition", lz.Idle, "onidle");
this.gm = new LzDelegate(this, "cancelFloater", lz.GlobalMouse, "onmouseup");
</method
>
<method
name
="adjustFloaterPosition
" args
="v
">
this.f.setAttribute("x", canvas.getMouse("x")-this.x_offset);
this.f.setAttribute("y", canvas.getMouse("y")-this.y_offset);
</method
>
<method
name
="cancelFloater
" args
="v
">
this.gm.unregisterAll();
this.d.unregisterAll();
this.f.destroy();
</method
>
</class
>
<view
>
<view
name
="stage
" width
="400
" height
="300
" x
="20
" bgcolor
="0xeaeaea
">
<!-- Area for viewing images -->
<text
>Stage
</text
>
<!-- ... -->
</view
>
<view
name
="listOfImages
" width
="200
" height
="300
" x
="500
" bgcolor
="0xffff00
">
<!-- Data-replicated list of images -->
<simplelayout
axis
="y
" spacing
="2
"/>
<imageRow
>One
</imageRow
>
<imageRow
>Two
</imageRow
>
<imageRow
>Three
</imageRow
>
</view
>
<view
name
="trash
" width
="200
" height
="75
" x
="500
" y
="310
" bgcolor
="0xff0000
">
<!-- Trash for disposing of images -->
<text
>Trash
</text
>
<!-- ... -->
</view
>
</view
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
To achieve this, you could create a view from script when the mouse goes down over one of the views you want to drag:
This code is not easy to read, although conceptually it's fairly simple —
"create a new view when the mouse button is depressed, and drag it until the mouse button is released".
Note also that you have to listen for the global onmouseup
event instead of the onmouseup
on the dynamically created view. Because it was positioned under the cursor after the mouse was down, the event system does
not get notified when the mouse goes up over it.
This approach is fairly laborious — you have to copy all of the attributes of the original view to the new one. If you wanted to have the text field appear in the dragged view, you would have to create a new text field to match it at run-time. As the dragged view becomes more complex, it will take longer to instantiate when the user depresses the mouse button, since view instantiation is fairly processor-expensive.
A better approach would be to have the floating view ready in advance:
Example 17.12. Building a "floating view"
<canvas
width
="100%
">
<!-- A draggable floating view -->
<class
name
="floater
" height
="20
" bgcolor
="0x93A5FF
" visible
="false
">
<dragstate
name
="dragger
"/>
<text
name
="txt
" width
="100
"/>
<method
name
="showFloater
" args
="xpos, ypos, itemwidth, itemlabel
">
myfloater.bringToFront();
this.setAttribute("x", xpos);
this.setAttribute("y", ypos);
this.setAttribute("width", itemwidth);
this.txt.setAttribute("text", itemlabel);
this.gm = new LzDelegate(this, "cancelFloater", lz.GlobalMouse, "onmouseup");
this.setAttribute("visible", true);
this.dragger.apply();
</method
>
<method
name
="cancelFloater
" args
="v
">
this.dragger.remove();
this.setAttribute("visible", false);
</method
>
</class
>
<!-- An instance of the floater -->
<floater
id
="myfloater
"/>
<!-- An item that will get clicked and dragged -->
<class
name
="imageRow
" bgcolor
="0x93A5FF
" width
="$once{parent.width}
" height
="20
">
<attribute
name
="text
" type
="text
"/>
<text
name
="txt
" text
="${parent.text}
" resize
="true
"/>
<handler
name
="onmousedown
">
myfloater.showFloater(this.getAttributeRelative("x", canvas),
this.getAttributeRelative("y", canvas),
this.width,
this.txt.text);
</handler
>
</class
>
<view
>
<view
name
="stage
" width
="400
" height
="300
" x
="20
" bgcolor
="0xeaeaea
">
<!-- Area for viewing images -->
<text
>Stage
</text
>
<!-- ... -->
</view
>
<view
name
="listOfImages
" width
="200
" height
="300
" x
="500
" bgcolor
="0xffff00
">
<!-- Data-replicated list of images -->
<simplelayout
axis
="y
" spacing
="2
"/>
<imageRow
>One
</imageRow
>
<imageRow
>Two
</imageRow
>
<imageRow
>Three
</imageRow
>
</view
>
<view
name
="trash
" width
="200
" height
="75
" x
="500
" y
="310
" bgcolor
="0xff0000
">
<!-- Trash for disposing of images -->
<text
>Trash
</text
>
<!-- ... -->
</view
>
</view
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 - 2009-2011 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
This code is much cleaner, and the floater code is now completely independent of the imagerow
code. Individual properties of the floater can still be set (in this case they're passed as arguments to the showFloater
method).
Taking this a step further, if our list of images were replicated against a dataset an even more elegant approach would be
to pass the data node of the replicated imageRow
instance to the floater, and have the floater do the right thing with the data.
Example 17.13. Using a dataset to drive layouts
<canvas
width
="100%
">
<!-- The data that provides the images -->
<dataset
name
="ds_items
">
<items
>
<item
description
="One
" src
="one.jpg
"/>
<item
description
="Two
" src
="two.jpg
"/>
<item
description
="Three
" src
="three.jpg
"/>
</items
>
</dataset
>
<!-- A draggable floating view -->
<class
name
="floater
" height
="20
" bgcolor
="0x93A5FF
" visible
="false
" datapath
="
">
<dragstate
name
="dragger
"/>
<text
name
="txt
" datapath
="@description
" resize
="true
"/>
<text
name
="imgurl
" datapath
="@src
" resize
="true
" align
="right
"/>
<method
name
="showFloater
" args
="xpos, ypos, itemwidth, dataNode
">
myfloater.bringToFront();
this.setAttribute("x", xpos);
this.setAttribute("y", ypos);
this.setAttribute("width", itemwidth);
this.datapath.setAttribute("p", dataNode);
this.gm = new LzDelegate(this, "cancelFloater", lz.GlobalMouse, "onmouseup");
this.setAttribute("visible", true);
this.dragger.apply();
</method
>
<method
name
="cancelFloater
" args
="v
">
this.dragger.remove();
this.setAttribute("visible", false);
</method
>
</class
>
<!-- An instance of the floater -->
<floater
id
="myfloater
"/>
<!-- An item that will get clicked and dragged -->
<class
name
="imageRow
" bgcolor
="0x93A5FF
" width
="$once{parent.width}
" height
="20
">
<attribute
name
="text
" type
="text
"/>
<text
name
="txt
" datapath
="@description
" resize
="true
"/>
<handler
name
="onmousedown
">
myfloater.showFloater(this.getAttributeRelative("x", canvas),
this.getAttributeRelative("y", canvas),
this.width,
this.datapath.p);
</handler
>
</class
>
<view
>
<view
name
="stage
" width
="400
" height
="300
" x
="20
" bgcolor
="0xeaeaea
">
<!-- Area for viewing images -->
<text
>Stage
</text
>
<!-- ... -->
</view
>
<view
name
="listOfImages
" width
="200
" height
="300
" x
="500
" bgcolor
="0xffff00
" datapath
="ds_items:/items
">
<!-- Data-replicated list of images -->
<simplelayout
axis
="y
" spacing
="2
"/>
<imageRow
datapath
="item
"/>
</view
>
<view
name
="trash
" width
="200
" height
="75
" x
="500
" y
="310
" bgcolor
="0xff0000
">
<!-- Trash for disposing of images -->
<text
>Trash
</text
>
<!-- ... -->
</view
>
</view
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 - 2009 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
The floater must have an empty datapath assigned in advance. Notice how since only a <datapointer>
element is passed to the floating view, the floater can access attributes of the data that were not referenced in the imageRow
class (for example,the src
attribute). This can become particularly useful when you want to "drop" the floater somewhere. The drop target may need to
access other data properties (or even delete the XML node in the dataset), and passing a pointer to it saves retracing it.
A floating view is not only for draggable objects. If you wanted to frame a specified view, regardless of where it was in the view hierarchy, you might also find a floating view to be the best option. See the StableBorderLayout section for more.
You should avoid placing views inside of views with a
resource. Instead, to have a background image in a container view, have
a view that attaches the resource be a child of the parent view. Use
options="ignorelayout"
to prevent the background image
from being affected by any layouts that act upon the children of its
container:
Example 17.14. ignorelayout
<view name="container"> <view resource="someimg.jpg" options="ignorelayout"/> <view name="childView"/> </view>
Since a layout extends Node, it can be addressed using dot syntax:
<canvas height="140"> <class name="coloredRect" width="20" height="80"/> <view name="container"> <simplelayout name="slayout" axis="x" spacing="10"/> <coloredRect bgcolor="red"/> <coloredRect bgcolor="aqua"/> <coloredRect bgcolor="yellow"/> </view> </canvas>
In the above example, the layout could be accessed by:
container.slayout
.
You might need to address a layout in script, in order to change
one of its attributes at run-time, to call the layout's
update()
method, or to lock it. A layout can also have
an id, so that it can be accessed globally.
All nodes have a layouts array, which contains pointers to all of the layouts that act upon its children:
Example 17.15. Addressing layouts
<canvas
height
="180
" width
="100%
">
<class
name
="coloredSquare
" width
="20
" height
="20
"/>
<view
name
="container
" bgcolor
="silver
" x
="10
" y
="10
">
<simplelayout
axis
="x
" spacing
="2
"/>
<simplelayout
id
="yLayout
" axis
="y
" spacing
="2
"/>
<coloredSquare
bgcolor
="red
"/>
<coloredSquare
bgcolor
="red
"/>
<coloredSquare
bgcolor
="red
"/>
<coloredSquare
bgcolor
="red
"/>
<coloredSquare
bgcolor
="red
"/>
</view
>
<button
y
="150
" x
="10
">Increase spacing of BOTH x and y layouts
<handler
name
="onclick
"><![CDATA[
for (var i=0; i<container.layouts.length; i++) {
var lyt = container.layouts[i];
lyt.setAttribute('spacing', lyt.spacing + 2);
}
]]>
</handler
>
</button
>
<button
y
="150
" x
="280
">Increase spacing of y layout ONLY
<handler
name
="onclick
">
yLayout.setAttribute('spacing', yLayout.spacing + 2);
</handler
>
</button
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
Layouts can be locked and unlocked using the lock()
and unlock()
methods respectively (inherited from the
layout
class). When you lock a layout, you prevent it from
updating. For example, if you click the colored boxes in the following
example to enlarge them, the simplelayout will automatically update,
and the spacing between the boxes will remain consistent. However, if
you lock the layout, then grow the boxes, the simplelayout will not
update, until the unlock()
method is called on the
layout:
Example 17.16. Locking and unlocking layouts
<canvas
height
="140
" width
="100%
">
<class
name
="coloredRect
" width
="20
" height
="80
" onclick
="this.setAttribute('width', this.width+2)
"/>
<view
x
="10
" y
="10
" name
="container
">
<simplelayout
name
="slayout
" axis
="x
" spacing
="10
"/>
<coloredRect
bgcolor
="red
"/>
<coloredRect
bgcolor
="aqua
"/>
<coloredRect
bgcolor
="yellow
"/>
<coloredRect
bgcolor
="green
"/>
<coloredRect
bgcolor
="blue
"/>
</view
>
<button
x
="10
" y
="110
">Lock
<handler
name
="onclick
">
container.slayout.lock();
</handler
>
</button
>
<button
x
="70
" y
="110
">Unlock
<handler
name
="onclick
">
container.slayout.unlock();
</handler
>
</button
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
The lock()
method sets the layout's
locked
attribute to true. The unlock()
method
calls the layout's update()
method, then sets the
locked
attribute to false
.
Layouts can be animated; that is, the properties of the layout can be set to vary over time. For an example and an explanation of how this is done, see Chapter 24, Animation
When none of the default OpenLaszlo layouts do what you need, you can write a custom layout of your own. This section shows the essential aspects of building one.
The update()
method is the most important method in
a layout — it controls what the layout does. For example, here
is a very basic custom layout that spaces views 20px apart. This
is similar to the behavior of the<simplelayout>
, but unlike <simplelayout>
this one spaces
the left hand edge of the views, rather than distributing them
evenly:
Example 17.17. Updating a layout
<canvas
height
="100
" width
="100%
">
<class
name
="col
" clickable
="true
" bgcolor
="blue
" width
="15
" height
="80
"/>
<layout
>
<attribute
name
="spacing
" value
="25
" type
="number
"/>
<method
name
="addSubview
" args
="s
">
super.addSubview(s);
this.update();
</method
>
<method
name
="update
" args
="e=null
"><![CDATA[
if (this.locked) return;
this.locked = true;
for (var i = 0; i < subviews.length; i++){
subviews[i].setAttribute('x', i * this.spacing);
}
this.locked= false;
]]>
</method
>
</layout
>
<col
/>
<col
/>
<col
/>
<col
/>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
All layouts have a subviews
array, which allows you to cycle through the sibling views and adjust their properties accordingly. The addSubview()
method is automatically called whenever a view is added—at init time, when a view is dynamically generated from script or
via data replication. It ensures that the update()
method gets called so that the layout updates. If the addSubview()
method were not present, the above example would still look fine when the application first started, but any dynamically created
views would appear out of the layout.
Notice both:
The test for the locked
attribute (if (this.locked) return;
) to ensure conformance with normal layout response to the locking and unlocking of layouts.
The use of the locked
attribute in the update()
method (this.locked = true;
and this.locked = false;
) to prevent simultaneous changes to the layout.
Here is a slightly more complicated example. In fact the layout defined below is the same as <simplelayout axis="x" spacing="10"/>
.
Example 17.18. Horizontal simplelayout
<canvas
height
="100
" width
="100%
">
<class
name
="col
" clickable
="true
" bgcolor
="blue
" width
="20
" height
="80
" onclick
="this.setAttribute('width', this.width+2)
"/>
<layout
>
<attribute
name
="spacing
" value
="10
" type
="number
"/>
<method
name
="addSubview
" args
="s
">
this.updateDelegate.register(s, "onwidth");
super.addSubview(s);
this.update();
</method
>
<method
name
="update
" args
="e=null
"><![CDATA[
if (this.locked) return;
this.locked = true;
var indent = 0;
for (var i = 0; i < subviews.length; i++){
var s = subviews[i];
s.setAttribute('x', indent);
indent += s.width + this.spacing;
}
this.locked = false;
]]>
</method
>
</layout
>
<col
/>
<col
/>
<col
/>
<col
/>
<col
/>
<col
/>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
Notice that the update()
method is slightly different
now — it adds up the total widths of the preceding siblings
(adding the spacing) and sets the x to the total. That is the basic
algorithm for simplelayout. Another very important difference is that
the addSubview()
method registers the inherited
updateDelegate()
to listen for changes to that
subview's width. updateDelegate()
is a delegate that
all subclasses of layout have. It calls that layout's
update()
method. In this example, the layout needs to
update when the width of any of the sibling views changes, so the call
to register it happens in the addSubview()
method.
Copyright © 2002-2010 Laszlo Systems, Inc. All Rights Reserved. Unauthorized use, duplication or distribution is strictly prohibited. This is the proprietary information of Laszlo Systems, Inc. Use is subject to license terms.