close

Filter

loading table of contents...

Content Application Developer Manual / Version 2401

Table Of Contents

5.4 Content Placeholders

The pages of a typical CAE based website are composed of several content objects where each page fragment corresponds to one or more contents. For example, a teaser area on a page may be modeled from teaser content items that are placed in a link list property. When rendering the page, then the entire content structure is rendered by recursively applying the content beans to matching templates, for example a content of type Teaser is translated to a content bean Teaser.class that is rendered by a template Teaser.ftl.

There are situations where it may not be adequate to add a new content type for every piece of functionality that should be used on a website. This may be true when there is only one or a very few content instances of this type.

Example: Consider a website function "Current Weather" that displays the weather forecast for the user's current location. Another example would be a "Login" form that enables the user to login to or log out from the website. In order to enable an editor to add, remove or replace such functionality in a page, it is necessary to represent it as a content item. On the other hand it would be a huge overhead to add a content type "Weather" and a content type "Login".

Such functionality can be easily added to an application using the Substitution API. The basic idea behind the API is, that there is a generic content type that serves as a kind of placeholder. Content of this type must have a string property containing an identifier (for example com.mycompany.weather) that is internally used to render the real information that is represented by the content item. This identifier is an arbitrary string, linking the content object to the substitution implementing the intended behavior. To avoid name clashes of logical identifiers, for instances with future project extensions, it is recommended to adopt the naming convention known from Java packages as shown here.

Example: Let's say that there is placeholder content type called Action

<DocType Name="Action">
  <StringProperty Name="id" Length="128"/>
</DocType>

with a corresponding content bean implementing this interface:

public interface Action {
  String getId();
}

Instead of rendering the Action bean using a template Action.ftl, a more special bean Weather could be rendered using a matching template Weather.ftl. This kind of substitution (for example an Action with an id com.mycompany.weather is substituted by an instance of bean Weather) is supported by the Substitution API. Note that the bean resulting from the substitution can be of an arbitrary type, and does not need to implement any particular interface.

In order to define such substitution, simply add an @Substitution annotated method to any bean in the application context:

package com.mycompany.weather.handlers;

import com.coremedia.objectserver.view.substitution.*;

public class WeatherHandler {

  // ...

  // Substitution ID "com.mycompany.weather" is arbitrary,
  // but uses package naming conventions to avoid name
  // clashes.
  // It must match the property value in the
  // corresponding content object, whose content bean will
  // be substituted with this Weather bean during rendering
  // by the ${cm:substitute} function.

  @Substitution("com.mycompany.weather")
  public Weather createWeatherBean(Action original,
                           HttpServletRequest request) {
    return new Weather(original,
      getCurrentWeather(request.getSession()));
  }
}

Example 5.4. Annotating a Substitution method


The (generic) template Action.ftl can perform this substitution by calling the Freemarker function cm.substitute() and dispatching the substitution result to its responsible template (for example Weather.ftl).

<#-- @ftlvariable name="self" type="com.mycompany.Action" -->
<@cm.include self=cm.substitute(self.id!"", self) />

Example 5.5. Use of cm.substitute() in CMAction.ftl


For more information on FreeMarker usage, see Section 6.5.1, “CoreMedia (cm)” in Frontend Developer Manual.

Using the @Substitution annotation isn't the only way to register a substitution. Consider a login example that requires a handler to perform the login action:

import com.coremedia.objectserver.view.substitution.*;

public LoginHandler {

  // ...

  @RequestMapping("/{id}/login")
  public ModelAndView handleLogin(
    @PathVariable("id")Page page,
    @RequestParam("user")String user,
    @RequestParam("password")String password,
    HttpServletRequest request){

    LoginState state=processLogin(user, password, request.getSession());
    ModelAndView result=HandlerHelper.createModel(page);
    SubstitutionRegistry.register("com.mycompany.login",
                                state, result);
    return result;
  }
}

Example 5.6. Registering a substitution programmatically


This example demonstrates the substitution from within a handler. The advantage in comparison to the annotation based approach is the fact that form data can be handled conveniently using the binding of Spring MVC.

In fact, the different approaches can be used in conjunction. An explicitly registered substitution (using the SubstitutionRegistry service) has precedence over the annotation approach. Thus, @Substitution can be used as a fallback in case that there hasn't been a registration by a handler.

Spring Forms

When using the Spring Form Freemarker integration, then it is necessary to have the form beans stored under certain names (other than self) in the request scope. For this reason, an optional modelAttribute can be specified in the @Substitution annotation. When this is done, then the substituted bean is stored under this name in the request. Example: An annotation @Substitution(value="com.mycompany.weather", modelAttribute="weatherBean") will cause the substituted bean to be stored in the request as an attribute weatherBean.

Search Results

Table Of Contents
warning

Your Internet Explorer is no longer supported.

Please use Mozilla Firefox, Google Chrome, or Microsoft Edge.