Chapter 14. Layout Widgets

Unfortunately, many web apps consume nontrivial amounts of time implementing and rediscovering CSS shenanigans to achieve layouts that have been realized many times already and that should be a lot easier than they often turn out to be. This chapter introduces the layout dijits, a number of useful containers for creating common layouts in markup. Layout containers allow you to automate incredibly common tasks such as producing a tabbed layout as well as producing arbitrary tiled layouts without resorting to custom CSS for floating content, calculating relative offsets, etc. Unlike the previous chapter on form widgets, this chapter is shorter, much simpler, and more predictable. There are only a handful of layout widgets; all of them have only a few configuration options and very few caveats.

Layout Dijit Commonalities

All layout dijits exist within the dijit.layout namespace and share a small set of baseline features that you should be aware of. In addition to inheriting from _Widget, _Container, and _Contained, they share a few extra commonalities. This section quickly reviews the commonalities, listed in Table 14-1, which are all pretty easy to get your head around.

Table 14-1. Layout dijit common methods

Name

Comment

isLayoutContainer

Returns whether the widget is a layout container.

layout( )

Overridden by widgets to size and position their contents (child widgets). This is called after startup, when the widget's content box is guaranteed to be set, and anytime the widget's size has been changed via resize.

resize(/*Object*/ size)

Used to explicitly set the size of a layout widget; accepts an object specifying the upper left along with a width and a height of the form {w : Integer, h: Integer, l : Integer, t : Integer}. (Anytime you override resize, you will almost always call layout in the overridden method because layout is the canonical location for handling size and positioning for the contained child widgets.)

An especially important takeaway from Table 14-1 is the relationship between layout and resize. To be clear, resize is used to change the size of widget, and it is almost always the case that resize calls layout to adjust the size of its children in response to resizing. Normally speaking, child nodes do not lay themselves out. The parent node lays them out inside of layout. As a general pattern, the startup lifecycle method kicks off resize, which in turn calls layout.

The layout dijits leverage features of _Container and _Contained especially heavily, so they are worth a review as well, provided in Table 14-2.

Table 14-2. Layout dijit container machinery

Name

Comment

removeChild(/*Object*/ dijit)

Removes the child widget from the container. (Silently fails if the widget is not a child or if the container does not have any children.)

addChild(/*Object*/ dijit, /*Integer?*/ insertIndex)

Adds a child widget to the container, optionally using the insertIndex to place it.

getParent( )

Commonly used by a child inside of a layout container to retrieve its parent. Returns a dijit instance.

getChildren( )

Commonly used by a layout container to enumerate each of its children dijits. Returns an Array of dijit instances.

getPreviousSibling( )

Used by descendants of StackContainer to reference the previous sibling, i.e., the one "to the left." Returns a dijit instance.

getNextSibling( )

Used by descendants of StackContainer to reference the next sibling, i.e., the one "to the right." Returns a dijit instance.

Programmatic Creation

As we'll see in upcoming examples, the pattern for programmatically creating layout dijits follows the same dijit creation pattern that involves providing a first argument with a collection of properties and a second parameter that provides a source node reference for the layout dijit. Once the layout dijit is created, the source node reference becomes the dijit's domNode. This all takes place via _Widget's create method, which was introduced as part of the dijit lifecycle in Chapter 12. Unlike many dijits you've learned about so far, however, you'll almost always need to explicitly call a layout dijit's startup method if you programmatically create layout dijits because they generally contain child widgets, and startup signals that the container is finished adding children—at which point the layout can proceed. After all, it wouldn't be prudent at all for a widget to lay itself out only to have other sibling widgets repeatedly drop in and restart the layout process. Thus, the parent's startup method generally involves calling the startup method on each child widget, which is the green light to start rendering.

If you are implementing a parent container, startup is your last chance to manipulate children before they are displayed.

Keyboard Support

Like with other all other dijits, keyboard support is quite full featured. You'll find that in almost all circumstances, the "obvious" keys work. For example, to navigate through an AccordionPane, you can use the up and down arrows as well as the Page Down and Page Up keys. In addition to providing accessibility as part of Dijit's a11y goals, this extensive keyboard support also enhances the general user experience.

ContentPane

A ContentPane is the most basic layout tile and it inherits directly from _Widget; conceptually, it is like a super-duper variation of an iframe except that it fits right into the page with all sorts of bells and whistles, not the least of which are the ability to render arbitrary snippets of HTML (not just full documents), reload content via XHR on demand, render widgets, and respect the page's theme. More often than not, a ContentPane is contained within another widget such as a TabContainer, although a ContentPane has several interesting uses cases on its own.

In its most generic usage, a layout pane does nothing special at all, as shown in Example 14-1.

Example 14-1. Creating a ContentPane in markup
<html>
    <head><title>Fun with ContentPane!</title>

        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css" />
        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dijit/themes/tundra/tundra.css" />

        <script
            djConfig="parseOnLoad:true",
            type="text/javascript"
            src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js">
        </script>

        <script type="text/javascript">
            dojo.require("dijit.layout.ContentPane");
        </script>

    </head>
    <body class="tundra">
        <div dojoType="dijit.layout.ContentPane">
            Nothing special going on here.
        </div>
    </body>
</html>

When browsing the source code or reading about Dojo online, you may notice that there is also a LinkPane dijit. Over the course of time, ContentPane evolved to absorb much of what LinkPane used to do, and it is likely that LinkPane will become deprecated because it offers a trivial amount of built-in functionality beyond that of ContentPane.

However, you can trivially fetch arbitrary content from a server and render it into the page by merely providing a reference to the server side URL. Suppose that a server-side URL called foo returns a snippet of text; you could use the ContentPane to display it like so:

<div id="foo" preload="false"  dojoType="dijit.layout.ContentPane" href="foo">

In this particular case, the foo URL might have returned just a simple string value, although it could have returned a widget that would have been automatically parsed and rendered into the page. Just note that the widget must already have been dojo.require'd into the page. For example, let's suppose the foo URL returned <div dojoType="dijit.form.Textarea"></div>, then by default, the dijit would be parsed and rendered into the page.

Table 14-3 presents a summary of everything that a ContentPane supports.

Table 14-3. ContentPane API

Name

Type

Comment

href

String

Used to designate the external data the pane should load.

extractContent

Boolean

If true, the visible content between the BODY tags of the document the ContentPane retrieves is extracted from it and placed into the pane. false by default.

parseOnLoad

Boolean

If true, any dijits returned in the data are automatically parsed and rendered. true by default.

preventCache

Boolean

Acts just like the preventCache parameter for a dojo.xhrGet. If true, an additional parameter is passed that changes with each request to prevent caching from occurring. false by default.

preload

Boolean

Used to force the pane to load content, even if it is not initially visible. (If the node is styled with display:none then content may not load unless preload is set to true.) false by default.

refereshOnShow

Boolean

Used to indicate whether the pane should reload every time the pane goes from a hidden state to a visible state. false by default.

loadingMessage

String

Defined in dijit.nls.loading. Provides a default message to the user while a load is in process. "Loading..." by default.

errorMessage

String

Defined in dijit.nls.loading. Provides a default message to the user when a load fails. "Sorry, an error occurred" by default.

isLoaded

Boolean

Used to provide an explicit status for whether content is loaded. Useful for inquiries involving content that is often refreshed.

refresh( )

Function

Used to force the content to refresh by downloading it again.

setHref(/*String*/ href)

Function

Used to change the location of the external content for the dijit. If preload is false, the content is not downloaded until the widget becomes visible again.

setContent(/*String | DOMNode | NodeList */data)

Function

Used to explicitly set local content for the pane.

cancel( )

Function

Cancels the in-progress download of content.

onLoad(/*Event*/evt)

Function

Extension point that is called after the load (and optional parsing of widgets) takes place.

onUnload(/*Event*/evt)

Function

Extension point that is called before existing content is cleared by a refresh, setHref, or setContent.

onDownloadStart

Function

Extension point that is called just before the download begins. By default, returns the string that is displayed as the loading message.

onContentError(/*Error*/ e)

Function

Extension point that is called when DOM errors occur. The string that is returned is what is displayed to the user.

onDownloadError(/*Error*/ e)

Function

Extension point that is called if an error occurs during the download. By default, returns the string that is displayed as the error message.

onDownloadEnd( )

Function

Extension point that is called when the download completes.

Given an existing node called foo, you could programmatically create a ContentPane like so:

var contentPane = new dijit.layout.ContentPane({ /* properties*/, "foo");
contentPane.startup(  ); //good practice to get in the habit of always calling startup

Because ContentPane is not a descendant of _Container, there are no built-in methods for adding children to a ContentPane. However, you can use a ContentPane's domNode reference to append another node inside of it using plain old JavaScript, which works just fine. For example, using the existing content pane from the previous example:

contentPane.domNode.appendChild(someOtherDijit.domNode);

You may be wondering why ContentPane does not directly support the interface provided by _Container. The unofficial answer is that a ContentPane, in general, does not need to perform a specific action when a child is placed into it for a specific reason. The reasons for adding children to a ContentPane are wide and varied. If you really wanted to, however, you could mixin or extend _Container into ContentPane.

BorderContainer

BorderContainer is a new layout dijit introduced in version 1.1 that resulted in LayoutContainer and SplitContainer getting deprecated because BorderContainer is essentially a union of the two. Although you may see examples on the web using LayoutContainer and SplitContainer, it is not a good idea to start building an application with deprecated features. For this reason, these two deprecated widgets are not covered in this book.

A BorderContainer provides an easy way to define a layout that normally involves several layout tiles that occur on the top/bottom/left/right/center, top/bottom/center, or left/right/center of the page. These tiles may have resizable handles, so the BorderContainer is an especially notable value-added widget in that it simplifies what could have otherwise been a grueling workload into a really simple widgetized solution. As you might have guessed, it is called a "border" container because up to four tiles surround its border with the center filling in to whatever is leftover.

Table 14-4 shows the API.

Table 14-4. BorderContainer API

Name

Type

Comment

design

String

Valid values include "headline" (the default) or "sidebar" and determine whether the top and bottom tiles extend the width and height or the top and bottom of the container.

liveSplitters

Boolean

Whether to continuously resize while the mouse drags or to resize on the onmouseup event.

persist

Boolean

Whether to save splitter positions as a cookie.

When using a BorderContainer, the additional attributes shown in Table 14-5, which BorderContainer depends on, are available via ContentPane.

You might find it interesting to know that the means of making these additional attributes available via ContentPane is that the BorderContainer resource file extends _Widget's prototype to contain these values behind the scenes. This is a clever solution to the problem as it uses JavaScript's dynamism to provide these extras on demand, instead of requiring an a priori solution, which would really junk up and create unnecessary couplings on ContentPane's implementation.

Table 14-5. Attributes available to children of BorderContainer

Name

Type

Comment

minSize

Integer

If provided, the minimum size in pixels of the ContentPane is restricted to this value. By default, this value is 0.

maxSize

Integer

If provided, the maximum size in pixels of the ContentPane is restricted to this value. By default, this value is Infinity.

splitter

Boolean

If provided, a splitter appears on the edge of the ContentPane so that resizing can occur. By default, this value is false, which means that the content is not resizable.

region

String

The BorderContainer layout tiles are ContentPane widgets, each of which should have a region attribute to specify how to lay out the widget. Valid values include "top", "bottom", "left", "right", and "center". By default, this value is an empty String. Values of "leading" and "trailing" are also possible and differ from "left" and "right" in that they are relative to the bidirectional layout orientation.

A layout that involves a top and bottom that extends the width of the container is called a headline layout, and a layout that involves a left and right that extends the width of the container is called a sidebar layout. Either layout can optionally contain additional tiles that increase the number of layout areas from three to five. In any case, the remaining space that is leftover is the center area that gets filled in with the center tile.

Let's kick things off with a simple headline layout in markup, shown in Example 14-2. The top will be a blue pane, the bottom a red panel, and the middle will remain white. The top pane has minimum height of 10 pixels and a maximum height of 100 pixels (its default height).

Example 14-2. Creating a BorderContainer in markup
<html>
    <head><title>Fun with BorderContainer!</title>


        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css" />
        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dijit/themes/tundra/tundra.css" />

        <script
            djConfig="parseOnLoad:true",
            type="text/javascript"
            src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js">
        </script>

        <script type="text/javascript">
            dojo.require("dijit.layout.ContentPane");
            dojo.require("dijit.layout.BorderContainer");
            dojo.require("dojo.parser");
        </script>
    </head>
    <body class="tundra">

        <div dojoType="dijit.layout.BorderContainer" design="headline"
             style="height:500px;width:500px;border:solid 3px;">

            <div dojoType="dijit.layout.ContentPane" region="top"
              style="background-color:blue;height:100px;" splitter="true"
                 minSize=10 maxSize=100>top</div>

            <div dojoType="dijit.layout.ContentPane" region="center">center</div>

            <div dojoType="dijit.layout.ContentPane" region="bottom"
              style="background-color:red;height:100px;" splitter="true">bottom</div>

        </div>
    </body>
</html>

Adding tiles to fill in the left and right sides takes only two additional ContentPane dijits, as shown in Figure 14-1. Consider the following revision to the BODY tag:

<body class="tundra">

        <div dojoType="dijit.layout.BorderContainer"
          design="headline" style="height:500px;width:500px;border:solid 3px;">

            <div dojoType="dijit.layout.ContentPane" region="top"
              style="background-color:blue;height:100px;" splitter="true"
                minSize=10 maxSize=100>top</div>

            <div dojoType="dijit.layout.ContentPane" region="center">center</div>

            <div dojoType="dijit.layout.ContentPane" region="bottom"
              style="background-color:red;height:100px;" splitter="true">bottom</div>

            <div dojoType="dijit.layout.ContentPane" region="left"
              style="background-color:yellow;width:100px;" splitter="true">
left</div>

            <div dojoType="dijit.layout.ContentPane" region="right"
              style="background-color:green;width:100px;" splitter="true"
>right</div>

        </div>
</body>
Left: the BorderContainer before adding in additional panels on the left and right; right: the BorderContainer after adding in left and right panels
Figure 14-1. Left: the BorderContainer before adding in additional panels on the left and right; right: the BorderContainer after adding in left and right panels

Like all other dijits, programmatically creating a BorderContainer entails the same basic constructor function that takes a collection of properties and a source node. Adding in the child ContentPanes involves systematically creating them one by one as well. Although more tedious than markup, it's the same basic pattern. Example 14-3 shows how you'd create Example 14-2 programmatically.

Example 14-3. Programmatically creating a BorderContainer
<html>
    <head><title>Fun with BorderContainer!</title>

        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css" />
        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dijit/themes/tundra/tundra.css" />


        <script
            djConfig="parseOnLoad:true",
            type="text/javascript"
            src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js">
        </script>

        <script type="text/javascript">
            dojo.require("dijit.layout.BorderContainer");
            dojo.require("dijit.layout.ContentPane");
            dojo.require("dojo.parser");
            dojo.addOnLoad(function(  ) {
                //the BorderContainer
                var bc = new dijit.layout.BorderContainer(
                    {
                        design: "headline",
                        style: "height:500px;width:500px;border:solid 3px"
                    },
                    "bc"
                );

                var topContentPane = new dijit.layout.ContentPane(
                    {
                        region: "top",
                        style: "background-color:blue;height:100px;",
                        splitter: true,
                        minSize : 10,
                        maxSize : 100
                    },
                    document.createElement("div")
                );

                var centerContentPane = new dijit.layout.ContentPane(
                    {
                        region: "center"
                    },
                    document.createElement("div")
                );

                var bottomContentPane = new dijit.layout.ContentPane(
                    {
                        region: "bottom",
                        style: "background-color:red;height:100px;",
                        splitter: true
                    },
                    document.createElement("div")
                );

                bc.startup(  ); // do initial layout (even though there are no
children)

                //now add the children.
                bc.addChild(topContentPane);
                bc.addChild(centerContentPane);
                bc.addChild(bottomContentPane);

            });
        </script>
    <head>
    <body class="tundra">
        <div id="bc"></div>
    </body>
</html>

The previous example called startup( ) to do initial layout and then used the addChild method to add children. The following approach would also have worked:

bc.domNode.appendChild(topContentPane.domNode);
bc.domNode.appendChild(centerContentPane.domNode);
bc.domNode.appendChild(bottomContentPane.domNode);
bc.startup(  );

BorderContainer dijits are quite flexible and can be nested arbitrarily if the situation calls for it. They're also a great way to set up a headline style or sidebar style layout with virtually no effort, although you should generally consider plain old CSS for production situations in which widgets don't add any value.

StackContainer

A StackContainer is a layout dijit that displays a sequence of tiles one at a time. A StackContainer is conceptually just like a slideshow in which you can page backward and forward through a "stack" of tiles. Like LayoutContainer, you provide any number of child widgets to the StackContainer, and it takes care of the display. In its most basic usage, you simply page through the available tiles, as shown in Example 14-4.

Example 14-4. Creating a StackContainer in markup
<div id="stack" dojoType="dijit.layout.StackContainer"
  style="width:100px; height:100px; margin:5px; border:solid 1px;">

    <div dojoType="dijit.layout.ContentPane">
        One fish...
    </div>
    <div dojoType="dijit.layout.ContentPane">
        Two fish...
    </div>
    <div dojoType="dijit.layout.ContentPane">
        Red fish...
    </div>
    <div dojoType="dijit.layout.ContentPane">
        Blue fish...
    </div>

</div>

<button dojoType="dijit.form.Button">&lt;
    <script type="dojo/method" event="onClick" args="evt">
        dijit.byId("stack").back(  );
    </script>
</button>

<button dojoType="dijit.form.Button">&gt;
    <script type="dojo/method" event="onClick" args="evt">
        dijit.byId("stack").forward(  );
    </script>
</button>

The usual container and generic layout methods apply to StackContainer; additionally, you should also note the features in Table 14-6.

Table 14-6. StackContainer API

Name (default)

Type

Comment

doLayout

Boolean

Used to change the size of the currently displayed child to match the container's size. true by default.

selectedChildWidget

Object

References the currently selected child widget. null by default.

selectChild(/*Object*/ page)

Function

Used to select a specific child widget.

forward( )

Function

Used to page forward to the next child widget.

back( )

Function

Used to page backward to the previous child widget.

The StackContainer also supports several additional features:

  • A closeChild(/*Object*/ child) method

  • An onClose( ) extension point

  • Children may exhibit the closeable, title, and selected attributes

  • Topics that are published when children are added, removed, or selected, <id>-addChild, <id>-removeChild, and <id>-selectChild, respectively

Because these features are most commonly associated with the TabContainer (which inherits from StackContainer), however, their formal introduction will be delayed until the next section.

If you've followed along with the previous example of programmatically creating layout dijits, Example 14-5 should seem awfully familiar.

Example 14-5. Programmatically creating a StackContainer
var container = new dijit.layout.StackContainer({}, "foo");

var leftChild = new dijit.layout.ContentPane({});
leftChild.domNode.innerHTML="page 1";

var rightChild = new dijit.layout.ContentPane({});
rightChild.domNode.innerHTML="page 2";

container.addChild(leftChild);
container.addChild(rightChild);

container.startup(  );

/* Skip from page 1 to page 2 with... */
dijit.byId("foo").forward(  );

Procrastination (a.k.a. Lazy Loading) May Yield Better Performance

The previous example uses explicit buttons for paging, but it is not uncommon to use a StackContainer as an application container to control the flow of an application with multiple pages. For example, your application might initially display a page with a search bar; once a button is pressed to trigger a search, you might page forward to display a search results screen. Assuming that you've defined every page of the application as a child of a StackContainer, this approach has the advantage of never explicitly reloading a page—a little bit of snazzy flare for a Web 2.0 style interface.

Although loading the entire application at one time when the page loads may be the best option for some circumstances, you could also elect to lazy-load content by configuring a child ContentPane to lazy load via its href attribute. Recall that this behavior is controlled by a ContentPane's preload attribute, which when false (the default) does not fetch content until it becomes visible. You can watch the Firebug console to confirm this behavior. For example, if the URL referenced below, which is entitled blueFish contained the text "Blue fish . . . " from Example 14-4, then the following adjustment would lazy load the fourth page of the StackContainer:

<div dojoType="dijit.layout.ContentPane" href="blueFish"></div>

Lazy loading is ideal for situations in which there may be application features that are essential, but not often used. A preferences pane is a prime candidate for lazy loading that often involves gobs of controls that may not appear on any other page of the application.

TabContainer

As it turns out, a TabContainer is really just a fancier version of a StackContainer—the primary difference is that a TabContainer comes with a snazzy set of tabs that can be used to control which page is displayed at any given time. In fact, the TabContainer inherits from StackContainer and provides only a few additional features that pertain to the list of tabs itself. Example 14-6 illustrates basic usage of the TabContainer.

Example 14-6. Creating a TabContainer in markup
<div dojoType="dijit.layout.TabContainer"
  style="width:225px; height:100px; margin:5px; border:solid 1px;">

    <div dojoType="dijit.layout.ContentPane" title="one">
        One fish...
    </div>
    <div dojoType="dijit.layout.ContentPane" title="two">
        Two fish...
    </div>

    <div dojoType="dijit.layout.ContentPane" title="red"
      closable=
    "true">Red fish...
        <script type="dojo/method" event="onClose" args="evt">
            console.log("Closing", this.title);
            return true; //must return true for close to occur!
        </script>
    </div>

    <div dojoType="dijit.layout.ContentPane" title="blue">
        Blue fish...
    </div>

</div>

Take special note that the tab controls take care of themselves; you simply provide a title attribute to each child of the TabContainer, and the rest is handled with internal automation that you don't have get be directly involved with (and that's the best kind). Additionally, notice that you may provide a closeable tab via the closable attribute, and an optional onClose extension point may perform a custom action when a close does occur. Be careful, though, because if true is not returned from onClose, the tab will not close.

Table 14-7 lists the features that pertain to TabContainer.

Table 14-7. TabContainer API

Name

Type

Comment

title

String

Mixed into _Widget from StackContainer. Used in a child to provide the title for its tab button.

closeable

Boolean

Mixed into _Widget from StackContainer. Used in a child to specify whether a tab should be closeable. When closeable, a small icon appears on the tab that provides a means of closing the tab. false by default.

onClose( )

Function

An extension point mixed into _Widget from StackContainer that provides a uniform way for children to provide an extension point that may be used to augment behavior when closed. Returns true by default.

tabPosition

String

Specifies where the list of tab buttons should appear. Possible values include "top" (the default), "button", "left-h", and "right-h".

<id>-addChild

<id>-removeChild

<id>-selectChild

dojo.publish topics

This functionality is inherited from StackContainer. The named topics are published when children are added, removed, or selected. <id> refers to the id value of the TabContainer.

The buttons you see on a tab container are honest to goodness dijit.form.Button buttons; do with them as you will.

Just like with StackContainer, you may lazy load content in a TabContainer via a ContentPane, as long as preload is set to be false.

And now, Example 14-7 shows how to use programmatic creation.

Example 14-7. Programmatically creating a TabContainer
var container = new dijit.layout.TabContainer({
    tabPosition: "left-h",
    style : "width:200px;height:200px;"
}, "foo");

var leftChild = new dijit.layout.ContentPane({title : "tab1"});
leftChild.domNode.innerHTML="tab 1";

var rightChild = new dijit.layout.ContentPane({title : "tab2", closable: true});
rightChild.domNode.innerHTML="tab 2";

container.addChild(leftChild);
container.addChild(rightChild);

container.startup(  );

AccordionContainer

Like a TabContainer, AccordionContainer inherits from StackContainer and is a means of displaying one child at a time from a collection of widgets. The visual difference is that the container looks like a vertical accordion, and animates when each child is selected.

One important difference in how you use AccordionContainer, however, is that you must use a special child container called AccordionPane that provides an explicit wrapper for its child widgets. The actual reasoning for why this is the case is not very interesting and has to do with how the underlying implementation for AccordionContainer. In general, just treat an AccordionPane like a ContentPane and be on your merry way.

As of version 1.1, AccordionPane does not support nested layout widgets such as SplitContainer; virtually all other types of content, however, should work just fine.

Example 14-8 shows a simple AccordionContainer in action.

Example 14-8. Creating an AccordionContainer in markup
<div id="foo" dojoType="dijit.layout.AccordionContainer"
  style="width:150px; height:150px; margin:5px">
    <div dojoType="dijit.layout.AccordionPane" title="one">
            <p>One fish...</p>
    </div>
    <div dojoType="dijit.layout.AccordionPane" title="two">
            <p>Two fish...</p>
    </div>
    <div dojoType="dijit.layout.AccordionPane" title="red">
            <p>Red fish...</p>
    </div>
    <div id="blue" dojoType="dijit.layout.AccordionPane" title="blue">
        <div dojoType="dijit.layout.ContentPane" href="blueFish"></div>
    </div>
</div>

With respect to API, AccordionContainer itself provides only one additional attribute beyond what StackContainer offers, shown in Table 14-8.

Table 14-8. AccordionContainer API

Name (default)

Type

Comment

duration (250)

Integer

An attribute of AccordionPane that provides the duration in milliseconds that it should take to slide the pane to select another one.

Although we could leave programmatic creation as an exercise for the interested reader, there is a slight difference to the creation pattern because AccordionPane is a dijit on its own, as shown in Example 14-9.

Example 14-9. Programmatically creating an AccordionContainer
var container = new dijit.layout.AccordionContainer({}, "foo");

var content1 = dojo.doc.createElement("p");
content1.innerHTML = "content 1";

var ap1 = new dijit.layout.AccordionPane({title: "pane1", selected : true}, content1);
container.addChild(ap1);

var content2 = dojo.doc.createElement("p");
content2.innerHTML = "content 2";

var ap2 = new dijit.layout.AccordionPane({title: "pane2"}, content2);
container.addChild(ap2);

container.startup(  );

Rendering and Visibility Considerations

You may have noticed while working through the examples in this chapter that you usually see the layout occur as the page loads; for example, you might see ordinary text HTML representing some of the layout content, and then all of a sudden it magically transforms into this great-looking layout. While not totally unacceptable, you will probably not want to see the rendering take place in many situations.

A common technique for working around the situation is to initially set the body of the page to be hidden, and then when the page finishes loading, make it visible all at one time. Accomplishing this technique is quite simple, and you merely have to provide a style (or class) indicating that the body of the page should be hidden, like so: <body style="visibility:hidden;"> should do the trick. Just remember to add the corresponding call to make it visible at the desired time. Assuming you've made the entire body hidden, adding a dojo.style(dojo.body( ), "visibility", "visible") to a dojo.addOnLoad displays the page's content. Any callback could be used in place of page load if for some reason you wanted to delay showing the page until some arbitrary event occurred (like an asynchronous callback that provides data for a custom widget, perhaps).

Recall that the difference between the CSS styles of visibility and display has to do with taking up space on the screen. In general, nodes styled with visibility:hidden are hidden from display but still take up space; nodes styled with display:none would not be visible and would take up no space—resulting in a noticeable shift of content when the display is changed to display:block.

One caveat that should be noted, however, is that layout containers do not always respond well when they are initially created within a hidden container. If you find that your layout containers are not visible when they should be, you may need to manually call their resize( ) method to force them to render properly. Historically, this issue has especially been the case when displaying a layout container within a dijit.Dialog.

Layout dijits do not always render properly if they are created in context that does not immediately make them visible. Almost all of the time, you can simply call the layout container's resize( ) method to render it.

Summary

After reading this chapter, you should be able to:

  • Appreciate the common design challenges (tabbed layouts, for example) that layout dijits alleviate

  • Understand the basic features provided by the various layout dijits

  • Create arbitrary layouts with the layout dijits both in markup and programmatically

  • Use BorderContainer to create flexible, tiled layouts that can arbitrarily resize

  • Use ContentPane to lazy load content either as a standalone or as part of another dijit

  • Use StackContainer and TabContainer to display multiple pages of data in an application

  • Understand some of the considerations with respect to initially displaying layout dijts as being hidden

  • Understand the existing limitations of AccordionPane with respect to embedding layout dijits

  • Understand the role that the base classes _Container and _Contained play with the layout dijits

Application widgets are coming up next.