[2.2.1-java] How to set selected option value for enum object property?

961 views
Skip to first unread message

Dmitri Zakharov

unread,
Nov 5, 2013, 4:12:56 PM11/5/13
to play-fr...@googlegroups.com
Hello play community,

I am having a problem pre-selecting an option of an HTML combo box for the enum object property. Here's a description of my code. I have a Company model class that contains a list of CompanyPhone(s):

@Entity
public class Company {
...
     @OneToMany(mappedBy = "company", cascade = CascadeType.ALL)
    public List<CompanyPhone> phones;
...
}

The CompanyPhone has a phoneType property of type PhoneType, which is enum:

@Column(name = "PHONE_TYPE")
public PhoneType type;

Below is PhoneType enum with static options() method to get a Map for displaying select box options in the HTML template:

public enum PhoneType {
    @EnumValue("MAI") MAIN,
    @EnumValue("MOB") MOBILE, 
    @EnumValue("FAX") FAX, 
    @EnumValue("CUS") CUSTOM;

    public static Map<String, String> options() {
        LinkedHashMap<String, String> options = new LinkedHashMap<String, String>();
        for (PhoneType v : PhoneType.values()) {
            try {
                EnumValue a = v.getClass().getField(v.name()).getAnnotation(EnumValue.class);
                options.put(a.value(), Messages.get(String.format("%s.%s", 
                        PhoneType.class.getSimpleName(), v.name())));
            } catch (NoSuchFieldException | SecurityException e) { /* ignore */ }
        }
        return options;
}
}

Based on Ebean documentation I've chosen to use @EnumValue annotation to specify the value that will be stored in the database. I also implemented formatter to convert between the PhoneType enum and String and registered it in onStart method of Global.java in the root package:

public class Global extends GlobalSettings {
       @Override
    public void onStart(Application app) {
        Formatters.register(PhoneType.class, new PhoneTypeFormatter());
        
    }
    ...
}

And here's the formatter class just for reference:

public class PhoneTypeFormatter extends SimpleFormatter<PhoneType> {
    @Override
    public PhoneType parse(String input, Locale locale) throws ParseException {
        PhoneType phoneType = null;
        for (PhoneType v : PhoneType.values()) {
            try {
                EnumValue a = v.getClass().getField(v.name()).getAnnotation(EnumValue.class);
                    if (input != null && a != null && input.equals(a.value())) {
                        phoneType = v; 
                        break;
                    }
            } catch (NoSuchFieldException | SecurityException e) { /* ignore */ }
        }
        return phoneType;
    }

    @Override
    public String print(PhoneType phoneType, Locale locale) {
        String v = null;
            try {
                v = (phoneType.getClass().getField(phoneType.name())
                        .getAnnotation(EnumValue.class)).value();
            } catch (NoSuchFieldException | SecurityException e) { /* ignore */  }
            return v;
    }
}

Now, to display the input fields for multiple phones I use Play2 @repeat directive in the HTML template file. Here's an extract:

...
@phoneGroup(field: Field, className: String = "companyPhone") = {
    <div class="control-group @className">
        <label class="control-label" for="@field("type").id">@Messages("company.phoneNumbers")</label>
        <div class="controls">
                  <select id="@field("type").id" name="@field("type").name" class="input-small">
            @for((value, text) <- models.PhoneType.options) {
                <option value="@value" 
                  @if(field("type").value != null && value == field("type").value) { selected }>@text</option>
            }
            </select>
            <input type="text" class="inputAreaCode" id="@field("areaCode").id" 
                    name="@field("areaCode").name" value="@field("areaCode").value"
                    placeholder="@Messages("phone.areaCode")">
            <input type="text" class="input-medium" id="@field("number").id" 
                    name="@field("number").name" value="@field("number").value"
                    placeholder="@Messages("phone.number")">
            <a class="removePhone btn btn-danger">@Messages("button.remove")</a>
        </div>
    </div>
}
...
@main {
...
    <div class="companyPhones well">
        @repeat(companyForm("phones"), min = 1) { phone =>
            @phoneGroup(phone)
        }
        @**
         * Keep the hidden block that will be used as template for Javascript copy code.
         **@
        @phoneGroup(
            companyForm("phones[x]"), 
            className = "companyPhone_template") 
        <div class="manage_repeat">
            <a class="addPhone btn btn-success">@Messages("phone.add")</a>
</div>
    </div>
...
}

So far so good. When I fill up the form and submit the data the phone numbers are properly stored in the database. But when I use the same form to edit existing data, the current phone type option is not selected in the combo box. It seems that the @if directive highlighted below in red doesn't do the job. However there are no errors displayed:

@phoneGroup(field: Field, className: String = "companyPhone") = {
...
                  <select id="@field("type").id" name="@field("type").name" class="input-small">
            @for((value, text) <- models.PhoneType.options) {
                <option value="@value" 
                  @if(field("type").value != null && value == field("type").value) { selected }>@text</option>
            }
            </select>
...
}

I tried to display the current value of @field("type").value in the span element (highlighted in blue) before the select and the output value on HTML page is correct, however the current value of "FAX" is not selected:

@phoneGroup(field: Field, className: String = "companyPhone") = {
...
         <span>Selected type: @field("type").value</span>
                  <select id="@field("type").id" name="@field("type").name" class="input-small">
            @for((value, text) <- models.PhoneType.options) {
                <option value="@value" 
                  @if(field("type").value != null && value == field("type").value) { selected }>@text</option>
            }
            </select>
...
}

The template above outputs the following HTML:

...
    <span>Selected type: FAX</span>
    <select id="phones_0__type" name="phones[0].type" class="input-small">
        <option value="MAI">Main</option>
        <option value="MOB">Mobile</option>
        <option value="FAX">Fax</option>
        <option value="CUS">Custom</option>
    </select>
...

I am stuck here. I am not familiar with Scala and the Scala documentation for templates is not very intuitive for me. I also tried to display the value of @field("type").value instead of "selected", just for fun, like that:

@phoneGroup(field: Field, className: String = "companyPhone") = {
...
         <span>Selected type: @field("type").value</span>
                  <select id="@field("type").id" name="@field("type").name" class="input-small">
            @for((value, text) <- models.PhoneType.options) {
                <option value="@value" 
                  @if(field("type").value != null) { @field("type").value }>@text</option>
            }
            </select>
...
}.

And it output correct value but in lower case:

...
    <span>Selected type: FAX</span>
    <select id="phones_0__type" name="phones[0].type" class="input-small">
        <option value="MAI" fax>Main</option>
        <option value="MOB" fax>Mobile</option>
        <option value="FAX" fax>Fax</option>
        <option value="CUS" fax>Custom</option>
    </select>
...

This looks strange that in one place it outputs in upper case and in the other in lower case. 

In any case I still can't figure out how to achieve the desired result, so that the current value (in this case "FAX") gets selected in the combo box. Can you, please, suggest how to do that or is there any other alternative approach?

Any help is appreciated. Thank you.

 






Ian Rae

unread,
Nov 5, 2013, 4:29:56 PM11/5/13
to play-fr...@googlegroups.com
value == field("type").value

Just guessing, but if these are strings, try value.equals(field("type").value)

Dmitri Zakharov

unread,
Nov 5, 2013, 4:38:05 PM11/5/13
to play-fr...@googlegroups.com
Hi Ian,

Tried it as you suggested:

<select id="@field("type").id" name="@field("type").name" class="input-small">
  @for((value, text) <- models.PhoneType.options) {
    <option value="@value" 
      @if(field("type").value != null && value.equals(field("type").value)) { selected }>@text</option>
  }
</select>

Didn't work the value of the option was not selected and not errors in the browser. I am wondering if it has something to do with formatter and when it is exactly triggered.

Thank you very much for reply.

Dmitri Zakharov

unread,
Nov 6, 2013, 5:24:57 PM11/6/13
to play-fr...@googlegroups.com
OK folks, after some browsing through the scala/views/helper files in Play source tree, I tried the if statement that I found in the inputRadioGroup.scala.html

 <input type="radio" id="@(id)_@v._1" name="@name" value="@v._1" @(if(value == Some(v._1)) "checked" else "") @toHtmlArgs(htmlArgs)>

So I changed the if statement in my template as follows:

<select id="@field("type").id" name="@field("type").name" class="input-small">
    @for((value, text) <- models.PhoneType.options) {
        <option value="@value" 
    @if(field("type").value != null && Some(value) == (field("type").value)) { selected  }>@text</option>
    }
</select>

So using Some(value) fixed the problem and the current option is now selected in the combo box. I probably need to learn a little bit of Scala to understand templates better.

Johan Andren

unread,
Nov 7, 2013, 3:55:48 AM11/7/13
to play-fr...@googlegroups.com
The idea with Option (which is what field().value returns) is to replace null, so your null check is not needed, also, you can use the exists-method on an option to get true if it matches a value, or false if it either does not match or has got no value.

So instead of your longish comparison you could do:
@if(field("type").exists(_ == value))

Dmitri Zakharov

unread,
Nov 7, 2013, 10:27:26 AM11/7/13
to play-fr...@googlegroups.com
Thank you for the tips Johan, I guess I have to learn a little bit of Scala to feel at ease with those tricks. The solution you suggested didn't work however. I tried the following template code:

<select id="@field("type").id" name="@field("type").name" class="input-small">
    @for((value, text) <- models.PhoneType.options) {
        <option value="@value" 
    @if(field("type").exists(_ == value)) { selected  }>@text</option>
    }
</select>

And it produced the "Compilation error" when I opened it in the browser: value exists is not a member of play.api.data.Field.

I browsed the scala html helpers source code for @select and @inputRadioGroup and that prompted me the following solution:

<select id="@field("type").id" name="@field("type").name" class="input-small">
    @options(models.PhoneType.options).map { v =>
        <option value="@v._1" 
            @if(Some(v._1) == (field("type").value)) { selected  }>@v._2</option>
    }
</select>

This one worked for me. Also the for loop does the same trick:

<select id="@field("type").id" name="@field("type").name" class="input-small">
    @for((value, text) <- models.PhoneType.options) {
        <option value="@value" 
            @if(Some(value) == (field("type").value)) { selected  }>@text</option>
    }
</select>
Reply all
Reply to author
Forward
0 new messages