Documentation Center

Iterative custom TCDL tag example

The following example illustrates how to create a custom TCDL tag called tcdl:ForEachCar that iterates over an array of cars defined in your Java class.

On your webpage, the TCDL tag would appear in the following kind of context:

<html>
  <body>
    <tcdl:ForEachCar>
      <tcdl:CarName />
    </tcdl:ForEachCar>
  </body>
</html>

The code that processes the tcdl:ForEachCar tag is as follows:

package com.tridion.tcdl.iterationtagrenderers;

import com.tridion.tcdl.*;

import java.util.ArrayList;
import java.util.List;

public class ForEachCarRenderer implements TagRenderer, IterationTagSupport {

  private final String currentCarPropertyName = "Foreach:CurrentCar";
  private final String carsPropertyName = "Foreach:Cars";
  private int currentIterationIndex = 0;

  @Override
  public boolean requiresCodeBlock(TransformContext context, OutputDocument target, Tag tag)
  {
    return false;
  }

  @Override
  public int doStartTag(Tag tag, StringBuffer tagBody, TransformContext context, OutputDocument target)
    throws TCDLTransformerException
  {
    // For the sake of simplicity, we initialize the context with a list of cars in the 
    // doStartTag() method of the iteration tag.
    initTransformContext(context);
    // We set the current car property so that the enclosed CarName tag can process it.
    setCurrentContextForIteration(context);

    List<String> carNames = context.getProperty(getCarsPropertyName(), new ArrayList<String>());
    tagBody.append(String.format("<h1>Available: %d Cars</h1>", carNames.size()));
    tagBody.append("\n");
    tagBody.append(String.format("<!-- Starting %s FOREACH -->", "Car"));

    return Tag.CONTINUE_TAG_EVALUATION;
  }

  @Override
  public int doAfterBody(Tag tag, StringBuffer tagBody, TransformContext context, OutputDocument target)
    throws TCDLTransformerException
  {

    List<String> carNames = context.getProperty(getCarsPropertyName(), new ArrayList<String>());
    if (currentIterationIndex < carNames.size()) {
      // There are still cars left to process.
      setCurrentContextForIteration(carNames, context);
      return IterationTagSupport.EVAL_BODY_AGAIN;
    }

    // There are no more cars left to process.
    return IterationTagSupport.SKIP_BODY;
  }

  @Override
  public String doEndTag(Tag tag, StringBuffer tagBody, TransformContext context, OutputDocument target)
    throws TCDLTransformerException
  {
    tagBody.append("\n");
    tagBody.append(String.format("<!-- Finishing %s FOREACH -->", "Car"));
    return tagBody.toString();
  }
  
  public String getCurrentCarPropertyName() {
    return currentCarPropertyName;
  }

  // One iteration: set the context, increase the iterator.
  private void setCurrentContextForIteration(List<String> carNames, TransformContext context)
  {
    if (currentIterationIndex < carNames.size()) {
      // We set the current car property in TransformContext
      // This TransformContext is available to all enclosed tags of the ForEachCar tag.
      context.setProperty(getCurrentCarPropertyName(), carNames.get(currentIterationIndex));
      currentIterationIndex++;
    }
  }
  
  private void setCurrentContextForIteration(TransformContext context) 
  {
    List<String> carNames = context.getProperty(getCarsPropertyName(), new ArrayList<String>());
    setCurrentContextForIteration(carNames, context);
  }

  private void initTransformContext(TransformContext localContext) 
  {
    List<String> cars = new ArrayList<String>();
    cars.add("Audi");
    cars.add("BMW");
    cars.add("Fiat");
    cars.add("Mazda");
    localContext.setProperty(getCarsPropertyName(), cars);
  }

  private String getCarsPropertyName() 
  {
    return carsPropertyName;
  }

}

The code that processes the tcdl:CarName tag is as follows:

package com.tridion.tcdl.iterationtagrenderers;

import com.tridion.tcdl.OutputDocument;
import com.tridion.tcdl.TCDLTransformerException;
import com.tridion.tcdl.Tag;	
import com.tridion.tcdl.TagRenderer;
import com.tridion.tcdl.TransformContext;

public class CarNameRenderer implements TagRenderer {

  @Override
  public int doStartTag(Tag tag, StringBuffer tagBody, TransformContext context, OutputDocument target)
    throws TCDLTransformerException
  {
    return Tag.CONTINUE_TAG_EVALUATION;
  }

  @Override
  public String doEndTag(Tag tag, StringBuffer tagBody, TransformContext context, OutputDocument target)
    throws TCDLTransformerException
  {
    // This example purposely simplifies the code for demonstration purposes:
    // It performs no exception handling, when you actual class obviously should do so.
  
    // We retrieve the handler of the enclosing tag to get context information.
    ForEachCarRenderer forEachRenderer = (ForEachCarRenderer)tag.getEnclosingTag().getHandler();
    final String currentCarPropertyName = forEachRenderer.getCurrentCarPropertyName();

    // We use TransformContext to access all properties saved in the parent's context.
    final String currentCarName= context.getProperty(currentCarPropertyName, "");
  
    tagBody.append(String.format("<div>%s</div>", currentCarName));
    return tagBody.toString();
  }

  @Override
  public boolean requiresCodeBlock(TransformContext context, OutputDocument target, Tag tag)
  {
    return false;
  }

}

The tag bundle XML file for the tags is as follows:

<TagRegistry>
  <Tags>
    <Tag Namespace="tcdl" Name="CarName">
      <Handler Class="com.tridion.tcdl.iterationtagrenderers.CarNameRenderer" AllowCodeBlock="true" />
    </Tag>
    <Tag Namespace="tcdl" Name="ForEachCar">
      <Handler Class="com.tridion.tcdl.iterationtagrenderers.ForEachCarRenderer" AllowCodeBlock="true" />
    </Tag>
  </Tags>
</TagRegistry>

The expected output for the HTML input would be as follows:

<html>
  <body>
    <h1>Available: 4 Cars</h1>
    <!-- Starting Car FOREACH -->
      <div>Audi</div>
      <div>BMW</div>
      <div>Fiat</div>
      <div>Mazda</div>
    <!-- Finishing Car FOREACH -->
  </body>
</html>