2015-08-17 17:00:26 -07:00
/ * *
* @ file
2015-09-04 13:20:09 -07:00
* Defines the Drupal JavaScript API .
2015-08-17 17:00:26 -07:00
* /
/ * *
2015-09-04 13:20:09 -07:00
* A jQuery object , typically the return value from a ` $ (selector) ` call .
*
* Holds an HTMLElement or a collection of HTMLElements .
2015-08-17 17:00:26 -07:00
*
* @ typedef { object } jQuery
*
* @ prop { number } length = 0
2015-09-04 13:20:09 -07:00
* Number of elements contained in the jQuery object .
2015-08-17 17:00:26 -07:00
* /
/ * *
2015-09-04 13:20:09 -07:00
* Variable generated by Drupal that holds all translated strings from PHP .
2015-08-17 17:00:26 -07:00
*
2015-09-04 13:20:09 -07:00
* Content of this variable is automatically created by Drupal when using the
* Interface Translation module . It holds the translation of strings used on
* the page .
2015-08-17 17:00:26 -07:00
*
2015-09-04 13:20:09 -07:00
* This variable is used to pass data from the backend to the frontend . Data
* contained in ` drupalSettings ` is used during behavior initialization .
2015-08-17 17:00:26 -07:00
*
* @ global
*
* @ var { object } drupalTranslations
* /
/ * *
* Global Drupal object .
*
2015-09-04 13:20:09 -07:00
* All Drupal JavaScript APIs are contained in this namespace .
*
2015-08-17 17:00:26 -07:00
* @ global
*
* @ namespace
* /
window . Drupal = { behaviors : { } , locale : { } } ;
2015-09-04 13:20:09 -07:00
// Class indicating that JavaScript is enabled; used for styling purpose.
2015-08-17 17:00:26 -07:00
document . documentElement . className += ' js' ;
// Allow other JavaScript libraries to use $.
if ( window . jQuery ) {
jQuery . noConflict ( ) ;
}
// JavaScript should be made compatible with libraries other than jQuery by
// wrapping it in an anonymous closure.
( function ( domready , Drupal , drupalSettings , drupalTranslations ) {
2015-10-21 21:44:50 -07:00
'use strict' ;
2015-08-17 17:00:26 -07:00
/ * *
2015-09-04 13:20:09 -07:00
* Helper to rethrow errors asynchronously .
2015-08-17 17:00:26 -07:00
*
2015-09-04 13:20:09 -07:00
* This way Errors bubbles up outside of the original callstack , making it
* easier to debug errors in the browser .
2015-08-17 17:00:26 -07:00
*
2015-09-04 13:20:09 -07:00
* @ param { Error | string } error
* The error to be thrown .
2015-08-17 17:00:26 -07:00
* /
2015-09-04 13:20:09 -07:00
Drupal . throwError = function ( error ) {
setTimeout ( function ( ) { throw error ; } , 0 ) ;
} ;
2015-08-17 17:00:26 -07:00
/ * *
2015-09-04 13:20:09 -07:00
* Custom error thrown after attach / detach if one or more behaviors failed .
* Initializes the JavaScript behaviors for page loads and Ajax requests .
2015-08-17 17:00:26 -07:00
*
* @ callback Drupal ~ behaviorAttach
*
2015-09-04 13:20:09 -07:00
* @ param { HTMLDocument | HTMLElement } context
* An element to detach behaviors from .
* @ param { ? object } settings
* An object containing settings for the current context . It is rarely used .
2015-08-17 17:00:26 -07:00
*
* @ see Drupal . attachBehaviors
* /
/ * *
2015-09-04 13:20:09 -07:00
* Reverts and cleans up JavaScript behavior initialization .
2015-08-17 17:00:26 -07:00
*
* @ callback Drupal ~ behaviorDetach
*
2015-09-04 13:20:09 -07:00
* @ param { HTMLDocument | HTMLElement } context
* An element to attach behaviors to .
2015-08-17 17:00:26 -07:00
* @ param { object } settings
2015-09-04 13:20:09 -07:00
* An object containing settings for the current context .
2015-08-17 17:00:26 -07:00
* @ param { string } trigger
2015-09-04 13:20:09 -07:00
* One of ` 'unload' ` , ` 'move' ` , or ` 'serialize' ` .
2015-08-17 17:00:26 -07:00
*
* @ see Drupal . detachBehaviors
* /
/ * *
* @ typedef { object } Drupal ~ behavior
*
* @ prop { Drupal ~ behaviorAttach } attach
2015-09-04 13:20:09 -07:00
* Function run on page load and after an Ajax call .
2015-08-17 17:00:26 -07:00
* @ prop { Drupal ~ behaviorDetach } detach
* Function run when content is serialized or removed from the page .
* /
/ * *
* Holds all initialization methods .
*
* @ namespace Drupal . behaviors
*
* @ type { Object . < string , Drupal ~ behavior > }
* /
/ * *
2015-09-04 13:20:09 -07:00
* Defines a behavior to be run during attach and detach phases .
*
* Attaches all registered behaviors to a page element .
2015-08-17 17:00:26 -07:00
*
* Behaviors are event - triggered actions that attach to page elements ,
* enhancing default non - JavaScript UIs . Behaviors are registered in the
* { @ link Drupal . behaviors } object using the method 'attach' and optionally
2015-09-04 13:20:09 -07:00
* also 'detach' .
2015-08-17 17:00:26 -07:00
*
2015-09-04 13:20:09 -07:00
* { @ link Drupal . attachBehaviors } is added below to the ` jQuery.ready ` event
* and therefore runs on initial page load . Developers implementing Ajax in
* their solutions should also call this function after new page content has
* been loaded , feeding in an element to be processed , in order to attach all
2015-08-17 17:00:26 -07:00
* behaviors to the new content .
*
2015-09-04 13:20:09 -07:00
* Behaviors should use ` var elements =
* $ ( context ) . find ( selector ) . once ( 'behavior-name' ) ; ` to ensure the behavior is
* attached only once to a given element . ( Doing so enables the reprocessing
* of given elements , which may be needed on occasion despite the ability to
* limit behavior attachment to a particular element . )
2015-08-17 17:00:26 -07:00
*
* @ example
* Drupal . behaviors . behaviorName = {
* attach : function ( context , settings ) {
2015-09-04 13:20:09 -07:00
* // ...
2015-08-17 17:00:26 -07:00
* } ,
* detach : function ( context , settings , trigger ) {
2015-09-04 13:20:09 -07:00
* // ...
2015-08-17 17:00:26 -07:00
* }
* } ;
*
2015-09-04 13:20:09 -07:00
* @ param { HTMLDocument | HTMLElement } [ context = document ]
* An element to attach behaviors to .
* @ param { object } [ settings = drupalSettings ]
2015-08-17 17:00:26 -07:00
* An object containing settings for the current context . If none is given ,
2015-09-04 13:20:09 -07:00
* the global { @ link drupalSettings } object is used .
2015-08-17 17:00:26 -07:00
*
* @ see Drupal ~ behaviorAttach
* @ see Drupal . detachBehaviors
*
* @ throws { Drupal ~ DrupalBehaviorError }
* /
Drupal . attachBehaviors = function ( context , settings ) {
context = context || document ;
settings = settings || drupalSettings ;
var behaviors = Drupal . behaviors ;
// Execute all of them.
for ( var i in behaviors ) {
if ( behaviors . hasOwnProperty ( i ) && typeof behaviors [ i ] . attach === 'function' ) {
// Don't stop the execution of behaviors in case of an error.
try {
behaviors [ i ] . attach ( context , settings ) ;
}
catch ( e ) {
2015-09-04 13:20:09 -07:00
Drupal . throwError ( e ) ;
2015-08-17 17:00:26 -07:00
}
}
}
} ;
// Attach all behaviors.
domready ( function ( ) { Drupal . attachBehaviors ( document , drupalSettings ) ; } ) ;
/ * *
2015-09-04 13:20:09 -07:00
* Detaches registered behaviors from a page element .
2015-08-17 17:00:26 -07:00
*
2015-09-04 13:20:09 -07:00
* Developers implementing Ajax in their solutions should call this function
* before page content is about to be removed , feeding in an element to be
* processed , in order to allow special behaviors to detach from the content .
2015-08-17 17:00:26 -07:00
*
2015-09-04 13:20:09 -07:00
* Such implementations should use ` .findOnce() ` and ` .removeOnce() ` to find
* elements with their corresponding ` Drupal.behaviors.behaviorName.attach `
* implementation , i . e . ` .removeOnce('behaviorName') ` , to ensure the behavior
2015-08-17 17:00:26 -07:00
* is detached only from previously processed elements .
*
2015-09-04 13:20:09 -07:00
* @ param { HTMLDocument | HTMLElement } [ context = document ]
* An element to detach behaviors from .
* @ param { object } [ settings = drupalSettings ]
2015-08-17 17:00:26 -07:00
* An object containing settings for the current context . If none given ,
2015-09-04 13:20:09 -07:00
* the global { @ link drupalSettings } object is used .
* @ param { string } [ trigger = 'unload' ]
2015-08-17 17:00:26 -07:00
* A string containing what ' s causing the behaviors to be detached . The
* possible triggers are :
2015-09-04 13:20:09 -07:00
* - ` 'unload' ` : The context element is being removed from the DOM .
* - ` 'move' ` : The element is about to be moved within the DOM ( for example ,
2015-08-17 17:00:26 -07:00
* during a tabledrag row swap ) . After the move is completed ,
* { @ link Drupal . attachBehaviors } is called , so that the behavior can undo
* whatever it did in response to the move . Many behaviors won ' t need to
* do anything simply in response to the element being moved , but because
* IFRAME elements reload their "src" when being moved within the DOM ,
* behaviors bound to IFRAME elements ( like WYSIWYG editors ) may need to
* take some action .
2015-09-04 13:20:09 -07:00
* - ` 'serialize' ` : When an Ajax form is submitted , this is called with the
2015-08-17 17:00:26 -07:00
* form as the context . This provides every behavior within the form an
* opportunity to ensure that the field elements have correct content
* in them before the form is serialized . The canonical use - case is so
* that WYSIWYG editors can update the hidden textarea to which they are
* bound .
*
* @ throws { Drupal ~ DrupalBehaviorError }
*
* @ see Drupal ~ behaviorDetach
* @ see Drupal . attachBehaviors
* /
Drupal . detachBehaviors = function ( context , settings , trigger ) {
context = context || document ;
settings = settings || drupalSettings ;
trigger = trigger || 'unload' ;
var behaviors = Drupal . behaviors ;
// Execute all of them.
for ( var i in behaviors ) {
if ( behaviors . hasOwnProperty ( i ) && typeof behaviors [ i ] . detach === 'function' ) {
// Don't stop the execution of behaviors in case of an error.
try {
behaviors [ i ] . detach ( context , settings , trigger ) ;
}
catch ( e ) {
2015-09-04 13:20:09 -07:00
Drupal . throwError ( e ) ;
2015-08-17 17:00:26 -07:00
}
}
}
} ;
/ * *
2015-09-04 13:20:09 -07:00
* Encodes special characters in a plain - text string for display as HTML .
2015-08-17 17:00:26 -07:00
*
* @ param { string } str
* The string to be encoded .
*
* @ return { string }
* The encoded string .
*
* @ ingroup sanitization
* /
Drupal . checkPlain = function ( str ) {
str = str . toString ( )
. replace ( /&/g , '&' )
. replace ( /"/g , '"' )
. replace ( /</g , '<' )
. replace ( />/g , '>' ) ;
return str ;
} ;
/ * *
2015-09-04 13:20:09 -07:00
* Replaces placeholders with sanitized values in a string .
2015-08-17 17:00:26 -07:00
*
* @ param { string } str
* A string with placeholders .
* @ param { object } args
* An object of replacements pairs to make . Incidences of any key in this
* array are replaced with the corresponding value . Based on the first
* character of the key , the value is escaped and / or themed :
2015-09-04 13:20:09 -07:00
* - ` '!variable' ` : inserted as is .
* - ` '@variable' ` : escape plain text to HTML ( { @ link Drupal . checkPlain } ) .
* - ` '%variable' ` : escape text and theme as a placeholder for user -
* submitted content ( { @ link Drupal . checkPlain } +
* ` {@link Drupal.theme}('placeholder') ` ) .
2015-08-17 17:00:26 -07:00
*
* @ return { string }
*
* @ see Drupal . t
* /
Drupal . formatString = function ( str , args ) {
// Keep args intact.
var processedArgs = { } ;
// Transform arguments before inserting them.
for ( var key in args ) {
if ( args . hasOwnProperty ( key ) ) {
switch ( key . charAt ( 0 ) ) {
// Escaped only.
case '@' :
processedArgs [ key ] = Drupal . checkPlain ( args [ key ] ) ;
break ;
// Pass-through.
case '!' :
processedArgs [ key ] = args [ key ] ;
break ;
// Escaped and placeholder.
default :
processedArgs [ key ] = Drupal . theme ( 'placeholder' , args [ key ] ) ;
break ;
}
}
}
return Drupal . stringReplace ( str , processedArgs , null ) ;
} ;
/ * *
2015-09-04 13:20:09 -07:00
* Replaces substring .
2015-08-17 17:00:26 -07:00
*
* The longest keys will be tried first . Once a substring has been replaced ,
* its new value will not be searched again .
*
* @ param { string } str
* A string with placeholders .
* @ param { object } args
* Key - value pairs .
* @ param { Array | null } keys
2015-09-04 13:20:09 -07:00
* Array of keys from ` args ` . Internal use only .
2015-08-17 17:00:26 -07:00
*
* @ return { string }
2015-09-04 13:20:09 -07:00
* The replaced string .
2015-08-17 17:00:26 -07:00
* /
Drupal . stringReplace = function ( str , args , keys ) {
if ( str . length === 0 ) {
return str ;
}
// If the array of keys is not passed then collect the keys from the args.
if ( ! Array . isArray ( keys ) ) {
keys = [ ] ;
for ( var k in args ) {
if ( args . hasOwnProperty ( k ) ) {
keys . push ( k ) ;
}
}
// Order the keys by the character length. The shortest one is the first.
keys . sort ( function ( a , b ) { return a . length - b . length ; } ) ;
}
if ( keys . length === 0 ) {
return str ;
}
// Take next longest one from the end.
var key = keys . pop ( ) ;
var fragments = str . split ( key ) ;
if ( keys . length ) {
for ( var i = 0 ; i < fragments . length ; i ++ ) {
// Process each fragment with a copy of remaining keys.
fragments [ i ] = Drupal . stringReplace ( fragments [ i ] , args , keys . slice ( 0 ) ) ;
}
}
return fragments . join ( args [ key ] ) ;
} ;
/ * *
2015-09-04 13:20:09 -07:00
* Translates strings to the page language , or a given language .
2015-08-17 17:00:26 -07:00
*
* See the documentation of the server - side t ( ) function for further details .
*
* @ param { string } str
2015-11-04 11:11:27 -08:00
* A string containing the English text to translate .
2015-08-17 17:00:26 -07:00
* @ param { Object . < string , string > } [ args ]
* An object of replacements pairs to make after translation . Incidences
* of any key in this array are replaced with the corresponding value .
* See { @ link Drupal . formatString } .
* @ param { object } [ options ]
2015-09-04 13:20:09 -07:00
* Additional options for translation .
2015-08-17 17:00:26 -07:00
* @ param { string } [ options . context = '' ]
* The context the source string belongs to .
*
* @ return { string }
2015-09-04 13:20:09 -07:00
* The formatted string .
2015-08-17 17:00:26 -07:00
* The translated string .
* /
Drupal . t = function ( str , args , options ) {
options = options || { } ;
options . context = options . context || '' ;
// Fetch the localized version of the string.
if ( typeof drupalTranslations !== 'undefined' && drupalTranslations . strings && drupalTranslations . strings [ options . context ] && drupalTranslations . strings [ options . context ] [ str ] ) {
str = drupalTranslations . strings [ options . context ] [ str ] ;
}
if ( args ) {
str = Drupal . formatString ( str , args ) ;
}
return str ;
} ;
/ * *
* Returns the URL to a Drupal page .
*
* @ param { string } path
* Drupal path to transform to URL .
*
* @ return { string }
2015-09-04 13:20:09 -07:00
* The full URL .
2015-08-17 17:00:26 -07:00
* /
Drupal . url = function ( path ) {
return drupalSettings . path . baseUrl + drupalSettings . path . pathPrefix + path ;
} ;
/ * *
2015-09-04 13:20:09 -07:00
* Returns the passed in URL as an absolute URL .
*
* @ param { string } url
* The URL string to be normalized to an absolute URL .
*
* @ return { string }
* The normalized , absolute URL .
*
* @ see https : //github.com/angular/angular.js/blob/v1.4.4/src/ng/urlUtils.js
* @ see https : //grack.com/blog/2009/11/17/absolutizing-url-in-javascript
* @ see https : //github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L53
* /
Drupal . url . toAbsolute = function ( url ) {
var urlParsingNode = document . createElement ( 'a' ) ;
// Decode the URL first; this is required by IE <= 6. Decoding non-UTF-8
// strings may throw an exception.
try {
url = decodeURIComponent ( url ) ;
}
catch ( e ) {
// Empty.
}
urlParsingNode . setAttribute ( 'href' , url ) ;
// IE <= 7 normalizes the URL when assigned to the anchor node similar to
// the other browsers.
return urlParsingNode . cloneNode ( false ) . href ;
} ;
/ * *
* Returns true if the URL is within Drupal ' s base path .
*
* @ param { string } url
* The URL string to be tested .
*
* @ return { bool }
* ` true ` if local .
*
* @ see https : //github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L58
* /
Drupal . url . isLocal = function ( url ) {
// Always use browser-derived absolute URLs in the comparison, to avoid
// attempts to break out of the base path using directory traversal.
var absoluteUrl = Drupal . url . toAbsolute ( url ) ;
var protocol = location . protocol ;
// Consider URLs that match this site's base URL but use HTTPS instead of HTTP
// as local as well.
if ( protocol === 'http:' && absoluteUrl . indexOf ( 'https:' ) === 0 ) {
protocol = 'https:' ;
}
var baseUrl = protocol + '//' + location . host + drupalSettings . path . baseUrl . slice ( 0 , - 1 ) ;
// Decoding non-UTF-8 strings may throw an exception.
try {
absoluteUrl = decodeURIComponent ( absoluteUrl ) ;
}
catch ( e ) {
// Empty.
}
try {
baseUrl = decodeURIComponent ( baseUrl ) ;
}
catch ( e ) {
// Empty.
}
// The given URL matches the site's base URL, or has a path under the site's
// base URL.
return absoluteUrl === baseUrl || absoluteUrl . indexOf ( baseUrl + '/' ) === 0 ;
} ;
/ * *
* Formats a string containing a count of items .
2015-08-17 17:00:26 -07:00
*
* This function ensures that the string is pluralized correctly . Since
* { @ link Drupal . t } is called by this function , make sure not to pass
* already - localized strings to it .
*
* See the documentation of the server - side
* \ Drupal \ Core \ StringTranslation \ TranslationInterface : : formatPlural ( )
* function for more details .
*
* @ param { number } count
* The item count to display .
* @ param { string } singular
* The string for the singular case . Please make sure it is clear this is
* singular , to ease translation ( e . g . use "1 new comment" instead of " 1
* new " ) . Do not use @ count in the singular string .
* @ param { string } plural
* The string for the plural case . Please make sure it is clear this is
* plural , to ease translation . Use @ count in place of the item count , as in
* "@count new comments" .
* @ param { object } [ args ]
* An object of replacements pairs to make after translation . Incidences
* of any key in this array are replaced with the corresponding value .
* See { @ link Drupal . formatString } .
* Note that you do not need to include @ count in this array .
* This replacement is done automatically for the plural case .
* @ param { object } [ options ]
* The options to pass to the { @ link Drupal . t } function .
*
* @ return { string }
* A translated string .
* /
Drupal . formatPlural = function ( count , singular , plural , args , options ) {
args = args || { } ;
args [ '@count' ] = count ;
var pluralDelimiter = drupalSettings . pluralDelimiter ;
var translations = Drupal . t ( singular + pluralDelimiter + plural , args , options ) . split ( pluralDelimiter ) ;
var index = 0 ;
// Determine the index of the plural form.
if ( typeof drupalTranslations !== 'undefined' && drupalTranslations . pluralFormula ) {
index = count in drupalTranslations . pluralFormula ? drupalTranslations . pluralFormula [ count ] : drupalTranslations . pluralFormula [ 'default' ] ;
}
else if ( args [ '@count' ] !== 1 ) {
index = 1 ;
}
return translations [ index ] ;
} ;
/ * *
* Encodes a Drupal path for use in a URL .
*
* For aesthetic reasons slashes are not escaped .
*
* @ param { string } item
* Unencoded path .
*
* @ return { string }
2015-09-04 13:20:09 -07:00
* The encoded path .
2015-08-17 17:00:26 -07:00
* /
Drupal . encodePath = function ( item ) {
return window . encodeURIComponent ( item ) . replace ( /%2F/g , '/' ) ;
} ;
/ * *
2015-09-04 13:20:09 -07:00
* Generates the themed representation of a Drupal object .
2015-08-17 17:00:26 -07:00
*
* All requests for themed output must go through this function . It examines
* the request and routes it to the appropriate theme function . If the current
* theme does not provide an override function , the generic theme function is
* called .
*
2015-09-04 13:20:09 -07:00
* @ example
* < caption > To retrieve the HTML for text that should be emphasized and
* displayed as a placeholder inside a sentence . < / c a p t i o n >
* Drupal . theme ( 'placeholder' , text ) ;
2015-08-17 17:00:26 -07:00
*
* @ namespace
*
* @ param { function } func
* The name of the theme function to call .
* @ param { ... args }
* Additional arguments to pass along to the theme function .
*
* @ return { string | object | HTMLElement | jQuery }
* Any data the theme function returns . This could be a plain HTML string ,
* but also a complex object .
* /
Drupal . theme = function ( func ) {
var args = Array . prototype . slice . apply ( arguments , [ 1 ] ) ;
if ( func in Drupal . theme ) {
return Drupal . theme [ func ] . apply ( this , args ) ;
}
} ;
/ * *
* Formats text for emphasized display in a placeholder inside a sentence .
*
* @ param { string } str
* The text to format ( plain - text ) .
*
* @ return { string }
* The formatted text ( html ) .
* /
Drupal . theme . placeholder = function ( str ) {
return '<em class="placeholder">' + Drupal . checkPlain ( str ) + '</em>' ;
} ;
} ) ( domready , Drupal , window . drupalSettings , window . drupalTranslations ) ;