Submitting from Bootstrap modals
Submitting a form from a Bootstrap modal window.
Bootstrap modals are components displaying HTML in a modal dialog style. Modals can help simplifying complex forms by removing parts out of the main form and by building a dialog oriented user experience.
In this example we will build a recipe book. Adding recipes is done by a Bootstrap modal. The code demonstrates how to build a modal window that interacts with the server to change its content and to validate its content.
At first we have a recipe book. This is how an empty recipe book looks like:
<h2>Recipes</h2>
<div id="recipe-list">
</div>
<a class="btn btn-primary onclick-disable"
href="/RecipeBook/Add"
target="#recipemodal"
onkeydown-click="+">
<i class="fas fa-plus spinner"></i> Add...
</a>
<div id="recipemodal" class="modal fade target" data-bs-backdrop="static" tabindex="-1">
</div>
Lines 3 to 4 contain the recipes list. It is currently empty.
Lines 6 to 11 make a button to add recipes. The button is a hyperlink to the URL providing the modal dialog content to add a recipe. The target
of the hyperlink is #recipemodal, the modal defined in lines 13 to 14. The onkeydown-click attribute indicates it can also be triggered by pressing the + key on the keyboard. The onclick-disable class tells the link to be disabled while it is loading its resource. This, to prevent side effects of users clicking the button multiple times when loading.
The recipe modal (lines 13 to 14) is a regular but empty modal with a target
class. More on this further.
When the Add link is pressed, the following HTML code is loaded, which is injected into the recipe modal:
<div class="modal-dialog">
<div class="modal-content">
<form class="onsubmit-disable"
action="/Recipe/Save" method="post"
onkeyenter-click="#save-btn">
<div class="modal-header">
<h5 class="modal-title">Add Recipe</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Name</label>
<input type="text" class="form-control" name="Name" autofocus />
</div>
<div onkeyenter-click="#addline-btn">
<table class="table table-borderless">
<tr>
<th>Ingredient</th>
<th>Dosage</th>
</tr>
<tr>
<td>
<select class="form-control" name="NewIngredient.Name">
<option>Baking Powder</option>
<option>Butter</option>
<option>Cocoa Powder</option>
<option>...</option>
</select>
</td>
<td>
<input type="text" class="form-control" name="NewIngredient.Dosage" />
</td>
</tr>
<tr>
<td colspan="2" align="right">
<button id="addline-btn" class="btn btn-secondary btn-sm"
type="submit"
formaction="/Recipe/AddLine">
Add line
</button>
</td>
</tr>
</table>
</div>
</div>
<div class="modal-footer">
<button id="save-btn" type="submit" class="btn btn-primary">
<i class="fas fa-save spinner"></i> Save
</button>
</div>
</form>
</div>
</div>
Because the target is a modal (or is contained inside a modal), the modal will be made visible automatically.
The modal consists of a form (lines 3 to 51) containing an input field for the name of the recipe (line 13) and a table (lines 16 to 43) for the ingredients. The second last row of the table (lines 21 to 33) contain a select box for the ingredient and a freeform textfield for the dosage. The last row of the table (lines 34 to 42) contain a button to add a line to the ingredients table. Finally, lines 47 to 49 define a submit button to save the recipe.
When the focus lays inside the table, pressing the ENTER key will trigger the Add line button as defined by the onkeyenter-click
attribute on line 15. However, when the focus is outside the table, for instance in the recipe name field, the ENTER key triggers the Save button as defined by the onkeyenter-click
attribute on line 5.
When the Add line button is pressed, the form is posted to the /Recipe/AddLine URL. Since the modal has the target
class and the form is contained in the modal, an inline target is found and the form is posted using Ajax. The returned HTML (the server will either return the form with validation errors or have added a line) will then replace the current form.
When a line is added, a row is added to the tabel similar to the following one, containg the ingredient and dosage entered, and the line to add a new ingredient will now contain the list of remaining ingredients to select from (you can never add the same ingredient twice):
<tr>
<td>
Olive Oil
<input type="hidden" name="Ingredients[0].Name" value="Olive Oil">
</td>
<td>
1 teaspoon
<input type="hidden" name="Ingredients[0].Dosage" value="1 teaspoon">
<span class="float-end">
<button type="submit" class="btn btn-light btn-sm"
formaction="/Recipe/RemoveLine?index=0">
×
</button>
</span>
</td>
</tr>
To remove lines, each line contains a submit button to the /Recipe/RemoveLine URL. Again, since the form is inside a target, an Ajax call will take care of submitting the form and returning new content for the modal.
When the recipe is saved, the form is submitted to its default URL /Recipe/Save. Here again, an Ajax call is issued and the server is expected to return new content for the modal. This is also what happens when the form contains validation errors: the form HTML including validation messages is returnd and loaded inside the modal.
If the form is valid, the modal has to be closed and the new recipe to appear in the list. Since the server evaluates the validity of the form, it is the server that decides whether the modal should be closed or not. To close the modal and add the recipe to the list, the servers response includes these 2 response headers:
X-Sircl-Target: #recipe-list
X-Sircl-Target-Method: append
as well as the following HTML body:
<div class="card-body">
<button type="button" class="btn btn-light float-end"
onclick-remove="<.card"
onclick-confirm="Are you sure ?">
×
</button>
<h5 class="card-title">Banana cake</h5>
<h6 class="card-subtitle">5 ingredient(s)</h6>
</div>
The server overwites the target to be the recipe list, and instructs the client to append the recipe to the existing list.
The HTML body represents the recipe card of the added recipe.
Notice how the × button does not invoke the server, but simply removes the card from the page by removing its HTML code.
Working demo
Requirements
This example requires following Sircl library files to be loaded:
or:
Or their non-minified counterparts.
See the Get Started section about how to set up your project to use the Sircl library.