2017-03-16 15:29:07 +00:00
/ * *
* @ file
2018-11-23 12:29:20 +00:00
* JavaScript behaviors for custom webform # states .
2017-03-16 15:29:07 +00:00
* /
( function ( $ , Drupal ) {
'use strict' ;
2018-11-23 12:29:20 +00:00
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' ) ;
2017-03-16 15:29:07 +00:00
}
2018-11-23 12:29:20 +00:00
// 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 ) {
2019-01-24 08:00:03 +00:00
$target . find ( 'legend span.fieldset-legend:not(.visually-hidden)' ) . addClass ( 'js-form-required form-required' ) ;
2018-11-23 12:29:20 +00:00
}
else {
2019-01-24 08:00:03 +00:00
$target . find ( 'legend span.fieldset-legend:not(.visually-hidden)' ) . removeClass ( 'js-form-required form-required' ) ;
2018-11-23 12:29:20 +00:00
}
}
// 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 ) ;
2017-03-16 15:29:07 +00:00
triggerEventHandlers ( this ) ;
} ) ;
}
else {
2018-11-23 12:29:20 +00:00
// @see https://www.sitepoint.com/jquery-function-clear-form-data/
$ ( ':input' , e . target ) . addBack ( ) . each ( function ( ) {
backupValueAndRequired ( this ) ;
clearValueAndRequired ( this ) ;
2017-03-16 15:29:07 +00:00
triggerEventHandlers ( this ) ;
} ) ;
}
2018-11-23 12:29:20 +00:00
}
} ) ;
2017-03-16 15:29:07 +00:00
2018-11-23 12:29:20 +00:00
$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 ) ;
}
2017-03-16 15:29:07 +00:00
} ) ;
2018-11-23 12:29:20 +00:00
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' } ) ;
}
}
2017-03-16 15:29:07 +00:00
/ * *
* Trigger an input ' s event handlers .
*
2018-11-23 12:29:20 +00:00
* @ param { element } input
2017-03-16 15:29:07 +00:00
* An input .
* /
function triggerEventHandlers ( input ) {
var $input = $ ( input ) ;
var type = input . type ;
2018-11-23 12:29:20 +00:00
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' ) {
2017-03-16 15:29:07 +00:00
$input
2018-11-23 12:29:20 +00:00
. trigger ( 'change' , extraParameters )
. trigger ( 'blur' , extraParameters ) ;
2017-03-16 15:29:07 +00:00
}
2018-11-23 12:29:20 +00:00
else if ( tag === 'select' ) {
2017-03-16 15:29:07 +00:00
$input
2018-11-23 12:29:20 +00:00
. trigger ( 'change' , extraParameters )
. trigger ( 'blur' , extraParameters ) ;
2017-03-16 15:29:07 +00:00
}
2018-11-23 12:29:20 +00:00
else if ( type !== 'submit' && type !== 'button' && type !== 'file' ) {
2017-03-16 15:29:07 +00:00
$input
2018-11-23 12:29:20 +00:00
. trigger ( 'input' , extraParameters )
. trigger ( 'change' , extraParameters )
. trigger ( 'keydown' , extraParameters )
. trigger ( 'keyup' , extraParameters )
. trigger ( 'blur' , extraParameters ) ;
2017-03-16 15:29:07 +00:00
}
}
/ * *
* Backup an input ' s current value and required attribute
*
2018-11-23 12:29:20 +00:00
* @ param { element } input
2017-03-16 15:29:07 +00:00
* An input .
* /
function backupValueAndRequired ( input ) {
var $input = $ ( input ) ;
var type = input . type ;
var tag = input . tagName . toLowerCase ( ) ; // Normalize case.
// Backup required.
2018-11-23 12:29:20 +00:00
if ( $input . prop ( 'required' ) && ! $input . hasData ( 'webform-required' ) ) {
$input . data ( 'webform-required' , true ) ;
2017-03-16 15:29:07 +00:00
}
// Backup value.
2018-11-23 12:29:20 +00:00
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 ) ;
}
2017-03-16 15:29:07 +00:00
}
}
/ * *
* Restore an input ' s value and required attribute .
*
2018-11-23 12:29:20 +00:00
* @ param { element } input
2017-03-16 15:29:07 +00:00
* 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.
2018-11-23 12:29:20 +00:00
if ( type === 'checkbox' || type === 'radio' ) {
$input . prop ( 'checked' , value ) ;
2017-03-16 15:29:07 +00:00
}
2018-11-23 12:29:20 +00:00
else if ( tag === 'select' ) {
2017-03-16 15:29:07 +00:00
$ . each ( value , function ( i , option _value ) {
2018-11-23 12:29:20 +00:00
$input . find ( "option[value='" + option _value + "']" ) . prop ( 'selected' , true ) ;
2017-03-16 15:29:07 +00:00
} ) ;
}
2018-11-23 12:29:20 +00:00
else if ( type !== 'submit' && type !== 'button' ) {
2017-03-16 15:29:07 +00:00
input . value = value ;
}
2018-11-23 12:29:20 +00:00
$input . removeData ( 'webform-value' ) ;
2017-03-16 15:29:07 +00:00
}
// Restore required.
2018-11-23 12:29:20 +00:00
var required = $input . data ( 'webform-required' ) ;
if ( typeof required !== 'undefined' ) {
if ( required ) {
$input . prop ( 'required' , true ) ;
}
$input . removeData ( 'webform-required' ) ;
2017-03-16 15:29:07 +00:00
}
}
/ * *
* Clear an input ' s value and required attributes .
*
2018-11-23 12:29:20 +00:00
* @ param { element } input
2017-03-16 15:29:07 +00:00
* An input .
* /
function clearValueAndRequired ( input ) {
var $input = $ ( input ) ;
2018-11-23 12:29:20 +00:00
// 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 ;
}
2017-03-16 15:29:07 +00:00
// Clear value.
var type = input . type ;
var tag = input . tagName . toLowerCase ( ) ; // Normalize case.
2018-11-23 12:29:20 +00:00
if ( type === 'checkbox' || type === 'radio' ) {
2017-03-16 15:29:07 +00:00
$input . prop ( 'checked' , false ) ;
}
2018-11-23 12:29:20 +00:00
else if ( tag === 'select' ) {
2017-03-16 15:29:07 +00:00
if ( $input . find ( 'option[value=""]' ) . length ) {
$input . val ( '' ) ;
}
else {
input . selectedIndex = - 1 ;
}
}
2018-11-23 12:29:20 +00:00
else if ( type !== 'submit' && type !== 'button' ) {
input . value = ( type === 'color' ) ? '#000000' : '' ;
2017-03-16 15:29:07 +00:00
}
// Clear required.
$input . prop ( 'required' , false ) ;
}
} ) ( jQuery , Drupal ) ;