Struts 2
  1. Struts 2
  2. WW-4145

file.ftl in xhtml theme directly references xhtml controlfooter.ftl

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 2.3.15.1
    • Fix Version/s: 2.3.16
    • Component/s: Other

      Description

      Should use ${parameters.theme} instead so can be used in theme extension.

      <#include "/${parameters.templateDir}/${parameters.theme}/controlheader.ftl" />
      <#include "/${parameters.templateDir}/simple/file.ftl" />
      <#include "/${parameters.templateDir}/${parameters.theme}/controlfooter.ftl" />
      
      1. ThemeExpansion-Alt.patch
        64 kB
        Jasper Rosenberg
      2. ThemeExpansion.patch
        63 kB
        Jasper Rosenberg

        Issue Links

          Activity

          Hide
          Lukasz Lenart added a comment -

          Are you sure? Right now I am going to do opposite, remove all ${parameters.them} and use hardcoded values as because of that you cannot extend/override just one tag. See [1]

          [1] http://markmail.org/message/jv4fobuyzyqz6zdm

          Show
          Lukasz Lenart added a comment - Are you sure? Right now I am going to do opposite, remove all ${parameters.them} and use hardcoded values as because of that you cannot extend/override just one tag. See [1] [1] http://markmail.org/message/jv4fobuyzyqz6zdm
          Hide
          Jasper Rosenberg added a comment -

          Hah, just read that thread.

          Unfortunately, I think a lot of stuff I've done will break if you make that change.

          If the super-theme doesn't reference $

          {parameters.theme}

          , you can't make changes to any of the head/footer templates without copying all the tags which kind of defeats the purpose of extension...

          Show
          Jasper Rosenberg added a comment - Hah, just read that thread. Unfortunately, I think a lot of stuff I've done will break if you make that change. If the super-theme doesn't reference $ {parameters.theme} , you can't make changes to any of the head/footer templates without copying all the tags which kind of defeats the purpose of extension...
          Hide
          Lukasz Lenart added a comment -

          But it doesn't work - or I'm missing something :\ Below is how I understand does it work:

          • define template (new folder under /template, ie. MyTheme)
          • override tag (create a file named like existing one, ie. checkboxlist.ftl)
          • define whatever you need in the new file
          • define theme.properties with parent = xhtml (for example)
          • S2 will look for files in /template/MyTheme/text.ftl, then under parent -> /template/xhtml/text.ftl and so on.
          • when S2 will find file, it will pass it to Freemarker to render and FM replaces all ${} with appropriate values (${parameters.theme} -> MyTheme}
          • right now S2 lost control over inheritance as FM will try to include file: /template/MyTheme/text.ftl which doesn't exist

          So that code:

          <#include "/${parameters.templateDir}/${parameters.theme}/controlheader.ftl" />
          <#include "/${parameters.templateDir}/simple/checkboxlist.ftl" />
          <#include "/${parameters.templateDir}/xhtml/controlfooter.ftl" /><#nt/>
          

          will be converted to:

          <#include "/template/MyTheme/controlheader.ftl" />
          <#include "/template/simple/checkboxlist.ftl" />
          <#include "/template/xhtml/controlfooter.ftl" /><#nt/>
          

          which is rendered by FM.

          I have been testing that with https://svn.apache.org/repos/asf/struts/sandbox/trunk/struts2examples/themes/

          Show
          Lukasz Lenart added a comment - But it doesn't work - or I'm missing something :\ Below is how I understand does it work: define template (new folder under /template, ie. MyTheme) override tag (create a file named like existing one, ie. checkboxlist.ftl) define whatever you need in the new file define theme.properties with parent = xhtml (for example) S2 will look for files in /template/MyTheme/text.ftl, then under parent -> /template/xhtml/text.ftl and so on. when S2 will find file, it will pass it to Freemarker to render and FM replaces all ${} with appropriate values (${parameters.theme} -> MyTheme} right now S2 lost control over inheritance as FM will try to include file: /template/MyTheme/text.ftl which doesn't exist So that code: <#include "/${parameters.templateDir}/${parameters.theme}/controlheader.ftl" /> <#include "/${parameters.templateDir}/simple/checkboxlist.ftl" /> <#include "/${parameters.templateDir}/xhtml/controlfooter.ftl" /> <#nt/> will be converted to: <#include "/template/MyTheme/controlheader.ftl" /> <#include "/template/simple/checkboxlist.ftl" /> <#include "/template/xhtml/controlfooter.ftl" /> <#nt/> which is rendered by FM. I have been testing that with https://svn.apache.org/repos/asf/struts/sandbox/trunk/struts2examples/themes/
          Hide
          Jasper Rosenberg added a comment -

          Ah, maybe this only worked for me because I needed to override the controlheader.ftl and controlfooter.ftl files (which is why I reported this issue in the first place).

          It seems to me that the real problem is the last step: "right now S2 lost control over inheritance as FM will try to include file: /template/MyTheme/text.ftl which doesn't exist"

          What if instead of passing in parameters.theme to the rendering, we pass in parameters.themeHierarchy which is in this case ["MyTheme", "xhtml", "simple"]

          We them have a macro which includes the first matching template found in a theme. Something like:

          <#macro includeThemeTemplate templateName>
            <#list parameters.themeHierarchy as theme>
              <#attempt>
                <#include "/${parameters.templateDir}/${theme}/${templateName}" />
                <#break/>
              <#recover>
              </#attempt>
            </#list>
            <#-- Cause an exception intentionally which will have the top most theme -->
            <#include "/${parameters.templateDir}/${parameters.themeHierarchy[0]}/${templateName}" />
          </#macro>
          

          So the new code would look like:

          <@includeThemeTemplate "controlheader.ftl"/>
          <#include "/${parameters.templateDir}/simple/checkboxlist.ftl" />
          <@includeThemeTemplate "controlfooter.ftl"/>
          

          This is just me writing Freemarker in place, so apologies if I have typos.

          Show
          Jasper Rosenberg added a comment - Ah, maybe this only worked for me because I needed to override the controlheader.ftl and controlfooter.ftl files (which is why I reported this issue in the first place). It seems to me that the real problem is the last step: "right now S2 lost control over inheritance as FM will try to include file: /template/MyTheme/text.ftl which doesn't exist" What if instead of passing in parameters.theme to the rendering, we pass in parameters.themeHierarchy which is in this case ["MyTheme", "xhtml", "simple"] We them have a macro which includes the first matching template found in a theme. Something like: <#macro includeThemeTemplate templateName> <#list parameters.themeHierarchy as theme> <#attempt> <#include "/${parameters.templateDir}/${theme}/${templateName}" /> <# break /> <#recover> </#attempt> </#list> <#-- Cause an exception intentionally which will have the top most theme --> <#include "/${parameters.templateDir}/${parameters.themeHierarchy[0]}/${templateName}" /> </#macro> So the new code would look like: <@includeThemeTemplate "controlheader.ftl" /> <#include "/${parameters.templateDir}/simple/checkboxlist.ftl" /> <@includeThemeTemplate "controlfooter.ftl" /> This is just me writing Freemarker in place, so apologies if I have typos.
          Hide
          Lukasz Lenart added a comment -

          Hmm.... nice idea

          Show
          Lukasz Lenart added a comment - Hmm.... nice idea
          Hide
          Lukasz Lenart added a comment -

          Btw I am bit stuck as I lost access to almost all ASF services and waiting for password reset

          Show
          Lukasz Lenart added a comment - Btw I am bit stuck as I lost access to almost all ASF services and waiting for password reset
          Hide
          Lukasz Lenart added a comment -

          I have asked question on SO [1] I'm wondering of efficiency of proposed solution.

          [1] http://stackoverflow.com/questions/17946476/how-to-implement-path-resolving-when-template-was-already-passed-to-freemerker-f

          Show
          Lukasz Lenart added a comment - I have asked question on SO [1] I'm wondering of efficiency of proposed solution. [1] http://stackoverflow.com/questions/17946476/how-to-implement-path-resolving-when-template-was-already-passed-to-freemerker-f
          Hide
          Jasper Rosenberg added a comment - - edited

          My Freemarker template implementation is definitely not going to be great, particularly because it is uncached.

          A better option might be to have a custom TemplateLoader at the top of the Configuration loader stack that was theme aware. That way it would naturally leverage the Freemarker caching layer. This is actually how we do template swapping in Freemarker for the A/B testing framework I built years ago.

          So we could have the include look like:

          <#include "/${parameters.templateDir}/_theme_/controlfooter.ftl" /><#nt/>
          

          Then our template loader could iterate through the theme hierarchy, replacing "_theme_" with each name in turn, and call the rest of the stack on each version until it got a hit which it would return.

          I will try to write an implementation later today. I have just gotten back from vacation, so I have to do some housekeeping first.

          Show
          Jasper Rosenberg added a comment - - edited My Freemarker template implementation is definitely not going to be great, particularly because it is uncached. A better option might be to have a custom TemplateLoader at the top of the Configuration loader stack that was theme aware. That way it would naturally leverage the Freemarker caching layer. This is actually how we do template swapping in Freemarker for the A/B testing framework I built years ago. So we could have the include look like: <#include "/${parameters.templateDir}/_theme_/controlfooter.ftl" /><#nt/> Then our template loader could iterate through the theme hierarchy, replacing "_theme_" with each name in turn, and call the rest of the stack on each version until it got a hit which it would return. I will try to write an implementation later today. I have just gotten back from vacation, so I have to do some housekeeping first.
          Hide
          Jasper Rosenberg added a comment -

          Hmmm, tried implementing this as a TemplateLoader but it is a bit nastier than I hoped, requiring passing the current template's theme hierarchy via thread local. I haven't been able to test it yet, but it is pretty straight forward.

          To integrate:
          1. Modify FreemakerManager.createTemplateLoader() to wrap its current result in the new ThemeTemplateLoader()
          2. Modify FreemarkerTemplateEngine.renderTemplate() to set and clear (in finally) the thread local on ThemeTemplateLoader with the list of possible templates around the iteration over the possible templates. (this is cheaty but efficient since the possible templates are the hierarchy, right?)

          import java.io.IOException;
          import java.io.Reader;
          import java.util.List;
          
          import org.apache.struts2.components.template.Template;
          
          import freemarker.cache.TemplateLoader;
          
          /**
           * This template loader iterates over the given theme hierarchy, and tries to find the first
           * that when replacing the given token name actually exists.
           */
          public class ThemeTemplateLoader implements TemplateLoader {
              private static final ThreadLocal<List<Template>> THEME_HIERARCHY = 
                      new ThreadLocal<List<Template>>();
              
              private final String themeToken;
              private final TemplateLoader parent;
              
              public ThemeTemplateLoader(String themeToken, TemplateLoader parent) {
                  this.themeToken = themeToken;
                  this.parent = parent;
              }
          
              /** Set thread local theme hierarchy. */
              public static void setThemeHierarchy(List<Template> themeHierarchy) {
                  THEME_HIERARCHY.set(themeHierarchy);
              }
              
              /** Clear thread local theme hierarchy. */
              public static void clearThemeHierarch() {
                  THEME_HIERARCHY.remove();
              }
              
              /** {@inheritDoc} */
              public Object findTemplateSource(String name) throws IOException {
                  int tokenIndex = (name == null) ? -1 : name.indexOf(themeToken);
                  if (tokenIndex < 0) {
                      return parent.findTemplateSource(name);
                  }
                  
                  String prefix = name.substring(0, tokenIndex);
                  String suffix = name.substring(tokenIndex + themeToken.length());
                  
                  List<Template> themeHierarchy = THEME_HIERARCHY.get();
                  for (Template template : themeHierarchy) {
                      String fullName = prefix + template.getTheme() + suffix;
          
                      Object templateSource = parent.findTemplateSource(fullName);
                      if (templateSource != null) {
                          return templateSource;
                      }
                  }
          
                  return null;
              }
              
              /** {@inheritDoc} */
              public long getLastModified(Object templateSource) {
                  return parent.getLastModified(templateSource);
              }
          
              /** {@inheritDoc} */
              public Reader getReader(Object templateSource, String encoding) throws IOException {
                  return parent.getReader(templateSource, encoding);
              }
          
              /** {@inheritDoc} */
              public void closeTemplateSource(Object templateSource) throws IOException {
                  parent.closeTemplateSource(templateSource);
              }
          }
          
          Show
          Jasper Rosenberg added a comment - Hmmm, tried implementing this as a TemplateLoader but it is a bit nastier than I hoped, requiring passing the current template's theme hierarchy via thread local. I haven't been able to test it yet, but it is pretty straight forward. To integrate: 1. Modify FreemakerManager.createTemplateLoader() to wrap its current result in the new ThemeTemplateLoader() 2. Modify FreemarkerTemplateEngine.renderTemplate() to set and clear (in finally) the thread local on ThemeTemplateLoader with the list of possible templates around the iteration over the possible templates. (this is cheaty but efficient since the possible templates are the hierarchy, right?) import java.io.IOException; import java.io.Reader; import java.util.List; import org.apache.struts2.components.template.Template; import freemarker.cache.TemplateLoader; /** * This template loader iterates over the given theme hierarchy, and tries to find the first * that when replacing the given token name actually exists. */ public class ThemeTemplateLoader implements TemplateLoader { private static final ThreadLocal<List<Template>> THEME_HIERARCHY = new ThreadLocal<List<Template>>(); private final String themeToken; private final TemplateLoader parent; public ThemeTemplateLoader( String themeToken, TemplateLoader parent) { this .themeToken = themeToken; this .parent = parent; } /** Set thread local theme hierarchy. */ public static void setThemeHierarchy(List<Template> themeHierarchy) { THEME_HIERARCHY.set(themeHierarchy); } /** Clear thread local theme hierarchy. */ public static void clearThemeHierarch() { THEME_HIERARCHY.remove(); } /** {@inheritDoc} */ public Object findTemplateSource( String name) throws IOException { int tokenIndex = (name == null ) ? -1 : name.indexOf(themeToken); if (tokenIndex < 0) { return parent.findTemplateSource(name); } String prefix = name.substring(0, tokenIndex); String suffix = name.substring(tokenIndex + themeToken.length()); List<Template> themeHierarchy = THEME_HIERARCHY.get(); for (Template template : themeHierarchy) { String fullName = prefix + template.getTheme() + suffix; Object templateSource = parent.findTemplateSource(fullName); if (templateSource != null ) { return templateSource; } } return null ; } /** {@inheritDoc} */ public long getLastModified( Object templateSource) { return parent.getLastModified(templateSource); } /** {@inheritDoc} */ public Reader getReader( Object templateSource, String encoding) throws IOException { return parent.getReader(templateSource, encoding); } /** {@inheritDoc} */ public void closeTemplateSource( Object templateSource) throws IOException { parent.closeTemplateSource(templateSource); } }
          Hide
          Lukasz Lenart added a comment -

          A bit tricky but it should work. And why not to use CopyOnWriteArrayList instead of ThreadLocal?

          Show
          Lukasz Lenart added a comment - A bit tricky but it should work. And why not to use CopyOnWriteArrayList instead of ThreadLocal?
          Hide
          Jasper Rosenberg added a comment -

          I'm not sure how CopyOnWriteArrayList would work. I'm using ThreadLocal to work around not being able to pass the theme hierarchy as a parameter to findTemplateSource() which is necessary since it varies by template (though is deterministic per template).

          We could move the top level template selection into the ThemeTemplateLoader itself though, which would be a bit more consistent. We could pass in the struts2 TemplateContext or Template rather than the hierarchy via ThreadLocal.

          I'm realizing that we would need the thread local to be available during template rendering as well so it is visible to the sub-includes which was pretty much the point in the first place

          Show
          Jasper Rosenberg added a comment - I'm not sure how CopyOnWriteArrayList would work. I'm using ThreadLocal to work around not being able to pass the theme hierarchy as a parameter to findTemplateSource() which is necessary since it varies by template (though is deterministic per template). We could move the top level template selection into the ThemeTemplateLoader itself though, which would be a bit more consistent. We could pass in the struts2 TemplateContext or Template rather than the hierarchy via ThreadLocal. I'm realizing that we would need the thread local to be available during template rendering as well so it is visible to the sub-includes which was pretty much the point in the first place
          Hide
          Lukasz Lenart added a comment -

          I thought you're using ThreadLocal to cache resolved themes/template paths. Anyway, can you prepare a fully working patch against trunk? Then I will be able to review your solution.

          Show
          Lukasz Lenart added a comment - I thought you're using ThreadLocal to cache resolved themes/template paths. Anyway, can you prepare a fully working patch against trunk? Then I will be able to review your solution.
          Hide
          Jasper Rosenberg added a comment -

          Okay, I think I actually have a working, non-invasive solution. I will try to get a real patch together tomorrow, but home right now, and not really set up well. This code is tested though and works. The basic solution is to support special notation for theme hierarchy templates. Basically, if you see "**" (you can choose any token), it assumes that the following path component is a theme that needs to be searched via the hierarchy.

          Usage looks like for xhtml/form.ftl for example:

          <#include "/${parameters.templateDir}/**${parameters.theme}/form-validate.ftl" />
          <#include "/${parameters.templateDir}/**${parameters.theme}/form-common.ftl" />
          <#if (parameters.validate?default(false))>
            onreset="${parameters.onreset?default('clearErrorMessages(this);clearErrorLabels(this);')}"
          <#else>
            <#if parameters.onreset??>
            onreset="${parameters.onreset?html}"
            </#if>
          </#if>
          >
          <#include "/${parameters.templateDir}/**${parameters.theme}/control.ftl" />
          

          This has a lot of advantages because you can leave off the "**" if you don't want to allow it to expand.

          Once I took this approach, the code became much simpler and for the most part let me leverage the existing hierarchy traversal methods already provided by the TemplateEngine.

          Here is the new top level template loader:

          import java.io.IOException;
          import java.io.Reader;
          import java.util.List;
          
          import org.apache.struts2.components.template.Template;
          import org.apache.struts2.components.template.TemplateEngine;
          
          import freemarker.cache.TemplateLoader;
          
          /**
           * When loading a template, if sees theme token in path, does a template search through
           * theme hierarchy for template, starting at the theme name after the token.
           */
          public class ThemeTemplateLoader implements TemplateLoader {
              private final String themeExpansionToken;
              private final TemplateLoader parent;
              private final TemplateEngine templateEngine;
              
              public ThemeTemplateLoader(
                  String themeExpansionToken, TemplateLoader parent, TemplateEngine templateEngine) {
                  this.themeExpansionToken = themeExpansionToken;
                  this.parent = parent;
                  this.templateEngine = templateEngine;
              }
              
              /** {@inheritDoc} */
              public Object findTemplateSource(String name) throws IOException {
                  int tokenIndex = (name == null) ? -1 : name.indexOf(themeExpansionToken);
                  if (tokenIndex < 0) {
                      return parent.findTemplateSource(name);
                  }
          
                  int themeEndIndex = name.indexOf('/', tokenIndex);
                  if (themeEndIndex < 0) {
                      return parent.findTemplateSource(name);
                  }
          
                  Template template = new Template(
                      name.substring(0, tokenIndex - 1), 
                      name.substring(tokenIndex + 1, themeEndIndex), 
                      name.substring(themeEndIndex + 1));
                  
                  List<Template> possibleTemplates = template.getPossibleTemplates(templateEngine);
                  for (Template possibleTemplate : possibleTemplates) {
                      Object templateSource = parent.findTemplateSource(possibleTemplate.toString());
                      if (templateSource != null) {
                          return templateSource;
                      }
                  }
          
                  return null;
              }
              
              /** {@inheritDoc} */
              public long getLastModified(Object templateSource) {
                  return parent.getLastModified(templateSource);
              }
          
              /** {@inheritDoc} */
              public Reader getReader(Object templateSource, String encoding) throws IOException {
                  return parent.getReader(templateSource, encoding);
              }
          
              /** {@inheritDoc} */
              public void closeTemplateSource(Object templateSource) throws IOException {
                  parent.closeTemplateSource(templateSource);
              }
          }
          

          The only necessary integration is in the FreemarkerManager. Just inject the "ftl" Template engine, and in init() change:

              config.setTemplateLoader(createTemplateLoader(servletContext, templatePath));
          

          to

                  config.setTemplateLoader(new ThemeTemplateLoader(
                      "**", 
                      createTemplateLoader(servletContext, templatePath), 
                      templateEngine));
          
          Show
          Jasper Rosenberg added a comment - Okay, I think I actually have a working, non-invasive solution. I will try to get a real patch together tomorrow, but home right now, and not really set up well. This code is tested though and works. The basic solution is to support special notation for theme hierarchy templates. Basically, if you see "**" (you can choose any token), it assumes that the following path component is a theme that needs to be searched via the hierarchy. Usage looks like for xhtml/form.ftl for example: <#include "/${parameters.templateDir}/**${parameters.theme}/form-validate.ftl" /> <#include "/${parameters.templateDir}/**${parameters.theme}/form-common.ftl" /> <# if (parameters.validate? default ( false ))> onreset= "${parameters.onreset? default ('clearErrorMessages( this );clearErrorLabels( this );')}" <# else > <# if parameters.onreset??> onreset= "${parameters.onreset?html}" </# if > </# if > > <#include "/${parameters.templateDir}/**${parameters.theme}/control.ftl" /> This has a lot of advantages because you can leave off the "**" if you don't want to allow it to expand. Once I took this approach, the code became much simpler and for the most part let me leverage the existing hierarchy traversal methods already provided by the TemplateEngine. Here is the new top level template loader: import java.io.IOException; import java.io.Reader; import java.util.List; import org.apache.struts2.components.template.Template; import org.apache.struts2.components.template.TemplateEngine; import freemarker.cache.TemplateLoader; /** * When loading a template, if sees theme token in path, does a template search through * theme hierarchy for template, starting at the theme name after the token. */ public class ThemeTemplateLoader implements TemplateLoader { private final String themeExpansionToken; private final TemplateLoader parent; private final TemplateEngine templateEngine; public ThemeTemplateLoader( String themeExpansionToken, TemplateLoader parent, TemplateEngine templateEngine) { this .themeExpansionToken = themeExpansionToken; this .parent = parent; this .templateEngine = templateEngine; } /** {@inheritDoc} */ public Object findTemplateSource( String name) throws IOException { int tokenIndex = (name == null ) ? -1 : name.indexOf(themeExpansionToken); if (tokenIndex < 0) { return parent.findTemplateSource(name); } int themeEndIndex = name.indexOf('/', tokenIndex); if (themeEndIndex < 0) { return parent.findTemplateSource(name); } Template template = new Template( name.substring(0, tokenIndex - 1), name.substring(tokenIndex + 1, themeEndIndex), name.substring(themeEndIndex + 1)); List<Template> possibleTemplates = template.getPossibleTemplates(templateEngine); for (Template possibleTemplate : possibleTemplates) { Object templateSource = parent.findTemplateSource(possibleTemplate.toString()); if (templateSource != null ) { return templateSource; } } return null ; } /** {@inheritDoc} */ public long getLastModified( Object templateSource) { return parent.getLastModified(templateSource); } /** {@inheritDoc} */ public Reader getReader( Object templateSource, String encoding) throws IOException { return parent.getReader(templateSource, encoding); } /** {@inheritDoc} */ public void closeTemplateSource( Object templateSource) throws IOException { parent.closeTemplateSource(templateSource); } } The only necessary integration is in the FreemarkerManager. Just inject the "ftl" Template engine, and in init() change: config.setTemplateLoader(createTemplateLoader(servletContext, templatePath)); to config.setTemplateLoader( new ThemeTemplateLoader( "**" , createTemplateLoader(servletContext, templatePath), templateEngine));
          Hide
          Jasper Rosenberg added a comment -

          Oh, and of course do a whole log of search and replace in the template files

          Show
          Jasper Rosenberg added a comment - Oh, and of course do a whole log of search and replace in the template files
          Hide
          Jasper Rosenberg added a comment -

          Also, I put the ThemeTemplateLoader in the init, because I think no matter what, you want it at the top of the loader stack, even if someone wants to change the base stack.

          Show
          Jasper Rosenberg added a comment - Also, I put the ThemeTemplateLoader in the init, because I think no matter what, you want it at the top of the loader stack, even if someone wants to change the base stack.
          Hide
          Jasper Rosenberg added a comment -

          Here is the patch as promised. I was able to isolate it to just the TemplateEngine.

          Also, I added a new parameter so a user could change the token if it turned out they were already using it for a different purpose.

          This had a nice side effect too, that a lot of the css_xhtml templates could be deleted because they were identical to those in its parent, xhtml.

          Show
          Jasper Rosenberg added a comment - Here is the patch as promised. I was able to isolate it to just the TemplateEngine. Also, I added a new parameter so a user could change the token if it turned out they were already using it for a different purpose. This had a nice side effect too, that a lot of the css_xhtml templates could be deleted because they were identical to those in its parent, xhtml.
          Hide
          Lukasz Lenart added a comment -

          WoW! Great work!

          One thing is wondering me, why not to move the code below to FreemarkerManager#init() ?

                  synchronized (config) {
                      TemplateLoader templateLoader = config.getTemplateLoader();
                      if (!(templateLoader instanceof FreemarkerThemeTemplateLoader)) {
                          config.setTemplateLoader(
                              new FreemarkerThemeTemplateLoader(uiThemeExpansionToken, templateLoader, this));
                      }
                  }
          
          Show
          Lukasz Lenart added a comment - WoW! Great work! One thing is wondering me, why not to move the code below to FreemarkerManager#init() ? synchronized (config) { TemplateLoader templateLoader = config.getTemplateLoader(); if (!(templateLoader instanceof FreemarkerThemeTemplateLoader)) { config.setTemplateLoader( new FreemarkerThemeTemplateLoader(uiThemeExpansionToken, templateLoader, this )); } }
          Hide
          Jasper Rosenberg added a comment -

          Thanks

          That is where I started, but I ended up with it in the FreemakerTemplateEngine for 2 reasons:

          1. The FreemarkerManager package had no theme/tag awareness that I could see, so it seemed odd to make it less generic. We would have to inject the FreemakerTemplateEngine into the manager to pass to the FreemarkerThemeTemplateLoader, or use the container to construct the FreemarkerThemeTemplateLoader and have it injected that way (along with the uiThemeExpansionToken).

          2. If a subclass of FreemarkerManager needs to replace the config template loader (which I do periodically when dynamically changing A/B tests), it is more fragile because the subclass needs to know to put the FreemarkerThemeTemplateLoader back on top. We could probably make that clearer with some documentation and explicit hooks for that case though.

          I do think there are some strong advantages to your suggestion, namely that we wouldn't need the synchronized block, which technically isn't sufficient anyway since it doesn't keep someone else in another thread from setting the template loader while you are in the synch block.

          I will create an alternate patch with this approach and you can decide which you prefer.

          Show
          Jasper Rosenberg added a comment - Thanks That is where I started, but I ended up with it in the FreemakerTemplateEngine for 2 reasons: 1. The FreemarkerManager package had no theme/tag awareness that I could see, so it seemed odd to make it less generic. We would have to inject the FreemakerTemplateEngine into the manager to pass to the FreemarkerThemeTemplateLoader, or use the container to construct the FreemarkerThemeTemplateLoader and have it injected that way (along with the uiThemeExpansionToken). 2. If a subclass of FreemarkerManager needs to replace the config template loader (which I do periodically when dynamically changing A/B tests), it is more fragile because the subclass needs to know to put the FreemarkerThemeTemplateLoader back on top. We could probably make that clearer with some documentation and explicit hooks for that case though. I do think there are some strong advantages to your suggestion, namely that we wouldn't need the synchronized block, which technically isn't sufficient anyway since it doesn't keep someone else in another thread from setting the template loader while you are in the synch block. I will create an alternate patch with this approach and you can decide which you prefer.
          Hide
          Jasper Rosenberg added a comment -

          Alternate patch that sticks the theme aware template loader into the loader stack in the FreemarkerManager instead of the FreemarkerTemplateEngine.

          Show
          Jasper Rosenberg added a comment - Alternate patch that sticks the theme aware template loader into the loader stack in the FreemarkerManager instead of the FreemarkerTemplateEngine.
          Hide
          Lukasz Lenart added a comment -

          You're are right, the second approach doesn't make sense. With the first I have missed that the FreemarkerThemeTemplateLoader depends on the TemplateEngine :\

          What about moving synchronized a bit deeper?

          
          TemplateLoader templateLoader = config.getTemplateLoader();
          if (!(templateLoader instanceof FreemarkerThemeTemplateLoader)) {
              synchronized (config) {
                  config.setTemplateLoader(
                      new FreemarkerThemeTemplateLoader(uiThemeExpansionToken, templateLoader, this));
                  }
              }
          }
          

          hm... ok, also doesn't make sense :\

          I think right the first patch is best available solution.

          Show
          Lukasz Lenart added a comment - You're are right, the second approach doesn't make sense. With the first I have missed that the FreemarkerThemeTemplateLoader depends on the TemplateEngine :\ What about moving synchronized a bit deeper? TemplateLoader templateLoader = config.getTemplateLoader(); if (!(templateLoader instanceof FreemarkerThemeTemplateLoader)) { synchronized (config) { config.setTemplateLoader( new FreemarkerThemeTemplateLoader(uiThemeExpansionToken, templateLoader, this )); } } } hm... ok, also doesn't make sense :\ I think right the first patch is best available solution.
          Hide
          Jasper Rosenberg added a comment -

          Sounds good. Feel free to change the expansion token value from "~~~". Maybe we should make it super explicit like "[expandTheme]"

          Show
          Jasper Rosenberg added a comment - Sounds good. Feel free to change the expansion token value from "~~~". Maybe we should make it super explicit like " [expandTheme] "
          Hide
          Lukasz Lenart added a comment -

          Just noticed this old issue, I must review the proposed solution there.

          Show
          Lukasz Lenart added a comment - Just noticed this old issue, I must review the proposed solution there.
          Hide
          Lukasz Lenart added a comment -

          The old solution isn't complete so I cannot apply it. I have tried apply both patches, though some tests are failing now. Can you check against trunk again?

          It looks like it's related to WW-3929 but that issue was resolved with 2.3.12 so I have no idea what is wrong :\

          Show
          Lukasz Lenart added a comment - The old solution isn't complete so I cannot apply it. I have tried apply both patches, though some tests are failing now. Can you check against trunk again? It looks like it's related to WW-3929 but that issue was resolved with 2.3.12 so I have no idea what is wrong :\
          Hide
          Lukasz Lenart added a comment -

          I found the problem and extend a bit your idea to engage theme inheriting in template resolving.

          Show
          Lukasz Lenart added a comment - I found the problem and extend a bit your idea to engage theme inheriting in template resolving.
          Hide
          Jasper Rosenberg added a comment -

          Excellent, glad you found a solution since I wasn't going to be able to look at it until Monday. Thanks!

          Show
          Jasper Rosenberg added a comment - Excellent, glad you found a solution since I wasn't going to be able to look at it until Monday. Thanks!
          Hide
          Lukasz Lenart added a comment -

          One more thing, as now ~~~ is used to check also parent theme I have introduced expandTheme variable into Freemarker's templates. I'm not sure if it's the right name, to show that it will search parent theme for a template, e.g.

          label.ftl from css_xhtml theme
          <#include "/${parameters.templateDir}/${parameters.expandTheme}/controlheader.ftl" />
          

          wdyt?

          Show
          Lukasz Lenart added a comment - One more thing, as now ~~~ is used to check also parent theme I have introduced expandTheme variable into Freemarker's templates. I'm not sure if it's the right name, to show that it will search parent theme for a template, e.g. label.ftl from css_xhtml theme <#include "/${parameters.templateDir}/${parameters.expandTheme}/controlheader.ftl" /> wdyt?
          Hide
          Jasper Rosenberg added a comment -

          Maybe name it "parameters.expandedTheme" as "expandTheme" sounds like a command? (or parameters.inheritingTheme" perhaps?) Otherwise, sounds good to me!

          Show
          Jasper Rosenberg added a comment - Maybe name it "parameters.expandedTheme" as "expandTheme" sounds like a command? (or parameters.inheritingTheme" perhaps?) Otherwise, sounds good to me!
          Hide
          Lukasz Lenart added a comment -

          Patch applied with some minor changes Thanks a lot for all your help!

          https://cwiki.apache.org/confluence/display/WW/Extending+Themes

          Show
          Lukasz Lenart added a comment - Patch applied with some minor changes Thanks a lot for all your help! https://cwiki.apache.org/confluence/display/WW/Extending+Themes
          Hide
          ASF subversion and git services added a comment -

          Commit 1536435 from Lukasz Lenart in branch 'struts2/trunk'
          [ https://svn.apache.org/r1536435 ]

          WW-4145 Improves extending theme mechanism to allow override just one file in parent theme

          Show
          ASF subversion and git services added a comment - Commit 1536435 from Lukasz Lenart in branch 'struts2/trunk' [ https://svn.apache.org/r1536435 ] WW-4145 Improves extending theme mechanism to allow override just one file in parent theme
          Hide
          Jasper Rosenberg added a comment -

          Looks great, can't wait for the next release!

          Show
          Jasper Rosenberg added a comment - Looks great, can't wait for the next release!
          Hide
          Hudson added a comment -

          SUCCESS: Integrated in Struts2-JDK6 #829 (See https://builds.apache.org/job/Struts2-JDK6/829/)
          WW-4145 Improves extending theme mechanism to allow override just one file in parent theme (lukaszlenart: rev 1536435)

          • /struts/struts2/trunk/core/src/main/java/org/apache/struts2/StrutsConstants.java
          • /struts/struts2/trunk/core/src/main/java/org/apache/struts2/components/UIBean.java
          • /struts/struts2/trunk/core/src/main/java/org/apache/struts2/components/template/FreemarkerTemplateEngine.java
          • /struts/struts2/trunk/core/src/main/java/org/apache/struts2/views/freemarker/FreemarkerManager.java
          • /struts/struts2/trunk/core/src/main/java/org/apache/struts2/views/freemarker/FreemarkerThemeTemplateLoader.java
          • /struts/struts2/trunk/core/src/main/resources/org/apache/struts2/default.properties
          • /struts/struts2/trunk/core/src/main/resources/struts-default.xml
          • /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/checkboxlist.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/combobox.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/controlheader.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/debug.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/doubleselect.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/file.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/form-close.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/form.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/inputtransferselect.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/label.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/optiontransferselect.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/password.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/radiomap.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/select.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/text.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/textarea.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/updownselect.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/a-close.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/checkbox.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/checkboxlist.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/combobox.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/div.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/file.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/form-common.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/form.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/hidden.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/inputtransferselect.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/label.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/optgroup.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/password.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/radiomap.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/reset.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/select.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/submit.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/text.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/simple/textarea.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/checkbox.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/checkboxlist.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/combobox.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/controlheader-core.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/controlheader.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/debug.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/doubleselect.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/file.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/form-close.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/form.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/inputtransferselect.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/label.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/optiontransferselect.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/password.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/radiomap.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/reset.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/select.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/submit-close.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/text.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/textarea.ftl
          • /struts/struts2/trunk/core/src/main/resources/template/xhtml/updownselect.ftl
          • /struts/struts2/trunk/core/src/test/java/org/apache/struts2/views/freemarker/FreeMarkerResultTest.java
          • /struts/struts2/trunk/core/src/test/java/org/apache/struts2/views/freemarker/FreemarkerManagerTest.java
          • /struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/freemarker/dynaAttributes.ftl
          • /struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/freemarker/manual-list.ftl
          Show
          Hudson added a comment - SUCCESS: Integrated in Struts2-JDK6 #829 (See https://builds.apache.org/job/Struts2-JDK6/829/ ) WW-4145 Improves extending theme mechanism to allow override just one file in parent theme (lukaszlenart: rev 1536435) /struts/struts2/trunk/core/src/main/java/org/apache/struts2/StrutsConstants.java /struts/struts2/trunk/core/src/main/java/org/apache/struts2/components/UIBean.java /struts/struts2/trunk/core/src/main/java/org/apache/struts2/components/template/FreemarkerTemplateEngine.java /struts/struts2/trunk/core/src/main/java/org/apache/struts2/views/freemarker/FreemarkerManager.java /struts/struts2/trunk/core/src/main/java/org/apache/struts2/views/freemarker/FreemarkerThemeTemplateLoader.java /struts/struts2/trunk/core/src/main/resources/org/apache/struts2/default.properties /struts/struts2/trunk/core/src/main/resources/struts-default.xml /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/checkboxlist.ftl /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/combobox.ftl /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/controlheader.ftl /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/debug.ftl /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/doubleselect.ftl /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/file.ftl /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/form-close.ftl /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/form.ftl /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/inputtransferselect.ftl /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/label.ftl /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/optiontransferselect.ftl /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/password.ftl /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/radiomap.ftl /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/select.ftl /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/text.ftl /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/textarea.ftl /struts/struts2/trunk/core/src/main/resources/template/css_xhtml/updownselect.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/a-close.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/checkbox.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/checkboxlist.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/combobox.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/div.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/file.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/form-common.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/form.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/hidden.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/inputtransferselect.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/label.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/optgroup.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/password.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/radiomap.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/reset.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/select.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/submit.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/text.ftl /struts/struts2/trunk/core/src/main/resources/template/simple/textarea.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/checkbox.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/checkboxlist.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/combobox.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/controlheader-core.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/controlheader.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/debug.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/doubleselect.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/file.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/form-close.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/form.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/inputtransferselect.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/label.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/optiontransferselect.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/password.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/radiomap.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/reset.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/select.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/submit-close.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/text.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/textarea.ftl /struts/struts2/trunk/core/src/main/resources/template/xhtml/updownselect.ftl /struts/struts2/trunk/core/src/test/java/org/apache/struts2/views/freemarker/FreeMarkerResultTest.java /struts/struts2/trunk/core/src/test/java/org/apache/struts2/views/freemarker/FreemarkerManagerTest.java /struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/freemarker/dynaAttributes.ftl /struts/struts2/trunk/core/src/test/resources/org/apache/struts2/views/freemarker/manual-list.ftl
          Hide
          adam brin added a comment - - edited

          To help clarify for those writing custom themes that extend. If my custom theme extends the controlheader and controlfooter, then in textarea.ftl, I need to change:

          original in textarea.ftl
          <#include "/${parameters.templateDir}/${parameters.theme}/controlheader.ftl" />
          <#include "/${parameters.templateDir}/simple/textarea.ftl" />
          <#include "/${parameters.templateDir}/${parameters.theme}/controlfooter.ftl" />
          

          <b>should be rewritten as</b>

          new in textarea.ftl
          <#include "/${parameters.templateDir}/${parameters.expandTheme}/controlheader.ftl" />
          <#include "/${parameters.templateDir}/simple/textarea.ftl" />
          <#include "/${parameters.templateDir}/${parameters.expandTheme}/controlfooter.ftl" />
          

          so that textarea.ftl is pulled from simple, but the custom controlheader.ftl and controlfooter.ftl are used?

          Show
          adam brin added a comment - - edited To help clarify for those writing custom themes that extend. If my custom theme extends the controlheader and controlfooter, then in textarea.ftl, I need to change: original in textarea.ftl <#include "/${parameters.templateDir}/${parameters.theme}/controlheader.ftl" /> <#include "/${parameters.templateDir}/simple/textarea.ftl" /> <#include "/${parameters.templateDir}/${parameters.theme}/controlfooter.ftl" /> <b>should be rewritten as</b> new in textarea.ftl <#include "/${parameters.templateDir}/${parameters.expandTheme}/controlheader.ftl" /> <#include "/${parameters.templateDir}/simple/textarea.ftl" /> <#include "/${parameters.templateDir}/${parameters.expandTheme}/controlfooter.ftl" /> so that textarea.ftl is pulled from simple, but the custom controlheader.ftl and controlfooter.ftl are used?
          Hide
          Lukasz Lenart added a comment - - edited

          ${parameters.expandTheme} is a recurrence which tells ThemeManager to load template from current theme and then from parent theme (defined in theme.properties) and so on.

          Please also notice that the ThemeManager builds list of possible templates based on current theme and inherited themes (/template/custom/textarea.ftl, /template/xhtml/textarea.ftl, /template/simple/textarea.ftl). This also true for templates which are loaded via ${parameters.expandTheme}.

          One thing, the textarea.ftl in xhtml theme is defined as follow:

          <#include "/${parameters.templateDir}/${parameters.expandTheme}/controlheader.ftl" />
          <#include "/${parameters.templateDir}/simple/textarea.ftl" />
          <#include "/${parameters.templateDir}/${parameters.expandTheme}/controlfooter.ftl" />
          

          So it contains ${parameters.expandTheme} already, so if you want to just override those, define them in your custom theme and ThemeManager will pick them up. You don't have to write custom textarea.ftl.

          It looks complicated but it's quite simple If still something is unclear, don't hesitate to ask.

          Show
          Lukasz Lenart added a comment - - edited ${parameters.expandTheme } is a recurrence which tells ThemeManager to load template from current theme and then from parent theme (defined in theme.properties) and so on. Please also notice that the ThemeManager builds list of possible templates based on current theme and inherited themes (/template/custom/textarea.ftl, /template/xhtml/textarea.ftl, /template/simple/textarea.ftl). This also true for templates which are loaded via ${parameters.expandTheme }. One thing, the textarea.ftl in xhtml theme is defined as follow: <#include "/${parameters.templateDir}/${parameters.expandTheme}/controlheader.ftl" /> <#include "/${parameters.templateDir}/simple/textarea.ftl" /> <#include "/${parameters.templateDir}/${parameters.expandTheme}/controlfooter.ftl" /> So it contains ${parameters.expandTheme } already, so if you want to just override those, define them in your custom theme and ThemeManager will pick them up. You don't have to write custom textarea.ftl. It looks complicated but it's quite simple If still something is unclear, don't hesitate to ask.
          Hide
          adam brin added a comment -

          I'm not sure I follow, but perhaps it'd be best if I open it as a separate ticket

          Show
          adam brin added a comment - I'm not sure I follow, but perhaps it'd be best if I open it as a separate ticket
          Hide
          Lukasz Lenart added a comment -

          Sure thing! You can always ask on the mailing list.

          Show
          Lukasz Lenart added a comment - Sure thing! You can always ask on the mailing list.

            People

            • Assignee:
              Lukasz Lenart
              Reporter:
              Jasper Rosenberg
            • Votes:
              0 Vote for this issue
              Watchers:
              6 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:

                Development