WOOGNL Helper Functions
The concept of helper functions is borrowed from Rails, though the implementation is a little different. Like before, the Properties file setup for this feature is:
ognl.helperFunctions=true
(helper functions does not require inline bindings, but inline bindings requires helper functions)
When you're writing code, there are lots of times when you find that you want reusable chunks of code that you can use in WOComponents, but it's dirty putting that code in the model. An example will make this more obvious.
Say you have a Person EO that has a first name and last name. You want to standardize how you format the name when you use it throughout your website. For instance, maybe you want "First Last", or "Last, First" or "F. Last". Where does this method go? It's really a view method, so putting it in the model is not right. But you don't want to put it in a WOComponent because you need it in a lot of places. You can't make it a component of its own, because you often have to pass this value into bindings of other components (say you have a HeaderFooter component and you want the title of a page to be the current user's display name). There's something missing that could make this easier – helper functions.
In WOOgnl, helper functions allow you to make "filters" that you can include in any binding, grouped by "target class". For instance, in the example above, the class name of the EO is "com.mdimension.model.Person", so I would make a class called "PersonHelper" (in any package – it uses NSBundle resolution like WOComponents in WODs) and you can make any number of static methods that all take (at least) a Person parameter. The class name for your helper is always "<TypeName>Helper" – StringHelper, CompanyHelper, etc. I'll show you how to remap these at the end if you want a different name.
Example:
public class PersonHelper {
public static String displayName(Person person) {
return person.firstName() + " " + person.lastName();
}
}
(note: this class can be sent a null, so in "real life" this should be more resilient)
In your WOD, you might say:
PersonName : WOString {
value = person|displayName;
}
In case that doesn't read clearly with the font in your mail app, that's a pipe (shift backslash) between person and displayName. Conceptually similar to a pipe in unix, this looks for the return type (or field type) of the binding before the pipe, looks up the corresponding helper, calls the displayName helper passing in the person object, and returns the string result of the helper as the "value" binding.
Helper functions can also take OGNL-style parameters. The "Person" (or whatever helper you are making) first parameter is always there, but say you wanted to pass in a boolean option as well:
public class PersonHelper {
public static String displayName(Person person, boolean lastNameFirst) {
// do that
}
}
In the WOD you might say:
PersonName : WOString {
value = person|displayName(true);
}
That "true" could also be another binding reference (just like in WOOgnl). In fact, behind the scenes, helper functions just expand into a big OGNL expression.
You can also register custom helper classes (if you don't want to name things the default way). By calling:
WOHelperFunctionRegistry.registry().setHelperInstanceForClassInFrameworkNamed(new MyStringHelper(), String.class, "app");
... would replace the default application scope string helper with MyStringHelper instead. Be careful about using and replacing the application scope helper, since you are changing the default helper instance that is used everywhere in your app. Which brings us to frameworks ...
It's very likely that you want per-framework helper functions (if nothing else than because you need to isolate your helpers from the application's). In a framework (unless you control the frameworks and the app and can guarantee you won't step on eachothers toes), you should register a custom helper for your types where the third parameter to setHelperInstanceForClassInFrameworkNamed is your framework name (or some unique identifier that you will use throughout your framework). So if I have MDTWOExtensions framework, I would register my (new MDTWOStringHelper(), String.class, MDTWOExtensions"). In my framework WODs, I would use the slightly extended calling syntax:
PersonName : WOString {
value = person|MDTWOExtensions.displayName(true);
}
This allows me to scope my helpers more effectively. If you leave off the framework scoping, it will always default to app level scope.
Other handy uses of helpers that we have in our own code:
- StringHelper.sanitize(String str) – remove dangerous HTML and Javascript tags from a string
- StringHelper.highlight(String str, String search) – highlight search result match in a string
- StringHelper.pluralize(String str) – pluralize a string
- StringHelper.html(String str) – convert a string to HTML
- MemberHelper.mailTo(Member member) – return a member.emailAddress()
- lots of others
Enjoy!
Mike Schrag