Multi-language Bootstrap 5 Frontend Form with File Upload, Checkboxes, Ajax Validation, Mails and editable Backend
If you live in Switzerland its daily business that you need frontend forms with 2 to 4 localizations. The received data has to be sent to the your client and their customer, a database and some export functions are nice too. Sounds easy but is quite a journey. And the documentation is not complete enough for dummies and far too complicated. In forums you find a lot of different advices, impossible for dummies like me too. I'm glad I have a good friend coaching me through this.
I hope I can help with some challenges I had and have cost me too much lifetime. Lets say we make kind of contact form:
Affected files overviews
- plugins
- - YourName
- - - your Plugin
- - - - components
- - - - models
- - - - etc
- themes
- - your Theme
- - - assets
- - - - bootstraps css combined with your own css
- - - pages
- - - - layout
- - - - - your layout
- - - - - your form
- - - - partials
- - - - - your scripts
Used WinterCMS Setting Pages
- Translations (input labels, validation messages, flash message)
- Mail Templates (mail template and contents)
- Mail Settings (SMTP connection)
- Administrators (setting the rights to edit data)
Dependencies in this example
- Translate Plugin
- Pages Plugin
- Bootstrap 5
- (Builder Plugin: recommend for easy plugin construction)
- jquery if not used from the WinterCMS itself)
The page, where the form is located
title = "Contact"
url = "/contact"
layout = "default"
is_hidden = 0
// NOTE: Its easier not make localized urls. Urls like /de/contact, /en/contact, /fr/contact is not such an issue and its practical as the marketing can use the shortcut /contact and the user lands automatically on the form in his browser language. Nice!
==
<?php
use \YourName\YourPlugin\Models\Contact;
use RainLab\Translate\Models\Message;
use Mail
//////// Ajax request through data-request="onHandleForm"
// Function has to start with "on", the rest is free but camelcase is needed
function onHandleForm()
{
//////// Fill one entry of your model Contact with all form fields and save it into your Model.
// Tip: use same code for database field, input name, input id and translations keys to keep overview and minimize typos
$item = new Item(Input::all());
$item->save();
// define hidden fields here before save() and not in the form f.e.:
// $item->created_at = Carbon\Carbon::now();
// $item->editstate_id = 1; etc...
// if something is wrong here you get an exception
// define validation fields also before save() here like this:
// translate them late in the corresponding system settings page of WinterCMS
$item->customMessages = [
'firstname.required' => Message::trans('contactform.errors.firstname_required'),
'lastname.required' => Message::trans('contactform.errors.lastname_required'),
'email.required' => Message::trans('contactform.errors.email_required'),
'email.email' => Message::trans('contactform.email_email'),
etc...
];
//////// Send Mails to client and perhaps his customer
// First get the fields you want to use in your mail template, f.e.
// the key of the mail template ('yourname.forms:...etc.') is totally free.
// use your logical labeling system:
$vars = [
'created_at' => Carbon\Carbon::now(),
'firstname' => Input::get('firstname'),
'lastname' => Input::get('lastname'),
'email' => Input::get('email')
];
Mail::send('yourname.forms::yourplugin.data', $vars, function($message) {
$message->to('yourclient@hiscompany.com', 'CompanyName');
$message->from('noreply@hiscompany.com', 'Website System');
});
Mail::send('yourname.forms::yourplugin', $vars, function($message) {
$message->to(Input::get('email'), '');
$message->from('noreply@hiscompany.com', 'CompanyName');
});
$this['sent'] = true;
////// Flash Message. The user should now that submitting worked.
// direct or localized, translation key according to your own logic:
Flash::success('Hey User, your did it!');
or:
Flash::success(Message::trans('contactform.flashmessage'));
// want to send the user to another page after submitting? Then:
return Redirect::to('/whateverpageyoulike');
}
?>
==
////// Your client perhaps wants to edit the intro text in all languages....
{% content 'contact-intro' %}
/////// The Form: put o the Ajax Functions you want with data-xx
<form class="" novalidate data-request="onHandleForm" data-request-files data-request-validate data-request-flash>
// you can show everywhere the overview of validation error messages like this:
// at the same time the field with error gets red and has the message underneath it to here.
<div class="alert alert-danger" data-validate-error>
<p data-message></p>
</div>
// just some examples
<div class="col-12 col-md-6 mb-3">
<label for="firstname">{{ 'contactform.label.firstname'|_ }} *</label>
<input type="text" id="firstname" name="firstname" class="form-control">
<div class="form-text text-danger" data-validate-for="firstname"></div>
</div>
<div class="col-12 col-md-6 mb-3">
<label for="lastname">{{ 'contactform.label.lastname'|_ }} *</label>
<input type="text" id="lastname" name="lastname" class="form-control">
<div class="form-text text-danger" data-validate-for="lastname"></div>
</div>
<div class="col-12 col-md-6 mb-3">
<label for="email">{{ 'contactform.label.email'|_ }} *</label>
<input type="text" id="email" name="email" class="form-control">
<div class="form-text text-danger" data-validate-for="email"></div>
</div>
<p>
{{ 'contactform.label.fileuploads'|_ }}
</p>
<div class="col-12 py-4">
<input id="files" type="file" name="files[]" multiple accept="application/pdf">
<div class="form-text text-danger" data-validate-for="files"></div>
</div>
<div class="col-12 text-center pt-3">
<button id="submit" name="submit" type="submit" class="btn btn-lg btn-primary"
data-attach-loading>
{{ 'contactform.label.submit'|_ }}
</button>
</div>
</form>
////// here we have to add the script for the validation magic
// attention, every Bootstrap version can have different class names here... this 5.
{% put scripts %}
<script>
$(window).on('ajaxInvalidField', function(event, fieldElement, fieldName, errorMsg, isFirst) {
$(fieldElement).closest('.form-control').addClass('is-invalid');
});
$(document).on('ajaxPromise', '[data-request]', function() {
$(this).closest('form').find('.form-control.is-invalid').removeClass('is-invalid');
});
</script>
{% endput %}
I hope you still can follow till here. As a dummy I still understand whats happening in this Laravel Code, wuu...
Layout Files and Scripts
I order to have the flash message working you need to add this code right before your body end tag. jQuery can be loaded from the WinterCMS directly too. Attention: The order is crucial here!!!
{% flash %}
<p
data-control="flash-message"
class="flash-message fade {{ type }}"
data-interval="10">
{{ message }}
</p>
{% endflash %}
<script src="{{ 'assets/.../jquery.js'|theme }}"></script>
or
<script src="{{ 'assets/vendor/jquery.js'|theme }}"></script>
{% framework extras %}
Layout Files and Scripts
If your not familiar how to create a plugin, learn that first. Here whats crucial for Model.php f.e. Contact.php in our example
<?php namespace YourName\YourPlugin\Models;
use Model;
use System\Models\File;
use DB;
use YourName\YourPlugin\Models\SomeSecondModelforCheckboxesOrDropwdowns;
class Contact extends Model
{
use \Winter\Storm\Database\Traits\Validation;
public $table = 'yourname_yourplugin_contact';
//////// Validation
// first the hidden fields with guarded
// then the validation rules
//max:number helps against bots that want to flood our databse
public $guarded = ['id', 'updated_at', 'created_at', 'editstate_id']; // thats the hidden field
public $rules = [
'firstname' => 'required|max:80',
'lastname' => 'required|max:20',
'email' => 'email|required|max:30',
];
//thats needed to, don't know why
public $customMessages = [];
//Relations
public $belongsToMany = [
'yoursecondmodels' => [
YoursecondModel::class, 'table' => 'yourname_yourplugin_contact_somethingelse'
]
];
public $attachMany = [
'files' => File::class,
];
}
Dropdown with selection from a second model
Can easily be done with building a component. The default.htm would be like this. Also see the Twig documentation for sorting or use the reorder functionality of the Plugin.
{% set yoursecondmodels = __SELF__. yoursecondmodels %}
<div class="mb-3">
<label for="something" class="form-label"><strong>{{ 'contactform.label.something'|_ }}</strong></label>
<select class="form-select" aria-label="select something" name="something" >
<option selected value="0">{{ 'contactform.select.pleasechoose' |_ }}</option>
{% for yoursecondmodel in yoursecondmodels|sort((a, b) => a.name <=> b.name) %}
{% if assurance.type == 'kk' %}
<option value="{{ yoursecondmodel.id }}">{{ yoursecondmodel.name }}</option>
{% endif %}
{% endfor %}
</select>
</div>
Checkboxes with selection from a second model
No one is understanding multiselects - - so its getting complicating with checkboxes. Use again a component for that. In the default.htm:
{% set yourthirdmodels = __SELF__.yourthirdmodels %}
<div class="mb-3">
<strong>{{ 'contactform.label.yourthirdmodels' |_ }}</strong><br>
{% for yourthirdmodel in yourthirdmodels %}
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox"
id="yourthirdmodel{{ yourthirdmodel.id }}"
value="{{ yourthirdmodel.id }}"
name="yourthirdmodel{{ yourthirdmodel.id }}"
>
<label class="form-check-label" for="yourthirdmodel{{ yourthirdmodel.id }}" >
{{ yourthirdmodel.name }}
</label>
</div>
{% endfor %}
</div>
In the function onHandleForm() we have to add this: (and now I'm getting a the limit of my skills, you can surely do this more elegantly with some Laravel knowledge...)
$checkboxes = [
Input::get('yourthirdmodel1'),
Input::get('yourthirdmodel2'),
etc.,
];
$checkboxesfiltered = array_filter($checkboxes);
// and then
$contact = new Contact(Input::except('yourthirdmodel1', 'yourthirdmodel2',etc.',etc'));
f.e..... $contact->created_at = Carbon\Carbon::now();
f.e. ...... $contact->customMessages = [];
etc...
$contact->yourthirdmodels = $checkboxesfiltered;
$contact->save();
I know. But it works.
Mail Contents You can pass field contents into the sent mails with simple twig tags {{ created_at }}. If you need multilanguage mail contents try to work with case 'de' {} case 'en' {}' in the send mail code and do different mail templates. I'm not sure if the Translation Plugin works in the mail templates....
For our OctoberCMS Friends and the Google Index: Multi-language Bootstrap 5 Frontend October Form with File Upload, Checkboxes, Ajax Validation, Mails and editable Backend.
There are no comments yet
Be the first one to comment