StretchPane

1. Introduction

This page describes the features, philosophy, usage and implementation of the StretchPane widget.

1.1. What is a StretchPane?

StretchPane is a Dojo container widget which intelligently sets its own absolute position and size within its parent element, according to simple rules you give it, and reacts to resize events to adjust its size and position as needed. As such, it offers an alternative approach in high-level page design.

1.2. Background

Some days ago, I was fighting with CSS for control over a simple page layout. What I wanted was:

  • A banner at the top
  • A main viewing area, containing:
    • A left links pane
    • A main viewing pane
  • A bottom row
This seems trivial, except that I wanted the left links pane to have a width of 100 pixels plus 10% of the page width. After reading through the CSS specs, and all the 'CSS is God' web pages out there, I realised that CSS was horrifically limited in that area - when specifying a property such as width, you must give the value as a simple number, either a percentage or an absolute measure such as pixels. This limitation inspired me to look into a 'smart widget' which can position itself according to simple rules. Hence StretchPane was born.

1.3. Quick Taste of StretchPane

Consider the following page markup:
<body>
  ...
  <div id="links" dojoType="StretchPane"
       x="10" y="60" w="100+w*0.1" h="300">
    <a href="javascript:void(showpage('page1'))">Page 1</a>
    <a href="javascript:void(showpage('page2'))">Page 2</a>
    ...
  </div>
  ...
</body>
The defining characteristic of StretchPane is the ability to pass in attributes x, y, w, and h. For these values, you can pass in either simple numbers, or simple arithmetic expressions which can use variables w and h (which are the width and height of the parent container, respectively).



The effect of the above markup is to place a links pane on the page whose top left corner is at (10, 60), whose height is a minimum of 300 pixels, and whose width will be 100 pixels, plus 10% of the parent container's width. In this case, the parent is the , so this will be 10% of the page's width. So if the browser window is sized such that the page is 800 pixels wide, our links pane will be 180 pixels wide.




2. Getting StretchPane

If, at the time you're reading this, StretchPane is not in the Dojo svn trunk, you can download the latest copy from the author's website:

  •  http://www.freenet.org.nz/dojo/StretchPane-0.1.tar.gz.

3. Features and Philosophy

StretchPane makes some alternatives available in page design. Depending on your situation, it may or may not be useful to you. Here is a list of some of its features and implications:

  • Absolute, Self-determined Positioning - all StretchPane widgets are automatically styled with position:absolute. This means that their position is controlled totally by the size of their parent, and their size/placement rules (the x,y,w,h attributes you give when you instantiate them in your markup).
  • Page as a Canvas - StretchPane encourages a spatial approach to design, and supports you to take control over where you put its components. Some web design artists, for instance, like to use a lot of empty (or text-free) space and place wording in small specific areas. StretchPane can be very helpful here - no more messing around with complicated tables.

  • Widget Independence - StretchPane widgets are not aware of the presence of other widgets within their parent container, and do not follow the normal 'layout flow'. They go where you tell them to go.
  • Animation Support - StretchPane has a small amount of animation support built in. For example, you might want the user to be able to click on a small container block on the left of the page, and see that block rapidly move and expand until it takes up all of the main viewing area. Then, when they're finished with that pane (or click on another pane), you might want it to shrink up and go back where it was.
  • Useful as a Wrapper - you can wrap virtually any widget inside a StretchPane, as the sole child, to give it the ability to dynamically size/place whenever the screen is resized.
  • Support for Nesting - StretchPane passes onResized events to its children. So you can have StretchPane instances inside StretchPane instances.

4. Usage and API

You control StretchPane instances in two ways:

  1. By the initial placement rules you provide in your page markup (or if you programmatically instantiate) - any combination of the attributes x, y, w, h, xorg and yorg, and

  2. API Methods for changing any combination of the placement rules at any time

4.1. Initial Placement Rules

Each of the placement rule attributes x, y, w, h you specify must consist of valid code for a Javascript expression. This expression evaluates in the context of a method of the StretchPane instance, and has the following objects in its local namespace:

  • W - (note, this is uppercase 'W') - the width of the parent container (or the width of the document, if there is no parent) in pixels
  • H - the height of the parent container (or the height of the document, if there is no parent) in pixels
  • this - the instance object, in case you want to do anything really fancy.
Additionally, since your rules for w and h are evaluated first, your rules for x and y may make use of w and h - that is, your widget can set its position based on its own current calculated size. Even if you don't have rules for w and h, your x and y rules can still refer to w and h, since these will either way be set to the widget's current width and height, respectively.



Typically, your rule expressions will be just simple numbers, or simple expressions using only W, H or this (as well as w and h, in the case of your x and y rules). However, if you've planted any functions in global namespace, you could invoke these freely. On the other hand, if you've subclassed StretchPane (or plugged in your own methods), you could invoke them via this.

Setting the Widget Origin

In addition to the x, y, w, h attributes, you can also set the 'origin' of the widget via the attributes xorg and yorg. By default, your widget's origin will be its top-left corner, but you can set the origin to be anywhere within the widget, for example, its centre.



Valid values for xorg are:

  • left - set the x-origin to the left border of the widget. Internally, this will be parsed to the value 0.0

  • middle, center or centre - set the x-origin to halfway between the widget's left and right borders. Internally, this will be parsed to the value 0.5

  • right - set the x-origin to the right border of the widget. Internally, this will be parsed to the value 1.0

  • any number between 0 and 1, inclusive.
Valid values for yorg are:

  • top - set the y-origin to the top border of the widget. Internally, this will be parsed to the value 0.0

  • middle, center or centre - set the y-origin to halfway between the widget's top and bottom borders. Internally, this will be parsed to the value 0.5

  • bottom - set the y-origin to the bottom border of the widget. Internally, this will be parsed to the value 1.0

  • any number between 0 and 1, inclusive.

API

StretchPane implements many of the base Widget API methods for visibility and size, and provides additional methods for placement and movement.



Here are some of the more useful methods, many of which are implementations of Widget base methods:

  • show() - make the widget visible (it is visible by default)
  • hide() - hide the widget (sets the style display:none)
  • setWidth(rule) - sets the rule for determining the widget's width. In this method, and the methods listed below, rule can be a number, a string with just a number, or a string containing a simple expression (refer previous section). The effect of this method will be immediate.
  • setHeight(rule) - sets the rule for determining the widget's height.
  • resize(widthRule, heightRule) - simultaneously changes the rules for determining the widget's width and height
  • setLeft(xRule) - sets the rule for determining the offset in pixels of the widget's left edge from the left edge of its containing widget (or the whole page, if the widget has no parent container)
  • setTop(yRule) - sets the rule for determining the offset in pixels of the widget's left edge from the left edge of its containing widget (or the whole page, if the widget has no parent container)
  • setOrigin(xorg, yorg) - changes the origin of the widget to (xorg, yorg)

  • setPosition(xRule, yRule) - simultaneously changes the rules for determining the offset in pixels of the widget's top left corner from the top left corner of its parent (or the page)
  • setPositionAndSize(xRule, yRule, widthRule, heightRule) - simultaneously changes the rules for determining the widget's position and size. The effect of this method is the same as calling setPosition(xRule, yRule) then resize(widthRule, heightRule), but this method is more efficient than calling both those methods in sequence
  • move(xRule, yRule, widthRule, heightRule, [duration, [nframes]]) - create an animated transition from one set of position/size rules to a new set of position/size rules. For each of the new rules, you can give an empty string (which means stay with the old respective rule). duration is the duration in milliseconds for the animation, default 1000 (1 second). nframes is the number of frames for the animation (default 10).

Implementation Notes

StretchPane is implemented as a subclass of ContentPane. Whenever you instantiate a StretchPane, whether in your html markup or programatically, the following takes place in the postCreate method:

  • The widget's position style is set to absolute
  • The rules you give are converted into statements, so:

       w="100+W/10"

    gets effectively converted to:

       var w = 100+W/10;

    this.domNode.style.width = "" + w + "px";

  • A function header and footer are wrapped around these statements, giving a string containing the source code for a function
  • The string is eval()'ed, and the resulting function object written to the widget as an attribute sizerFunc and thus becomes a new method of your widget.

  • If the widget has no parent widgets, then its onResized() handler method will be connected to the window.onresize event. This is the mechanism which causes the size/position rules to act whenever the window gets resized.
If ever you set a new rule or set of rules, this whole process of converting rules into statements, creating sizerFunc source code, eval()'ing the code to a function object then writing that object to the widget as the new sizerFunc attribute is repeated.



As you can see, the dynamic resizing/relocating of a StretchPane widget according to existing rules is a relatively cheap operation, but changing rules is more expensive. Therefore it is advisable to try to abstain from rule changes until they are actually needed.




Conclusion

This article has discussed the features, philosophy, usage, interface and implementation of StretchPane, such that between reading this page and studying the two examples in dojo/tests/widget/test_StretchPane*.js, you should have a fair understanding of what it's about and how to use it.