2015-08-17 17:00:26 -07:00
/ * *
* @ file
* Provides Ajax page updating via jQuery $ . ajax .
*
* Ajax is a method of making a request via JavaScript while viewing an HTML
* page . The request returns an array of commands encoded in JSON , which is
* then executed to make any changes that are necessary to the page .
*
* Drupal uses this file to enhance form elements with ` #ajax['url'] ` and
* ` #ajax['wrapper'] ` properties . If set , this file will automatically be
* included to provide Ajax capabilities .
* /
( function ( $ , window , Drupal , drupalSettings ) {
2015-10-21 21:44:50 -07:00
'use strict' ;
2015-08-17 17:00:26 -07:00
/ * *
* Attaches the Ajax behavior to each Ajax form element .
*
* @ type { Drupal ~ behavior }
* /
Drupal . behaviors . AJAX = {
attach : function ( context , settings ) {
function loadAjaxBehavior ( base ) {
var element _settings = settings . ajax [ base ] ;
if ( typeof element _settings . selector === 'undefined' ) {
element _settings . selector = '#' + base ;
}
$ ( element _settings . selector ) . once ( 'drupal-ajax' ) . each ( function ( ) {
element _settings . element = this ;
element _settings . base = base ;
Drupal . ajax ( element _settings ) ;
} ) ;
}
// Load all Ajax behaviors specified in the settings.
for ( var base in settings . ajax ) {
if ( settings . ajax . hasOwnProperty ( base ) ) {
loadAjaxBehavior ( base ) ;
}
}
// Bind Ajax behaviors to all items showing the class.
$ ( '.use-ajax' ) . once ( 'ajax' ) . each ( function ( ) {
var element _settings = { } ;
// Clicked links look better with the throbber than the progress bar.
2015-09-04 13:20:09 -07:00
element _settings . progress = { type : 'throbber' } ;
2015-08-17 17:00:26 -07:00
// For anchor tags, these will go to the target of the anchor rather
// than the usual location.
if ( $ ( this ) . attr ( 'href' ) ) {
element _settings . url = $ ( this ) . attr ( 'href' ) ;
element _settings . event = 'click' ;
}
element _settings . dialogType = $ ( this ) . data ( 'dialog-type' ) ;
element _settings . dialog = $ ( this ) . data ( 'dialog-options' ) ;
element _settings . base = $ ( this ) . attr ( 'id' ) ;
element _settings . element = this ;
Drupal . ajax ( element _settings ) ;
} ) ;
// This class means to submit the form to the action using Ajax.
$ ( '.use-ajax-submit' ) . once ( 'ajax' ) . each ( function ( ) {
var element _settings = { } ;
// Ajax submits specified in this manner automatically submit to the
// normal form action.
element _settings . url = $ ( this . form ) . attr ( 'action' ) ;
// Form submit button clicks need to tell the form what was clicked so
// it gets passed in the POST request.
element _settings . setClick = true ;
// Form buttons use the 'click' event rather than mousedown.
element _settings . event = 'click' ;
// Clicked form buttons look better with the throbber than the progress
// bar.
2015-09-04 13:20:09 -07:00
element _settings . progress = { type : 'throbber' } ;
2015-08-17 17:00:26 -07:00
element _settings . base = $ ( this ) . attr ( 'id' ) ;
element _settings . element = this ;
Drupal . ajax ( element _settings ) ;
} ) ;
}
} ;
/ * *
* Extends Error to provide handling for Errors in Ajax .
*
* @ constructor
*
* @ augments Error
*
* @ param { XMLHttpRequest } xmlhttp
* XMLHttpRequest object used for the failed request .
* @ param { string } uri
* The URI where the error occurred .
2015-09-04 13:20:09 -07:00
* @ param { string } customMessage
* The custom message .
2015-08-17 17:00:26 -07:00
* /
2015-09-04 13:20:09 -07:00
Drupal . AjaxError = function ( xmlhttp , uri , customMessage ) {
2015-08-17 17:00:26 -07:00
var statusCode ;
var statusText ;
var pathText ;
var responseText ;
var readyStateText ;
if ( xmlhttp . status ) {
2015-10-21 21:44:50 -07:00
statusCode = '\n' + Drupal . t ( 'An AJAX HTTP error occurred.' ) + '\n' + Drupal . t ( 'HTTP Result Code: !status' , { '!status' : xmlhttp . status } ) ;
2015-08-17 17:00:26 -07:00
}
else {
2015-10-21 21:44:50 -07:00
statusCode = '\n' + Drupal . t ( 'An AJAX HTTP request terminated abnormally.' ) ;
2015-08-17 17:00:26 -07:00
}
2015-10-21 21:44:50 -07:00
statusCode += '\n' + Drupal . t ( 'Debugging information follows.' ) ;
pathText = '\n' + Drupal . t ( 'Path: !uri' , { '!uri' : uri } ) ;
2015-08-17 17:00:26 -07:00
statusText = '' ;
// In some cases, when statusCode === 0, xmlhttp.statusText may not be
// defined. Unfortunately, testing for it with typeof, etc, doesn't seem to
// catch that and the test causes an exception. So we need to catch the
// exception here.
try {
2015-10-21 21:44:50 -07:00
statusText = '\n' + Drupal . t ( 'StatusText: !statusText' , { '!statusText' : $ . trim ( xmlhttp . statusText ) } ) ;
2015-08-17 17:00:26 -07:00
}
catch ( e ) {
// Empty.
}
responseText = '' ;
// Again, we don't have a way to know for sure whether accessing
// xmlhttp.responseText is going to throw an exception. So we'll catch it.
try {
2015-10-21 21:44:50 -07:00
responseText = '\n' + Drupal . t ( 'ResponseText: !responseText' , { '!responseText' : $ . trim ( xmlhttp . responseText ) } ) ;
2015-08-17 17:00:26 -07:00
}
catch ( e ) {
// Empty.
}
// Make the responseText more readable by stripping HTML tags and newlines.
2015-10-21 21:44:50 -07:00
responseText = responseText . replace ( /<("[^"]*"|'[^']*'|[^'">])*>/gi , '' ) ;
responseText = responseText . replace ( /[\n]+\s+/g , '\n' ) ;
2015-08-17 17:00:26 -07:00
// We don't need readyState except for status == 0.
2015-10-21 21:44:50 -07:00
readyStateText = xmlhttp . status === 0 ? ( '\n' + Drupal . t ( 'ReadyState: !readyState' , { '!readyState' : xmlhttp . readyState } ) ) : '' ;
2015-08-17 17:00:26 -07:00
2015-10-21 21:44:50 -07:00
customMessage = customMessage ? ( '\n' + Drupal . t ( 'CustomMessage: !customMessage' , { '!customMessage' : customMessage } ) ) : '' ;
2015-09-04 13:20:09 -07:00
2015-08-17 17:00:26 -07:00
/ * *
* Formatted and translated error message .
*
* @ type { string }
* /
2015-09-04 13:20:09 -07:00
this . message = statusCode + pathText + statusText + customMessage + responseText + readyStateText ;
2015-08-17 17:00:26 -07:00
/ * *
* Used by some browsers to display a more accurate stack trace .
*
* @ type { string }
* /
this . name = 'AjaxError' ;
} ;
Drupal . AjaxError . prototype = new Error ( ) ;
Drupal . AjaxError . prototype . constructor = Drupal . AjaxError ;
/ * *
* Provides Ajax page updating via jQuery $ . ajax .
*
* This function is designed to improve developer experience by wrapping the
* initialization of { @ link Drupal . Ajax } objects and storing all created
* objects in the { @ link Drupal . ajax . instances } array .
*
* @ example
* Drupal . behaviors . myCustomAJAXStuff = {
* attach : function ( context , settings ) {
*
* var ajaxSettings = {
* url : 'my/url/path' ,
* // If the old version of Drupal.ajax() needs to be used those
* // properties can be added
* base : 'myBase' ,
* element : $ ( context ) . find ( '.someElement' )
* } ;
*
* var myAjaxObject = Drupal . ajax ( ajaxSettings ) ;
*
* // Declare a new Ajax command specifically for this Ajax object.
* myAjaxObject . commands . insert = function ( ajax , response , status ) {
* $ ( '#my-wrapper' ) . append ( response . data ) ;
* alert ( 'New content was appended to #my-wrapper' ) ;
* } ;
*
* // This command will remove this Ajax object from the page.
* myAjaxObject . commands . destroyObject = function ( ajax , response , status ) {
* Drupal . ajax . instances [ this . instanceIndex ] = null ;
* } ;
*
* // Programmatically trigger the Ajax request.
* myAjaxObject . execute ( ) ;
* }
* } ;
*
* @ param { object } settings
* The settings object passed to { @ link Drupal . Ajax } constructor .
* @ param { string } [ settings . base ]
* Base is passed to { @ link Drupal . Ajax } constructor as the 'base'
* parameter .
* @ param { HTMLElement } [ settings . element ]
* Element parameter of { @ link Drupal . Ajax } constructor , element on which
* event listeners will be bound .
*
* @ return { Drupal . Ajax }
*
* @ see Drupal . AjaxCommands
* /
Drupal . ajax = function ( settings ) {
if ( arguments . length !== 1 ) {
throw new Error ( 'Drupal.ajax() function must be called with one configuration object only' ) ;
}
// Map those config keys to variables for the old Drupal.ajax function.
var base = settings . base || false ;
var element = settings . element || false ;
delete settings . base ;
delete settings . element ;
// By default do not display progress for ajax calls without an element.
if ( ! settings . progress && ! element ) {
settings . progress = false ;
}
var ajax = new Drupal . Ajax ( base , element , settings ) ;
ajax . instanceIndex = Drupal . ajax . instances . length ;
Drupal . ajax . instances . push ( ajax ) ;
return ajax ;
} ;
/ * *
* Contains all created Ajax objects .
*
* @ type { Array . < Drupal . Ajax > }
* /
Drupal . ajax . instances = [ ] ;
/ * *
* Ajax constructor .
*
* The Ajax request returns an array of commands encoded in JSON , which is
* then executed to make any changes that are necessary to the page .
*
* Drupal uses this file to enhance form elements with ` #ajax['url'] ` and
* ` #ajax['wrapper'] ` properties . If set , this file will automatically be
* included to provide Ajax capabilities .
*
* @ constructor
*
* @ param { string } [ base ]
* Base parameter of { @ link Drupal . Ajax } constructor
* @ param { HTMLElement } [ element ]
* Element parameter of { @ link Drupal . Ajax } constructor , element on which
* event listeners will be bound .
* @ param { object } element _settings
* @ param { string } element _settings . url
* Target of the Ajax request .
* @ param { string } [ element _settings . event ]
* Event bound to settings . element which will trigger the Ajax request .
* @ param { string } [ element _settings . method ]
* Name of the jQuery method used to insert new content in the targeted
* element .
* /
Drupal . Ajax = function ( base , element , element _settings ) {
var defaults = {
event : element ? 'mousedown' : null ,
keypress : true ,
selector : base ? '#' + base : null ,
effect : 'none' ,
speed : 'none' ,
method : 'replaceWith' ,
progress : {
type : 'throbber' ,
message : Drupal . t ( 'Please wait...' )
} ,
submit : {
2015-09-04 13:20:09 -07:00
js : true
2015-08-17 17:00:26 -07:00
}
} ;
$ . extend ( this , defaults , element _settings ) ;
/ * *
* @ type { Drupal . AjaxCommands }
* /
this . commands = new Drupal . AjaxCommands ( ) ;
this . instanceIndex = false ;
// @todo Remove this after refactoring the PHP code to:
// - Call this 'selector'.
// - Include the '#' for ID-based selectors.
// - Support non-ID-based selectors.
if ( this . wrapper ) {
/ * *
* @ type { string }
* /
this . wrapper = '#' + this . wrapper ;
}
/ * *
* @ type { HTMLElement }
* /
this . element = element ;
/ * *
* @ type { object }
* /
this . element _settings = element _settings ;
// If there isn't a form, jQuery.ajax() will be used instead, allowing us to
// bind Ajax to links as well.
if ( this . element && this . element . form ) {
/ * *
* @ type { jQuery }
* /
this . $form = $ ( this . element . form ) ;
}
// If no Ajax callback URL was given, use the link href or form action.
if ( ! this . url ) {
var $element = $ ( this . element ) ;
if ( $element . is ( 'a' ) ) {
this . url = $element . attr ( 'href' ) ;
}
else if ( this . element && element . form ) {
this . url = this . $form . attr ( 'action' ) ;
}
}
// Replacing 'nojs' with 'ajax' in the URL allows for an easy method to let
// the server detect when it needs to degrade gracefully.
// There are four scenarios to check for:
// 1. /nojs/
// 2. /nojs$ - The end of a URL string.
// 3. /nojs? - Followed by a query (e.g. path/nojs?destination=foobar).
// 4. /nojs# - Followed by a fragment (e.g.: path/nojs#myfragment).
2015-09-04 13:20:09 -07:00
var originalUrl = this . url ;
2015-08-17 17:00:26 -07:00
this . url = this . url . replace ( /\/nojs(\/|$|\?|#)/g , '/ajax$1' ) ;
2015-09-04 13:20:09 -07:00
// If the 'nojs' version of the URL is trusted, also trust the 'ajax'
// version.
if ( drupalSettings . ajaxTrustedUrl [ originalUrl ] ) {
drupalSettings . ajaxTrustedUrl [ this . url ] = true ;
}
2015-08-17 17:00:26 -07:00
// Set the options for the ajaxSubmit function.
// The 'this' variable will not persist inside of the options object.
var ajax = this ;
/ * *
* Options for the ajaxSubmit function .
*
* @ name Drupal . Ajax # options
*
* @ type { object }
*
* @ prop { string } url
* @ prop { object } data
* @ prop { function } beforeSerialize
* @ prop { function } beforeSubmit
* @ prop { function } beforeSend
* @ prop { function } success
* @ prop { function } complete
* @ prop { string } dataType
* @ prop { object } accepts
* @ prop { string } accepts . json
* @ prop { string } type
* /
ajax . options = {
url : ajax . url ,
data : ajax . submit ,
beforeSerialize : function ( element _settings , options ) {
return ajax . beforeSerialize ( element _settings , options ) ;
} ,
beforeSubmit : function ( form _values , element _settings , options ) {
ajax . ajaxing = true ;
return ajax . beforeSubmit ( form _values , element _settings , options ) ;
} ,
beforeSend : function ( xmlhttprequest , options ) {
ajax . ajaxing = true ;
return ajax . beforeSend ( xmlhttprequest , options ) ;
} ,
2015-09-04 13:20:09 -07:00
success : function ( response , status , xmlhttprequest ) {
2015-08-17 17:00:26 -07:00
// Sanity check for browser support (object expected).
// When using iFrame uploads, responses must be returned as a string.
if ( typeof response === 'string' ) {
response = $ . parseJSON ( response ) ;
}
2015-09-04 13:20:09 -07:00
// Prior to invoking the response's commands, verify that they can be
// trusted by checking for a response header. See
// \Drupal\Core\EventSubscriber\AjaxResponseSubscriber for details.
// - Empty responses are harmless so can bypass verification. This
// avoids an alert message for server-generated no-op responses that
// skip Ajax rendering.
// - Ajax objects with trusted URLs (e.g., ones defined server-side via
// #ajax) can bypass header verification. This is especially useful
// for Ajax with multipart forms. Because IFRAME transport is used,
// the response headers cannot be accessed for verification.
if ( response !== null && ! drupalSettings . ajaxTrustedUrl [ ajax . url ] ) {
if ( xmlhttprequest . getResponseHeader ( 'X-Drupal-Ajax-Token' ) !== '1' ) {
2015-10-21 21:44:50 -07:00
var customMessage = Drupal . t ( 'The response failed verification so will not be processed.' ) ;
2015-09-04 13:20:09 -07:00
return ajax . error ( xmlhttprequest , ajax . url , customMessage ) ;
}
}
2015-08-17 17:00:26 -07:00
return ajax . success ( response , status ) ;
} ,
2015-09-04 13:20:09 -07:00
complete : function ( xmlhttprequest , status ) {
2015-08-17 17:00:26 -07:00
ajax . ajaxing = false ;
if ( status === 'error' || status === 'parsererror' ) {
2015-09-04 13:20:09 -07:00
return ajax . error ( xmlhttprequest , ajax . url ) ;
2015-08-17 17:00:26 -07:00
}
} ,
dataType : 'json' ,
type : 'POST'
} ;
if ( element _settings . dialog ) {
ajax . options . data . dialogOptions = element _settings . dialog ;
}
// Ensure that we have a valid URL by adding ? when no query parameter is
// yet available, otherwise append using &.
if ( ajax . options . url . indexOf ( '?' ) === - 1 ) {
ajax . options . url += '?' ;
}
else {
ajax . options . url += '&' ;
}
ajax . options . url += Drupal . ajax . WRAPPER _FORMAT + '=drupal_' + ( element _settings . dialogType || 'ajax' ) ;
// Bind the ajaxSubmit function to the element event.
$ ( ajax . element ) . on ( element _settings . event , function ( event ) {
2015-09-04 13:20:09 -07:00
if ( ! drupalSettings . ajaxTrustedUrl [ ajax . url ] && ! Drupal . url . isLocal ( ajax . url ) ) {
throw new Error ( Drupal . t ( 'The callback URL is not local and not trusted: !url' , { '!url' : ajax . url } ) ) ;
}
2015-08-17 17:00:26 -07:00
return ajax . eventResponse ( this , event ) ;
} ) ;
// If necessary, enable keyboard submission so that Ajax behaviors
// can be triggered through keyboard input as well as e.g. a mousedown
// action.
if ( element _settings . keypress ) {
$ ( ajax . element ) . on ( 'keypress' , function ( event ) {
return ajax . keypressResponse ( this , event ) ;
} ) ;
}
// If necessary, prevent the browser default action of an additional event.
// For example, prevent the browser default action of a click, even if the
// Ajax behavior binds to mousedown.
if ( element _settings . prevent ) {
$ ( ajax . element ) . on ( element _settings . prevent , false ) ;
}
} ;
/ * *
* URL query attribute to indicate the wrapper used to render a request .
*
* The wrapper format determines how the HTML is wrapped , for example in a
* modal dialog .
*
* @ const { string }
* /
Drupal . ajax . WRAPPER _FORMAT = '_wrapper_format' ;
/ * *
* Request parameter to indicate that a request is a Drupal Ajax request .
*
* @ const
* /
Drupal . Ajax . AJAX _REQUEST _PARAMETER = '_drupal_ajax' ;
/ * *
* Execute the ajax request .
*
* Allows developers to execute an Ajax request manually without specifying
* an event to respond to .
* /
Drupal . Ajax . prototype . execute = function ( ) {
// Do not perform another ajax command if one is already in progress.
if ( this . ajaxing ) {
return ;
}
try {
this . beforeSerialize ( this . element , this . options ) ;
$ . ajax ( this . options ) ;
}
catch ( e ) {
// Unset the ajax.ajaxing flag here because it won't be unset during
// the complete response.
this . ajaxing = false ;
2015-10-21 21:44:50 -07:00
window . alert ( 'An error occurred while attempting to process ' + this . options . url + ': ' + e . message ) ;
2015-08-17 17:00:26 -07:00
}
} ;
/ * *
* Handle a key press .
*
* The Ajax object will , if instructed , bind to a key press response . This
* will test to see if the key press is valid to trigger this event and
* if it is , trigger it for us and prevent other keypresses from triggering .
* In this case we ' re handling RETURN and SPACEBAR keypresses ( event codes 13
* and 32. RETURN is often used to submit a form when in a textfield , and
* SPACE is often used to activate an element without submitting .
*
* @ param { HTMLElement } element
* @ param { jQuery . Event } event
* /
Drupal . Ajax . prototype . keypressResponse = function ( element , event ) {
// Create a synonym for this to reduce code confusion.
var ajax = this ;
// Detect enter key and space bar and allow the standard response for them,
// except for form elements of type 'text', 'tel', 'number' and 'textarea',
// where the spacebar activation causes inappropriate activation if
// #ajax['keypress'] is TRUE. On a text-type widget a space should always
// be a space.
if ( event . which === 13 || ( event . which === 32 && element . type !== 'text' &&
element . type !== 'textarea' && element . type !== 'tel' && element . type !== 'number' ) ) {
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
$ ( ajax . element _settings . element ) . trigger ( ajax . element _settings . event ) ;
}
} ;
/ * *
* Handle an event that triggers an Ajax response .
*
* When an event that triggers an Ajax response happens , this method will
* perform the actual Ajax call . It is bound to the event using
* bind ( ) in the constructor , and it uses the options specified on the
* Ajax object .
*
* @ param { HTMLElement } element
* @ param { jQuery . Event } event
* /
Drupal . Ajax . prototype . eventResponse = function ( element , event ) {
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
// Create a synonym for this to reduce code confusion.
var ajax = this ;
// Do not perform another Ajax command if one is already in progress.
if ( ajax . ajaxing ) {
return ;
}
try {
if ( ajax . $form ) {
// If setClick is set, we must set this to ensure that the button's
// value is passed.
if ( ajax . setClick ) {
// Mark the clicked button. 'form.clk' is a special variable for
// ajaxSubmit that tells the system which element got clicked to
// trigger the submit. Without it there would be no 'op' or
// equivalent.
element . form . clk = element ;
}
ajax . $form . ajaxSubmit ( ajax . options ) ;
}
else {
ajax . beforeSerialize ( ajax . element , ajax . options ) ;
$ . ajax ( ajax . options ) ;
}
}
catch ( e ) {
// Unset the ajax.ajaxing flag here because it won't be unset during
// the complete response.
ajax . ajaxing = false ;
2015-10-21 21:44:50 -07:00
window . alert ( 'An error occurred while attempting to process ' + ajax . options . url + ': ' + e . message ) ;
2015-08-17 17:00:26 -07:00
}
} ;
/ * *
* Handler for the form serialization .
*
* Runs before the beforeSend ( ) handler ( see below ) , and unlike that one , runs
* before field data is collected .
*
* @ param { HTMLElement } element
* @ param { object } options
* @ param { object } options . data
* /
Drupal . Ajax . prototype . beforeSerialize = function ( element , options ) {
// Allow detaching behaviors to update field values before collecting them.
// This is only needed when field values are added to the POST data, so only
// when there is a form such that this.$form.ajaxSubmit() is used instead of
// $.ajax(). When there is no form and $.ajax() is used, beforeSerialize()
// isn't called, but don't rely on that: explicitly check this.$form.
if ( this . $form ) {
var settings = this . settings || drupalSettings ;
Drupal . detachBehaviors ( this . $form . get ( 0 ) , settings , 'serialize' ) ;
}
// Inform Drupal that this is an AJAX request.
options . data [ Drupal . Ajax . AJAX _REQUEST _PARAMETER ] = 1 ;
// Allow Drupal to return new JavaScript and CSS files to load without
// returning the ones already loaded.
// @see \Drupal\Core\Theme\AjaxBasePageNegotiator
// @see \Drupal\Core\Asset\LibraryDependencyResolverInterface::getMinimalRepresentativeSubset()
// @see system_js_settings_alter()
var pageState = drupalSettings . ajaxPageState ;
options . data [ 'ajax_page_state[theme]' ] = pageState . theme ;
options . data [ 'ajax_page_state[theme_token]' ] = pageState . theme _token ;
options . data [ 'ajax_page_state[libraries]' ] = pageState . libraries ;
} ;
/ * *
* Modify form values prior to form submission .
*
* @ param { object } form _values
* @ param { HTMLElement } element
* @ param { object } options
* /
Drupal . Ajax . prototype . beforeSubmit = function ( form _values , element , options ) {
// This function is left empty to make it simple to override for modules
// that wish to add functionality here.
} ;
/ * *
* Prepare the Ajax request before it is sent .
*
* @ param { XMLHttpRequest } xmlhttprequest
* @ param { object } options
* @ param { object } options . extraData
* /
Drupal . Ajax . prototype . beforeSend = function ( xmlhttprequest , options ) {
// For forms without file inputs, the jQuery Form plugin serializes the
// form values, and then calls jQuery's $.ajax() function, which invokes
// this handler. In this circumstance, options.extraData is never used. For
// forms with file inputs, the jQuery Form plugin uses the browser's normal
// form submission mechanism, but captures the response in a hidden IFRAME.
// In this circumstance, it calls this handler first, and then appends
// hidden fields to the form to submit the values in options.extraData.
// There is no simple way to know which submission mechanism will be used,
// so we add to extraData regardless, and allow it to be ignored in the
// former case.
if ( this . $form ) {
options . extraData = options . extraData || { } ;
// Let the server know when the IFRAME submission mechanism is used. The
// server can use this information to wrap the JSON response in a
// TEXTAREA, as per http://jquery.malsup.com/form/#file-upload.
options . extraData . ajax _iframe _upload = '1' ;
// The triggering element is about to be disabled (see below), but if it
// contains a value (e.g., a checkbox, textfield, select, etc.), ensure
// that value is included in the submission. As per above, submissions
// that use $.ajax() are already serialized prior to the element being
// disabled, so this is only needed for IFRAME submissions.
var v = $ . fieldValue ( this . element ) ;
if ( v !== null ) {
options . extraData [ this . element . name ] = v ;
}
}
// Disable the element that received the change to prevent user interface
// interaction while the Ajax request is in progress. ajax.ajaxing prevents
// the element from triggering a new request, but does not prevent the user
// from changing its value.
$ ( this . element ) . prop ( 'disabled' , true ) ;
if ( ! this . progress || ! this . progress . type ) {
return ;
}
// Insert progress indicator.
var progressIndicatorMethod = 'setProgressIndicator' + this . progress . type . slice ( 0 , 1 ) . toUpperCase ( ) + this . progress . type . slice ( 1 ) . toLowerCase ( ) ;
if ( progressIndicatorMethod in this && typeof this [ progressIndicatorMethod ] === 'function' ) {
this [ progressIndicatorMethod ] . call ( this ) ;
}
} ;
/ * *
* Sets the progress bar progress indicator .
* /
Drupal . Ajax . prototype . setProgressIndicatorBar = function ( ) {
var progressBar = new Drupal . ProgressBar ( 'ajax-progress-' + this . element . id , $ . noop , this . progress . method , $ . noop ) ;
if ( this . progress . message ) {
progressBar . setProgress ( - 1 , this . progress . message ) ;
}
if ( this . progress . url ) {
progressBar . startMonitoring ( this . progress . url , this . progress . interval || 1500 ) ;
}
this . progress . element = $ ( progressBar . element ) . addClass ( 'ajax-progress ajax-progress-bar' ) ;
this . progress . object = progressBar ;
$ ( this . element ) . after ( this . progress . element ) ;
} ;
/ * *
* Sets the throbber progress indicator .
* /
Drupal . Ajax . prototype . setProgressIndicatorThrobber = function ( ) {
this . progress . element = $ ( '<div class="ajax-progress ajax-progress-throbber"><div class="throbber"> </div></div>' ) ;
if ( this . progress . message ) {
this . progress . element . find ( '.throbber' ) . after ( '<div class="message">' + this . progress . message + '</div>' ) ;
}
$ ( this . element ) . after ( this . progress . element ) ;
} ;
/ * *
* Sets the fullscreen progress indicator .
* /
Drupal . Ajax . prototype . setProgressIndicatorFullscreen = function ( ) {
this . progress . element = $ ( '<div class="ajax-progress ajax-progress-fullscreen"> </div>' ) ;
$ ( 'body' ) . after ( this . progress . element ) ;
} ;
/ * *
* Handler for the form redirection completion .
*
* @ param { Array . < Drupal . AjaxCommands ~ commandDefinition > } response
* @ param { number } status
* /
Drupal . Ajax . prototype . success = function ( response , status ) {
// Remove the progress element.
if ( this . progress . element ) {
$ ( this . progress . element ) . remove ( ) ;
}
if ( this . progress . object ) {
this . progress . object . stopMonitoring ( ) ;
}
$ ( this . element ) . prop ( 'disabled' , false ) ;
2015-11-17 13:42:33 -08:00
// Save element's ancestors tree so if the element is removed from the dom
// we can try to refocus one of its parents. Using addBack reverse the
// result array, meaning that index 0 is the highest parent in the hierarchy
// in this situation it is usually a <form> element.
var elementParents = $ ( this . element ) . parents ( '[data-drupal-selector]' ) . addBack ( ) . toArray ( ) ;
// Track if any command is altering the focus so we can avoid changing the
// focus set by the Ajax command.
var focusChanged = false ;
2015-08-17 17:00:26 -07:00
for ( var i in response ) {
if ( response . hasOwnProperty ( i ) && response [ i ] . command && this . commands [ response [ i ] . command ] ) {
this . commands [ response [ i ] . command ] ( this , response [ i ] , status ) ;
2015-11-17 13:42:33 -08:00
if ( response [ i ] . command === 'invoke' && response [ i ] . method === 'focus' ) {
focusChanged = true ;
}
}
}
// If the focus hasn't be changed by the ajax commands, try to refocus the
// triggering element or one of its parents if that element does not exist
// anymore.
if ( ! focusChanged && this . element && ! $ ( this . element ) . data ( 'disable-refocus' ) ) {
var target = false ;
for ( var n = elementParents . length - 1 ; ! target && n > 0 ; n -- ) {
target = document . querySelector ( '[data-drupal-selector="' + elementParents [ n ] . getAttribute ( 'data-drupal-selector' ) + '"]' ) ;
}
if ( target ) {
$ ( target ) . trigger ( 'focus' ) ;
2015-08-17 17:00:26 -07:00
}
}
// Reattach behaviors, if they were detached in beforeSerialize(). The
// attachBehaviors() called on the new content from processing the response
// commands is not sufficient, because behaviors from the entire form need
// to be reattached.
if ( this . $form ) {
var settings = this . settings || drupalSettings ;
Drupal . attachBehaviors ( this . $form . get ( 0 ) , settings ) ;
}
// Remove any response-specific settings so they don't get used on the next
// call by mistake.
this . settings = null ;
} ;
/ * *
* Build an effect object to apply an effect when adding new HTML .
*
* @ param { object } response
* @ param { string } [ response . effect ]
* @ param { string | number } [ response . speed ]
*
* @ return { object }
* /
Drupal . Ajax . prototype . getEffect = function ( response ) {
var type = response . effect || this . effect ;
var speed = response . speed || this . speed ;
var effect = { } ;
if ( type === 'none' ) {
effect . showEffect = 'show' ;
effect . hideEffect = 'hide' ;
effect . showSpeed = '' ;
}
else if ( type === 'fade' ) {
effect . showEffect = 'fadeIn' ;
effect . hideEffect = 'fadeOut' ;
effect . showSpeed = speed ;
}
else {
effect . showEffect = type + 'Toggle' ;
effect . hideEffect = type + 'Toggle' ;
effect . showSpeed = speed ;
}
return effect ;
} ;
/ * *
* Handler for the form redirection error .
*
2015-09-04 13:20:09 -07:00
* @ param { object } xmlhttprequest
2015-08-17 17:00:26 -07:00
* @ param { string } uri
2015-09-04 13:20:09 -07:00
* @ param { string } customMessage
2015-08-17 17:00:26 -07:00
* /
2015-09-04 13:20:09 -07:00
Drupal . Ajax . prototype . error = function ( xmlhttprequest , uri , customMessage ) {
2015-08-17 17:00:26 -07:00
// Remove the progress element.
if ( this . progress . element ) {
$ ( this . progress . element ) . remove ( ) ;
}
if ( this . progress . object ) {
this . progress . object . stopMonitoring ( ) ;
}
// Undo hide.
$ ( this . wrapper ) . show ( ) ;
// Re-enable the element.
$ ( this . element ) . prop ( 'disabled' , false ) ;
// Reattach behaviors, if they were detached in beforeSerialize().
if ( this . $form ) {
2015-09-04 13:20:09 -07:00
var settings = this . settings || drupalSettings ;
2015-08-17 17:00:26 -07:00
Drupal . attachBehaviors ( this . $form . get ( 0 ) , settings ) ;
}
2015-09-04 13:20:09 -07:00
throw new Drupal . AjaxError ( xmlhttprequest , uri , customMessage ) ;
2015-08-17 17:00:26 -07:00
} ;
/ * *
* @ typedef { object } Drupal . AjaxCommands ~ commandDefinition
*
* @ prop { string } command
* @ prop { string } [ method ]
* @ prop { string } [ selector ]
* @ prop { string } [ data ]
* @ prop { object } [ settings ]
* @ prop { bool } [ asterisk ]
* @ prop { string } [ text ]
* @ prop { string } [ title ]
* @ prop { string } [ url ]
* @ prop { object } [ argument ]
* @ prop { string } [ name ]
* @ prop { string } [ value ]
* @ prop { string } [ old ]
* @ prop { string } [ new ]
* @ prop { bool } [ merge ]
* @ prop { Array } [ args ]
*
* @ see Drupal . AjaxCommands
* /
/ * *
* Provide a series of commands that the client will perform .
*
* @ constructor
* /
Drupal . AjaxCommands = function ( ) { } ;
Drupal . AjaxCommands . prototype = {
/ * *
* Command to insert new content into the DOM .
*
* @ param { Drupal . Ajax } ajax
* @ param { object } response
* @ param { string } response . data
* @ param { string } [ response . method ]
* @ param { string } [ response . selector ]
* @ param { object } [ response . settings ]
* @ param { number } [ status ]
* /
insert : function ( ajax , response , status ) {
// Get information from the response. If it is not there, default to
// our presets.
var wrapper = response . selector ? $ ( response . selector ) : $ ( ajax . wrapper ) ;
var method = response . method || ajax . method ;
var effect = ajax . getEffect ( response ) ;
var settings ;
// We don't know what response.data contains: it might be a string of text
// without HTML, so don't rely on jQuery correctly interpreting
// $(response.data) as new HTML rather than a CSS selector. Also, if
// response.data contains top-level text nodes, they get lost with either
// $(response.data) or $('<div></div>').replaceWith(response.data).
var new _content _wrapped = $ ( '<div></div>' ) . html ( response . data ) ;
var new _content = new _content _wrapped . contents ( ) ;
// For legacy reasons, the effects processing code assumes that
// new_content consists of a single top-level element. Also, it has not
// been sufficiently tested whether attachBehaviors() can be successfully
// called with a context object that includes top-level text nodes.
// However, to give developers full control of the HTML appearing in the
// page, and to enable Ajax content to be inserted in places where DIV
// elements are not allowed (e.g., within TABLE, TR, and SPAN parents),
// we check if the new content satisfies the requirement of a single
// top-level element, and only use the container DIV created above when
// it doesn't. For more information, please see
// https://www.drupal.org/node/736066.
if ( new _content . length !== 1 || new _content . get ( 0 ) . nodeType !== 1 ) {
new _content = new _content _wrapped ;
}
// If removing content from the wrapper, detach behaviors first.
switch ( method ) {
case 'html' :
case 'replaceWith' :
case 'replaceAll' :
case 'empty' :
case 'remove' :
settings = response . settings || ajax . settings || drupalSettings ;
Drupal . detachBehaviors ( wrapper . get ( 0 ) , settings ) ;
}
// Add the new content to the page.
wrapper [ method ] ( new _content ) ;
// Immediately hide the new content if we're using any effects.
if ( effect . showEffect !== 'show' ) {
new _content . hide ( ) ;
}
// Determine which effect to use and what content will receive the
// effect, then show the new content.
if ( new _content . find ( '.ajax-new-content' ) . length > 0 ) {
new _content . find ( '.ajax-new-content' ) . hide ( ) ;
new _content . show ( ) ;
new _content . find ( '.ajax-new-content' ) [ effect . showEffect ] ( effect . showSpeed ) ;
}
else if ( effect . showEffect !== 'show' ) {
new _content [ effect . showEffect ] ( effect . showSpeed ) ;
}
// Attach all JavaScript behaviors to the new content, if it was
// successfully added to the page, this if statement allows
// `#ajax['wrapper']` to be optional.
if ( new _content . parents ( 'html' ) . length > 0 ) {
// Apply any settings from the returned JSON if available.
settings = response . settings || ajax . settings || drupalSettings ;
Drupal . attachBehaviors ( new _content . get ( 0 ) , settings ) ;
}
} ,
/ * *
* Command to remove a chunk from the page .
*
* @ param { Drupal . Ajax } [ ajax ]
* @ param { object } response
* @ param { string } response . selector
* @ param { object } [ response . settings ]
* @ param { number } [ status ]
* /
remove : function ( ajax , response , status ) {
var settings = response . settings || ajax . settings || drupalSettings ;
$ ( response . selector ) . each ( function ( ) {
Drupal . detachBehaviors ( this , settings ) ;
} )
. remove ( ) ;
} ,
/ * *
* Command to mark a chunk changed .
*
* @ param { Drupal . Ajax } [ ajax ]
* @ param { object } response
* @ param { string } response . selector
* @ param { bool } [ response . asterisk ]
* @ param { number } [ status ]
* /
changed : function ( ajax , response , status ) {
if ( ! $ ( response . selector ) . hasClass ( 'ajax-changed' ) ) {
$ ( response . selector ) . addClass ( 'ajax-changed' ) ;
if ( response . asterisk ) {
$ ( response . selector ) . find ( response . asterisk ) . append ( ' <abbr class="ajax-changed" title="' + Drupal . t ( 'Changed' ) + '">*</abbr> ' ) ;
}
}
} ,
/ * *
* Command to provide an alert .
*
* @ param { Drupal . Ajax } [ ajax ]
* @ param { object } response
* @ param { string } response . text
* @ param { string } response . title
* @ param { number } [ status ]
* /
alert : function ( ajax , response , status ) {
window . alert ( response . text , response . title ) ;
} ,
/ * *
* Command to set the window . location , redirecting the browser .
*
* @ param { Drupal . Ajax } [ ajax ]
* @ param { object } response
* @ param { string } response . url
* @ param { number } [ status ]
* /
redirect : function ( ajax , response , status ) {
window . location = response . url ;
} ,
/ * *
* Command to provide the jQuery css ( ) function .
*
* @ param { Drupal . Ajax } [ ajax ]
* @ param { object } response
* @ param { object } response . argument
* @ param { number } [ status ]
* /
css : function ( ajax , response , status ) {
$ ( response . selector ) . css ( response . argument ) ;
} ,
/ * *
* Command to set the settings used for other commands in this response .
*
* @ param { Drupal . Ajax } [ ajax ]
* @ param { object } response
* @ param { bool } response . merge
* @ param { object } response . settings
* @ param { number } [ status ]
* /
settings : function ( ajax , response , status ) {
if ( response . merge ) {
$ . extend ( true , drupalSettings , response . settings ) ;
}
else {
ajax . settings = response . settings ;
}
} ,
/ * *
* Command to attach data using jQuery ' s data API .
*
* @ param { Drupal . Ajax } [ ajax ]
* @ param { object } response
* @ param { string } response . name
* @ param { string } response . selector
* @ param { string | object } response . value
* @ param { number } [ status ]
* /
data : function ( ajax , response , status ) {
$ ( response . selector ) . data ( response . name , response . value ) ;
} ,
/ * *
* Command to apply a jQuery method .
*
* @ param { Drupal . Ajax } [ ajax ]
* @ param { object } response
* @ param { Array } response . args
* @ param { string } response . method
* @ param { string } response . selector
* @ param { number } [ status ]
* /
invoke : function ( ajax , response , status ) {
var $element = $ ( response . selector ) ;
$element [ response . method ] . apply ( $element , response . args ) ;
} ,
/ * *
* Command to restripe a table .
*
* @ param { Drupal . Ajax } [ ajax ]
* @ param { object } response
* @ param { string } response . selector
* @ param { number } [ status ]
* /
restripe : function ( ajax , response , status ) {
// :even and :odd are reversed because jQuery counts from 0 and
// we count from 1, so we're out of sync.
// Match immediate children of the parent element to allow nesting.
$ ( response . selector ) . find ( '> tbody > tr:visible, > tr:visible' )
. removeClass ( 'odd even' )
. filter ( ':even' ) . addClass ( 'odd' ) . end ( )
. filter ( ':odd' ) . addClass ( 'even' ) ;
} ,
/ * *
* Command to update a form ' s build ID .
*
* @ param { Drupal . Ajax } [ ajax ]
* @ param { object } response
* @ param { string } response . old
* @ param { string } response . new
* @ param { number } [ status ]
* /
update _build _id : function ( ajax , response , status ) {
$ ( 'input[name="form_build_id"][value="' + response . old + '"]' ) . val ( response . new ) ;
} ,
/ * *
* Command to add css .
*
* Uses the proprietary addImport method if available as browsers which
* support that method ignore @ import statements in dynamically added
* stylesheets .
*
* @ param { Drupal . Ajax } [ ajax ]
* @ param { object } response
* @ param { string } response . data
* @ param { number } [ status ]
* /
add _css : function ( ajax , response , status ) {
// Add the styles in the normal way.
$ ( 'head' ) . prepend ( response . data ) ;
// Add imports in the styles using the addImport method if available.
var match ;
var importMatch = /^@import url\("(.*)"\);$/igm ;
if ( document . styleSheets [ 0 ] . addImport && importMatch . test ( response . data ) ) {
importMatch . lastIndex = 0 ;
do {
match = importMatch . exec ( response . data ) ;
document . styleSheets [ 0 ] . addImport ( match [ 1 ] ) ;
} while ( match ) ;
}
}
} ;
} ) ( jQuery , this , Drupal , drupalSettings ) ;