| <!doctype html> |
| <!-- Generated by FreeMarker/Docgen from DocBook --> |
| <html lang="en" class="page-type-section"> |
| <head prefix="og: http://ogp.me/ns#"> |
| <meta charset="utf-8"> |
| <title>Directives - FreeMarker Manual</title> |
| <meta http-equiv="X-UA-Compatible" content="IE=edge"> |
| <meta name="viewport" content="width=device-width,initial-scale=1"> |
| <meta name="format-detection" content="telephone=no"> |
| <meta property="og:site_name" content="FreeMarker Manual"> |
| <meta property="og:title" content="Directives"> |
| <meta property="og:locale" content="en_US"> |
| <meta property="og:url" content="http://example.com/pgui_datamodel_directive.html"> |
| <link rel="canonical" href="http://example.com/pgui_datamodel_directive.html"> |
| <link rel="icon" href="favicon.png" type="image/png"> |
| <link rel="stylesheet" type="text/css" href="docgen-resources/docgen.min.css?1594338517553"> |
| </head> |
| <body itemscope itemtype="https://schema.org/Code"> |
| <meta itemprop="url" content="http://example.com/"> |
| <meta itemprop="name" content="FreeMarker Manual"> |
| |
| <!--[if lte IE 9]> |
| <div style="background-color: #C00; color: #fff; padding: 12px 24px;">Please use a modern browser to view this website.</div> |
| <![endif]--><div class="header-top-bg"><div class="site-width header-top"><a class="logo" href="http://example.com" role="banner"> <img itemprop="image" src="logo.png" alt="My Logo"> |
| </a></div></div><div class="header-bottom-bg"><div class="site-width search-row"><a href="index.html" class="navigation-header">FreeMarker Manual</a><div class="navigation-header"></div></div><div class="site-width breadcrumb-row"><ul class="breadcrumb" itemscope itemtype="http://schema.org/BreadcrumbList"><li class="step-0" itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a class="label" itemprop="item" href="index.html"><span itemprop="name">FreeMarker Manual</span></a></li><li class="step-1" itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a class="label" itemprop="item" href="pgui.html"><span itemprop="name">Programmer's Guide</span></a></li><li class="step-2" itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a class="label" itemprop="item" href="pgui_datamodel.html"><span itemprop="name">The Data Model</span></a></li><li class="step-3" itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a class="label" itemprop="item" href="pgui_datamodel_directive.html"><span itemprop="name">Directives</span></a></li></ul><div class="bookmarks" title="Bookmarks"><span class="sr-only">Bookmarks:</span><ul class="bookmark-list"><li><a href="alphaidx.html">Index</a></li><li><a href="gloss.html">Glossary</a></li><li><a href="ref.html">Reference</a></li><li><a href="app_faq.html">FAQ</a></li><li><a href="preface.html#test_target">Bőregér</a></li></ul></div></div></div> <div class="main-content site-width"> |
| <div class="content-wrapper no-toc"> |
| <div id="table-of-contents-wrapper" class="col-left"> |
| </div> |
| <div class="col-right"><div class="page-content"><div class="page-title"><div class="pagers top"><a class="paging-arrow previous" href="pgui_datamodel_method.html"><span>Previous</span></a><a class="paging-arrow next" href="pgui_datamodel_node.html"><span>Next</span></a></div><div class="title-wrapper"> |
| <h1 class="content-header header-section1" id="pgui_datamodel_directive" itemprop="headline">Directives</h1> |
| </div></div><div class="page-menu"> |
| <div class="page-menu-title">Page Contents</div> |
| <ul><li><a class="page-menu-link" href="#autoid_35" data-menu-target="autoid_35">Example 1</a></li><li><a class="page-menu-link" href="#autoid_36" data-menu-target="autoid_36">Example 2</a></li><li><a class="page-menu-link" href="#autoid_37" data-menu-target="autoid_37">Notices</a></li></ul> </div><p>Java programmers can implement user-defined directives in Java |
| using the <code class="inline-code">TemplateDirectiveModel</code> interface. See in |
| the API documentation.</p> <div class="callout note"> |
| <strong class="callout-label">Note:</strong> |
| |
| <p><code class="inline-code">TemplateDirectiveModel</code> was introduced in |
| FreeMarker 2.3.11, replacing the soon to be depreciated |
| <code class="inline-code">TemplateTransformModel</code>.</p> |
| </div> |
| |
| |
| |
| |
| |
| <h2 class="content-header header-section2" id="autoid_35">Example 1</h2> |
| |
| |
| <p>We will implement a directive which converts all output |
| between its start-tag and end-tag to upper case. Like, this |
| template:</p> |
| |
| |
| |
| <div class="code-wrapper"><pre class="code-block code-template">foo |
| <strong><@upper></strong> |
| bar |
| <#-- All kind of FTL is allowed here --> |
| <#list ["red", "green", "blue"] as color> |
| ${color} |
| </#list> |
| baaz |
| <strong></@upper></strong> |
| wombat</pre></div> |
| |
| <p>will output this:</p> |
| |
| |
| |
| <div class="code-wrapper"><pre class="code-block code-output">foo |
| BAR |
| RED |
| GREEN |
| BLUE |
| BAAZ |
| wombat</pre></div> |
| |
| <p>This is the source code of the directive class:</p> |
| |
| |
| |
| <div class="code-wrapper"><pre class="code-block code-unspecified">package com.example; |
| import java.io.IOException; |
| import java.io.Writer; |
| import java.util.Map; |
| |
| import freemarker.core.Environment; |
| import freemarker.template.TemplateDirectiveBody; |
| import freemarker.template.TemplateDirectiveModel; |
| import freemarker.template.TemplateException; |
| import freemarker.template.TemplateModel; |
| import freemarker.template.TemplateModelException; |
| |
| /** |
| * FreeMarker user-defined directive that progressively transforms |
| * the output of its nested content to upper-case. |
| * |
| * |
| * <p><b>Directive info</b></p> |
| * |
| * <p>Directive parameters: None |
| * <p>Loop variables: None |
| * <p>Directive nested content: Yes |
| */ |
| public class UpperDirective implements TemplateDirectiveModel { |
| |
| public void execute(Environment env, |
| Map params, TemplateModel[] loopVars, |
| TemplateDirectiveBody body) |
| throws TemplateException, IOException { |
| // Check if no parameters were given: |
| if (!params.isEmpty()) { |
| throw new TemplateModelException( |
| "This directive doesn't allow parameters."); |
| } |
| if (loopVars.length != 0) { |
| throw new TemplateModelException( |
| "This directive doesn't allow loop variables."); |
| } |
| |
| // If there is non-empty nested content: |
| if (body != null) { |
| // Executes the nested body. Same as <#nested> in FTL, except |
| // that we use our own writer instead of the current output writer. |
| body.render(new UpperCaseFilterWriter(env.getOut())); |
| } else { |
| throw new RuntimeException("missing body"); |
| } |
| } |
| |
| /** |
| * A {@link Writer} that transforms the character stream to upper case |
| * and forwards it to another {@link Writer}. |
| */ |
| private static class UpperCaseFilterWriter extends Writer { |
| |
| private final Writer out; |
| |
| UpperCaseFilterWriter (Writer out) { |
| this.out = out; |
| } |
| |
| public void write(char[] cbuf, int off, int len) |
| throws IOException { |
| char[] transformedCbuf = new char[len]; |
| for (int i = 0; i < len; i++) { |
| transformedCbuf[i] = Character.toUpperCase(cbuf[i + off]); |
| } |
| out.write(transformedCbuf); |
| } |
| |
| public void flush() throws IOException { |
| out.flush(); |
| } |
| |
| public void close() throws IOException { |
| out.close(); |
| } |
| } |
| |
| }</pre></div> |
| |
| <p>Now we still need to create an instance of this class, and |
| make this directive available to the template with the name "upper" |
| (or with whatever name we want) somehow. A possible solution is to |
| put the directive in the data-model:</p> |
| |
| |
| |
| <div class="code-wrapper"><pre class="code-block code-unspecified">root.put("upper", new com.example.UpperDirective());</pre></div> |
| |
| <p>But typically it is better practice to put commonly used |
| directives into the <code class="inline-code">Configuration</code> as <a href="pgui_config_sharedvariables.html">shared |
| variables</a>.</p> |
| |
| <p>It is also possible to put the directive into an FTL library |
| (collection of macros and like in a template, that you |
| <code class="inline-code">include</code> or <code class="inline-code">import</code> in other |
| templates) using the <a href="ref_builtins_expert.html#ref_builtin_new"><code>new</code> |
| built-in</a>:</p> |
| |
| |
| |
| <div class="code-wrapper"><pre class="code-block code-template"><#-- Maybe you have directives that you have implemented in FTL --> |
| <#macro something> |
| ... |
| </#macro> |
| |
| <#-- Now you can't use <#macro upper>, but instead you can: --> |
| <#assign upper = "com.example.UpperDirective"?new()></pre></div> |
| |
| |
| |
| |
| |
| <h2 class="content-header header-section2" id="autoid_36">Example 2</h2> |
| |
| |
| <p>We will create a directive that executes its nested content |
| again and again for the specified number of times (similarly to |
| <code class="inline-code">list</code> directive), optionally separating the the |
| output of the repetations with a <code class="inline-code"><hr></code>-s. |
| Let's call this directive "repeat". Example template:</p> |
| |
| |
| |
| <div class="code-wrapper"><pre class="code-block code-template"><#assign x = 1> |
| |
| <strong><@repeat count=4></strong> |
| Test ${x} |
| <#assign x = x + 1> |
| <strong></@repeat></strong> |
| |
| <strong><@repeat count=3 hr=true></strong> |
| Test |
| <strong></@repeat></strong> |
| |
| <strong><@repeat count=3; cnt></strong> |
| ${cnt}. Test |
| <strong></@repeat></strong></pre></div> |
| |
| <p>Output:</p> |
| |
| |
| |
| <div class="code-wrapper"><pre class="code-block code-output"> Test 1 |
| Test 2 |
| Test 3 |
| Test 4 |
| |
| Test |
| <hr> Test |
| <hr> Test |
| |
| 1. Test |
| 2. Test |
| 3. Test |
| </pre></div> |
| |
| <p>The class:</p> |
| |
| |
| |
| <div class="code-wrapper"><pre class="code-block code-unspecified">package com.example; |
| import java.io.IOException; |
| import java.io.Writer; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| import freemarker.core.Environment; |
| import freemarker.template.SimpleNumber; |
| import freemarker.template.TemplateBooleanModel; |
| import freemarker.template.TemplateDirectiveBody; |
| import freemarker.template.TemplateDirectiveModel; |
| import freemarker.template.TemplateException; |
| import freemarker.template.TemplateModel; |
| import freemarker.template.TemplateModelException; |
| import freemarker.template.TemplateNumberModel; |
| |
| /** |
| * FreeMarker user-defined directive for repeating a section of a template, |
| * optionally with separating the output of the repetations with |
| * <tt>&lt;hr></tt>-s. |
| * |
| * |
| * <p><b>Directive info</b></p> |
| * |
| * <p>Parameters: |
| * <ul> |
| * <li><code>count</code>: The number of repetations. Required! |
| * Must be a non-negative number. If it is not a whole number then it will |
| * be rounded <em>down</em>. |
| * <li><code>hr</code>: Tells if a HTML "hr" element could be printed between |
| * repetations. Boolean. Optional, defaults to <code>false</code>. |
| * </ul> |
| * |
| * <p>Loop variables: One, optional. It gives the number of the current |
| * repetation, starting from 1. |
| * |
| * <p>Nested content: Yes |
| */ |
| public class RepeatDirective implements TemplateDirectiveModel { |
| |
| private static final String PARAM_NAME_COUNT = "count"; |
| private static final String PARAM_NAME_HR = "hr"; |
| |
| public void execute(Environment env, |
| Map params, TemplateModel[] loopVars, |
| TemplateDirectiveBody body) |
| throws TemplateException, IOException { |
| |
| // --------------------------------------------------------------------- |
| // Processing the parameters: |
| |
| int countParam = 0; |
| boolean countParamSet = false; |
| boolean hrParam = false; |
| |
| Iterator paramIter = params.entrySet().iterator(); |
| while (paramIter.hasNext()) { |
| Map.Entry ent = (Map.Entry) paramIter.next(); |
| |
| String paramName = (String) ent.getKey(); |
| TemplateModel paramValue = (TemplateModel) ent.getValue(); |
| |
| if (paramName.equals(PARAM_NAME_COUNT)) { |
| if (!(paramValue instanceof TemplateNumberModel)) { |
| throw new TemplateModelException( |
| "The \"" + PARAM_NAME_HR + "\" parameter " |
| + "must be a number."); |
| } |
| countParam = ((TemplateNumberModel) paramValue) |
| .getAsNumber().intValue(); |
| countParamSet = true; |
| if (countParam < 0) { |
| throw new TemplateModelException( |
| "The \"" + PARAM_NAME_HR + "\" parameter " |
| + "can't be negative."); |
| } |
| } else if (paramName.equals(PARAM_NAME_HR)) { |
| if (!(paramValue instanceof TemplateBooleanModel)) { |
| throw new TemplateModelException( |
| "The \"" + PARAM_NAME_HR + "\" parameter " |
| + "must be a boolean."); |
| } |
| hrParam = ((TemplateBooleanModel) paramValue) |
| .getAsBoolean(); |
| } else { |
| throw new TemplateModelException( |
| "Unsupported parameter: " + paramName); |
| } |
| } |
| if (!countParamSet) { |
| throw new TemplateModelException( |
| "The required \"" + PARAM_NAME_COUNT + "\" paramter" |
| + "is missing."); |
| } |
| |
| if (loopVars.length > 1) { |
| throw new TemplateModelException( |
| "At most one loop variable is allowed."); |
| } |
| |
| // Yeah, it was long and boring... |
| |
| // --------------------------------------------------------------------- |
| // Do the actual directive execution: |
| |
| Writer out = env.getOut(); |
| if (body != null) { |
| for (int i = 0; i < countParam; i++) { |
| // Prints a <hr> between all repetations if the "hr" parameter |
| // was true: |
| if (hrParam && i != 0) { |
| out.write("<hr>"); |
| } |
| |
| // Set the loop variable, if there is one: |
| if (loopVars.length > 0) { |
| loopVars[0] = new SimpleNumber(i + 1); |
| } |
| |
| // Executes the nested body (same as <#nested> in FTL). In this |
| // case we don't provide a special writer as the parameter: |
| body.render(env.getOut()); |
| } |
| } |
| } |
| |
| }</pre></div> |
| |
| |
| |
| |
| |
| <h2 class="content-header header-section2" id="autoid_37">Notices</h2> |
| |
| |
| <p>It's important that a |
| <code class="inline-code">TemplateDirectiveModel</code> object usually should not |
| be stateful. The typical mistake is the storing of the state of the |
| directive call execution in the fields of the object. Think of |
| nested calls of the same directive, or directive objects used as |
| shared variables accessed by multiple threads concurrently.</p> |
| |
| <p>Unfortunatelly, <code class="inline-code">TemplateDirectiveModel</code>-s |
| don't support passing parameters by position (rather than by name). |
| This is fixed starting from FreeMarker 2.4.</p> |
| <div class="bottom-pagers-wrapper"><div class="pagers bottom"><a class="paging-arrow previous" href="pgui_datamodel_method.html"><span>Previous</span></a><a class="paging-arrow next" href="pgui_datamodel_node.html"><span>Next</span></a></div></div></div></div> </div> |
| </div> |
| <div class="site-footer"><div class="site-width"><div class="footer-bottom"> <p class="last-generated"> |
| Last generated: |
| <time itemprop="dateModified" datetime="2020-07-09T23:48:37Z" title="Thursday, July 9, 2020 11:48:37 PM GMT">2020-07-09 23:48:37 GMT</time> </p> |
| <p class="copyright"> |
| © <span itemprop="copyrightYear">1999</span>–2020 |
| <a itemtype="http://schema.org/Organization" itemprop="copyrightHolder" href="https://apache.org/">The Apache Software Foundation</a> </p> |
| </div></div></div></body> |
| </html> |