Creating custom Intershop ISML functions with CustomTag

The problem

I wanted to capitalize a certain value from the pipeline dictionary in an ISML template. Intershop provides ISML functions of the kind ucase and lcase to transform a string to uppercase or lowercase respectively. When the value of the dictionary name Product:Name is AwesomeNotebook:

<isprint value="#ucase(Product:Name)#">

this will print AWESOMENOTEBOOK. Unfortunately, a similar function to capitalize a string is not provided by Intershop. Ideally, you would want to be able to implement your own methods, so that you could type:

<isprint value="#capitalize(Product:Name)#">

What about custom modules?

Intershop does provide the ability to create custom modules. These are basically just tags you can give whatever name you like, and you can provide an implementation in Java or ISML. The interface is defined in a modules.isml file. You could, for example, define a custom tag isprintdefault that prints a default value whenever the value passed to it is null or the empty string:

<isprintdefault default="(unknown)" value="#Product:Price">

This would print (unknown) every time Product:Price does not contain a value.

This solution, however, has two drawbacks:

  • For a Java implementation, this would come down to writing scriptlet. A few lines of scriptlet is fine ofcourse, but as the size of the code increases, it becomes harder and harder to maintain, because there is no compilation-time check and debugging is nearly impossible (say hello to a thousand print statements).
  • Also, it is cumbersome for implementing something like the capitalize function mentioned above, because you would have to provide the input to the custom module, put the result in the pipeline dictionary, and then use the pipeline dictionary name:
<iscapitalize dictname="capitalizedStr" input="#Product:Name#">
<isprint value="#capitalizedStr#">

Instead of just using the returned capitalized string directly:

<isprint value="#capitalize(Product:Name)#">

Solution: ISML Custom Tag

An ISML custom tag looks just like a custom module:

<ISUUID name="CategoryRenderEntityID">

(Incidently, this creates a UUID and stores it in the pipeline dictionary variable name CategoryRenderEntityID). According to the documentation, the difference between a custom tag and a custom module is that the former “provides a significant performace increase” compared to the latter.

Two purposes

Custom tags can be used for two purposes:

  • Performing a single operation, like we saw with the ISUUID custom tag above, where the tag performs a single action (creating a new UUID and putting it in the pipeline dictionary).
  • Inserting an instance of a helper or utilities class into the pipeline dictionary.

Creating an ISML Custom Tag

Say, we want to create an ISML Custom Tag named <ishelper>. First, we would define it in a modules.properties file, located at <cartridge>/staticfiles/cartridge/config/modules.properties:

ismodules.helper.class=custom_base.internal.modules.Helper
ismodules.helper.isStrict=false
ismodules.helper.parameters=alias
ismodules.helper.parameter.alias.type=java.lang.String
ismodules.helper.parameter.alias.isRequired=false

The class property should point to the implementing Java class. Since a pipeline dictionary is available to custom tags, we can define whether we want a clean, strict dictionary or not, much like we can do in pipelines. Parameters and return values can be defined by providing their name, type and whether or not they are mandatory.

So, the properties file above defines a custom tag <ishelper> (put a leading is to your lowercase class name), that has an optional attribute alias of type String. Other parameters can be added by using comma’s:

ismodules.helper.parameters=alias,more,params

Don’t forget to also add a type and whether the parameter is required or not. The type of the parameter can be any Java class, so you’re not limited to just strings.

The Java class with the implementing code must extend the abstract class CustomTag. It is required to override the method processOpenTag, which will be called every time the custom tag is encountered in the ISML template.

Now then, we can create “custom ISML functions” by adding an instance of our Helper class to the pipeline dictionary:

public class Helper extends CustomTag {
    @Override
    public void processOpenTag(PageContext paramPageContext,
                               ServletResponse paramServletResponse,
                               AbstractTemplate paramAbstractTemplate,
                               int paramInt)
            throws IOException, ServletException
    {
        PipelineDictionary dict = AbstractTemplate
                .getTemplateExecutionConfig()
                .getPipelineDictionary();
        String alias = dict.getOptional("alias", "Helper");
        dict.put(alias, new Helper());
    }
}

If we wanted a function capitalize in our templates, we could add such a method to our Java class:

public String getCapitalize(String str) {
    /* do groundbreaking stuff */
    return capitalizedStr;
}

Note that the method name should start with get in order for it to be available in the pipeline dictionary in our templates. We can now use the method as follows:

<isprint value="#Helper:Capitalize(Product:Name)#">

Don’t forget, however, to add the custom tag somewhere at the beginning of the template before using it:

<ishelper alias="MyCustomUtilsClass">
<!-- snip -->
<isloop iterator="MyProducts" alias="product">
    <isprint value="#MyCustomUtilsClass:Capitalize(product:Name)#"
</isloop>

(Note that the alias attribute is optional. If you leave it out, it will default to Helper: see the implementation above. This will be the alias of the class we’re using to invoke methods on:

<isprint value="#MyCustomUtilsClass:noArgMethod()#">

Remember that the name of the actual class that contains the custom tag is part of the tag name: ishelper.)

Alternative method of making a Java class available in the pipeline dictionary

Obviously, we do not have to rely on an ISML custom tag to put an instance of a Java class in the pipeline dictionary. We could just create a pipelet that does this, and put this pipelet in a pipeline that gets called a lot (a Prefix pipeline would be good). This would remove the need to put <ishelper> (or whatever we named the custom tag) at the top of our templates. But the helper class would only be available if the Prefix pipeline was called, so you would need to check that carefully. Putting the custom tag at the top of the template makes the import explicit and easily verifiable.

A word of caution

Custom tags are very handy, but they extend the internal CustomTag class. This means Intershop does not encourage using them in custom project development. In fact, you won’t find any information about them in the documentation.