ExtGWT Rich Internet Application: Crafting UI Real Estate

Oded Nissan

September 2012

(For more resources on ExtGwt, see here.)


Layouts are a fundamental part of the GXT library. They provide the ability to create flexible and beautiful application UIs easily. However, with this power comes a level of complexity. A solid understanding of layouts is the key to using the library effectively.

With GWT Panels, the panel itself is responsible for creating the panel's markup and inserting its children at the appropriate location, and creating appropriate markup as changes are made. Unlike GWT Panels, LayoutContainer (a concrete GXT container with support for layouts) does not physically connect its child components to the container's DOM. The Document Object Model is used to represent an HTML document in a tree-like structure in the browser's memory. We can dynamically change the content of the HTML page by manipulating the DOM. Rather, it is the job of the layout to both build the internal structure of the container, and to connect its child widgets.

In order for a GXT container's HTML to be rendered, the container's layout() method must execute. This is different from GWT panels, in which the HTML is rendered when the components are attached to the panel. There are several ways in which the layout can execute. For now, let's go with the simplest case in which the layout executes when the container is attached. Attached is a GWT term that indicates that the widget is part of the browser's DOM. Attaching and detaching could be a subject on its own, so let's just assume it means when the widget is added to and removed from the page.

When we add a container to RootPanel (for example, RootPanel.get(). add(container)), the container will be attached, and the container's layout will execute, generating the needed HTML markup. If we add another component to the now rendered container, (container.add(new Label("New Item"))) we will have to manually execute/ refresh the container (container.layout()) for the additions (as well as removals) to be effected. This sort of Lazy-Rendering is the default behavior of GXT as of 2.2.3 with GXT 3 planning to use the same approach as GWT itself.

Many GXT layouts can be used in conjunction with LayoutData, which are configuration objects assigned to each child widget within a container, and provides the layout object with additional information to be used when executing the layout.

Aside from a layout being executed when the container is attached, or when layout() is called manually on the container, there are two other ways in which a layout will be executed. After a container executes its layout, it looks and sees if any of its children are containers. When it finds a child container, it then executes its layout. So as long as there is a chain of containers, the execution of layouts will cascade to the child containers. This is a very important concept as you can lay out a top-level container, and the child containers will have a chance to adjust their layouts as well.

A container's layout will also execute when its size is adjusted. This is default behavior, and can be disabled. This is another important concept as it means that if a container's size is changed, the layout has a chance to update based on the container's new size.

(For more resources on ExtGwt, see here.)

Organizing navigation with AccordionLayout

Aptly named after the musical instrument, AccordionLayout is GXT's implementation of the popular accordion structures found in many UI toolkits. It is a FitLayout (child is expanded or sized to fit the dimension of its container) that is implemented to render child components (must be ContentPanel) as mutually exclusively collapsible sections whose headings are always visible.

You can expand and collapse a section with either the collapse tool (default) or by clicking on its heading if the collapse tool has been disabled.

An important point to note with AccordionLayout is that it only takes ContentPanel items and only one (defaults to the first) is viewable (expanded) at a given time.

How to do it...

All we really need to do is set AccordionLayout as the layout for a container and then add ContentPanel child items to the container, with each ContentPanel having an appropriate heading.

public HtmlContainer makeLinks(String[] links){ StringBuilder sb = new StringBuilder("<ul class="accordion-list">"); for (String link : links) { sb.append("<li>").append(link).append("</li>"); } sb.append("</ul>"); HtmlContainer html = new HtmlContainer(sb.toString()); return html; } // create the accordion ContentPanel accordionCt = new ContentPanel(); accordionCt.setSize(180, 200); accordionCt.setHeading("Navigation"); accordionCt.setBodyBorder(false); accordionCt.setLayout(new AccordionLayout()); // add the products panel ContentPanel panel = new ContentPanel(); panel.setHeading("Products"); accordionCt.add(panel); // put links into "products" String[] links = new String[]{"view", "create", "search"}; panel.add(makeLinks(links)); // add the sales panel panel = new ContentPanel(); panel.setHeading("Sales"); accordionCt.add(panel); // put links into "sales" links = new String[]{"orders", "returns", "invoices"}; panel.add(makeLinks(links)); // add the reports panel panel = new ContentPanel(); panel.setHeading("Reports"); accordionCt.add(panel); // put links into "reports" links = new String[]{"summary", "stock", "Ad-hoc"}; panel.add(makeLinks(links)); // add the issues panel panel = new ContentPanel(); // setAnimCollapse(false); panel.setHeading("Issues"); panel.setBodyStyle("padding:10px;"); panel.addText("<p>we don't have any issues right ...</p>"); accordionCt.add(panel); GxtCookBk.getAppCenterPanel().add(accordionCt);


How it works...

First we define a makeLinks() convenience method that takes in an array of string literals which it formats as an unordered HTML list using StringBuilder, and then returned as the contents on HtmlContainer. This is what we finally present within the ContentPanel objects rendered with AccordionLayout.

With that out of the way, we initialize accordionCt as ContentPanel configured to occupy a dimension of 180 by 200 (setSize()), given a title of "Navigation" with setHeading(), and with inner borders turned off with setBodyBorder(false); and lastly we set its layout to new AccordionLayout() using accordionCt.setLayout(). Next we create a products ContentPanel and employ our earlier defined makeLinks() method to generate HtmlContainer of an unordered list which we then add to the panel with panel.add (makeLinks(links)). We also do the same for a sales and reports ContentPanel all of which gets added to our AccordionLayout panel with accordionCt.add(panel).

The issues panel is slightly different from the others because rather than adding a list of strings (links) we set a message on it (it's a panel, you can do anything with it!) rightly saying that there are no issues with this recipe.

There's more...

By default the ContentPanel items rendered with AccordionLayout can be toggled (expanded/collapsed) with the tool button on the far right of its heading or by just clicking anywhere on the title/heading bar. If the tool button is hidden with accordionLayout. setHideCollapseTool(true) then accordionLayout.setTitleCollapse() must not be given false (default is true) else you can't go from one panel to the other within the AccordionLayout container.

An alternative to using AccordionLayout is to expand and collapse a panel as a component is clicked.

Snapping components even when resized

Having your UI components maintain their sanity when the browser window or their container is resized without any effort from the coder is bliss. GXT provides AnchorLayout which enables contained components to anchor relatively to a container's dimensions, maintaining the anchor rules even when the container is resized.

The components rendered with AnchorLayout are sized with an anchor-spec which is a string of two values used for horizontal and vertical anchoring respectively. The values which can be expressed as percentages or offsets or even both, determine how a component is anchored to its container. A value of 100% 50% would render a component the complete width of its container and half its height, if only one value is provided in the anchor-spec it is assumed to be the width while the height will default to auto.

Similarly, an anchor-spec of -30 -120 would render the component using the complete width of its container minus 30 pixels and the complete height of its container minus 120 pixels, and if only one offset value is given it is assumed to be the right edge offset value while the bottom offset value will default to zero. A hybrid offset value of -80 70% would render the width offset from the container's right edge by 80 pixels and 70 percent of the container's height.

How to do it...

We simply add fields to FormPanel specifying the anchoring rule for the field with FormData instance (FormData extends AnchorData) which will be used by the AnchorLayout implementation (FormLayout) that FormPanel has.

// create a window Window window = new Window(); window.setPlain(true); window.setSize(400, 265); window.setHeading("Robust Component Anchoring - Resize Now!"); window.setLayout(new FitLayout()); // create a FormPanel FormPanel form = new FormPanel(); form.setBorders(false); form.setBodyBorder(false); form.setLabelWidth(55); form.setPadding(5); form.setHeaderVisible(false); // add TextField to the form TextField field = new TextField(); field.setFieldLabel("Sent To"); form.add(field, new FormData("100%")); // add TextField to the form field = new TextField(); field.setFieldLabel("Subject"); form.add(field, new FormData("100%")); // add HtmlEditor to the form HtmlEditor html = new HtmlEditor(); html.setHideLabel(true); form.add(html, new FormData("100% -53")); // add the buttons and the form to the window. window.addButton(new Button("Send")); window.addButton(new Button("Cancel")); window.add(form); // show the window window.show();

How it works...

We begin by creating a GXT window (so we can easily demonstrate resizing) and then we add FormPanel to it. FormPanel uses FormLayout which is actually a glorified AnchorLayout. This is a useful layout for laying out form-related components by auto-aligning them.

When adding a field to FormPanel, there is no need to explicitly add a label for the field, as we set the field's label using the field's setFieldLabel() method and the label is automatically added and aligned by the layout.

Next, we just add Field widgets to our FormPanel, providing the anchor-spec with FormData which is also AnchorData in disguise.

The two TextField widgets are given a one-valued anchoring-spec of 100% meaning they would occupy the whole width of their container (FormPanel) but with a height of auto, while the HtmlEditor field whose label is hidden with html.setHideLabel(true) will also occupy the entire available width of the container just like the previous fields, but would have a height of the container's height minus 53 pixels.

A trial will convince you, resize the window and see!

UI cardinality with BorderLayout

It is almost natural for web developers to organize their UIs into regions; for example, header (top or north), footer (bottom or south), say navigation or adverts (left and right or east and west), and the center which is for serious stuff. As a result of this need, many UI toolkits have come up with all sorts of imaginable ways to help developers easily divide up the browser or a container into regions which they can further nest to conjure truly complex UI arrangements.

BorderLayout is GXT's simple yet powerful solution to what the web has been trying to solve with HTML tables and CSS grids. It allows components to be added to a container with LayoutRegion which can be NORTH, EAST, SOUTH, WEST, or CENTER. The only catch to using this layout is that if it has only one child component then it must be placed in the center region with LayoutRegion.CENTER.

How to do it...

We simply set the layout of a container to BorderLayout and then add child components to it specifying a region with LayoutRegion encapsulated within BorderLayoutData which can have other settings such as Margins.

// create the main panel ContentPanel mainView = new ContentPanel(); mainView.setSize(550, 380); mainView.setHeaderVisible(false); mainView.setBodyBorder(false); mainView.setLayout(new BorderLayout()); // set up west-side ContentPanel panel = new ContentPanel(); panel.setHeading("West"); BorderLayoutData westData = new BorderLayoutData( LayoutRegion.WEST); westData.setSize(130); westData.setMinSize(100); westData.setMaxSize(180); westData.setSplit(true); westData.setCollapsible(true); westData.setMargins(new Margins(0, 5, 0, 0)); mainView.add(panel, westData); // set up center panel = new ContentPanel(); panel.setHeading("Center"); panel.setStyleAttribute("background", "#fff"); BorderLayoutData centerData = new BorderLayoutData( LayoutRegion.CENTER); centerData.setMargins(new Margins(5)); mainView.add(panel, centerData); // set up north-side panel = new ContentPanel(); panel.setHeading("North"); BorderLayoutData northData = new BorderLayoutData(LayoutRegion. NORTH,100); northData.setCollapsible(true); northData.setFloatable(true); northData.setHideCollapseTool(true); northData.setSplit(true); northData.setMargins(new Margins(0, 0, 5, 0)); mainView.add(panel, northData); //set up east-side panel = new ContentPanel(); panel.setHeading("East"); BorderLayoutData eastData = new BorderLayoutData( LayoutRegion.EAST); centerData.setMargins(new Margins(0, 0, 0, 5)); mainView.add(panel, eastData); // set up south-side panel = new ContentPanel(); panel.setHeading("South"); BorderLayoutData southData = new BorderLayoutData(LayoutRegion.SOUTH, 100); southData.setSplit(true); southData.setCollapsible(true); southData.setFloatable(true); southData.setMargins(new Margins(5, 0, 0, 0)); mainView.add(panel, southData); /* * GxtCookbk is the application's entry point class. * We access its main content panel using the * static GxtCookBk.getAppCenterPanel() call. * We add our viewPort to the main content panel. */ GxtCookBk.getAppCenterPanel().add(mainView);

How it works...

First we create our container object as a ContentPanel assigned to mainView and then we give it the cardinal layout using mainView.setLayout(new BorderLayout()).

We create a "west-side" ContentPanel and then we initialize westData with new BorderLayoutData() passing in LayoutRegion.WEST. On westData we set a width, minimum width and maximum width using setSize(), setMinSize(), and setMaxSize() respectively, thus ensuring that this region is expandable within those limits but also because it can be resized by dragging the split bar. Resizing is supported by calling westData.setSplit (true).

We also make it collapsible so that it can be minimized by pressing the minimize arrow on the top of the panel and then give it some surrounding space with setMargins(), controlling its top, right, bottom, and left margins. The panel is then added to mainView using the westData LayoutData.

Remember, we must provide a center region to BorderLayout, so we add ContentPanel to the center using centerData initialized as a BorderLayoutData with LayoutRegion. CENTER and given an all-round margin of 5 pixels.

Next, we add east, south, and north regions to mainView, by creating ContentPanel, a new BorderLayoutData() class and adding both the panel and the BorderLayout instance to the mainView panel.


(For more resources on ExtGwt, see here.)

Building a basic wizard with CardLayout

Wizards have become common place (except the ones in Harry Potter); they allow us to complete complex tasks in simple intuitive steps and can be as simple as just a sequence of navigable steps or as complex as having steps dynamically added from an RPC call based on a selection in a previous step.

The good news is that CardLayout can be used to build a basic wizard, devoid of all the bells and whistles that your imagination can conjure. CardLayout renders the child components of a container such that only one component is fitted or visible (CardLayout extends FitLayout) in the container at a time. The only way to move from one child component to the next is by calling setActiveItem() on the layout, giving it the component to display while the others stay hidden.

The drawback to this whole thing is that CardLayout itself does not provide a mechanism for handling navigation between the components it is rendering, nor does the supposedly handy CardPanel that GXT provides does this. Thankfully, building a real WizardPanel with basic navigation is trivial and that's what we set out to achieve here.

How to do it...

This one is a little bit involved, but all we are doing is simply defining an API with the Wizard interface which is then implemented in WizardPanel. WizardPanel uses CardLayout and does almost everything with the getActiveItem() and setActiveItem() methods of the CardLayout API.

/** * implement FormPanel as a wizard in WizardPanel. */ public class WizardPanel extends FormPanel { enum DIR{ NEXT, BACK } private Button nextBtn, prevBtn; protected CardLayout cardLayout; protected FormButtonBinding btnBind; protected List<layoutcontainer> cards; /** * constructor for WizardPanel */ public WizardPanel() { super(); // create a CardLayout. cardLayout = new CardLayout(); setLayout(cardLayout); cards = new ArrayList(); setButtonAlign(HorizontalAlignment.RIGHT); // add a Back button prevBtn = new Button("Back", new SelectionListener() { @Override public void componentSelected(ButtonEvent ce) { navigate(DIR.BACK); } }); // disable the Back button and add it to the panel. prevBtn.setEnabled(false); addButton(prevBtn); // create the Next button. nextBtn = new Button("Next", new SelectionListener() { @Override public void componentSelected(ButtonEvent ce) { navigate(DIR.NEXT); } }); btnBind = new FormButtonBinding(this); btnBind.addButton(nextBtn); addButton(nextBtn); } public Button getNextBtn() { return nextBtn; } public Button getPrevBtn() { return prevBtn; } /** * check if there is a next card. * @return true if there is a next card. */ public boolean hasNext() { boolean has = false; LayoutContainer active = getActive(); if(!cards.isEmpty() && cards.indexOf(active)+1 < cards.size()){ has = true; } return has; } /** * check if there is a previous card. * @return true if there is a previous card. */ public boolean hasPrevious() { boolean has = false; LayoutContainer active = getActive(); if(!cards.isEmpty() && cards.indexOf(active) >= 1){ has = true; } return has; } /** * get the next card. * @return LayoutContainer the next card. */ public LayoutContainer getNext() { LayoutContainer active = getActive(); LayoutContainer next = cards.get( cards.indexOf(active)+1 ); return next; } /** * get the previous card. * @return LayoutContainer the previous card. */ public LayoutContainer getPrevious() { LayoutContainer active = getActive(); LayoutContainer next = cards.get( cards.indexOf(active)-1 ); return next; } /** * navigate between the cards according to the DIR * @param dir DIR enum. */ public void navigate(DIR dir) { LayoutContainer target = null; if(DIR.NEXT.equals(dir)){ target = getNext(); } else if(DIR.BACK.equals(dir)){ target = getPrevious(); } cardLayout.setActiveItem(target); // don't confuse our navigation sequence boolean hasNext = hasNext(); if(hasNext){ btnBind.startMonitoring(); }else{ btnBind.stopMonitoring(); } getNextBtn().setEnabled(hasNext); getPrevBtn().setEnabled(hasPrevious()); } /** * add a card to the panel * @param card the card to add. */ public void addCard(LayoutContainer card) { cards.add(card); add(card); } /** * add a list of cards. * @param cards List of cards. */ public void addCards(List<layoutcontainer> cards) { for (LayoutContainer card : cards) { addCard(card); } } /** * get the active card * @return LayoutContainer the active card. */ public LayoutContainer getActive() { LayoutContainer active = (LayoutContainer) cardLayout. getActiveItem(); return active; } @Override public boolean isValid(boolean preventMark) { boolean valid = true; for (Field f : getFields()) { if (f.isRendered() && f.isVisible() && !f.isValid(preventMark)) { valid = false; } } return valid; } } // create a WizardPanel WizardPanel wizardPanel = new WizardPanel(); wizardPanel.setSize(450, 300); wizardPanel.setHeading("GXT Wizard"); wizardPanel.setStyleAttribute("background", "#fff"); // create the first card. LayoutContainer card = new LayoutContainer(); card.addText("Please click Next to sign in .."); // add card to the panel. wizardPanel.addCard(card); // create the second card. card = new LayoutContainer(new FormLayout()); card.addText("Enter your login details below"); // create username field and add it to the card. TextField usrName = new TextField(); usrName.setName("username"); usrName.setAllowBlank(false); usrName.setFieldLabel("Username"); card.add(usrName); // create the password field and add it to the card. TextField pswd = new TextField(); pswd.setName("pswd"); pswd.setPassword(true); pswd.setAllowBlank(false); pswd.setFieldLabel("Password"); card.add(pswd); wizardPanel.addCard(card); card = new LayoutContainer(); card.addText("Welcome to GXT WizardPanel!"); // add card to the panel wizardPanel.addCard(card); /* * GxtCookbk is the application's entry point class. * We access its main content panel using the * static GxtCookBk.getAppCenterPanel() call. * We add our panel to the main content panel. */ GxtCookBk.getAppCenterPanel().add(wizardPanel);


How it works...

WizardPanel defines hasNext() and hasPrevious() methods both of which will return true or false indicating if there is a next or previous component to navigate to. It also defines a navigate() method which expects a NEXT or BACK direction from its DIR enum which indicates the direction of the navigation.

getNext(), getActive(), and getPrevious() methods all of which return LayoutContainer are used to do actual navigation.

Finally, it provides addCard() and addCards() methods which respectively accepts a LayoutContainer or list of LayoutContainer objects allowing us to easily add components to the wizard.

The WizardPanel constructor creates CardLayout and sets it as the layout to be used by the panel. It then creates an ArrayList to hold the card which will be added to the panel and creates the Next and Back buttons and binds them to the navigate() method, so they implement the forward and backward navigation.

WizardPanel provides the getActive() method to simply return the active component of the CardLayout as a LayoutContainer. hasNext() then returns true only if there are items beyond the active one from its list of cards and hasPrevious() only returns true if the active card is not the first one.

getNext() and getPrevious() first obtain the currently visible card with getActive() and then returns the next or previous card relative to the active card respectively.

The crux of WizardPanel is its navigate() method, which uses a conditional block to set the target LayoutContainer to the value of either getNext() or getPrevious() depending on the direction of navigation as represented by the dir argument. The second conditional block is used to stop validation of the form when we reach the end of the wizard otherwise the next button will remain enabled even when there are no more next steps to navigate to.

The last two lines of the navigate() method are used to enable/disable the next and previous navigation buttons based on the outcome of hasNext() and hasPrevious() respectively.

A very important override of WizardPanel is the isValid() method from its FormPanel superclass. It is implemented to only check validation rules for rendered and visible fields (those in current card), otherwise we would not be able to leave the wizard's first step and move to the wizard's second step because there happens to be a mandatory field in the second step which we have not even gotten to yet and will not be able to navigate to.

Using WizardPanel is as easy as using a standard GXT panel; we instantiate it with new WizardPanel() and then add components to it with its addCard() or addCards() methods.

RowLayout vertical and horizontal aligning

RowLayout renders a container's components in a single vertical or horizontal row. It is very flexible and allows configurable options for height, width, and margins for each child component.

RowLayout supports both pixel and percentage based measurement values using a RowData (LayoutData) object. A value from 0 to 1 (inclusive) is treated as a percentage while values greater than 1 are treated as pixels. However, the size of a component will be determined from the component itself (computed size) if given a RowData value of -1, for either the height or the width (but not both), thus allowing the component to decide its size and not the layout.

How to do it...

We simply set RowLayout as the layout for a container and then add child components to the container, providing a RowData object during the add operation. RowData will eventually determine how the components are rendered and sized in rows.

// create a ContentPanel with RowLayout ContentPanel panel = new ContentPanel(); panel.setSize(400, 300); panel.setHeading("RowLayout: Vertical Orientation"); panel.setLayout(new RowLayout()); Text item1 = new Text("First Row"); item1.setBorders(true); Text item2 = new Text("Second Row"); item2.setBorders(true); // add the rows panel.add(item1, new RowData(1, -1, new Margins(4))); panel.add(item2, new RowData(1, 1, new Margins(0, 4, 0, 4))); /* * GxtCookbk is the application's entry point class. * We access its main content panel using the * static GxtCookBk.getAppCenterPanel() call. * We add our panel to the main content panel, * specifying a margin of * 5 pixels for all * dimentions of the panel */GxtCookBk.getAppCenterPanel().add(panel, new FlowData(5)); // create a ContentPanel with RowLayoutpanel = new ContentPanel(); panel.setSize(400, 300); panel.setHeading("RowLayout: Horizontal Orientation"); panel.setLayout(new RowLayout(Orientation.HORIZONTAL)); item1 = new Text("First Row"); item1.setBorders(true); item2 = new Text("Second Row"); item2.setBorders(true); // add the rows panel.add(item1, new RowData(-1, 1, new Margins(4))); panel.add(item2, new RowData(1, 1, new Margins(4, 0, 4, 0))); /* * GxtCookbk is the application's entry point class. * We access its main content panel using the * static GxtCookBk.getAppCenterPanel() call. * We add our panel to the main content panel, * specifying a margin of * 5 pixels for all * dimentions of the panel */GxtCookBk.getAppCenterPanel().add(panel, new FlowData(5));

How it works...

We begin by creating a ContentPanel with a RowLayout with the default Orientation.VERTICAL to render components vertically.

Next, we create two borderless Text widgets which are then added to the panel. The first Text is added with RowData indicating that item1 will occupy 100 percent of its container's width but will have a height that is determined by its contents and will also have a margin of four pixels around it. item2 will occupy 100 percent of the width of the container as well as 100 percent of what is left of its container's height after the height of item1 has been computed. It will have a margin of four pixels for its left and right borders.

We also demonstrate the horizontal rendering of RowLayout with the second ContentPanel whose RowLayout is explicitly given Orientation.HORIZONTAL. The two (re-initialized) borderless Text widgets are also added to it but this time item1 will have a computed width of 100 percent of the container height and four pixel surrounding margin while item2 gets 100 percent of the available container width (of course after item1 width is determined) and 100 percent of the container height, with four pixel top and bottom margin but zero pixel right and left margin.

Building grids with ColumnLayout

ColumnLayout positions and sizes a container's children horizontally, with each component specifying how much of the container's available width it will take up. This width specification which can be expressed in pixels or as a percentage is encapsulated as a ColumnData object, the child widgets are then sized by ColumnData and positioned in horizontal columns. If you need to render components in a small number of horizontal blocks, such as a three column (left, middle, and right) segmented form, then ColumnLayout is your best bet.

How to do it...

Set ColumnLayout as the layout for a container and then add components to the container providing a ColumnData object during the add operation that will specify how much of the container's width the component will occupy.

The sum of the values given to ColumnData during all the add operations on the container should be less or equal to 1 (for percentage values) or less or equal to the width of the container, otherwise you could get some really bizarre overlapping on the child components.

LayoutContainer main = new LayoutContainer(new ColumnLayout()); main.setBorders(true); main.setSize(400, 250); main.setStyleAttribute("padding", "10px"); LayoutContainer panel = new LayoutContainer(new CenterLayout()); panel.setHeight(220); panel.setBorders(true); panel.add(new Html("Left Column")); main.add(panel, new ColumnData(.33)); panel = new LayoutContainer(new CenterLayout()); panel.setHeight(220); panel.setBorders(true); panel.add(new Html("Mid Column")); main.add(panel, new ColumnData(.33)); panel = new LayoutContainer(new CenterLayout()); panel.setHeight(220); panel.setBorders(true); panel.add(new Html("Right Column")); main.add(panel, new ColumnData(.33)); GxtCookBk.getAppCenterPanel().add(main);

How it works...

Using ColumnLayout turns out to be quite simple. We create the container as a LayoutContainer object assigned to main but initialized with new columnLayout() as the constructor argument.

After some cosmetic configurations on main, we proceed to create the child components as LayoutContainer panels initialized with CenterLayout. CenterLayout is not a requirement for ColumnLayout, it is just used here on the panel objects so that the HTML widgets added to them will be centered instead of snapping to the top-left corner.

Our example code simply creates three LayoutContainer panels, each having a height of 220 pixels (ColumnLayout does not handle height of its components) and a width of 33 percent of the container's width. We could have easily used a pixel value with main. add(panel, new columnData(120)), in which case that panel will take up 120 pixels of horizontal space within main.

Building DashBoards

The discovery and further exploitation of the XMLHTTPRequest object (xhr) leads to single-page style web apps such as Gmail wherein the user can do just about everything the app offers within that single webpage without refreshing the browser. The idea of a UI where several portions of an application can be mashed up into draggable panes is not totally new to web ninjas; the JSR 258, which is called portlet specification, is an enterprise scale attempt at dashboards.

However, the rich UI toolkits of today, XHR in the modern browser and Web2.x communication systems (JSON, JSONP, GWT RPC, Comet, WebSockets, and so on) all combine to make building dash boards (Portal/Portlets) less painful and more fun.

Consider having to build a dashboard with several closable, collapsible, draggable, and re- orderable views/panes on it, with one view showing the current users' Facebook activity stream, the other showing recent 50 mails from Gmail and yet another showing the user's latest updates from Twitter, such that from a single console (dashboard) the user is able to peruse his or her (fair) share of today's organized privacy invasion.

The GXT Portal and Portlet classes are specialized UI containers that you can employ to create a dashboard interface. However, it has nothing to do with the JSR 258 Portlet specification.

How to do it...

Simply create a Portal initializing it with the number of columns it will have, then configure the width of the columns and add Portlet components to Portal, each having a heading, and specify which of the columns in Portal it should initially (because it will eventually be dragged) show up in.

private void configurePortlet(final ContentPanel portlet) { portlet.setCollapsible(true); portlet.setAnimCollapse(false); portlet.setStyleName("x-window-tc", true); portlet.getHeader().addTool(new ToolButton("x-tool-gear")); ToolButton closeTb = new ToolButton("x-tool-close", new SelectionLis tener(){ @Override public void componentSelected(IconButtonEvent evt) { portlet.removeFromParent(); } }); portlet.getHeader().addTool(closeTb); } // create the Portal Portal portal = new Portal(3); portal.setColumnWidth(0, .50); portal.setColumnWidth(1, .25); portal.setColumnWidth(2, .25); portal.setStyleAttribute("backgroundColor", "white"); // create Portlet Portlet portlet = new Portlet(); portlet.setHeight(250); configurePortlet(portlet); portlet.setHeading("Portlet 1"); portlet.addText("Portlet 1"); // add portlet to the portal portal.add(portlet, 0); // create Portlet portlet = new Portlet(); portlet.setHeight(200); configurePortlet(portlet); portlet.setHeading("Portlet 2"); portlet.addText("Portlet 2"); // portlet to the portal portal.add(portlet, 1); // create Portlet portlet = new Portlet(); portlet.setHeight(65); portlet.setHeaderVisible(false); portlet.setLayout(new FormLayout()); // create and add search field to the portlet. TextField search = new TextField(); search.setHideLabel(true); search.setEmptyText("Search ..."); search.setStyleAttribute("padding", "10px 0 0 10px"); portlet.add(search); // add portlet to the portal portal.add(portlet, 2); /* * GxtCookbk is the application's entry point class. * We access its main content panel using the * static GxtCookBk.getAppCenterPanel() call. * We add our portlet to the main content panel. */ GxtCookBk.getAppCenterPanel().add(portal);

How it works...

We begin by instantiating our dashboard container with new Portal(3), meaning it will have three horizontal regions. Internally, the Portal constructor (as we have invoked it) will place three LayoutContainer panels horizontally using ColumnLayout; each of these panels in turn use a vertically oriented RowLayout to render the views/panes (Portlet) that will be added to Portal.

After the Portal instantiation, we use setColumnWidth() to size the three internal LayoutContainer panels, giving the first one 50 percent width while the second and third both get 25 percent width each.

We then create a Portlet, which is a specialized ContentPanel for use in a Portal. Our configurePortlet() helper method is then invoked with the just created Portlet from where we make it collapsible, turn off the collapse animation, and add a dummy settings ToolButton. We also add a close ToolButton which is used to remove the Portlet from its container (Portal).

After setting a heading and some text on Portlet, we add it to the first column (LayoutContainer) of the Portal using portal.add (portlet, O). The second portlet is much the same as the first except that it has a height of 200 pixels and it is added to the second column in the portal. The third portlet however is 65 pixels high, has no header bar (therefore cannot be dragged or closed even if we had used configurePortlet() on it), and uses a FormLayout.

The third portlet shows that we can do anything with Portlet as we can with ContentPanel which it actually is, so we give it TextField that looks like a search box because we hide its label component using the setHideLabell() method,which provides an elegant alternative to using a TextField with a label, as we usually do in forms.

More resources link : :

You've been reading an excerpt of:

Ext JS 4 Web Application Development Cookbook

Explore Title