play framework: how to repopulate form on validati

2020-07-22 19:44发布

问题:

I'm building some crude CMS-like functionality (to get acquinted with Play Framework). For this test-case I've build 2 pages, 1 for listing tags and 1 for creating/editing/saving tags.

The flow is like this (routes-file):

#list tags
GET /tags  Application.listTags 

#view/edit existing tag
GET /tag/{<(?!new$)(.+)>name} Application.showTag  

#new tag
GET /tag/new  Application.showTag   

the create/view/edit page displays a form which gets it's values from a tagDTO. The normal flow works without problems, but when the form gives validation-errors (e.g: the tag-name must exist) I want to display the page again, repopulating the form with the edited values.

For this (following the Play Framework conventions) I could use the 'flash'-object which contains these last values, but the form is already bound to the tagDTO (which is null on redirect) instead of the 'flash'-object.

First the code: Application.java

.....

public static void showTag(String name) {
    TagDTO tagDTO = TagDTO.buildDTOFromModelOrNew(name);
    render(tagDTO); 
}


/**
 * Save tag and redirect to Show
 * 
 * @param name
 * @param displayname
 * @param isnew
 */
public static void saveTag(
        @Required(message="Name is required") String name,
        String displayname,
        boolean isnew) 
{
    checkAuthenticity();
    if(validation.hasErrors()) {
        params.flash(); 
        validation.keep(); 
        showTag(null);
    }

    //fetch tagDTO based on backend or create new if not exist
    TagDTO tag = TagDTO.buildDTOFromModelOrNew(name);

    // Append / Overwrite values
    tag.displayname = displayname;
    tag.name = name;

    //save result to model
    TagDTO.buildAndSaveModelFromDTO(tag);

    flash.success("Thanks for " + (isnew?"creating":"updating") + " tag " + tag.name);

    //redirect to show
    showTag(tag.name);
} 

And ShowTag.html

    #{extends 'main.html' /}
    #{if flash.success}
        <p class="success">${flash.success}</p>
    #{/if}

    #{ifErrors}
        <p class="errors">Oops...</p>
    #{/ifErrors}

    #{form @Application.saveTag()}
        #{authenticityToken /}
        <p>
            <label for="name">Name: </label>
            <input type="text" name="name" id="name" value="${tagDTO.name}" />
            <span class="error">#{error 'name' /}</span>
        </p>
        <p>
            <label for="displayname">Displayname: </label>
            <input type="text" name="displayname" id="displayname" value="${tagDTO.displayname}" />
            <span class="error">#{error 'displayname' /}</span> 
        </p>
        <p>
            <input type="hidden" name="isnew" value="${tagDTO.isnew}" />
            <input type="submit" value="Submit your comment" />
        </p>
    #{/form}

Now I could think of some ways to make it work, but none really elegant:

  1. bind the form to the flash-object (or params-object) and populate the flas/params- object from the tagDTO

  2. on validation-failure, refetch the tagDTO (not avail anymore so DB-call necessary) and overwrite values in tagDTO with values available in flash-object, bind form to tagDTO.

  3. like 2, but using some sort of cache to quickly fetch tagDTO (so no need for db-call)

  4. Some general mechanism to (de)serialize tagDTO from/to the session.

In short, I don't like any of them really. What would you consider to be a best practice in this situation? Or is there any functionality in the Play Framework that I'm missing?

回答1:

This is where the explicit render calls comes handy. Retain the form values from previous submission and give it back (if validation fails) as follows,

checkAuthenticity();
if(validation.hasErrors()) {
    render("@showTag", name, displayname, isnew);
}

This will avoid the extra redirect (307 in case of Play!) that would have happened if you had called 'action from another action'.



回答2:

Render the form again and avoid the redirect is a solution. I think it's OK if a user press F5 he will get the error again. But I think you should create a reload/cancel button, so the user can dismiss all the information.

To have always the correct URL you can do the following in the routes.conf:

GET  /tag/create     TagController.create
POST /tag/create     TagController.insert

The flash solution has the disadvantage that your cookie can get really big.