3

I have a form in a template which makes a request to the specified action (a controller).

The controller decides that there is an issue with the inputs and needs to provide error output for the user. To do this I am using the flash error feature of Craft.

However, I'm unsure how to re-populate the form with the old values when it is next shown.

At the moment I am redirecting to the form page again using $this->redirect but the user inputs get erased each time.

1 Answer 1

4

There are several things you may need to dig into more to get the full picture here but here's a general idea of how you can approach this in Craft 3.

Flash errors are useful to display a message in the Control Panel, but you'll need to prepare a model with errors and signal to the controller that the form submission has failed to return errors to your template. Also, it's not necessary to use the redirect method in this situation as the Controller request can return your errors to the template simply by preparing them using setRouteParams and returning null.

A common pattern that Craft and several plugins use to do this is as follows. The method below would be the method in your controller that receives the form submission:

public function actionMyControllerAction()
{
    $this->requirePostRequest();

    // Build a model of the data you are submitting
    $model = new MyModel();
    $model->someOptionalAttribute = Craft::$app->getRequest()->getBodyParam('someOptionalAttribute');

    // The `getRequiredBodyParam` method can help validate required fields
    $model->someRequiredAttribute = Craft::$app->getRequest()->getRequiredBodyParam('someRequiredAttribute');

    // After you prepare the model, hand things off to a service 
    // to validate and/or save things to the database
    // The "saveThisModel" method can run any additional 
    // validation you need to run, add errors to the model,
    // and return `false` if validation fails 
    if (!Plugin::getInstance()->myService->saveThisModel($model)) {

        Craft::$app->getSession()->setError(Craft::t('my-plugin', 'Unable to save item.'));

        // This is where you name the thing you are returning to your template with errors.
        // In this example, the 'myModel' variable would be available to your submitted template
        Craft::$app->getUrlManager()->setRouteParams([
            'myModel' => $model
        ]);

        return null;
    }

    Craft::$app->getSession()->setNotice(Craft::t('my-plugin', 'Item saved.'));

    // Redirects to wherever the submitted 'redirect' input points to
    return $this->redirectToPostedUrl();
}

If validation fails, in your template, the myModel variable will be available and you can access all errors as an array, or an array of errors for an individual field. You'll need to loop through this in your template. For simplicity here, I'm just dumping them:

All Errors:

{{ dump(myModel.getErrors()) }}

Errors for a specific field:

{{ dump(myModel.getError('someOptionalAttribute')) }}

You may also have to add some conditional logic around checking if the myModel variable exists or has errors. You can use Craft's form macros in your template to help with this. Search the Craft codebase templates folder for forms. and you'll find several examples. Here's a simplified Text Field macro that would display an error for the Categories title field.

{% import "_includes/forms" as forms %}

{{ forms.textField({
    label: "Title"|t('app'),
    siteId: category.siteId,
    id: 'title',
    name: 'title',
    value: category.title,
    errors: category.getErrors('title'),
    required: true,
    maxlength: 255
}) }}

In this case, the Craft Categories controller the actionSaveCategory is returning the category variable to the template using the same pattern that is described above:

if (!Craft::$app->getElements()->saveElement($category)) {

    // ...

    Craft::$app->getSession()->setError(Craft::t('app', 'Couldn’t save category.'));

    // Send the category back to the template
    Craft::$app->getUrlManager()->setRouteParams([
        'category' => $category
    ]);

    return null;
}
3
  • Awesome answer thanks! quick question, how do you access the forms macro? I tried using this but it says that it's unknown.
    – Lea Hayes
    May 26, 2019 at 21:43
  • 1
    You'll need to import Craft's form macro file at the top of your template {% import "_includes/forms" as forms %}. You can find lots of examples of this if you take a look through the vendor/craftcms/cms/templates folder. I've updated the example with the macro to include this line in the answer above. May 28, 2019 at 11:35
  • You also need to grab the model on your edit route, using Craft::$app->urlManager->getRouteParams() and getting the category key, which will contain the model from the save action
    – Matanya
    Dec 9, 2021 at 17:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.