382 lines
12 KiB
JavaScript
382 lines
12 KiB
JavaScript
/**
|
|
* @file
|
|
* JavaScript behaviors for custom webform #states.
|
|
*/
|
|
|
|
(function ($, Drupal) {
|
|
|
|
'use strict';
|
|
|
|
Drupal.webform = Drupal.webform || {};
|
|
Drupal.webform.states = Drupal.webform.states || {};
|
|
Drupal.webform.states.slideDown = Drupal.webform.states.slideDown || {};
|
|
Drupal.webform.states.slideDown.duration = 'slow';
|
|
Drupal.webform.states.slideUp = Drupal.webform.states.slideUp || {};
|
|
Drupal.webform.states.slideUp.duration = 'fast';
|
|
|
|
/**
|
|
* Check if an element has a specified data attribute.
|
|
*
|
|
* @param {string} data
|
|
* The data attribute name.
|
|
*
|
|
* @return {boolean}
|
|
* TRUE if an element has a specified data attribute.
|
|
*/
|
|
$.fn.hasData = function (data) {
|
|
return (typeof this.data(data) !== 'undefined');
|
|
};
|
|
|
|
/**
|
|
* Check if element is within the webform or not.
|
|
*
|
|
* @return {boolean}
|
|
* TRUE if element is within the webform.
|
|
*/
|
|
$.fn.isWebform = function () {
|
|
return $(this).closest('form[id^="webform"]').length ? true : false;
|
|
};
|
|
|
|
// The change event is triggered by cut-n-paste and select menus.
|
|
// Issue #2445271: #states element empty check not triggered on mouse
|
|
// based paste.
|
|
// @see https://www.drupal.org/node/2445271
|
|
Drupal.states.Trigger.states.empty.change = function change() {
|
|
return this.val() === '';
|
|
};
|
|
|
|
// Apply solution included in #1962800 patch.
|
|
// Issue #1962800: Form #states not working with literal integers as
|
|
// values in IE11.
|
|
// @see https://www.drupal.org/project/drupal/issues/1962800
|
|
// @see https://www.drupal.org/files/issues/core-states-not-working-with-integers-ie11_1962800_46.patch
|
|
//
|
|
// This issue causes pattern, less than, and greater than support to break.
|
|
// @see https://www.drupal.org/project/webform/issues/2981724
|
|
var states = Drupal.states;
|
|
Drupal.states.Dependent.prototype.compare = function compare(reference, selector, state) {
|
|
var value = this.values[selector][state.name];
|
|
|
|
var name = reference.constructor.name;
|
|
if (!name) {
|
|
name = $.type(reference);
|
|
|
|
name = name.charAt(0).toUpperCase() + name.slice(1);
|
|
}
|
|
if (name in states.Dependent.comparisons) {
|
|
return states.Dependent.comparisons[name](reference, value);
|
|
}
|
|
|
|
if (reference.constructor.name in states.Dependent.comparisons) {
|
|
return states.Dependent.comparisons[reference.constructor.name](reference, value);
|
|
}
|
|
|
|
return _compare2(reference, value);
|
|
};
|
|
function _compare2(a, b) {
|
|
if (a === b) {
|
|
return typeof a === 'undefined' ? a : true;
|
|
}
|
|
|
|
return typeof a === 'undefined' || typeof b === 'undefined';
|
|
}
|
|
|
|
// Adds pattern, less than, and greater than support to #state API.
|
|
// @see http://drupalsun.com/julia-evans/2012/03/09/extending-form-api-states-regular-expressions
|
|
Drupal.states.Dependent.comparisons.Object = function (reference, value) {
|
|
if ('pattern' in reference) {
|
|
return (new RegExp(reference['pattern'])).test(value);
|
|
}
|
|
else if ('!pattern' in reference) {
|
|
return !((new RegExp(reference['!pattern'])).test(value));
|
|
}
|
|
else if ('less' in reference) {
|
|
return (value !== '' && parseFloat(reference['less']) > parseFloat(value));
|
|
}
|
|
else if ('greater' in reference) {
|
|
return (value !== '' && parseFloat(reference['greater']) < parseFloat(value));
|
|
}
|
|
else {
|
|
return reference.indexOf(value) !== false;
|
|
}
|
|
};
|
|
|
|
var $document = $(document);
|
|
|
|
$document.on('state:required', function (e) {
|
|
if (e.trigger && $(e.target).isWebform()) {
|
|
var $target = $(e.target);
|
|
// Fix #required file upload.
|
|
// @see Issue #2860529: Conditional required File upload field don't work.
|
|
if (e.value) {
|
|
$target.find('input[type="file"]').attr({'required': 'required', 'aria-required': 'true'});
|
|
}
|
|
else {
|
|
$target.find('input[type="file"]').removeAttr('required aria-required');
|
|
}
|
|
|
|
// Fix required label for checkboxes and radios.
|
|
// @see Issue #2938414: Checkboxes don't support #states required
|
|
// @see Issue #2731991: Setting required on radios marks all options required.
|
|
// @see Issue #2856315: Conditional Logic - Requiring Radios in a Fieldset.
|
|
// Fix #required for fieldsets.
|
|
// @see Issue #2977569: Hidden fieldsets that become visible with conditional logic cannot be made required.
|
|
if ($target.is('.js-webform-type-radios, .js-webform-type-checkboxes, fieldset')) {
|
|
if (e.value) {
|
|
$target.find('legend span.fieldset-legend:not(.visually-hidden)').addClass('js-form-required form-required');
|
|
}
|
|
else {
|
|
$target.find('legend span.fieldset-legend:not(.visually-hidden)').removeClass('js-form-required form-required');
|
|
}
|
|
}
|
|
|
|
// Fix #required for radios.
|
|
// @see Issue #2856795: If radio buttons are required but not filled form is nevertheless submitted.
|
|
if ($target.is('.js-webform-type-radios, .js-form-type-webform-radios-other')) {
|
|
if (e.value) {
|
|
$target.find('input[type="radio"]').attr({'required': 'required', 'aria-required': 'true'});
|
|
}
|
|
else {
|
|
$target.find('input[type="radio"]').removeAttr('required aria-required');
|
|
}
|
|
}
|
|
|
|
// Fix #required for checkboxes.
|
|
// @see Issue #2938414: Checkboxes don't support #states required.
|
|
// @see checkboxRequiredhandler
|
|
if ($target.is('.js-webform-type-checkboxes, .js-form-type-webform-checkboxes-other')) {
|
|
var $checkboxes = $target.find('input[type="checkbox"]');
|
|
if (e.value) {
|
|
// Bind the event handler and add custom HTML5 required validation
|
|
// to all checkboxes.
|
|
$checkboxes.bind('click', checkboxRequiredhandler);
|
|
if (!$checkboxes.is(':checked')) {
|
|
$checkboxes.attr({'required': 'required', 'aria-required': 'true'});
|
|
}
|
|
}
|
|
else {
|
|
// Remove custom HTML5 required validation from all checkboxes
|
|
// and unbind the event handler.
|
|
$checkboxes
|
|
.removeAttr('required aria-required')
|
|
.unbind('click', checkboxRequiredhandler);
|
|
}
|
|
}
|
|
|
|
// Issue #2986017: Fieldsets shouldn't have required attribute.
|
|
if ($target.is('fieldset')) {
|
|
$target.removeAttr('required aria-required');
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
$document.on('state:readonly', function (e) {
|
|
if (e.trigger && $(e.target).isWebform()) {
|
|
$(e.target).prop('readonly', e.value).closest('.js-form-item, .js-form-wrapper').toggleClass('webform-readonly', e.value).find('input, textarea').prop('readonly', e.value);
|
|
}
|
|
});
|
|
|
|
$document.on('state:visible state:visible-slide', function (e) {
|
|
if (e.trigger && $(e.target).isWebform()) {
|
|
if (e.value) {
|
|
$(':input', e.target).addBack().each(function () {
|
|
restoreValueAndRequired(this);
|
|
triggerEventHandlers(this);
|
|
});
|
|
}
|
|
else {
|
|
// @see https://www.sitepoint.com/jquery-function-clear-form-data/
|
|
$(':input', e.target).addBack().each(function () {
|
|
backupValueAndRequired(this);
|
|
clearValueAndRequired(this);
|
|
triggerEventHandlers(this);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
$document.bind('state:visible-slide', function (e) {
|
|
if (e.trigger && $(e.target).isWebform()) {
|
|
var effect = e.value ? 'slideDown' : 'slideUp';
|
|
var duration = Drupal.webform.states[effect].duration;
|
|
$(e.target).closest('.js-form-item, .js-form-submit, .js-form-wrapper')[effect](duration);
|
|
}
|
|
});
|
|
Drupal.states.State.aliases['invisible-slide'] = '!visible-slide';
|
|
|
|
$document.on('state:disabled', function (e) {
|
|
if (e.trigger && $(e.target).isWebform()) {
|
|
// Make sure disabled property is set before triggering webform:disabled.
|
|
// Copied from: core/misc/states.js
|
|
$(e.target)
|
|
.prop('disabled', e.value)
|
|
.closest('.js-form-item, .js-form-submit, .js-form-wrapper').toggleClass('form-disabled', e.value)
|
|
.find('select, input, textarea, button').prop('disabled', e.value);
|
|
|
|
// Trigger webform:disabled.
|
|
$(e.target).trigger('webform:disabled')
|
|
.find('select, input, textarea, button').trigger('webform:disabled');
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Trigger custom HTML5 multiple checkboxes validation.
|
|
*
|
|
* @see https://stackoverflow.com/a/37825072/145846
|
|
*/
|
|
function checkboxRequiredhandler() {
|
|
var $checkboxes = $(this).closest('.js-webform-type-checkboxes, .js-form-type-webform-checkboxes-other').find('input[type="checkbox"]');
|
|
if ($checkboxes.is(':checked')) {
|
|
$checkboxes.removeAttr('required aria-required');
|
|
}
|
|
else {
|
|
$checkboxes.attr({'required': 'required', 'aria-required': 'true'});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Trigger an input's event handlers.
|
|
*
|
|
* @param {element} input
|
|
* An input.
|
|
*/
|
|
function triggerEventHandlers(input) {
|
|
var $input = $(input);
|
|
var type = input.type;
|
|
var tag = input.tagName.toLowerCase();
|
|
// Add 'webform.states' as extra parameter to event handlers.
|
|
// @see Drupal.behaviors.webformUnsaved
|
|
var extraParameters = ['webform.states'];
|
|
if (type === 'checkbox' || type === 'radio') {
|
|
$input
|
|
.trigger('change', extraParameters)
|
|
.trigger('blur', extraParameters);
|
|
}
|
|
else if (tag === 'select') {
|
|
$input
|
|
.trigger('change', extraParameters)
|
|
.trigger('blur', extraParameters);
|
|
}
|
|
else if (type !== 'submit' && type !== 'button' && type !== 'file') {
|
|
$input
|
|
.trigger('input', extraParameters)
|
|
.trigger('change', extraParameters)
|
|
.trigger('keydown', extraParameters)
|
|
.trigger('keyup', extraParameters)
|
|
.trigger('blur', extraParameters);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Backup an input's current value and required attribute
|
|
*
|
|
* @param {element} input
|
|
* An input.
|
|
*/
|
|
function backupValueAndRequired(input) {
|
|
var $input = $(input);
|
|
var type = input.type;
|
|
var tag = input.tagName.toLowerCase(); // Normalize case.
|
|
|
|
// Backup required.
|
|
if ($input.prop('required') && !$input.hasData('webform-required')) {
|
|
$input.data('webform-required', true);
|
|
}
|
|
|
|
// Backup value.
|
|
if (!$input.hasData('webform-value')) {
|
|
if (type === 'checkbox' || type === 'radio') {
|
|
$input.data('webform-value', $input.prop('checked'));
|
|
}
|
|
else if (tag === 'select') {
|
|
var values = [];
|
|
$input.find('option:selected').each(function (i, option) {
|
|
values[i] = option.value;
|
|
});
|
|
$input.data('webform-value', values);
|
|
}
|
|
else if (type !== 'submit' && type !== 'button') {
|
|
$input.data('webform-value', input.value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Restore an input's value and required attribute.
|
|
*
|
|
* @param {element} input
|
|
* An input.
|
|
*/
|
|
function restoreValueAndRequired(input) {
|
|
var $input = $(input);
|
|
|
|
// Restore value.
|
|
var value = $input.data('webform-value');
|
|
if (typeof value !== 'undefined') {
|
|
var type = input.type;
|
|
var tag = input.tagName.toLowerCase(); // Normalize case.
|
|
|
|
if (type === 'checkbox' || type === 'radio') {
|
|
$input.prop('checked', value);
|
|
}
|
|
else if (tag === 'select') {
|
|
$.each(value, function (i, option_value) {
|
|
$input.find("option[value='" + option_value + "']").prop('selected', true);
|
|
});
|
|
}
|
|
else if (type !== 'submit' && type !== 'button') {
|
|
input.value = value;
|
|
}
|
|
$input.removeData('webform-value');
|
|
}
|
|
|
|
// Restore required.
|
|
var required = $input.data('webform-required');
|
|
if (typeof required !== 'undefined') {
|
|
if (required) {
|
|
$input.prop('required', true);
|
|
}
|
|
$input.removeData('webform-required');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear an input's value and required attributes.
|
|
*
|
|
* @param {element} input
|
|
* An input.
|
|
*/
|
|
function clearValueAndRequired(input) {
|
|
var $input = $(input);
|
|
|
|
// Check for #states no clear attribute.
|
|
// @see https://css-tricks.com/snippets/jquery/make-an-jquery-hasattr/
|
|
if ($input.closest('[data-webform-states-no-clear]').length) {
|
|
return;
|
|
}
|
|
|
|
// Clear value.
|
|
var type = input.type;
|
|
var tag = input.tagName.toLowerCase(); // Normalize case.
|
|
if (type === 'checkbox' || type === 'radio') {
|
|
$input.prop('checked', false);
|
|
}
|
|
else if (tag === 'select') {
|
|
if ($input.find('option[value=""]').length) {
|
|
$input.val('');
|
|
}
|
|
else {
|
|
input.selectedIndex = -1;
|
|
}
|
|
}
|
|
else if (type !== 'submit' && type !== 'button') {
|
|
input.value = (type === 'color') ? '#000000' : '';
|
|
}
|
|
|
|
// Clear required.
|
|
$input.prop('required', false);
|
|
}
|
|
|
|
})(jQuery, Drupal);
|