protected $name;
protected $type;
protected $title;
+ protected $value;
protected $options = [];
protected $settings = [];
protected $required = true;
return $this;
}
+ /**
+ * @return string
+ */
+ public function get_value() {
+ return $this->value;
+ }
+
+ /**
+ * Set field value
+ * @param string $value
+ * @return Field
+ */
+ public function value($value): self {
+ $this->value = $value;
+ return $this;
+ }
/**
class Email extends Field
{
public function render($settings) {
- return '<input type="email" name="'. $this->get_name() .'" placeholder="'. $settings['placeholder'] .'" '. $settings['validation'] .'>';
+ // For email fields, Parsley JS behaviour is changed to only re-validate on blur (instead of on each input keystroke)
+ // It needs to be like this because when the form fails validation, it scrolls and focuses on the first error.
+ // While typing an e-mail address, the validation will fail multiple times by default (with each keystroke) until a full address is entered.
+ // To prevent UX problems, it's best to wait until the e-mail field loses focus before trying to re-validate.
+ return '<input type="email" name="'. $this->get_name() .'" placeholder="'. $settings['placeholder'] .'" '. $settings['validation'] .' data-parsley-trigger-after-failure="blur">';
}
}
--- /dev/null
+<?php
+
+namespace Cube\Forms\Builder\Fields;
+
+use Cube\Forms\Builder\Field;
+
+class Hidden extends Field
+{
+ public function render($settings) {
+ return '<input type="hidden" name="'. $this->get_name() .'" value="'. $this->get_value() .'">';
+ }
+}
use Cube\Forms\Builder\Fields\Checkbox;
use Cube\Forms\Builder\Fields\Date;
use Cube\Forms\Builder\Fields\Email;
+use Cube\Forms\Builder\Fields\Hidden;
use Cube\Forms\Builder\Fields\Radio;
use Cube\Forms\Builder\Fields\Select;
use Cube\Forms\Builder\Fields\Text;
Textarea::field('imagery-online', __('Images en ligne', 'ccv'))->required(false), // Again, a manually handled field
Checkbox::field('imagery-posted', __('Images envoyé par courrier', 'ccv'))->required(false),
+ // Unique session identifier for uploads that go directly to CCV's NAS (upload.ccv-montpellier.fr)
+ // Made up of timestamp YYMMDDHHMM + nonce
+ Hidden::field('imagery-phone-token', __('ID sur le NAS', 'ccv'))->value(date('ymdHi') . '_'. wp_create_nonce('NAS-upload')),
+
//=== PERSONAL INFORMATION
Text::field('last-name', _x('Nom', 'Nom de famille', 'ccv')),
Text::field('first-name', __('Prénom', 'ccv')),
return $buttons;
});
-// Add format classes to
+// Add format classes to style dropdown
add_filter( 'tiny_mce_before_init', function($init_array) {
// Text size styles
(function($) {
// Check the parent radio button when clicking on any element with this data attribute
- $(document).on('click', '[data-update-imagery-type]', function () {
+ $(document).on('click', '[data-update-imagery-type]', function(event) {
let radio = $(this).closest('.imagery-type-wrapper').find('input[name="imagery-type"]');
radio.prop('checked', true);
clearPostCheckbox(); // We can clear it here because this event is only triggered by the other options
+ event.preventDefault();
});
$(document).on('click', 'input[name="imagery-type"]', function() {
-
if ('imagery_post' !== $(this).attr('id')) {
clearPostCheckbox();
}
// Settings come from JSON in the data attribute of the element to make this more flexible
document.querySelectorAll('[data-flatpickr]')
.forEach(function(picker) {
- flatpickr(picker, JSON.parse(picker.dataset.flatpickr));
+ let settings = JSON.parse(picker.dataset.flatpickr);
+
+ // Trigger the input on the text field so that Parsley JS will re-validate the field and clear any errors
+ settings.onClose = function(selectedDates, dateStr, instance) {
+ jQuery(instance.altInput).trigger('input');
+ };
+
+ flatpickr(picker, settings);
});
// Finally, scroll to the message in case it isn't in view
setTimeout(function() {
- let wrapperOffsetTop = Math.max(0, wrapperElement.getBoundingClientRect().top + window.scrollY - 100); // Scroll just above it to give some space
+ let headerHeight = document.querySelector('header.site').offsetHeight || 0; // Need to account for fixed header when scrolling up
+ let wrapperOffsetTop = Math.max(0, wrapperElement.getBoundingClientRect().top + window.scrollY - headerHeight);
window.scrollTo({...{top: wrapperOffsetTop}, ...{behavior: 'smooth'}});
}, 50);
}
$.listen('parsley:field:error', function() {
let firstErrorOffset = $('.parsley-error').first().offset();
- firstErrorOffset.top = Math.max(0, firstErrorOffset.top - 50); // Scroll a bit above the element
+ let headerHeight = document.querySelector('header.site').offsetHeight || 0; // Need to account for fixed header when scrolling up
+ firstErrorOffset.top = Math.max(0, firstErrorOffset.top - headerHeight - 50); // Scroll a bit above the element
window.scrollTo({...firstErrorOffset, ...{behavior: 'smooth'}});
});
<div class="cube-form-messages hidden @stack('message_class')"></div>
</form>
+
@stack("afterform")
</div>
{{-- IMAGES ONLINE --}}
- @php $phonetoken=bin2hex(random_bytes(5)); @endphp
-
<div class="imagery-type-wrapper flex mb-8">
<input type="radio" id="imagery_web" name="imagery-type" value="{{ __('Images en ligne', 'ccv') }}">
- <input type="hidden" name="imagery-phone-token" value="{{ $phonetoken }}">
<label for="imagery_web" class="imagery-icon">@svg('imagery-web', 'w-22 md:w-20 sm:w-16')</label>
<div class="ml-4">
<div class="text-lg sm:text-base font-normal leading-tight mb-1">
{{-- IMAGES FROM PHONE --}}
<div class="imagery-type-wrapper flex mb-8">
+ {!! $form->input('imagery-phone-token') !!}
<input type="radio" id="imagery_phone" name="imagery-type" value="{{ __('Images téléversées depuis portable') }}">
<label for="imagery_phone" class="imagery-icon">@svg('imagery-phone', 'w-22 md:w-20 sm:w-16')</label>
<div class="ml-4">
</div>
+
@push('afterform')
<form action="https://upload.ccv-montpellier.fr/upload.php" id="phone-file-upload-form" method="post"
enctype="multipart/form-data" style="visibility:hidden;height:1px;position:absolute;top:0;">
<input type="file" multiple="multiple" name="files[]" id="phone-file-upload-field" accept="image/*" capture />
</form>
<script>
- jQuery(function () {
- // Click browse triggers the file form. It opens the dialog to capture images or browse files
- jQuery("#phone-image-browse").on('click touchend', function () {
- jQuery("#phone-file-upload-field").click();
- return false;
+ (function($) {
+ // Clicking browse triggers the file form. It opens the dialog to capture images or browse files
+ $('#phone-image-browse').on('click touchend', function () {
+ $('#phone-file-upload-field').click();
});
// If the field has been updated (e.g not canceled), submit the form
- jQuery("#phone-file-upload-field").on('change', function () {
+ $('#phone-file-upload-field').on('change', function () {
var form_data = new FormData();
var fileInput = document.getElementById('phone-file-upload-field');
// Read selected files
var totalfiles = fileInput.files.length;
for (var index = 0; index < totalfiles; index++) {
- form_data.append("files[]", fileInput.files[index]);
+ form_data.append('files[]', fileInput.files[index]);
}
- form_data.append('token', '{{ $phonetoken }}');
+ form_data.append('token', '{{ $form->get_field('imagery-phone-token')->get_value() }}');
- var form = jQuery("#phone-file-upload-form");
- var button = jQuery("#phone-image-browse");
+ var form = $('#phone-file-upload-form');
+ var button = $('#phone-image-browse');
button.text("{{ __("Chargement des fichiers", 'ccv') }}");
// AJAX request
- jQuery.ajax({
+ $.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: form_data,
return true;
});
- });
+ })(jQuery);
</script>
@endpush
{!! $form->field('phone', ['show_title' => false]) !!}
{!! $form->button(__('Contactez-moi', 'ccv'), ['class' => 'btn mt-8']) !!}
</div>
+
+
+{{-- Custom classes for form message container (success message) --}}
+@push('message_class')
+ pt-8
+@endpush
// Browsersync
mix.browserSync({
proxy: proxyURL,
- port: 63000, // Changed so it works over port forwarding (paris.cubedesigners.com:63000)
+ host: proxyURL, // Enables ccv.test:3000
+ port: 3000,
open: false,
});