2015-08-17 17:00:26 -07:00
/ * *
* @ file
* Form features .
* /
/ * *
* Triggers when a value in the form changed .
*
* The event triggers when content is typed or pasted in a text field , before
* the change event triggers .
*
* @ event formUpdated
* /
( function ( $ , Drupal , debounce ) {
2015-10-21 21:44:50 -07:00
'use strict' ;
2015-08-17 17:00:26 -07:00
/ * *
* Retrieves the summary for the first element .
*
* @ return { string }
* /
$ . fn . drupalGetSummary = function ( ) {
var callback = this . data ( 'summaryCallback' ) ;
return ( this [ 0 ] && callback ) ? $ . trim ( callback ( this [ 0 ] ) ) : '' ;
} ;
/ * *
* Sets the summary for all matched elements .
*
* @ param { function } callback
* Either a function that will be called each time the summary is
* retrieved or a string ( which is returned each time ) .
*
* @ return { jQuery }
*
* @ fires event : summaryUpdated
*
* @ listens event : formUpdated
* /
$ . fn . drupalSetSummary = function ( callback ) {
var self = this ;
// To facilitate things, the callback should always be a function. If it's
// not, we wrap it into an anonymous function which just returns the value.
if ( typeof callback !== 'function' ) {
var val = callback ;
callback = function ( ) { return val ; } ;
}
return this
. data ( 'summaryCallback' , callback )
// To prevent duplicate events, the handlers are first removed and then
// (re-)added.
. off ( 'formUpdated.summary' )
. on ( 'formUpdated.summary' , function ( ) {
self . trigger ( 'summaryUpdated' ) ;
} )
// The actual summaryUpdated handler doesn't fire when the callback is
// changed, so we have to do this manually.
. trigger ( 'summaryUpdated' ) ;
} ;
/ * *
* Prevents consecutive form submissions of identical form values .
*
* Repetitive form submissions that would submit the identical form values
* are prevented , unless the form values are different to the previously
* submitted values .
*
* This is a simplified re - implementation of a user - agent behavior that
* should be natively supported by major web browsers , but at this time , only
* Firefox has a built - in protection .
*
* A form value - based approach ensures that the constraint is triggered for
* consecutive , identical form submissions only . Compared to that , a form
* button - based approach would ( 1 ) rely on [ visible ] buttons to exist where
* technically not required and ( 2 ) require more complex state management if
* there are multiple buttons in a form .
*
* This implementation is based on form - level submit events only and relies
* on jQuery ' s serialize ( ) method to determine submitted form values . As such ,
* the following limitations exist :
*
* - Event handlers on form buttons that preventDefault ( ) do not receive a
* double - submit protection . That is deemed to be fine , since such button
* events typically trigger reversible client - side or server - side
* operations that are local to the context of a form only .
* - Changed values in advanced form controls , such as file inputs , are not
* part of the form values being compared between consecutive form submits
* ( due to limitations of jQuery . serialize ( ) ) . That is deemed to be
* acceptable , because if the user forgot to attach a file , then the size of
* HTTP payload will most likely be small enough to be fully passed to the
* server endpoint within ( milli ) seconds . If a user mistakenly attached a
* wrong file and is technically versed enough to cancel the form submission
* ( and HTTP payload ) in order to attach a different file , then that
* edge - case is not supported here .
*
* Lastly , all forms submitted via HTTP GET are idempotent by definition of
* HTTP standards , so excluded in this implementation .
*
* @ type { Drupal ~ behavior }
* /
Drupal . behaviors . formSingleSubmit = {
attach : function ( ) {
function onFormSubmit ( e ) {
var $form = $ ( e . currentTarget ) ;
var formValues = $form . serialize ( ) ;
var previousValues = $form . attr ( 'data-drupal-form-submit-last' ) ;
if ( previousValues === formValues ) {
e . preventDefault ( ) ;
}
else {
$form . attr ( 'data-drupal-form-submit-last' , formValues ) ;
}
}
$ ( 'body' ) . once ( 'form-single-submit' )
. on ( 'submit.singleSubmit' , 'form:not([method~="GET"])' , onFormSubmit ) ;
}
} ;
/ * *
* Sends a 'formUpdated' event each time a form element is modified .
*
* @ param { HTMLElement } element
*
* @ fires event : formUpdated
* /
function triggerFormUpdated ( element ) {
$ ( element ) . trigger ( 'formUpdated' ) ;
}
/ * *
* Collects the IDs of all form fields in the given form .
*
* @ param { HTMLFormElement } form
*
* @ return { Array }
* /
function fieldsList ( form ) {
var $fieldList = $ ( form ) . find ( '[name]' ) . map ( function ( index , element ) {
// We use id to avoid name duplicates on radio fields and filter out
// elements with a name but no id.
return element . getAttribute ( 'id' ) ;
} ) ;
// Return a true array.
return $ . makeArray ( $fieldList ) ;
}
/ * *
* Triggers the 'formUpdated' event on form elements when they are modified .
*
* @ type { Drupal ~ behavior }
*
* @ fires event : formUpdated
* /
Drupal . behaviors . formUpdated = {
attach : function ( context ) {
var $context = $ ( context ) ;
var contextIsForm = $context . is ( 'form' ) ;
var $forms = ( contextIsForm ? $context : $context . find ( 'form' ) ) . once ( 'form-updated' ) ;
var formFields ;
if ( $forms . length ) {
// Initialize form behaviors, use $.makeArray to be able to use native
// forEach array method and have the callback parameters in the right
// order.
$ . makeArray ( $forms ) . forEach ( function ( form ) {
var events = 'change.formUpdated input.formUpdated ' ;
var eventHandler = debounce ( function ( event ) { triggerFormUpdated ( event . target ) ; } , 300 ) ;
formFields = fieldsList ( form ) . join ( ',' ) ;
form . setAttribute ( 'data-drupal-form-fields' , formFields ) ;
$ ( form ) . on ( events , eventHandler ) ;
} ) ;
}
// On ajax requests context is the form element.
if ( contextIsForm ) {
formFields = fieldsList ( context ) . join ( ',' ) ;
// @todo replace with form.getAttribute() when #1979468 is in.
var currentFields = $ ( context ) . attr ( 'data-drupal-form-fields' ) ;
// If there has been a change in the fields or their order, trigger
// formUpdated.
if ( formFields !== currentFields ) {
triggerFormUpdated ( context ) ;
}
}
} ,
detach : function ( context , settings , trigger ) {
var $context = $ ( context ) ;
var contextIsForm = $context . is ( 'form' ) ;
if ( trigger === 'unload' ) {
var $forms = ( contextIsForm ? $context : $context . find ( 'form' ) ) . removeOnce ( 'form-updated' ) ;
if ( $forms . length ) {
$ . makeArray ( $forms ) . forEach ( function ( form ) {
form . removeAttribute ( 'data-drupal-form-fields' ) ;
$ ( form ) . off ( '.formUpdated' ) ;
} ) ;
}
}
}
} ;
/ * *
* Prepopulate form fields with information from the visitor browser .
*
* @ type { Drupal ~ behavior }
* /
Drupal . behaviors . fillUserInfoFromBrowser = {
attach : function ( context , settings ) {
var userInfo = [ 'name' , 'mail' , 'homepage' ] ;
var $forms = $ ( '[data-user-info-from-browser]' ) . once ( 'user-info-from-browser' ) ;
if ( $forms . length ) {
userInfo . map ( function ( info ) {
var $element = $forms . find ( '[name=' + info + ']' ) ;
var browserData = localStorage . getItem ( 'Drupal.visitor.' + info ) ;
var emptyOrDefault = ( $element . val ( ) === '' || ( $element . attr ( 'data-drupal-default-value' ) === $element . val ( ) ) ) ;
if ( $element . length && emptyOrDefault && browserData ) {
$element . val ( browserData ) ;
}
} ) ;
}
$forms . on ( 'submit' , function ( ) {
userInfo . map ( function ( info ) {
var $element = $forms . find ( '[name=' + info + ']' ) ;
if ( $element . length ) {
localStorage . setItem ( 'Drupal.visitor.' + info , $element . val ( ) ) ;
}
} ) ;
} ) ;
}
} ;
} ) ( jQuery , Drupal , Drupal . debounce ) ;