Fork me on GitHub

Metaphor Repository

The metaphor repository is a pattern for a programmer driven implementation of web interfaces clearly structured by repetitive elements. Those interfaces tend to have much copies in template code if realized with common engines. As template code is not bound to the model, but rather fully parameterized in Snippetory each and every Region of a Template and can be reused where ever appropriate. And templates can be composed within the template definition or by java code. In the latter case even from multiple definition sources. You can reuse the templates in different environments, with different logic, or the same logic with different templates. The metaphor repository, however, concentrates on encapsulating logic, that can be used in several places.

A structured page

In fact being a repository of snippets makes it a fine base for a repository of metaphors, as we already have the repository. But what is a metaphor? Looking on Wikipedia I extracted a tangible thing to represent a less tangible thing as the thing I'm interested in. The less tangible thing is a re-used view concept like a section, or an changeable attribute consisting of label and editor. The tangible thing is the template code representing it, which in turn leads to the visual representation. By the way, 'tangible' is a metaphor, too, as it's used here as neither source code nor a visual representation are tangible but both are less abstract than a design concept...

From a developer point of view the thing I call metaphor, is very similar to a component, just simpler. It's completely focused on viewing and, following the principles of Snippetory, it has no fix data binding, but data is bound by controller code. The Metaphor Repository encapsulates a bunch of templates and the associated builder logic and provides a convenient interface to build a piece of a structured web interface.

In essence the metaphor repository is a variation of the builder pattern, where most of the building process is done by Snippetory. This allows quite a number of builders within a single class. The template definition might be configurable. Slight differences in logic are implemented using inheritance.

An example

The template

Like most of the examples I start with the template. This helps me to clarify what data has to be bound. And the template can be easily extracted from the example you can see on the picture. This is a metaphor repository with an attribute editor with a text, a single choice, and a multiple choice control and, as well, a description element as we can see it at the bottom of the example. Of course section and heading are available, too:

  <t:page>
  <center>
  <form action="$target(enc='url')" >
  <table width="600">
    <tr>
      <td colspan="3"><h1>$title</h1></td>
    </tr>
    <t:section>
    <tr>
      <th colspan="3" class="head">$title</th>
    </tr>
    <t:attribute>
    <tr>
      <td class="space"></td>
      <td class="label">$label</td>
      <td>
      <t:control><input type="text" /></t:control>
      <t:msg><span class="err">$error</span></t:msg>
      </td>
    </tr>
    </t:attribute>
    <t:description>
    <tr>
      <td class="space"></td>
      <td colspan="2">$description</td>
    </tr>
    </t:description>
    <t:controls>
    <!-- As controls are embedded to an additional section I'm free to mock for better 
         presentation ... -->
    <tr>
      <td class="space"></td>
      <td class="label">Field 2</td>
      <td>
        <t:checkbox>
        <label>
          <input type="checkbox" name="$name" value="$value" />
          $label
        </label><br />
        </t:checkbox>
        <t:selected_checkbox>
        <label>
          <input type="checkbox" name="$name" value="$value" checked="checked" />
          $label
        </label><br />
        </t:selected_checkbox>
        <label><input type="checkbox" name="r2" />Choice 2</label><br /> 
        <label><input type="checkbox" name="r2" />Choice 3</label>  
      </td>
    </tr>
    <!-- ... or write just the components for compacter representation -->
    <t:text><input type="text" name="$name" value="$value" /></t:text>
    <t:select>
      <select name="$name">
        <option>Please select...</option>
        <t:option><option id="$value">$label</option></t:option>
        <t:selected_option><option selected="selected" id="$value">$label</option></t:selected_option>
      </select>
    </t:select>
    </t:controls>
    <tr>
      <td colspan="3">&nbsp;</td>
    </tr>
    </t:section>
    <tr>
      <td colspan="3" style="text-align: right;"><input type="submit" value="Back"/></td>
    </tr>
   </table>
  </form>
  </center>
  </t:page>

In real live a good place for our template code might be the application frame template as it's always loaded anyway and it makes previewing easy.

How to use

Now, let's look on how the metaphor repository is used. This means how one of the pages is generated using the metaphor repository. Once we know the necessary features of the API and we have the template to bind the data to it will be easy to write the missing link: The metaphor repository itself. But first the API usage:

    ResourceBundle labels = ResourceBundle.getBundle("labels", Locale.US);
    Map<String, String> errors =  new HashMap<String, String>();
    errors.put("field12", "Error messages might be red");
    String data11 = "val1";
    String data12 = "val2";
    String data13 = "";
    String data21 = "";
    List<String> values22 = Arrays.asList("choice1", "choice2", "choice3");
    List<String> data22 = Arrays.asList("choice1", "choice2");
    List<String> values23 = Arrays.asList("opt1", "opt2", "opt3");
    String data23 = "opt2";

    // render the data
    // first create the new page. The constructor takes some important data.
    Page page = new Page(labels.getString("data_enter"), "dataSink.do", errors, labels);
    
    // The first section contains only text fields
    Section section1 = page.createSection(labels.getString("sec1"));
    section1
      .addTextAttrib("field11", data11)
      .addTextAttrib("field12", data12)
      .addTextAttrib("field13", data13)
      .render();
    
    // The second section adds more complicated controls
    Section section2 = page.createSection(labels.getString("sec2"));
    section2
      .addTextAttrib("field21", data21)
      .addMultiSelectionAttrib("field22", values22, data22)
      .addSelectionAttrib("field23", values23, data23)
      .render();
    
    // for an example putting the template here makes it simple. In real live an own file
    // might be better. At least a little more advanced formatting would be nice ;-)
    Template explanation = 
        XML_ALIKE.parse("{v:text} <ul><t:points><li>{v:point}</li></t:points></ul>");
    explanation.set("text", labels.getString("desc.text"));
    explanation.get("points").set("point", labels.getString("desc.point1")).render();
    explanation.get("points").set("point", labels.getString("desc.point2")).render();
    
    // render the explanation
    Section section3 = page.createSection(labels.getString("sec3"));
    section3.addDescription(explanation).render();
    
    // finally put it all out
        page.render(System.out);

We can see how the metaphor repository delivers a compact definition of the page. Where quite some code is necessary to write a traditional complete template. The possibility to combine template fragments from different sources allows flexibility even with pretty simple implementation.

The Implementation

To complete the example, here comes the implementation of the metaphor repository. With only 2 entities and 8 methods, we get an implementation useful for quite some cases. The example is almost completely localized (only the locale is hard coded), including the correct presentation of dates and number, which is one of the basic features of the Snippetory templating solution. Of cause there is much room for improvement, as this is still a simple example. In some cases an edit-able and a pure presentation version of a page is needed. This could be achieved by just using a derived type.

public class Page implements EncodedData {
  private final Template template;
  private final Template page;
  private final Map<String, String> errors;
  private final ResourceBundle labels;

  public Page(String title, String target, Map<String, String> errors, ResourceBundle labels) {
    this.errors = errors;
    this.labels = labels;
    template = Repo.readResource("MetaRep.html").syntax(Syntaxes.FLUYT_X)
        .encoding(Encodings.html).locale(Locale.US).parse();
    page = template.get("page");
    page.set("title", title);
    page.set("target", target);
  }

  public Section createSection(String title) {
    return new Section(title);
  }

  public void render(PrintStream out) throws IOException {
    page.render();
    template.render(out);
  }

  public class Section implements EncodedData {
    private Template section;

    private Section(String title) {
      section = page.get("section").set("title", title);
    }

    public Section addTextAttrib(String name, Object value) {
      Template attrib = getAttrib(name);
      Template control = section.get("controls", "text").set("name", name).set("value", value);
      control.render(attrib, "control");
      attrib.render();
      return this;
    }

    private Template getAttrib(String name) {
      Template attrib = section.get("attribute");
      attrib.set("label", labels.getString(name));
      if (errors.containsKey(name)) attrib.get("msg").set("error", errors.get(name)).render();
      return attrib;
    }

    public Section addSelectionAttrib(String name, Collection<?> values, Object selected) {
      Template attrib = getAttrib(name);
      Template control = section.get("controls", "select");
      control.set("name", name);
      for (Object value : values) {
        String type = value.equals(selected) ? "selected_option" : "option";
        Template option = control.get(type).set("value", value).set("label", labels.getString(value.toString()));
        option.render("option");
      }
      control.render(attrib, "control");
      attrib.render();
      return this;
    }

    public Section addMultiSelectionAttrib(String name, Collection<?> values, Collection<?> selected) {
      Template attrib = getAttrib(name);
      for (Object value : values) {
        String type = selected.contains(value) ? "selected_checkbox" : "checkbox";
        Template control = section.get("controls", type).set("name", name).set("value", value)
            .set("label", labels.getString(value.toString()));
        control.render(attrib, "control");
      }
      attrib.render();
      return this;
    }

    public Section addDescription(Object desc) {
      section.get("description").set("description", desc).render();
      return this;
    }

    public void render() {
      section.render();
    }

    @Override
    public String getEncoding() {
      return Encodings.html.name();
    }

    @Override
    public CharSequence toCharSequence() {
      return section.toCharSequence();
    }
  }

  @Override
  public String toString() {
    page.render();
    return template.toString();
  }

  @Override
  public String getEncoding() {
    return Encodings.html.name();
  }

  @Override
  public CharSequence toCharSequence() {
    return template.toCharSequence();
  }
}

As we can see the containers are marked with the @Encoded annotation. This means the contained data is already provided valid for an encoding and there is no need to escape it.

The template contains the controls I decided to support in a structure similar to the one I want to render into. The portions that will not be needed are marked as mock. However, as the names have to be unique I appended a number. So far it was very simple. Let's think about how to use it. First I create a repository. The most important thing it need is of course the template from the example before. The only thing our Repository can do is create sections. The Sections in turn will receive properties via add calls. Once fully configured the section is rendered to the target page.

Now I think it's pretty clear how to implement the metaphor repository, at least the coarse structure. The most important issue still open is the encoding. If we convert the Section to a string the result will contain HTML. The target page contains HTML, too. If it receives a string it will assume it to be data and do HTML escaping. This is not what I want.

Bernd Ebertz

Head, Founder and chief technology evangelist of
jproggy.org
Java, and all Java-based marks are trademarks or registered trademarks of Oracle.
This site is not affiliated in any way with Oracle.