Implementing a Custom Browser Area

A browser area is one of the most vital parts of a cockpit perspective. It's responsible for displaying the perspective's main content and managing browsers. Moreover, searching for, displaying, as well as activating items is all done here. Understand how browser areas work and learn how you can create your own browser area.

The Easy Way - Using the Default Browser Area

The cockpit extension comes with a powerful default browser area implementation. The default browser area offers standard browser handling functionality such as minimizing, restoring, closing and creating new browsers. Moreover, together with the standard browser components, functionality for searching for and displaying all kinds of items is already provided.

In order to use the default browser area, you need to to specify your browser area's main type in Spring. A browser area's main type is the type for which it is primarily responsible. For example the browser area and the browsers in a product perspective typically offers the means for searching for and displaying all kinds of products, thus the main type of the browser area would be the Product.

Example

The following code snippet shows how to configure a browser area with its main type set to Product:

<bean id="MyPerspective" class="mypackage.MyPerspective" scope="session" parent="BasePerspective">
		...
		<property name="browserArea">
			<bean id="MyBrowserArea" parent="BaseSearchBrowserArea">
				<property name="rootSearchTypeCode" value="Product"/>
			</bean>
		</property>
		...
	</bean>

The important part is here:

<property name="rootSearchTypeCode" value="Product"/>

where you specify the type code of the area's main type.

The Result

The result can be seen in the pictures below

Creating Your Own Custom Browser Area

This section guides you through the steps required to create your own customized browser area.

Browser Area Logic and Layout

A browser area is basically made up of two things: a Java class and a ZUL file.

The first one typically contains all the logic, whereas the latter defines the layout of the area. However, in some cases it is useful to have some simple logic in the ZUL file as well. An example of this can be seen in the Custom Layout section below.

Custom Layout

The cockpit extension is shipped with a standard browser area ZUL file, baseSearchBrowserArea.zul which can be used as template when writing your own customized area. An example of its layout can be seen in the picture below.

The construction of a layout is simple. At the top, there is a panel with a search field and a search button is shown. After entering a search string to the search field, a user can either click the search button or press ENTER in order to open up a new browser window.

The ZUL file holds the logic for opening up the new browser and the following snippet illustrates how it can be done.

...
<toolbarbutton label="Search" ...>
	<attribute name="onClick">
		// create new browser
		MyBrowserModel browserModel = new MyBrowserModel();

		// get the browser area
		UIBrowserArea area = UICockpitSession.getCurrentPerspective().getBrowserArea();

		// add the browser at position 0 to the browser area
		if(area.addVisibleBrowser(0,browserModel))
		{
			// update browser area
			UICockpitSession.getCurrentPerspective().getBrowserArea().update();
		}

		// give the new browser focus
		browserModel.focus();
	</attribute>
</toolbarbutton>
...

In the center area the browser area's main content is shown. This area typically contains one or more browsers, depending on the mode used. In this particular example, a search browser is shown, which allows the user to search for and display items.

Getting Started - Writing Your Own Custom Browser Area ZUL File

You do not have to use the baseSearchBrowserArea.zul as a base when creating your own custom browser area.

You can write your own browser area .zul file from scratch. It requires basic ZK knowledge.

If you plan to use the AbstractBrowserArea class or any of its subclasses, you need to consider few requirements:

  • The root component in your ZUL file should give the browser area focus when clicked, that is it needs to support click event handling
  • The browser area requires a main content component of type <borderlayout> without any children to be set

These requirements need to be met in order for the browser area to work correctly.

Create your very own ZUL file, customBrowserArea.zul which illustrates how this can be done

customBrowserArea.zul

<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c"?>

<div width="100%" height="100%" onClick="UICockpitSession.getCurrentPerspective().getBrowserArea().setFocus(true)" action="onclick: comm.sendClick(#{self},null)">
	<borderlayout width="100%" height="100%" onCreate="UICockpitSession.getCurrentPerspective().getBrowserArea().initBrowsers(self)"/>
</div>

The call needs some further explanation:

UICockpitSession.getCurrentPerspective().getBrowserArea().initBrowsers(self)

This line is responsible for setting the area's main content component. As the second requirement above states, this component needs to be of type <borderlayout>. The area's main content component is the container used by the area to hold and display browsers. It is the browser area that controls what kind and how many of browsers it contains. As the name indicates, once the main content component has been set, the area is automatically initialized by calls to its methods initialize() and update() - in that order. The initialize() is discussed further in the Custom Logic - Start from Scratch or Extend an Existing Class section below.

Your custom ZUL file, customBrowserArea.zul does not contain anything but a main content component, so add a label and a button.

<div width="100%" height="100%" onClick="UICockpitSession.getCurrentPerspective().getBrowserArea().setFocus(true)" action="onclick: comm.sendClick(#{self},null)">
	<label value="My customized browser area"/>
	<borderlayout width="100%" height="80%" onCreate="UICockpitSession.getCurrentPerspective().getBrowserArea().initBrowsers(self)"/>
	<toolbarbutton label="What's this?">
		<attribute name="onClick">
			Messagebox.show("It's an amazingly pimped browser area!");
		</attribute>
	</toolbarbutton>
</div>

The result looks like this:

The custom layout example provided here shows how you can customize the layout of your browser area and add some simple logic by writing your own ZUL file. In this case showing a pop-up dialog box once the What's this? button is clicked.

Custom Logic - Start from Scratch or Extend an Existing Class

Every browser area used in a base cockpit application needs to implement the UIBrowserArea interface.

The interface itself contains more than thirty methods and implementing your own browser area from scratch can be quite time-consuming. Instead, the preferred way to go is to extend one of the existing browser areas. In this tutorial you extend the most general class implementing the UIBrowserArea interface, namely AbstractBrowserArea.

Getting Started - Creating the Class CustomBrowserArea.java

Create a class which extends the abstract class AbstractBrowserArea. AbstractBrowserArea provides default implementations for most of the methods of the UIBrowserArea interface. However, there are still a few abstract methods which need to be implemented.

The following snippet shows the code so far:

CustomBrowserArea.java

public class CustomBrowserArea extends AbstractBrowserArea
{
	@Override
	public BrowserModelListener getBrowserListener()
	{
		return null;
	}

	@Override
	public void initialize()
	{

	}

	@Override
	public void saveQuery(final BrowserModel browserModel)
	{

	}

}

As you can see above, you need to provide the implementations for three methods:

  • getBrowserListener - It should return the model listener to be used by the area for handling browser events.
  • initialize - It is called when the browser area is created. Typically creates and displays a browser.
  • saveQuery - It is called when a query should be saved. Typically passes the event on to any registered BrowserAreaListeners.
Implementing the initialize() Method

As mentioned above, the initialize() method typically creates and displays the area's initial browser. This procedure can be divided into the following steps:

  1. Create browser model.
  2. Add the created browser to the area's visible browsers.
  3. Give the browser focus.
  4. Update the browser's items, that is its content.

In this example you add a browser of type DefaultSearchBrowserModel, which is the same kind of browser which is used by the default browser area. The DefaultSearchBrowserModel class implements the SearchBrowserModel interface, and offers not only the functionality for displaying items in different fashions, but also the simple search, advanced search dialogs,and item activation.

This browser requires a main type of type ObjectTemplate to be passed as constructor parameter.

Creating a new instance with main type set to Item looks like this:

BrowserModel browserModel = new DefaultSearchBrowserModel(UISessionUtils.getCurrentSession().getTypeService().getObjectTemplate("Item"));

When you created the browser, you need to tell the area to show it, so you add it to the area's visible browsers:

this.addVisibleBrowser(browserModel);

Finally give our browser focus and make sure its content is updated:

this.setFocusedBrowser(browserModel);
	browserModel.updateItems();

The complete initialize method now looks like this with some minor things added:

@Override
public void initialize()
{
	BrowserModel browserModel = null;
	if(this.browsers == null || this.browsers.isEmpty())
	{
		browserModel = new DefaultSearchBrowserModel( UISessionUtils.getCurrentSession().getTypeService().getObjectTemplate("Item") );
	}
	else
	{
		browserModel = this.browsers.get(0);
	}
	this.addVisibleBrowser(browserModel);
	this.setFocusedBrowser(browserModel);

	browserModel.updateItems();
}
Implementing the getBrowserListener() Method

In order for a browser area to be notified of and react on browser events it needs to register a BrowserModelListener with the browsers it is managing. Depending on what kind of browsers your browser area support, the getBrowserListener() method needs to return an adequate browser listener. The following UML diagram shows the browser model hierarchy. The required corresponding browser model listener is shown beside each browser model.

Your custom browser area supports search browsers, so the listener needs to implement the SearchBrowserModelListener interface.

You use the DefaultSearchContextBrowserModelListener shipped with the cockpit framework. Writing your own browser model listener isn't a subject of this article, but the users who wish to write their own browser model listeners, this class can serve as a good starting point.

Creating and returning a DefaultSearchContextBrowserModelListener is easy. You need to pass the browser area as constructor parameter. This is important, since the listener needs to be able to access the browser area, for example in order to minimize a browser. To see how the method is implemented, the following The Complete Class CustomBrowserArea.java section.

Implementing the saveQuery(final BrowserModel browserModel) Method

If you want to be able to save queries and do not need to provide some custom behavior when doing so this method only needs to notify the registered BrowserAreaListeners that a query should be saved. You need to use the following line of code:

@Override
public void saveQuery(final BrowserModel browserModel)
{
	this.fireBrowserQuerySaved(browserModel)
}
The Complete Class CustomBrowserArea.java

The complete code looks like this:

CustomBrowserArea.java

public class CustomBrowserArea extends AbstractBrowserArea
{
	private boolean initialized = false;
	private BrowserModelListener browserListener = null;

	@Override
	public BrowserModelListener getBrowserListener()
	{
		// always return the same listener
		if(this.browserListener == null)
		{
			// create a new browser model listener and pass this browser area as parameter
			this.browserListener = new DefaultSearchContextBrowserModelListener(this);
		}
		return this.browserListener;
	}

	@Override
	public void initialize()
	{
		// only initialize once
		if(!this.initialized)
		{			
			BrowserModel browserModel = null;
			if(this.browsers == null || this.browsers.isEmpty())
			{
				// area has no browsers, so create a new one
				browserModel = new DefaultSearchBrowserModel( UISessionUtils.getCurrentSession().getTypeService().getObjectTemplate("Item") );
			}
			else
			{
				// area already has a browser, so use the first one
				browserModel = this.browsers.get(0);
			}
			// show browser
			this.addVisibleBrowser(browserModel);

			// give browser focus
			this.setFocusedBrowser(browserModel);

			// make sure browser is updated
			browserModel.updateItems();
		}
	}

	@Override
	public void saveQuery(final BrowserModel browserModel)
	{
		// pass on the event to any registered browser area listeners
		this.fireBrowserQuerySaved(browserModel);
	}

}

BrowserAreaListeners - Passing on Events to the Perspective

Not all events can be completely handled by the browser areas themselves. Some actions might also affect other areas, thus implying that the event is passed on to the perspective somehow.

For example when a browser is focused, opened, minimized or closed, the navigation area's open browser section needs to be updated in order to reflect the new state. Below is an example how it looks when a new browser is opened.

For this purpose, BrowserAreaListeners are used. Normally the perspective registers a BrowserAreaListener with its browser area once the area is created. Some of the BrowserAreaListener methods are listed below.

  • void itemActivated(final TypedObject activeItem) - Called when an item is activated (opened).
  • void browserMinimized(final BrowserModel browserModel) - Called when a browser is minimized.
  • void browserFocused(final BrowserModel browserModel) - Called when a browser is focused.
Notifying all registered BrowserAreaListeners

When extending the AbstractBrowserArea all methods needed in order to notify the browser area listeners are already provided. Their names typically correspond to the names of the methods in the BrowserAreaListener interface, with the added prefix "fire". Let's take item activation as an example.

When an item is activated in a browser, the corresponding browser notifies its registered BrowserModelListeners. In your case it is the listener returned by your browser area's getBrowserListener() method, by calling their itemActivated(final TypedObject item) method. Now, typically the area does not need to do anything itself, but merely acts as a proxy, forwarding the event to the perspective. This is done by calling fireItemActivated(final TypedObject item).

To sum it up, the flow looks like this:

  1. Item is activated in browser.
  2. For each registered browser model listener, the browser calls the itemActivated(final TypedObject item) method.
  3. The browser model listener passes the event on to the registered browser area listeners e.g. perspective by calling fireItemActivated(item).