Skip to content
OpenCms documentation
OpenCms documentation

Nested containers

OpenCms allows you to nest containers. You can use the <cms:container> tag in formatter JSPs. Then content elements placed on your webpage add new containers. Here is what you should know when using nested containers.

Containers are holes in a template where content can be dropped. When writing a template JSP in OpenCms, you use the <cms:container> tag to add such holes. Nested containers are also such holes where content can be dropped. But they are not defined in the template JSP. They are defined in a content's formatter. If you add content rendered by such a formatter to your page, it will make up a new container inside the container where you place the content. Hence, a nested container. To distinguish nested containers from the containers defined in a template, we call the latter ones top-level containers.

Nested containers are not part of the content itself. If you place the content that renders the nested container again on your page, or on a different page, the elements in the nested container will not be inserted. For each element-instance of the content that renders the nested container, the container is completely independent.

Nested containers are tied to a container page element. If you move a content that renders a nested container on a page (and the formatter does not change), the element in the nested container will move with it. If you remove the content, or change its formatter, the nested container will vanish and its elements will not be visible anymore.

Nested container have various applications:

  • Tab elements where each tab can hold different contents,
  • Flexible template design, by using "structure" contents to fix the page layout,
  • Fine-grained restrictions on where to put different content, e.g., allowing content of some type only in nested containers.

If your focus is on grouping content, such that the whole group can be moved across and used in various pages, nested containers are not what you need. Use instead Model groups.

To expose a nested container via a formatter, use the <cms:container>-tag in the formatter. You can use the tag exactly like in a template JSP. There is only one detail you should know: in contrast to top-level containers, the name attribute of a nested container will not directly form the container's name. This is necessary to keep container names unique per page, even if two formatters add containers with the same name attribute.

You should take care that each container rendered by your formatter has a unique name inside your formatter. This is for example relevant when you design a tab element with a varying number of tabs, each tab having its own container. Here's an example of a formatter for such a tab element.

<%@page buffer="none" session="false" trimDirectiveWhitespaces="true" taglibs="c,cms" %>

<cms:formatter var="content" val="value" rdfa="rdfa">

<div class="margin-bottom-30">
    <div class="tab-v1">
        <ul class="nav nav-tabs">

            <c:forEach var="label" items="${content.valueList.Label}" varStatus="status">
                <li class="${status.first? 'active':''}">
                    <a href="#${cms.element.instanceId}-tab-container${status.count}" 
                       data-toggle="tab">
                        ${label}
                    </a>
                </li>
            </c:forEach>

        </ul>

        <div class="tab-content">

            <c:forEach var="label" items="${content.valueList.Label}" varStatus="status">
                <cms:container name="tab-container${status.count}" 
                               type="content" tagClass="tab-pane ${status.first? 'active':''}">
                </cms:container>
            </c:forEach>

        </div><!--/tab-content-->
    </div><!--/tab-v1-->
</div>

</cms:formatter>

You should not miss two important details:

  • To keep the name attribute of the tab containers unique inside of the formatter, the ${status.count} property is used inside the second <c:forEach> loop.
  • The id attribute of the HTML element rendered for the container tag is {element id}-{name attribute}, as seen from the first <c:forEach> loop where the id is needed to implement tab switching. The id is also the container's name you can retrieve for example by using ${cms.container.name} inside the container.

Nested containers are similar to normal containers managed by the container page. When content that exposes a nested container is added to a page, the container will be stored similar to a top-level container in the container page. The only additional information is the element id of the container's parent element, i.e., the id of the container page element that represents the content you just placed on the page. Have a look into a container page that holds information about a nested container to see the details.

When an element that exposed a nested container is removed from a page, the information about the nested container is also removed from the container page.

Formatter of the layout element

<%@page buffer="none" session="false" trimDirectiveWhitespaces="true" taglibs="c,cms,fmt,fn" %>
<cms:formatter var="content">
	<div class="row">
	<c:set var="containers" value="${content.valueList.Container}" />
	<c:set var="containerNum" value="${cms:getListSize(containers)}" />
	<c:set var="containerCols">
		<fmt:formatNumber value="${12 div containerNum}" maxFractionDigits="0" />
	</c:set>
	<c:forEach var="container" items="${containers}">
		<div class="col-lg-${containerCols} col-md-${containerCols} 
		     col-sm-${containerCols} col-xs-${containerCols}">
			<cms:container name="${container.value.Id}" 
			               type="${container.value.Type}" 
						   editableby="${container.value.EditableBy}" 
						   tagClass="big-colored-border" param="1 of ${containerNum}">
				<h4>
					Please add content (if you have role "${container.value.EditableBy}")!
				</h4>
			</cms:container>
		</div>
	</c:forEach>
	</div>
</cms:formatter>

The most interesting part is the <cms:container> tag. Using this tag, the formatter exposes a new container. Setting the (optional) attribute editableby, it restricts the users that are able to add or remove contents from the exposed container. Via tagClass we add a css class to the HTML-tag (div) that is rendered for the container. In the example, the gray borders are added by that class. The type attribute is used to specify which formatters can be used in the container and the name attribute becomes (prefixed with the layout element's id) the id of the container in the container page, and the HTML-tag rendered by the container tag. Last but not least, the (optional) param attribute is used to attach some extra data to the container: Here, the information on which container of how many in the containers in the row this one is.

In the body of the <cms:container> tag we placed some HTML. It is shown whenever the container is empty.

Formatter of the content element reading the container's extra information

<%@page buffer="none" session="false" taglibs="c,cms" %>
<cms:formatter var="content" val="value" rdfa="rdfa" >
<div style="margin-bottom:30px;" ${not value.Image.isSet?rdfa.Image:""} >
    <div class="headline"><h3 ${rdfa.Title}>${value.Title}</h3></div>
	<c:if test="${value.Image.isSet}">
		<div style="float:left;" ${rdfa.Image}>
			<cms:img src="${value.Image}" height="100" width="300" />
		</div>
	</c:if>
    <div ${content.rdfa.Text}>
        ${value.Text}
    </div>
	<div style="clear:left;"></div>
	<c:if test="${not empty cms.container.param}">
		<p>
			Value of my containers param attribute: <em>${cms.container.param}</em>
		</p>
	</c:if>
</div>
</cms:formatter>

The formatter used for articles in the nested containers read the surounding container's param attribute and output the value. Look for ${cms.container.param} in the formatter code below.