Simple way to use parameterised UI messages in Wicket?
Asked Answered
B

6

13

Wicket has a flexible internationalisation system that supports parameterising UI messages in many ways. There are examples e.g. in StringResourceModel javadocs, such as this:

WeatherStation ws = new WeatherStation();
add(new Label("weatherMessage", new StringResourceModel(
    "weather.${currentStatus}", this, new Model<String>(ws)));

But I want something really simple, and couldn't find a good example of that.

Consider this kind of UI message in a .properties file:

msg=Value is {0}

Specifically, I wouldn't want to create a model object (with getters for the values to be replaced; like WeatherStation in the above example) only for this purpose. That's just overkill if I already have the values in local variables, and there is otherwise no need for such object.

Here's a stupid "brute force" way to replace the {0} with the right value:

String value = ... // contains the dynamic value to use
add(new Label("message", getString("msg").replaceAll("\\{0\\}", value)));

Is there a clean, more Wicket-y way to do this (that isn't awfully much longer than the above)?

Bivalve answered 20/10, 2010 at 10:35 Comment(0)
C
5

I think the most consistent WICKETY way could be accomplished by improving Jonik's answer with MessageFormat:

.properties:

msg=Saving record {0} with value {1}

.java:

add(new Label("label", MessageFormat.format(getString("msg"),obj1,obj2)));
//or
info(MessageFormat.format(getString("msg"),obj1,obj2));

Why I like it:

  • Clean, simple solution
  • Uses plain Java and nothing else
  • You can replace as many values as you want
  • Work with labels, info(), validation, etc.
  • It's not completely wickety but it is consistent with wicket so you may reuse these properties with StringResourceModel.

Notes:

if you want to use Models you simply need to create a simple model that override toString function of the model like this:

abstract class MyModel extends AbstractReadOnlyModel{
    @Override
    public String toString()
    {
        if(getObject()==null)return "";
        return getObject().toString();
    }
}

and pass it as MessageFormat argument.

I don't know why Wicket does not support Model in feedback message. but if it was supported there was no reason to use these solutions and you could use StringResourceModel everywhere.

Chucklehead answered 25/4, 2015 at 7:8 Comment(1)
Yes, indeed, this is basically an improved version of String.format() which I suggested, with the key difference that MessageFormat truly supports many languages since the order of replaced parameters is explicitly specified.Bivalve
S
12

Take a look at Example 4 in the StringResourceModel javadoc - you can pass a null model and explicit parameters:

add(new Label("message",
         new StringResourceModel(
             "msg", this, null, value)));

msg=Value is {0}
Shuttlecock answered 21/4, 2012 at 14:16 Comment(1)
StringResourceModel doesn't have a constructor with more than 3 arguments in the current version 8.5.* . Easiest way now: MessageFormat.format(new StringResourceModel(MY_KEY).getString(), arg1, arg2, arg3);Marital
F
5

There's a way, which although still involves creating a model, doesn't requires a bean with a getter.

given this message in a properties file:

msg=${} persons

Here's how to replace the placeholder with a value, be it a local variable, a field or a literal:

add(new Label("label", new StringResourceModel("msg", new Model<Serializable>(5))));
Furtive answered 21/10, 2010 at 10:7 Comment(2)
Thanks! Btw, do you know of any name for this ${} notation, and is it documented somewhere? Would it be possible to replace more than one placeholder (something like ${0} and ${1}?) in a message using this approach?Bivalve
${} is simply shorthand for method calls against the model object itself. Don't know the name. ${} i believe just does model.toString(), but for things like convention-over-configuration of resource key names for page titles you can do something like "add( new Label( "title", new StringResourceModel( "${class.simpleName}.title", this, currentPageModel ) )"Dull
C
5

I think the most consistent WICKETY way could be accomplished by improving Jonik's answer with MessageFormat:

.properties:

msg=Saving record {0} with value {1}

.java:

add(new Label("label", MessageFormat.format(getString("msg"),obj1,obj2)));
//or
info(MessageFormat.format(getString("msg"),obj1,obj2));

Why I like it:

  • Clean, simple solution
  • Uses plain Java and nothing else
  • You can replace as many values as you want
  • Work with labels, info(), validation, etc.
  • It's not completely wickety but it is consistent with wicket so you may reuse these properties with StringResourceModel.

Notes:

if you want to use Models you simply need to create a simple model that override toString function of the model like this:

abstract class MyModel extends AbstractReadOnlyModel{
    @Override
    public String toString()
    {
        if(getObject()==null)return "";
        return getObject().toString();
    }
}

and pass it as MessageFormat argument.

I don't know why Wicket does not support Model in feedback message. but if it was supported there was no reason to use these solutions and you could use StringResourceModel everywhere.

Chucklehead answered 25/4, 2015 at 7:8 Comment(1)
Yes, indeed, this is basically an improved version of String.format() which I suggested, with the key difference that MessageFormat truly supports many languages since the order of replaced parameters is explicitly specified.Bivalve
B
2

When faced with something like described in the question, I would now use:

.properties:

msg=Saving record %s with value %d

Java:

add(new Label("label", String.format(getString("msg"), record, value)));

Why I like it:

  • Clean, simple solution
  • Uses plain Java and nothing else
  • You can replace as many values as you want (unlike with the ${} trick). Edit: well, if you actually need to support many languages where the replaced values might be in different order, String.format() is no good. Instead, using MessageFormat is a similar approach that properly supports this.

Disclaimer: this is "too obvious", but it's simpler than the other solutions (and definitely nicer than my original replaceAll() hack). I originally sought for a "Wicket-y" way, while this kinda bypasses Wicket—then again, who cares? :-)

Bivalve answered 20/4, 2012 at 16:5 Comment(2)
One more advantage: you can use the exact same approach for feedback messages (methods like info(), error() in Component and Session), lending consistency to your code. For example: info(String.format(getString("events.created"), events.size()));Bivalve
You CAN have different order with %s solution: msg=%2$s %1$sGog
N
2

In case you have a Model in your Component which holds an object with values you want to access from your placeholders as substitutions, you can write:

new StringResourceModel("salutation.text", getModel());

Let's imagine getModel()'s return type is IModel<User> and User contains fields like firstName and lastName. In this case you can easily access firstName and lastName fields inside your property string:

salutation.text=Hej ${firstName} ${lastName}, have a nice day!

Further information you can find here: https://ci.apache.org/projects/wicket/apidocs/8.x/org/apache/wicket/model/StringResourceModel.html#StringResourceModel-java.lang.String-org.apache.wicket.model.IModel-

Neon answered 16/10, 2019 at 12:56 Comment(2)
I think this is the pure wicket way of doing this task.Sweetsop
this is the correct answer here, the wicket way as @Sweetsop saidCasa
D
0

Creating a Model for your Label really is The Wicket Way. That said, you can make it easy on yourself with the occasional utility function. Here's one I use:

/**
 * Creates a resource-based label with fixed arguments that will never change. Arguments are wrapped inside of a
 * ConvertingModel to provide for automatic conversion and translation, if applicable.
 * 
 * @param The component id
 * @param resourceKey The StringResourceModel resource key to use
 * @param component The component from which the resourceKey should be resolved
 * @param args The values to use for StringResourceModel property substitutions ({0}, {1}, ...).
 * @return the new static label
 */
public static Label staticResourceLabel(String id, String resourceKey, Component component, Serializable... args) {
    @SuppressWarnings("unchecked")
    ConvertingModel<Serializable>[] models = new ConvertingModel[args.length];
    for ( int i = 0; i < args.length; i++ ) {
        models[i] = new ConvertingModel<Serializable>( new Model<Serializable>( args[i] ), component );
    }
    return new CustomLabel( id, new StringResourceModel( resourceKey, component, null, models ) );
}

Details I'm glossing over here are:

  1. I've created my own ConvertingModel which will automatically convert objects to their String representation based on the IConverters available to the given component
  2. I've created my own CustomLabel that applies custom label text post-processing (as detailed in this answer)

With a custom IConverter for, say, a Temperature object, you could have something like:

Properties key:
temperature=The current temperature is ${0}.

Page.java code:
// Simpler version of method where wicket:id and resourceKey are the same
add( staticResourceLabel( "temperature", new Temperature(5, CELSIUS) ) );

Page.html:
<span wicket:id='temperature'>The current temperature is 5 degrees Celsius.</span>

The downside to this approach is that you no longer have direct access to the Label class, you can't subclass it to override isVisible() or things like that. But for my purposes it works 99% of the time.

Dull answered 8/12, 2010 at 22:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.