I'm building a site using Freemarker and have started heavily using macros. I know in Freemarker 2.3 that passing a null value into a macro as a parameter is equivalent to not passing a parameter at all so I've created a global variable called "null" to simulate null checking in my macros:
<#assign null="NUL" />
Now in my macros I can do this:
<#maco doSomething param1=null>
<#if param1 != null>
<div>WIN!</div>
</#if>
</#macro>
The problem comes if I want to pass a parameter that isn't a scalar. For instance, passing a List (which in Freemarker is a SimpleSequence) to a macro and checking against my null keyword yields the error:
freemarker.template.TemplateException: The only legal comparisons are between two numbers, two strings, or two dates. Left hand operand is a freemarker.template.SimpleSequence Right hand operand is a freemarker.template.SimpleScalar
I took a look at the freemarker code and I can see the issue (ComparisonExpression.isTrue()):
if(ltm instanceof TemplateNumberModel && rtm instanceof TemplateNumberModel) {
...
}
else if(ltm instanceof TemplateDateModel && rtm instanceof TemplateDateModel) {
...
}
else if(ltm instanceof TemplateScalarModel && rtm instanceof TemplateScalarModel) {
...
}
else if(ltm instanceof TemplateBooleanModel && rtm instanceof TemplateBooleanModel) {
...
}
// Here we handle compatibility issues
else if(env.isClassicCompatible()) {
...
}
else {
throw new TemplateException("The only legal comparisons...", env);
}
So the only solution I can think of is to set isClassicCompatible to true, which I think will call toString() on both objects and compare the result. However, the documentation specifically says anything relying on old features should be rewritten.
My quesion is, is there a solution to this that doesn't rely on deprecated features?
The null
reference is by design an error in FreeMarker. Defining a custom null value - which is a string - is not a good idea for the reasons you mention. The following constructs should be used instead:
- Macro and function parameters can have a default value, so the callers can omit them
- To check if a variable is
null
, you should use the??
operator:<#if (name??)>
- When you use a variable that can be
null
, you should use the!
operator to specify a default value:name!"No name"
- To check if a sequence (or a string) is empty, use the
?has_content
builtin:<#if (names?has_content)>
You can specify an empty sequence as default parameter value in a macro, and simply test whether it's empty.
Here's what I did, which seems to work in most scenarios:
The default value should be an empty string, and the null-check should be ?has_content.
<#macro someMacro optionalParam="" >
<#if (optionalParam?has_content)>
<#-- NOT NULL -->
<#else>
<#-- NULL -->
</#if>
</#macro>
A coworker has suggested a null-checking function may be the most elegant solution:
<#assign null="NUL" />
<#function is_null variable>
<#if variable?is_string & variable == null>
<#return true />
<#else>
<#return false />
</#if>
</#function>
Running with that idea, I found another possible solution in the mailing list, which is to create a null-checking function in Java by extending TemplateMethodModelEx. I can then insert it directly into my model map and have it globally available in all my templates. The resulting Freemarker code is pretty clean:
<#maco doSomething param1=null>
<#if !is_null(param1)>
<div>WIN!</div>
</#if>
</#macro>
This seems to be the best way to simulate null parameters inside a macro.
You can check if it's not a scalar first:
<#if !param1?is_scalar || param1 != null)>
This is another solution that may fit to your need.
<#macro el user={}>
...
<input type="text" class="form-control" name="firstName"
value="${(user.identity.firstName)!''}">
...
</#macro>
I define a default empty object {}
instead of null to avoid 2 big if else block in my element.
make sure that when you give a default value to the attribute (firstName in my case), to put the parenthesis to all the expression then the ! sign.
So whatever identity
is undefined or firstName
is undefined, freemarker use the default value
(user.identity.firstName)!''
Actually i do not understand why null's are not allowed. I have a java function to retrieve a specific image URL with given optional / nullable parameters like height.
I wrote a macro for it using following workaround:
Java function
/**
* Converts the given value to <code>null</code> if it equals the default
* value, else returns it directly.
*
* @param value
* @param defaultValue
*
* @return
*/
public static Object nullIfDefault(final Object value, final Object defaultValue)
{
if (value != null && value.equals(defaultValue))
return null;
return value;
}
/**
* Converts the given value to <code>null</code> if it equals an empty
* string, else returns it directly.
*
* @param value
*
* @return
*/
public static Object nullIfEmpty(final Object value)
{
return ObjectUtils.nullIfDefault(value, "");
}
Macro
Import static function
<#assign ObjectUtils=statics['com.example.ObjectUtils']/>
<#macro compEntityImage imageRetriever entity height="" width="">
<img src="${imageRetriever.getMediaFileUrl(entity,
ObjectUtils.nullIfEmpty(height),
ObjectUtils.nullIfEmpty(width))}">
</#macro>
Markup
<@compEntityImgage imageRetriever=imageRetriver entity=product height=500/>
来源:https://stackoverflow.com/questions/6553004/simulate-null-parameters-in-freemarker-macros