Skip to content

Composite Components

Mrdigs edited this page Apr 9, 2014 · 7 revisions

Composite Components are custom components that comprise a number of standard components to make a new component that you can invoke from your JSP page.

Using Tag Files

The simplest way to make a composite is to use a JSP tag file. As an example, let's build a composite formgroup with a text control and a dropdown button, as shown in the Bootstrap documentation. We'll call this composite a "dropdowninput".

Firstly, we need to create a tag file in WEB-INF/tags/ called dropdowninput.tag (the path is irrelevant but the filename is not):

<%@ taglib uri="http://bootstrapjsp.org/" prefix="b" %>
<%@ attribute name="label" %>
<%@ attribute name="name" %>
<%@ attribute name="value" %>
<b:inputgroup>
	<b:formcontrol name="${name}" value="${value}"/>
	<b:inputgroupbutton>
		<b:button mold="dropdown" label="${label}"/>
		<b:dropdownmenu>
			<jsp:doBody/>
		</b:dropdownmenu>
	</b:inputgroupbutton>
</b:inputgroup>

Now it can be referenced from a JSP page using a taglib directive (I've used an "ex" prefix for "example", or "extra"). Note how the menu items are added to the menu because of where the <jsp:doBody> tag is in the tag file:

<%@ taglib uri="http://bootstrapjsp.org/" prefix="b" %>
<%@ taglib prefix="ex" tagdir="/WEB-INF/tags" %>
<ex:dropdowninput name="name" value="value" label="Click Me">
	<b:menuitem>An Action</b:menuitem>
	<b:menuitem>Another Action</b:menuitem>
</ex:dropdowninput>

The trouble with this though is that only the label, name and value attributes are supported. What if I wanted, for example to add a class attribute to my composite from my JSP page? I could of course add the attribute to the tag file, but then if I needed to add an id attribute later, I'd be going back to add yet another attribute.

Fortunately, Bootstrap.jsp components allow you to set a number of attributes at once by passing a Map of name/value pairs in the attributes attribute. Fortunate, because a Map is how dynamic attributes are handled by JSP tag files.

Amending the tag file to the following will allow me to add any attribute I want to my new composite:

<%@ tag dynamic-attributes="attributes" %>
<%@ taglib uri="http://bootstrapjsp.org/" prefix="b" %>
<%@ attribute name="label" %>
<%@ attribute name="name" %>
<%@ attribute name="value" %>
<b:inputgroup attributes="${attributes}">
	<b:formcontrol name="${name}" value="${value}"/>
	<b:inputgroupbutton>
		<b:button mold="dropdown" label="${label}"/>
		<b:dropdownmenu>
			<jsp:doBody/>
		</b:dropdownmenu>
	</b:inputgroupbutton>
</b:inputgroup>
<%@ taglib uri="http://bootstrapjsp.org/" prefix="b" %>
<%@ taglib prefix="ex" tagdir="/WEB-INF/tags" %>
<ex:dropdowninput name="name" value="value" label="Click Me" class="dropdowninput" id="input1">
	<b:menuitem>An Action</b:menuitem>
	<b:menuitem>Another Action</b:menuitem>
</ex:dropdowninput>

Using Molds

All Components can be instantiated and appended to each other in Java, so you can build Composites in Java using the Molding System to append child Components.

The above Composite could be written as a Mold, with an extra supported attribute direction that takes a value 'ltr' or 'rtl' to control which side of the input the button appears, like so:

import org.bootstrapjsp.facet.Mold;
import org.bootstrapjsp.support.NestedTagSupport;
import org.bootstrapjsp.tags.core.dropdown.DropdownMenu;
import org.bootstrapjsp.tags.core.form.FormControl;
import org.bootstrapjsp.tags.core.inputgroup.InputGroup;
import org.bootstrapjsp.tags.core.inputgroup.InputGroupButton;
import org.bootstrapjsp.tags.core.misc.Button;

public class DropdownInputMold extends Mold<InputGroup> {

	private final Button button = new Button();

	private final FormControl formControl = new FormControl();

	private boolean leftToRight = true;
	
	public DropdownInputMold() {
		this.button.setAttribute("mold", "dropdown");
	}
	
	@Override
	public void apply(InputGroup inputGroup, String mold) {
		// Create a new InputGroupButton with a dropdown button and menu inside
		final InputGroupButton inputGroupButton = new InputGroupButton();
		inputGroupButton.appendChild(this.button, NestedTagSupport.BEFORE_BODY);
		inputGroupButton.appendChild(new DropdownMenu());
		// Add the form control to the InputGroup either first or last
		int position = this.leftToRight ? NestedTagSupport.BEFORE_BODY : NestedTagSupport.AFTER_BODY;
		inputGroup.appendChild(this.formControl, position);
		// Add the InputGroupButton (this wraps around the body of the
		// InputGroup tag so that menu items can be added to it in the JSP)
		inputGroup.appendChild(inputGroupButton);
	}
	
	@Override
	public boolean setAttribute(String name, Object value) {
		if ("label".equals(name)) {
			// The label attribute should be applied to the button
			this.button.setAttribute(name, value);
			return true;
		} else if ("value".equals(name) || "type".equals(name)) {
			// The value and type attributes should be applied to the form control
			this.formControl.setAttribute(name, value);
			return true;
		} else if ("direction".equals(name)) {
			if ("ltr".equals(value) || "rtl".equals(value)) {
				this.leftToRight = "ltr".equals(value);
				return true;
			}
		}
		// Otherwise, handle the attribute normally
		return false;
	}
}

The important thing to notice is how the position parameter is used with the appendChild() method to control whether the child is appended before, after, or around the body of the InputGroup.

Our mold can be now be used like so:

<%@ taglib uri="http://bootstrapjsp.org/" prefix="b" %>
<b:inputgroup mold="DropdownInputMold" name="name" value="value" label="Click Me">
	<b:menuitem>An Action</b:menuitem>
	<b:menuitem>Another Action</b:menuitem>
</b:inputgoup>

Using a mold is more involved and less intuitive than a tag file, but for Composites with complex logic is often easier.

Combining Molds and Tag Files

You can of course use a tag file to produce a new tag that uses a mold to build the Composite:

<%@ tag dynamic-attributes="attributes" %>
<%@ taglib uri="http://bootstrapjsp.org/" prefix="b" %>
<b:inputgroup mold="DropdownInputMold" attributes="${attributes}">
	<jsp:doBody/>
</b:inputgroup>