|
1 | | -# Handling Multiple Submit Buttons |
| 1 | +# Multiple Submit Buttons |
2 | 2 |
|
3 | | -TODO |
| 3 | +Alright, we've got our "Create" button working like a charm, but what if we |
| 4 | +want a couple of different workflows: "Create and Close" to save the part |
| 5 | +and jump back to the list, and "Create and Add Another" to save the part |
| 6 | +but stay on the form page for quick data entry. |
4 | 7 |
|
| 8 | +No worries, Symfony is fully capable of handling this. Let's dive into |
| 9 | +the second method of adding buttons to a form using the Form type, |
| 10 | +and use it to drive our business logic. |
| 11 | + |
| 12 | +## Adding a Second Submit Button Inside the Form Type |
| 13 | + |
| 14 | +First things first, let's tweak the name of our current button in the |
| 15 | +`new.html.twig` template to "Create and Close". Now, pop open your Form |
| 16 | +type class in `src/Form/StarshipPartType.php`. It's time to drop in a |
| 17 | +second button below the fields. Let's add another one using |
| 18 | +`->add('createAndAddNew', SubmitType::class)`. This handy |
| 19 | +`SubmitType::class` tells Symfony to render it as a `<button type="submit">`. |
| 20 | + |
| 21 | +If you hop over to the browser and refresh, you'll see our two buttons. The |
| 22 | +new one doesn't quite look like a button - that's because we've reset |
| 23 | +styles. But technically, in the code, it's `<button type="submit">`. |
| 24 | +We'll spruce up the styles later. |
| 25 | + |
| 26 | +## Accessing Unmapped Fields in Symfony |
| 27 | + |
| 28 | +For now, in the controller, we're already aware that `form->getData()` |
| 29 | +hands us a mapped entity, which in our case is `StarshipPart`. This time, |
| 30 | +though, we need to get our hands on an unmapped field - the submit button |
| 31 | +we just added. This field doesn't have a matching property on the entity, |
| 32 | +which is why we call it "unmapped". |
| 33 | + |
| 34 | +No sweat, we can access any field. Directly below the `addFlash()`, let's |
| 35 | +cook up a `$createAndAddNewBtn` variable that equals |
| 36 | +`$form->get('createAndAddNew')`. This should match the button name on your |
| 37 | +Form type. Let's give it a quick test run first. Down below, I'm going to |
| 38 | +write `dd($createAndAddNewBtn)`. |
| 39 | + |
| 40 | +Back to the browser, filling the form notes is optional, so just the name |
| 41 | +and price will do. I'm going to hit our "Create and Add New" button now, |
| 42 | +and there's our dump. It's a Submit Button with some intriguing fields. |
| 43 | +Take a closer look and you'll spot the magical `clicked = true`. Believe it |
| 44 | +or not, this little gem is how we can tell which button was actually |
| 45 | +clicked, and we can harness this to power different business logic. |
| 46 | + |
| 47 | +## Harnessing Button Clicks to Execute Business Logic |
| 48 | + |
| 49 | +Back to PHPStorm, I'm going to get rid of the `dd()` and give PhpStorm a |
| 50 | +little help. To solve the autocomplete, above the button, I'm going to add |
| 51 | +`/** @var SubmitButton $createAndAddNewBtn */`. Now, inside your form |
| 52 | +submit logic, try typing in `$createAndAddNewBtn->isClicked()`. That's |
| 53 | +exactly what we need. Wrap it in an `if` statement, and if the button was |
| 54 | +clicked, let's return `$this->redirectToRoute('app_admin_starship_part_new')`. |
| 55 | + |
| 56 | +Now, give the form another whirl. Here's our flash message: |
| 57 | + |
| 58 | +> The part was successfully created |
| 59 | +
|
| 60 | +We see it twice because I just resubmitted the form. In the new scenario, |
| 61 | +it should be only one message. So we were redirected back to the form page. |
| 62 | +You can test out the "Create and Close" button. That should still bring you |
| 63 | +back to the part list page. |
| 64 | + |
| 65 | +So, this was a completely valid and super useful way of adding form buttons |
| 66 | +inside the Form Type. But if you can avoid cramming buttons in the Form type |
| 67 | +and stick to plain HTML buttons, your designer teammates will silently |
| 68 | +thank you. |
| 69 | + |
| 70 | +## The Intricacies of Symfony's Form Type: Field Type Guessing |
| 71 | + |
| 72 | +Let's zip back to the Form type for a second. You've probably noticed this |
| 73 | +`$builder->add('price', null)`. The second argument to the `add` method |
| 74 | +should be the type of the field and for some fields like `price`, `name`, |
| 75 | +`notes`, it is `null`. But why `null` instead of a real type like |
| 76 | +`IntegerType` in this case, for example? |
| 77 | + |
| 78 | +Well, Symfony has this nifty feature called "field type guessing". When you |
| 79 | +set the type to `null`, Symfony will inspect entity properties metadata. |
| 80 | +For example, if `price` is an integer property, Symfony will pick `IntegerType`. |
| 81 | +If `name` is a string then Symfony will opt for a `TextType`. If we had, |
| 82 | +let's say, an `isActive` boolean, then Symfony would select a `CheckboxType`, |
| 83 | +and so on. |
| 84 | + |
| 85 | +You can confirm this by peeking at the rendered HTML. Inspect the code for |
| 86 | +the `price` field, and you'll see that it's not just `<input type="text">`, |
| 87 | +it's `<input type="number">`, which gives us some handy arrows to increase |
| 88 | +and decrease the value. This is because the `price` property on the |
| 89 | +`StarshipPart` entity is an integer and the Symfony Form Component catches |
| 90 | +sight of it. That's why it uses the more optimized field type. |
| 91 | + |
| 92 | +Most of the time, Symfony guesses exactly what you want. But when Symfony |
| 93 | +can't guess correctly, or when you want something different, you can always |
| 94 | +override the default guessed type by passing the type explicitly. |
| 95 | + |
| 96 | +For example, like this one `IntegerType::class`. If you refresh, nothing will |
| 97 | +change, because that's exactly what was set behind the scenes. |
| 98 | + |
| 99 | +## Exploring Symfony's Built-in Form Field Types |
| 100 | + |
| 101 | +But what built-in form field types does Symfony have and where can you find |
| 102 | +them? Great question, and I'm glad you asked! Symfony docs will always |
| 103 | +be there to help with that. But Symfony also comes with a super nerd-friendly |
| 104 | +tool for discovering everything about built-in field types. |
| 105 | + |
| 106 | +In your terminal, run: |
| 107 | + |
| 108 | +```terminal |
| 109 | +symfony console debug:form |
| 110 | +``` |
| 111 | + |
| 112 | +This dumps all available form types, including your own Form Type classes, |
| 113 | +which we can see here as a service. |
| 114 | + |
| 115 | +If you want to inspect a specific type, just specify it as an argument to |
| 116 | +the command. For example, run: |
| 117 | + |
| 118 | +```terminal |
| 119 | +symfony console debug:form TextType |
| 120 | +``` |
| 121 | + |
| 122 | +And you'll see all options that `TextType` supported, which options are |
| 123 | +required and which options come from parent types it extends. Try another one, |
| 124 | +for example, run: |
| 125 | + |
| 126 | +```terminal |
| 127 | +symfony console debug:form EntityType |
| 128 | +``` |
| 129 | + |
| 130 | +You'll see that the `class` option is required and MakerBundle already |
| 131 | +filled that for us. So smart! Options are set as the third argument |
| 132 | +in the array. |
| 133 | + |
| 134 | +And by the way, Symfony doesn't only guess *field types*, it also guesses |
| 135 | +*field type options*. For example, if a Doctrine property is `nullable = |
| 136 | +true` like for this `notes` field, then Symfony will automatically make it |
| 137 | +optional. And the reverse for the other fields, Symfony will automatically |
| 138 | +set `required` HTML attribute if field is not nullable. |
| 139 | + |
| 140 | +You can see it in the HTML inspector. For example, we don't see this |
| 141 | +`notes` field is required, but if you inspect the `name`, you will see it |
| 142 | +has a `required="required"`. And that's why when you hit "Submit" button |
| 143 | +with an empty form, you will see these HTML5 validation errors on empty form |
| 144 | +submit. |
| 145 | + |
| 146 | +And of course, you can easily override that if needed, too. Options can be |
| 147 | +set within an array as the third argument. And if you pass an array and set |
| 148 | +`required` to `false`, you will override the default behavior and the |
| 149 | +`price` field is not required anymore. |
| 150 | + |
| 151 | +I'm going to revert back this code because we do want the `price` field as |
| 152 | +a required field, and we do want it set in the database. |
| 153 | + |
| 154 | +Now, we'll dive much deeper into form field types later in this course. |
| 155 | +For now, let's switch gears to something fun and stylish: making our form |
| 156 | +look actually nice by applying a built-in Symfony form theme to it. |
| 157 | + |
| 158 | +Catch you in the next chapter! |
0 commit comments