Container widgets - tooltwist/documentation GitHub Wiki

A container widget is a widget that may contain other widgets. Examples include div, grid and frame widgets. The widgets that are placed inside the container are referred to as the children of the widget.

The following steps outline how to create a container widget such that it can be used in the Designer, saves properties, and generates properly. In this example we create a container that allows a single child, with an child index of "child".

Step 1 - extend ContainerWidget

By default widgets extend WbdWidgetController. By extending the ContainerWidget class the widget will correctly handle generation of it's child widgets as well, including things like publishing images and stylesheets, and creating sprites for iframes placed within the widget.

public class DivWidget extends ContainerWidget

Step 2 - Rendering within the Designer

This involves several parts:

  • finding the child if there is one.
  • if there is no child, use style class designer-droppable to indicate an area where the user may drag a child.
  • Give the area containing the child an ID that defines this widget.
  • render the child if there is one.

Here's example code from the div widget:

	private static final String INDEX_OF_CHILD = "child";

	@Override
	public void renderForDesigner(WbdGenerator generator, WbdWidget instance, UimData ud, WbdRenderHelper rh) throws WbdException
		...
	
		// Find the child
		WbdChildIndex index = new WbdChildIndex(INDEX_OF_CHILD);
		WbdWidget child = instance.findChildByIndex(index);

		// Make it dropable if we're editing
		String styleClass = "";
		if (child == null && instance.mayEdit(ud)) {
			styleClass += " designer-droppable";
		}

		// Work out an ID for the area where we can drop stuff.
		WidgetId dropAreaWidgetId = new WidgetId(instance);
		dropAreaWidgetId.setPrefix("cell");
		dropAreaWidgetId.setIndex(index);
		String id = "id='" + dropAreaWidgetId.fullPath() + "'";
		
		// Display the cell inside the frame
		rh.append("<div "+id+" class=\"" + styleClass + "\">");
		rh.append(XData.htmlString(label));
		if (child != null) {
			// There is a child in this frame cell
			if (child.getController() instanceof ZoneWidget)
				child.getController().renderForPreview(generator, instance, ud, rh);
			else
				child.renderForDesignerWithWrapper(generator, ud, rh);
		}
		rh.append("</div>");
		...
	}

Step 3 - Generate production code

	@Override
	public void renderForJSP(WbdGenerator generator, WbdWidget instance, UimHelper ud, WbdRenderHelper rh) throws Exception {
		...

		// Find a child if it exists
		WbdChildIndex index = new WbdChildIndex("child");
		WbdWidget child = instance.findChildByIndex(index);

		// Display the div and let the child generate it's own code.
		rh.append("<div class='"+styleClass+"' style=\""+style+"\">");
		if (child != null) {
			WbdWidgetController controller = child.getController();
			controller.renderForJSP(generator, child, (UimHelper)ud, rh);
		}
		rh.append("</div>\n");
		...
	}

Step 4 - Save the widget

The properties of this widget can be saved in the normal manner, but we need to specifically save the child widget to file also.

	@Override
	protected void writeProperties(WbdGenerator generator, WbdWidget instance, PrintWriter pw, int indent) throws WbdException
	{
		// Write the properties of this widget.
		instance.getProperties().writeProperties(pw, indent, null);

		// If there is a child, write it out now.
		WbdChildIndex index = new WbdChildIndex(INDEX_OF_CHILD);
		WbdWidget child = instance.findChildByIndex(index);
		if (child != null) {
			pw.println(indentStr(indent) + "<child>");
//			pw.println(indentStr(indent + 1) + "<index>" + index.getIndexStr() + "</index>");
//			instance.getProperties().writeProperties(pw, indent + 1, index);
			child.saveToFile(generator, pw, indent + 1);
			pw.println(indentStr(indent) + "</child>");
		}
	}

In this example, the child saves it's own properties. The commented out code can be used to allow our widget to store extra information about the area containing the child.

Step 5 - Load the widget

This is the reverse of the saving method.

	@Override
	protected void loadPropertiesFromXml(WbdGenerator generator, WbdWidget widget, XNodes node) throws WbdException
	{
		// Read the properties of this widget
		super.loadPropertiesFromXml(generator, widget, node);

		// Get the child, if there is one.
		try
		{
			XNodes children = node.getNodes("./child");
			if (children.next()) {
				XNodes widgetNode = children.getNodes("./widget");
				if (widgetNode.next()) {
					WbdWidget child = WbdWidget.loadBasicPropertiesFromXml(generator, widgetNode);
					WbdChildIndex index = new WbdChildIndex(INDEX_OF_CHILD);
					child.setParent(widget, index);
				}
			}
		}
		catch (XDataException e)
		{
			throw new WbdException("Error finding cell widget: " + e);
		}
	}

Handling multiple children

In the example above we allow only a single child within our container widget. If you wish to have multiple children, you first need to have multiple droppable areas displayed in the Designer. The children can then be stored using different child indexes.

For a sophisticated example, look at the source code for the grid widget.

--

⚠️ **GitHub.com Fallback** ⚠️