WOOgnl and Helper Functions

108 views
Skip to first unread message

Mike Schrag

unread,
Mar 5, 2007, 12:45:39 PM3/5/07
to wot...@googlegroups.com
To tie up the WOOgnl features, I need to cover 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
mailto:member.emailAddress()
* lots of others :)

Enjoy!

ms


Denis Frolov

unread,
Mar 9, 2007, 7:11:41 AM3/9/07
to wotips
Great post, Mike! I have one question though: does helper method needs
to be static? I can't make a helper function from a framework work in
deployment if it is static:

Method "lineBreaksToBr" failed for object
dm.extensions.DMStringHelper@a0f02a [java.lang.NoSuchMethodException:
lineBreaksToBr(java.lang.String)]

Removing "static" in declaration solves this issue. The strange thing
is that it works with and without "static" in development mode
(launched from Eclipse).

Helper function is registered in a framework's principal class
finishIntialization() like this:

WOHelperFunctionRegistry.registry().setHelperInstanceForClassInFrameworkNamed(new
DMStringHelper(), String.class, "DMExtensions");

and used in application component .wod like this:

ContentsString : ERXInlineTemplate {
cachingEnabled = false;
html = object.contents|DMExtensions.lineBreaksToBr;
wod = "";
proxyParent = true;

Mike Schrag

unread,
Mar 9, 2007, 7:32:34 AM3/9/07
to wot...@googlegroups.com
> Great post, Mike! I have one question though: does helper method needs
> to be static? I can't make a helper function from a framework work in
> deployment if it is static:
Doh! They should actually NOT be static. Thanks for catching that.
One of the nice things, in fact, that it is NOT static, because you
can subclass helpers and inherit methods.

>> public class PersonHelper {
>> public static String displayName(Person person, boolean
>> lastNameFirst) {
>> // do that
>> }
>> }

make that:
public class PersonHelper {
public String displayName(Person person, boolean
lastNameFirst) {
// do that
}
}

Sorry about that. That static doesn't work in deployment is really
weird, but I'm going to pretend you never said that and the problem
will no longer exist.

ms

Reply all
Reply to author
Forward
0 new messages