Winter CMS resources and help articles

Simple and to the point. Optimized by the community.

Multi-language Bootstrap 5 Frontend Form with File Upload, Checkboxes, Ajax Validation, Mails and editable Backend

0
by ImpactFactory, last modified on February 10th, 2022

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.

Discussion

0 comments

We use cookies to measure the performance of this website. Do you want to accept these cookies?