Uploaded image for project: 'Tiles'
  1. Tiles
  2. TILES-569

Proposal for conditionals in tiles definitions



    • Improvement
    • Status: Closed
    • Major
    • Resolution: Won't Fix
    • 3.0.1
    • None
    • tiles-core
    • None



      I've recently tried the OptionsRenderer suggested in this tutorial http://tech.finn.no/2012/07/25/the-ultimate-view-tiles-3/4/, but wasn't able to succeed. The defined attributes wasn't available for some reason.

      So I took the idea and implemented my own solution. Perhaps it's something for the trunk? My syntax is as follows:

      	<definition name="main/*/*" template="/WEB-INF/tiles/_layout/main.jsp">
      	    <put-attribute name="title" value="/WEB-INF/tiles/[{1}/{2}|{1}|_common]/title.jsp"/>
      	    <put-attribute name="navigation" value="/WEB-INF/tiles/[{1}/{2}|{1}|_common]/navigation.jsp"/>
      	    <put-attribute name="content" value="/WEB-INF/tiles/[{1}/{2}|{1}|_common]/content.jsp"/>
      	    <put-attribute name="footer" value="/WEB-INF/tiles/[{1}/{2}|{1}|_common]/footer.jsp"/>
      	    <put-attribute name="javaScript" value="/WEB-INF/tiles/[{1}/{2}|{1}|_common]/javaScript.jsp"/>

      The renderer searches for patterns like [opt_1|opt_2|...|opt_n] which may occur several times in the value partameters. With a backtrack algorithm the first matching valid path is searched and rendered.

      I like the compactness of the notation, no need for attribute lists, which didn't function in my case.

      Here is my renderer implementation, perhaps you are interested in integrating it. The path evaluation eventually has to be refactored. I don't know if there is a more elegant way and how costly the current solution is. Perhaps there has to be done some caching...

       * Renderer implementation supporting the notation "[CHOICE_1|...|CHOICE_n]" in the paths.
       * The first matching path replacement will be choosen. 
      public final class ChoiceRenderer implements Renderer {
          private static final Pattern CHOICE_PATTERN = Pattern.compile("\\[([^\\]]+)\\]");
          private final Renderer renderer;
           * Creates a new instance wrapping the given renderer.
          public ChoiceRenderer(Renderer renderer) {
              this.renderer = renderer;
           * @see org.apache.tiles.request.render.Renderer#isRenderable(java.lang.String, org.apache.tiles.request.Request)
          public boolean isRenderable(String path, Request request) {
              // only the overall format is checked, so no extra handling here
              return this.renderer.isRenderable(path, request);
           * @see org.apache.tiles.request.render.Renderer#render(java.lang.String, org.apache.tiles.request.Request)
          public void render(String path, Request request) throws IOException {
              Matcher matcher = CHOICE_PATTERN.matcher(path);
              List<String[]> groups = new ArrayList<String[]>();
              StringBuffer sb = new StringBuffer();
              while (matcher.find()) {
                  // adds a pattern to the resulting path, which will be replaced by the
                  // available choices
                  matcher.appendReplacement(sb, "{[" + groups.size() + "]}");
              if (groups.isEmpty()) {
                  this.renderer.render(path, request);
              } else {
                  backtrackPaths(sb.toString(), request, groups, 0);
          private String backtrackPaths(String pathPattern, Request request, List<String[]> groups, int depth)
                  throws IOException {
              String matchPath = null;
              String[] parts = groups.get(depth);
              for (int i = 0; i < parts.length; ++i) {
                  String path = pathPattern.replace("{[" + depth + "]}", parts[i]);
                  if (depth == groups.size() - 1) {
                      if (isPathValid(path, request)) {
                          // found the first matching choice
                          this.renderer.render(path, request);
                          matchPath = path;
                  } else {
                      matchPath = backtrackPaths(path, request, groups, depth + 1);
              return matchPath;
          // TODO should we use caching here?
          private boolean isPathValid(String path, Request request) {
              boolean rtn = false;
              // apparently the corresponding Renderer method seems to check the
              // path's format only
              if (this.renderer.isRenderable(path, request)) {
                  try {
                      rtn = request.getApplicationContext().getResource(path) != null;
                  } catch (IllegalArgumentException e) {
                      // TODO the javadoc states that null will be returned, but
                      // instead of it an exception is thrown.
                      // Seems to be a bug?!
                      boolean throwException = true;
                      if (e.getCause() instanceof FileNotFoundException) {
                          FileNotFoundException fex = (FileNotFoundException) e.getCause();
                          throwException = fex.getMessage().indexOf(path) == -1;
                      if (throwException) {
                          // seems to be a different reason as our searched path
                          throw e;
              return rtn;


        Issue Links



              Unassigned Unassigned
              ewert Marc Ewert
              0 Vote for this issue
              2 Start watching this issue