]> _ Git - ccv-wordpress.git/commitdiff
Form validation, localisation, UX + email formatting. WIP #3445 @10
authorStephen Cameron <stephen@cubedesigners.com>
Wed, 29 Apr 2020 19:04:20 +0000 (21:04 +0200)
committerStephen Cameron <stephen@cubedesigners.com>
Wed, 29 Apr 2020 19:04:20 +0000 (21:04 +0200)
wp-content/mu-plugins/cube/src/Forms/Base.php
wp-content/mu-plugins/cube/src/Forms/Consultation.php
wp-content/themes/CCV/package.json
wp-content/themes/CCV/resources/assets/styles/components/forms.styl
wp-content/themes/CCV/resources/views/forms/common/email.blade.php
wp-content/themes/CCV/webpack.mix.js
wp-content/themes/CCV/yarn.lock

index 4a4c5d581e9747c3fae8112cd600dc7a6f13e0d6..f81b0697267817c18b7cef6ca683eb13d571a6e0 100644 (file)
@@ -50,9 +50,9 @@ class Base
 
     public function register_scripts() {
 
-        // Base forms functionality
-        wp_register_script('cube-forms', asset('scripts/forms.js'), ['jquery'], null, true);
-        wp_enqueue_script('cube-forms'); // Always needed, so enqueue it directly here
+        //=== Cube Forms
+        // Base forms functionality - enqueued directly since it is always required
+        wp_enqueue_script('cube-forms', asset('scripts/forms.js'), ['jquery'], null, true);
 
         // JS variables
         wp_add_inline_script(
@@ -65,6 +65,48 @@ class Base
             'before' // Output before script so we can use the vars
         );
 
+
+        //=== Parsley JS Validation
+        wp_enqueue_script('parsleyjs', asset('scripts/parsley/parsley.min.js'), ['jquery'], null, true);
+
+        // Load localisation if available (English is default so not needed)
+        $parsley_locales = ['ar', 'fr', 'ru']; // List of possible locales (see webpack.mix.js for which ones are copied)
+        if (function_exists('pll_current_language')) {
+            $current_locale = pll_current_language('slug');
+
+            if (in_array($current_locale, $parsley_locales)) {
+                // Include it inline since it's small and to save an extra HTTP request
+                wp_add_inline_script('parsleyjs', asset("scripts/parsley/locale/{$current_locale}.js")->contents());
+            }
+        }
+
+        // Parsley JS initialisation and overrides
+        // Ref: https://stackoverflow.com/a/30122442
+        wp_add_inline_script('parsleyjs', "
+            const parsleyConfig = {
+                classHandler: function(parsleyField) {
+                    var fieldWrapper = parsleyField.\$element.parents('.form-field');
+                    return (fieldWrapper.length > 0) ? fieldWrapper : parsleyField;
+                },
+            
+                errorsContainer: function(parsleyField) {
+                    var inputWrapper = parsleyField.\$element.parents('.form-field-input');
+                    return (inputWrapper.length > 0) ? inputWrapper : parsleyField;
+                }
+            };
+            
+            jQuery('.cube-form').parsley(parsleyConfig);
+            
+            // On validation errors, scroll to the first invalid input
+            jQuery.listen('parsley:field:error', function() {
+                window.scrollTo({...jQuery('.parsley-error').first().offset(), ...{behavior: 'smooth'}});
+            });
+            
+        ");
+
+
+        //=== Flatpickr Calendar
+
         // Elementor has an older, incompatible version of Flatpickr so we need to replace it with our version
         if (wp_script_is('flatpickr', 'registered')) {
             wp_deregister_script('flatpickr');
@@ -165,7 +207,7 @@ class Base
             }
         }
 
-        $message = view('forms.common.email', compact('data'));
+        $message = view('forms.common.email', compact('data', 'subject'));
 
         $success = wp_mail($to, $subject, $message, $headers);
 
@@ -210,9 +252,15 @@ class Base
             'placeholder' => $field['title'],
             'field_before' => '',
             'field_after' => '',
+            'validation' => '',
         ];
         $settings = array_merge($default_settings, $settings);
 
+        // Set up default validation that can be overridden by setting validation field
+        if ($field['required'] && empty($settings['validation'])) {
+            $settings['validation'] = 'required'; // Default but could also be set with other ParsleyJS attributes
+        }
+
         $res = '';
 
         if ($settings['show_title']) {
@@ -270,7 +318,7 @@ class Base
 
         foreach ($options as $option) {
             $res .= '<label title="'. $option .'">';
-            $res .= '<input type="'. $type .'" name="'. $input_name .'" value="'. $option .'">';
+            $res .= '<input type="'. $type .'" name="'. $input_name .'" value="'. $option .'" '. $settings['validation'] .'>';
 
             if (isset($settings['show_labels']) && $settings['show_labels']) {
                 $res .= '<span class="form-label">'. $option .'</span>';
@@ -338,7 +386,7 @@ class Base
         wp_enqueue_script('cube-flatpickr');
 
         $res  = '<div data-flatpickr="'. esc_attr(json_encode($flatpickr)) .'" class="flex">';
-        $res .= '<input type="date" placeholder="'. $settings['placeholder'] .'" name="'. $name .'" data-input>';
+        $res .= '<input type="date" placeholder="'. $settings['placeholder'] .'" name="'. $name .'" data-input '. $settings['validation'] .'>';
 
         if ($settings['show_icon']) {
             $res .= '<img src="' . asset('images/calendar.svg') . '" class="ml-2 mr-6 cursor-pointer" data-toggle>';
@@ -372,7 +420,7 @@ class Base
      * @return string
      */
     public function textarea($name, $settings = []) {
-        return '<textarea name="'. $name .'"></textarea>';
+        return '<textarea name="'. $name .'" '. $settings['validation'] .'></textarea>';
     }
 
     /**
@@ -382,7 +430,7 @@ class Base
      * @return string
      */
     public function text($name, $settings = []) {
-        return '<input type="text" name="'. $name .'" placeholder="'. $settings['placeholder'] .'">';
+        return '<input type="text" name="'. $name .'" placeholder="'. $settings['placeholder'] .'" '. $settings['validation'] .'>';
     }
 
     /**
@@ -392,7 +440,7 @@ class Base
      * @return string
      */
     public function email($name, $settings = []) {
-        return '<input type="email" name="'. $name .'" placeholder="'. $settings['placeholder'] .'">';
+        return '<input type="email" name="'. $name .'" placeholder="'. $settings['placeholder'] .'" '. $settings['validation'] .'>';
     }
 
     /**
@@ -410,7 +458,7 @@ class Base
             $options = $settings['placeholder'] + $options; // Prepend to options
         }
 
-        $res  = '<select name="'. $name .'">';
+        $res  = '<select name="'. $name .'" '. $settings['validation'] .'>';
         foreach ($options as $value => $label) {
             $res .= '<option value="'. $value .'">'. $label .'</option>';
         }
@@ -478,10 +526,12 @@ class Base
      * @param $title
      * @param $type
      * @param array $options
+     * @param bool $required
      */
-    public function add_field($name, $title, $type, $options = []) {
+    public function add_field($name, $title, $type, $options = [], $required = true) {
         $this->fields[$name]['title'] = $title;
         $this->fields[$name]['type'] = $type;
+        $this->fields[$name]['required'] = $required;
 
         if (!empty($options)) {
             $this->set_field_options($name, $options);
index f4aed020eec41082278240063dcb2e6b2af70ed9..bbff867be372306f1cd6a163b0f6875d6375ccfa 100644 (file)
@@ -35,7 +35,7 @@ class Consultation extends Base
 
         $this->add_field('date-first-symptoms', __('Date des premiers symptômes :', 'ccv'), self::DATE);
 
-        $this->add_field('date-pain-since', __('Douleurs permanentes depuis (le cas échéant) :', 'ccv'), self::DATE);
+        $this->add_field('date-pain-since', __('Douleurs permanentes depuis (le cas échéant) :', 'ccv'), self::DATE, [], false);
 
         $this->add_field('pain-arms-legs', __('Avez-vous des douleurs dans les bras ou les jambes (sciatiques, cruralgies, névralgies) ?', 'ccv'), self::BINARY);
 
@@ -49,7 +49,8 @@ class Consultation extends Base
                 __('Haut de la jambe droite', 'ccv'),
                 __('Bas de la jambe gauche', 'ccv'),
                 __('Bas de la jambe droite', 'ccv'),
-            ]
+            ],
+            false // Set not required
         );
 
         $this->add_field('main-pain', __('La douleur principale est-elle ?', 'ccv'), self::RADIO,
@@ -62,26 +63,26 @@ class Consultation extends Base
 
         $this->add_field('tingling-numbness', __('Avez-vous des fourmillements ou une sensation d’engourdissement dans un des membres ? ', 'ccv'), self::BINARY);
 
-        $this->add_field('tingling-numbness-date', __('Si oui depuis quand ?', 'ccv'), self::DATE);
+        $this->add_field('tingling-numbness-date', __('Si oui depuis quand ?', 'ccv'), self::DATE, [], false);
 
         $this->add_field('strength-loss', __('Avez-vous une perte de force importante dans un des membres ?', 'ccv'), self::BINARY);
 
-        $this->add_field('strength-loss-date', __('Si oui depuis quand ?', 'ccv'), self::DATE);
+        $this->add_field('strength-loss-date', __('Si oui depuis quand ?', 'ccv'), self::DATE, [], false);
 
         //=== TREATMENTS
-        $this->add_field('medication', __('Indiquez ici les médicaments que vous avez pris pour vos douleurs (le cas échéant)', 'ccv'), self::TEXTAREA);
+        $this->add_field('medication', __('Indiquez ici les médicaments que vous avez pris pour vos douleurs (le cas échéant)', 'ccv'), self::TEXTAREA, [], false);
 
         $this->add_field('kine-osteo', __('Kinésithérapie ou ostéopathie', 'ccv'), self::BINARY);
         $this->add_field('corset', __('Corset ou ceinture lombaire', 'ccv'), self::BINARY);
         $this->add_field('hospitalisation', __('Séjour en hospitalisation', 'ccv'), self::BINARY);
         $this->add_field('infiltration', __('Infiltration ou thermocoagulation', 'ccv'), self::BINARY);
 
-        $this->add_field('surgeries', __('Indiquez ici vos précédentes chirurgies de la colonne et leurs dates (le cas échéant)', 'ccv'), self::TEXTAREA);
+        $this->add_field('surgeries', __('Indiquez ici vos précédentes chirurgies de la colonne et leurs dates (le cas échéant)', 'ccv'), self::TEXTAREA, [], false);
 
         //=== IMAGERY
-        $this->add_field('imagery-type', __('Imagerie', 'ccv'), null); // This is a special case and will be output manually so only using this for the e-mail label
-        $this->add_field('imagery-online', __('Images en ligne', 'ccv'), self::TEXTAREA); // Again, a manually handled field
-        $this->add_field('imagery-posted', __('Images envoyé par courrier', 'ccv'), self::CHECKBOX);
+        $this->add_field('imagery-type', __('Imagerie', 'ccv'), null, [], false); // This is a special case and will be output manually so only using this for the e-mail label
+        $this->add_field('imagery-online', __('Images en ligne', 'ccv'), self::TEXTAREA, [], false); // Again, a manually handled field
+        $this->add_field('imagery-posted', __('Images envoyé par courrier', 'ccv'), self::CHECKBOX, [], false);
 
         //=== PERSONAL INFORMATION
         $this->add_field('last-name', _x('Nom', 'Nom de famille', 'ccv'), self::TEXT);
@@ -93,7 +94,7 @@ class Consultation extends Base
         $this->add_field('email', __('Email', 'ccv'), self::EMAIL);
         $this->add_field('sex', __('Sexe', 'ccv'), self::RADIO, [_x('M', 'Sexe (M)', 'ccv'), _x('F', 'Sexe (F)', 'ccv')]);
         $this->add_field('age', __('Âge :', 'ccv'), self::TEXT);
-        $this->add_field('message', __('Avez vous un message (ou une demande) spécifique à nous formuler ?', 'ccv'), self::TEXTAREA);
+        $this->add_field('message', __('Avez vous un message (ou une demande) spécifique à nous formuler ?', 'ccv'), self::TEXTAREA, [], false);
 
         // Special field: if a surgeon is selected, their e-mail address will be override the default delivery address
         $this->add_field('surgeon', __('Chirurgien spécifique'), self::SELECT,
@@ -103,7 +104,9 @@ class Consultation extends Base
                 'Dr Grégory EDGARD-ROSA' => 'xxxxx@ccv-montpellier.fr',
                 'Dr Martin GRAU ORTIZ' => 'xxxxx@ccv-montpellier.fr',
                 'Dr Caroline HIRSH' => 'xxxxx@ccv-montpellier.fr',
-            ]);
+            ],
+            false // Set not required
+        );
     }
 
     public function pre_process() {
index 88e8d1d0e9d8d390f65601b2157ff751bd1d6f17..e7a5f24d9d030afd3bf91052c15d89f1438d9d33 100644 (file)
@@ -32,6 +32,7 @@
     "mix-tailwindcss": "^1.0.2",
     "mmenu-light": "^2.3.1",
     "npm-run-all": "^4.1",
+    "parsleyjs": "^2.9.2",
     "purgecss-with-wordpress": "^1.1.0",
     "rimraf": "^2.6",
     "rupture": "^0.7.1",
index 49a283f575bb60f669077125022c84996e5ba77f..7b0bdec4575cc4d5d14016b1a484173ac821ede8 100644 (file)
@@ -108,8 +108,8 @@ input[type="submit"]
   margin-bottom: 0.75em
 
 .form-field-input
-  > *:not(:last-child)
-    margin-right: 1.5em
+  > *:not(:first-child)
+    margin-left: 1.5em
 
   label
     white-space: nowrap // Ensure text stays beside input control
@@ -158,16 +158,27 @@ input[type="submit"]
 
 // ParsleyJS validation
 .parsley-errors-list
-  @apply text-pink
-  position: relative
-  top: -2.45em
+  @apply text-red
   list-style: none
+  position: absolute
+  left: 0
+  bottom: -2em
   padding: 0
+  margin-left: 0 !important
   font-size: 0.7em
-  text-align: right
+  white-space: nowrap
+
+  li:before
+    display: none
 
 .parsley-error
-  border-color: theme('colors.red') !important
+  padding-bottom: 0.5em // Add a little space for the validation message
+
+  .form-field-input
+    position: relative
+
+  input, textarea, select, .form-label:before
+    border-color: theme('colors.red') !important
 
 
 /** Search form */
index 6ff73b70936ea9d02efa7fe583e2512dd9fb39c5..c56c129ad5536be88672d1ae241d0a4121bbac0d 100644 (file)
@@ -9,16 +9,28 @@
       body {
         font-family: Arial, sans-serif;
       }
+      table {
+        border-style: solid;
+        border-color: #aaa;
+        border-width: 1px 1px 0 1px;
+      }
+      td {
+        padding: 12px 18px 12px 12px;
+        border-bottom: 1px solid #aaa;
+      }
     </style>
   </head>
   <body>
-    <table cellpadding="10" cellspacing="0" border="0">
+
+    <h1 style="font-size: 24px; margin-bottom: 1.5em;">{{ $subject }}</h1>
+
+    <table cellpadding="0" cellspacing="0" border="0">
       @foreach($data as $label => $value)
-        <tr @if ($loop->even) bgcolor="#eeeeee" @endif>
-          <td valign="top"><strong>{{ $label }}</strong></td>
+        <tr @if($loop->even) bgcolor="#eeeeee" @endif>
+          <td valign="top" align="right"><strong>{{ rtrim($label, ':') }}</strong></td>
           <td>
             @if (is_array($value))
-              <ul style="margin-left: 0">
+              <ul style="margin: 0; padding-left: 1em;">
                 <li>{!! implode('</li><li>', $value) !!}</li>
               </ul>
             @else
index 3dafd0de07201867d5c1a137d3861872d24a0d3d..67b1ce05a657f652af812b40daa2e70a4e23586f 100644 (file)
@@ -63,6 +63,12 @@ mix.js(src`scripts/app.js`, 'scripts')
    .js(src`scripts/customizer.js`, 'scripts')
    .extract(['mmenu-light']); // Extract any libraries that will rarely change to a vendor.js file
 
+// ParsleyJS validator
+mix.copy('node_modules/parsleyjs/dist/parsley.min.js', publicPath`scripts/parsley`);
+// Set which locales to copy
+['ar', 'fr', 'ru']
+  .forEach(locale => mix.copy(`node_modules/parsleyjs/dist/i18n/${locale}.js`, publicPath`scripts/parsley/locale`));
+
 // Lity lightbox
 mix.copy('node_modules/lity/dist/lity.js', publicPath`scripts`);
 mix.stylus(src`styles/components/lity-lightbox.styl`, 'styles/lity.css', stylusConfig).options(stylusOptions).tailwind();
index 03938365104649b8901fed64c60f19eb4f342c10..c32b6c6c74ed01430435fb3c404aff3dd828acdc 100644 (file)
@@ -4621,6 +4621,11 @@ jest-worker@^25.1.0:
     merge-stream "^2.0.0"
     supports-color "^7.0.0"
 
+jquery@>=1.8.0:
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.0.tgz#9980b97d9e4194611c36530e7dc46a58d7340fc9"
+  integrity sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ==
+
 js-dom-router@^1.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/js-dom-router/-/js-dom-router-1.0.0.tgz#c4b05674f6f0734f5bf9b9f389f1b3d9c62e4103"
@@ -6067,6 +6072,13 @@ parseurl@~1.3.2, parseurl@~1.3.3:
   resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
   integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
 
+parsleyjs@^2.9.2:
+  version "2.9.2"
+  resolved "https://registry.yarnpkg.com/parsleyjs/-/parsleyjs-2.9.2.tgz#67c96961d371821f2623965fa2cc81a4522874cb"
+  integrity sha512-DKS2XXTjEUZ1BJWUzgXAr+550kFBZrom2WYweubqdV7WzdNC1hjOajZDfeBPoAZMkXumJPlB3v37IKatbiW8zQ==
+  dependencies:
+    jquery ">=1.8.0"
+
 pascalcase@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"