-->

Play Framework - Register a custom DataBinder for

2019-03-06 14:03发布

问题:

Using Play 2.3.7 (Java) I have the following scenario.

I have a class CSVData which contains a list of type CSVField. Here are the attributes for these classes:

public class CSVData{

private String name;
private String description;
private String dataFilePath;
private List<CSVField> fields;
private Double latitude;
private Double longitude;


// rest of class... }

and

public class CSVField {
    private String name;
    private String type;

...}

The difficulty when making a form to input CSVData is that I have this nested List<CSVField> attribute and CSVField is a custom type containing two strings. I need the form to be dynamic in that it should be able to accept an arbitrary amount of CSVFields (at least 1). According to the Java Form Documentation, it seems like I should register a custom DataBinder for CSVField, however I can't find any examples that do this with multiple input strings. This example is similar, but it only binds one field.

Here is a video of what type of user input I would like to have. I made my view using this example code for adding dynamic fields. The combination of the text field (name) and select dropdown item (type) is what I need to bind to a CSVField and then add to the List<CSVField> fields in the CSVData object. How can I do this using the Play Framework?


EDIT: In my controller I have tried this

Form<CSVData> formData = Form.form(CSVData.class).bindFromRequest();

And in the view I try this

@helper.repeat(csvForm("fields"), min = 1) { csvField =>

    @multiDataField(csvField,
        label = "Column Name and Type",
        gsnTypes,
        help = "Enter the column names and respective types for the data items in the file")

}

Where multiDataField is this template. But it doesn't bind the dynamic fields properly and throws an invalid validation error on fields. I think my problem is I don't know what name attributes to use in my multiDataField template. Any advice?

回答1:

You don't need any customer databinder. Lists with complex objects are supported without any additional binding registration.

In the view you can use the @repeat Helper and in the controller you are already doing it well.

Here you have a complete example about Play and Forms, or directly in TypeSafe

EDIT

Inside the repeat block, csvField is an instance of every Form object in your List. Then you will need to add all the HTML elements you need for your view. For example (simplified without Bootstrap):

@helper.repeat(csvForm("fields"), min = 1) { csvField =>
    Name: <input type="text" name='@csvField("name").name' value='@csvField("name").value'>
    Type: <input type="text" name='@csvField("type").name' value='@csvField("type").value'>
}

You can find a more complete example with the samples provide in Play 2.2.x. To compile it in 2.3.x maybe something need to be changed a bit and is not using Bootstrap 3.x, but the logic is the same.

EDIT (2)

If you want to add dynamically elements to the view, you will need to take care that when a new element is added, the right array number is set. For that you will need to use JQuery:

$('.addCSVField').click(function() {
    var CSVFields = $(this).parents('.CSVField');
    var template = $('.CSVField_template', CSVFields);
    template.before('<div class="clearfix CSVField">' + template.html() + '</div>');
    renumber();
})

$('.removeCSVField').click(function() {
    $(this).parents('.CSVField').remove();
    renumber(); 
})  

var renumber = function() {
    $('.CSVField').each(function(i) {
        $('input', this).each(function() {
            $(this).attr('name', $(this).attr('name').replace(/fields\[.+?\]/g, 'fields[' + i + ']'));
        })
    })
}

And then you will need to change your HTML/Scala code to something like that:

@fieldGroup(field: Field, className: String = "CSVField") = {
    <div class="well @className">
        <a class="removeCSVField btn danger pull-right">Remove this field</a>
        Name: <input type="text" name='@field("name").name' value='@field("name").value'>
        Type: <input type="text" name='@field("type").name' value='@field("type").value'>
    </div>
}   

@repeat(csvForm("fields")) { csvField =>
    @fieldGroup(csvField)
}

@**
 * Keep an hidden block that will be used as template for Javascript copy code
 **@
@fieldGroup(csvForm("fields[x]"),className = "CSVField_template")   
<a class="addCSVField btn success">Add another field</a>

And to add a CSS style .CSVField_template{display: none;}

I did not test any of that, so it may not compile. However, I just followed a similar apporach as in the Forms example (play 2.2.x