This repository has been archived on 2025-01-19. You can view files and clone it, but cannot push or open issues or pull requests.
drupalcampbristol/web/modules/contrib/webform/js/webform.states.js
2019-01-24 08:00:03 +00:00

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);