Mercurial > hg > cmmr2012-drupal-site
comparison core/misc/drupal.es6.js @ 0:c75dbcec494b
Initial commit from drush-created site
author | Chris Cannam |
---|---|
date | Thu, 05 Jul 2018 14:24:15 +0000 |
parents | |
children | a9cd425dd02b |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c75dbcec494b |
---|---|
1 /** | |
2 * @file | |
3 * Defines the Drupal JavaScript API. | |
4 */ | |
5 | |
6 /** | |
7 * A jQuery object, typically the return value from a `$(selector)` call. | |
8 * | |
9 * Holds an HTMLElement or a collection of HTMLElements. | |
10 * | |
11 * @typedef {object} jQuery | |
12 * | |
13 * @prop {number} length=0 | |
14 * Number of elements contained in the jQuery object. | |
15 */ | |
16 | |
17 /** | |
18 * Variable generated by Drupal that holds all translated strings from PHP. | |
19 * | |
20 * Content of this variable is automatically created by Drupal when using the | |
21 * Interface Translation module. It holds the translation of strings used on | |
22 * the page. | |
23 * | |
24 * This variable is used to pass data from the backend to the frontend. Data | |
25 * contained in `drupalSettings` is used during behavior initialization. | |
26 * | |
27 * @global | |
28 * | |
29 * @var {object} drupalTranslations | |
30 */ | |
31 | |
32 /** | |
33 * Global Drupal object. | |
34 * | |
35 * All Drupal JavaScript APIs are contained in this namespace. | |
36 * | |
37 * @global | |
38 * | |
39 * @namespace | |
40 */ | |
41 window.Drupal = { behaviors: {}, locale: {} }; | |
42 | |
43 // JavaScript should be made compatible with libraries other than jQuery by | |
44 // wrapping it in an anonymous closure. | |
45 (function (Drupal, drupalSettings, drupalTranslations) { | |
46 /** | |
47 * Helper to rethrow errors asynchronously. | |
48 * | |
49 * This way Errors bubbles up outside of the original callstack, making it | |
50 * easier to debug errors in the browser. | |
51 * | |
52 * @param {Error|string} error | |
53 * The error to be thrown. | |
54 */ | |
55 Drupal.throwError = function (error) { | |
56 setTimeout(() => { | |
57 throw error; | |
58 }, 0); | |
59 }; | |
60 | |
61 /** | |
62 * Custom error thrown after attach/detach if one or more behaviors failed. | |
63 * Initializes the JavaScript behaviors for page loads and Ajax requests. | |
64 * | |
65 * @callback Drupal~behaviorAttach | |
66 * | |
67 * @param {HTMLDocument|HTMLElement} context | |
68 * An element to detach behaviors from. | |
69 * @param {?object} settings | |
70 * An object containing settings for the current context. It is rarely used. | |
71 * | |
72 * @see Drupal.attachBehaviors | |
73 */ | |
74 | |
75 /** | |
76 * Reverts and cleans up JavaScript behavior initialization. | |
77 * | |
78 * @callback Drupal~behaviorDetach | |
79 * | |
80 * @param {HTMLDocument|HTMLElement} context | |
81 * An element to attach behaviors to. | |
82 * @param {object} settings | |
83 * An object containing settings for the current context. | |
84 * @param {string} trigger | |
85 * One of `'unload'`, `'move'`, or `'serialize'`. | |
86 * | |
87 * @see Drupal.detachBehaviors | |
88 */ | |
89 | |
90 /** | |
91 * @typedef {object} Drupal~behavior | |
92 * | |
93 * @prop {Drupal~behaviorAttach} attach | |
94 * Function run on page load and after an Ajax call. | |
95 * @prop {Drupal~behaviorDetach} detach | |
96 * Function run when content is serialized or removed from the page. | |
97 */ | |
98 | |
99 /** | |
100 * Holds all initialization methods. | |
101 * | |
102 * @namespace Drupal.behaviors | |
103 * | |
104 * @type {Object.<string, Drupal~behavior>} | |
105 */ | |
106 | |
107 /** | |
108 * Defines a behavior to be run during attach and detach phases. | |
109 * | |
110 * Attaches all registered behaviors to a page element. | |
111 * | |
112 * Behaviors are event-triggered actions that attach to page elements, | |
113 * enhancing default non-JavaScript UIs. Behaviors are registered in the | |
114 * {@link Drupal.behaviors} object using the method 'attach' and optionally | |
115 * also 'detach'. | |
116 * | |
117 * {@link Drupal.attachBehaviors} is added below to the `jQuery.ready` event | |
118 * and therefore runs on initial page load. Developers implementing Ajax in | |
119 * their solutions should also call this function after new page content has | |
120 * been loaded, feeding in an element to be processed, in order to attach all | |
121 * behaviors to the new content. | |
122 * | |
123 * Behaviors should use `var elements = | |
124 * $(context).find(selector).once('behavior-name');` to ensure the behavior is | |
125 * attached only once to a given element. (Doing so enables the reprocessing | |
126 * of given elements, which may be needed on occasion despite the ability to | |
127 * limit behavior attachment to a particular element.) | |
128 * | |
129 * @example | |
130 * Drupal.behaviors.behaviorName = { | |
131 * attach: function (context, settings) { | |
132 * // ... | |
133 * }, | |
134 * detach: function (context, settings, trigger) { | |
135 * // ... | |
136 * } | |
137 * }; | |
138 * | |
139 * @param {HTMLDocument|HTMLElement} [context=document] | |
140 * An element to attach behaviors to. | |
141 * @param {object} [settings=drupalSettings] | |
142 * An object containing settings for the current context. If none is given, | |
143 * the global {@link drupalSettings} object is used. | |
144 * | |
145 * @see Drupal~behaviorAttach | |
146 * @see Drupal.detachBehaviors | |
147 * | |
148 * @throws {Drupal~DrupalBehaviorError} | |
149 */ | |
150 Drupal.attachBehaviors = function (context, settings) { | |
151 context = context || document; | |
152 settings = settings || drupalSettings; | |
153 const behaviors = Drupal.behaviors; | |
154 // Execute all of them. | |
155 Object.keys(behaviors || {}).forEach((i) => { | |
156 if (typeof behaviors[i].attach === 'function') { | |
157 // Don't stop the execution of behaviors in case of an error. | |
158 try { | |
159 behaviors[i].attach(context, settings); | |
160 } | |
161 catch (e) { | |
162 Drupal.throwError(e); | |
163 } | |
164 } | |
165 }); | |
166 }; | |
167 | |
168 /** | |
169 * Detaches registered behaviors from a page element. | |
170 * | |
171 * Developers implementing Ajax in their solutions should call this function | |
172 * before page content is about to be removed, feeding in an element to be | |
173 * processed, in order to allow special behaviors to detach from the content. | |
174 * | |
175 * Such implementations should use `.findOnce()` and `.removeOnce()` to find | |
176 * elements with their corresponding `Drupal.behaviors.behaviorName.attach` | |
177 * implementation, i.e. `.removeOnce('behaviorName')`, to ensure the behavior | |
178 * is detached only from previously processed elements. | |
179 * | |
180 * @param {HTMLDocument|HTMLElement} [context=document] | |
181 * An element to detach behaviors from. | |
182 * @param {object} [settings=drupalSettings] | |
183 * An object containing settings for the current context. If none given, | |
184 * the global {@link drupalSettings} object is used. | |
185 * @param {string} [trigger='unload'] | |
186 * A string containing what's causing the behaviors to be detached. The | |
187 * possible triggers are: | |
188 * - `'unload'`: The context element is being removed from the DOM. | |
189 * - `'move'`: The element is about to be moved within the DOM (for example, | |
190 * during a tabledrag row swap). After the move is completed, | |
191 * {@link Drupal.attachBehaviors} is called, so that the behavior can undo | |
192 * whatever it did in response to the move. Many behaviors won't need to | |
193 * do anything simply in response to the element being moved, but because | |
194 * IFRAME elements reload their "src" when being moved within the DOM, | |
195 * behaviors bound to IFRAME elements (like WYSIWYG editors) may need to | |
196 * take some action. | |
197 * - `'serialize'`: When an Ajax form is submitted, this is called with the | |
198 * form as the context. This provides every behavior within the form an | |
199 * opportunity to ensure that the field elements have correct content | |
200 * in them before the form is serialized. The canonical use-case is so | |
201 * that WYSIWYG editors can update the hidden textarea to which they are | |
202 * bound. | |
203 * | |
204 * @throws {Drupal~DrupalBehaviorError} | |
205 * | |
206 * @see Drupal~behaviorDetach | |
207 * @see Drupal.attachBehaviors | |
208 */ | |
209 Drupal.detachBehaviors = function (context, settings, trigger) { | |
210 context = context || document; | |
211 settings = settings || drupalSettings; | |
212 trigger = trigger || 'unload'; | |
213 const behaviors = Drupal.behaviors; | |
214 // Execute all of them. | |
215 Object.keys(behaviors || {}).forEach((i) => { | |
216 if (typeof behaviors[i].detach === 'function') { | |
217 // Don't stop the execution of behaviors in case of an error. | |
218 try { | |
219 behaviors[i].detach(context, settings, trigger); | |
220 } | |
221 catch (e) { | |
222 Drupal.throwError(e); | |
223 } | |
224 } | |
225 }); | |
226 }; | |
227 | |
228 /** | |
229 * Encodes special characters in a plain-text string for display as HTML. | |
230 * | |
231 * @param {string} str | |
232 * The string to be encoded. | |
233 * | |
234 * @return {string} | |
235 * The encoded string. | |
236 * | |
237 * @ingroup sanitization | |
238 */ | |
239 Drupal.checkPlain = function (str) { | |
240 str = str.toString() | |
241 .replace(/&/g, '&') | |
242 .replace(/</g, '<') | |
243 .replace(/>/g, '>') | |
244 .replace(/"/g, '"') | |
245 .replace(/'/g, '''); | |
246 return str; | |
247 }; | |
248 | |
249 /** | |
250 * Replaces placeholders with sanitized values in a string. | |
251 * | |
252 * @param {string} str | |
253 * A string with placeholders. | |
254 * @param {object} args | |
255 * An object of replacements pairs to make. Incidences of any key in this | |
256 * array are replaced with the corresponding value. Based on the first | |
257 * character of the key, the value is escaped and/or themed: | |
258 * - `'!variable'`: inserted as is. | |
259 * - `'@variable'`: escape plain text to HTML ({@link Drupal.checkPlain}). | |
260 * - `'%variable'`: escape text and theme as a placeholder for user- | |
261 * submitted content ({@link Drupal.checkPlain} + | |
262 * `{@link Drupal.theme}('placeholder')`). | |
263 * | |
264 * @return {string} | |
265 * The formatted string. | |
266 * | |
267 * @see Drupal.t | |
268 */ | |
269 Drupal.formatString = function (str, args) { | |
270 // Keep args intact. | |
271 const processedArgs = {}; | |
272 // Transform arguments before inserting them. | |
273 Object.keys(args || {}).forEach((key) => { | |
274 switch (key.charAt(0)) { | |
275 // Escaped only. | |
276 case '@': | |
277 processedArgs[key] = Drupal.checkPlain(args[key]); | |
278 break; | |
279 | |
280 // Pass-through. | |
281 case '!': | |
282 processedArgs[key] = args[key]; | |
283 break; | |
284 | |
285 // Escaped and placeholder. | |
286 default: | |
287 processedArgs[key] = Drupal.theme('placeholder', args[key]); | |
288 break; | |
289 } | |
290 }); | |
291 | |
292 return Drupal.stringReplace(str, processedArgs, null); | |
293 }; | |
294 | |
295 /** | |
296 * Replaces substring. | |
297 * | |
298 * The longest keys will be tried first. Once a substring has been replaced, | |
299 * its new value will not be searched again. | |
300 * | |
301 * @param {string} str | |
302 * A string with placeholders. | |
303 * @param {object} args | |
304 * Key-value pairs. | |
305 * @param {Array|null} keys | |
306 * Array of keys from `args`. Internal use only. | |
307 * | |
308 * @return {string} | |
309 * The replaced string. | |
310 */ | |
311 Drupal.stringReplace = function (str, args, keys) { | |
312 if (str.length === 0) { | |
313 return str; | |
314 } | |
315 | |
316 // If the array of keys is not passed then collect the keys from the args. | |
317 if (!Array.isArray(keys)) { | |
318 keys = Object.keys(args || {}); | |
319 | |
320 // Order the keys by the character length. The shortest one is the first. | |
321 keys.sort((a, b) => a.length - b.length); | |
322 } | |
323 | |
324 if (keys.length === 0) { | |
325 return str; | |
326 } | |
327 | |
328 // Take next longest one from the end. | |
329 const key = keys.pop(); | |
330 const fragments = str.split(key); | |
331 | |
332 if (keys.length) { | |
333 for (let i = 0; i < fragments.length; i++) { | |
334 // Process each fragment with a copy of remaining keys. | |
335 fragments[i] = Drupal.stringReplace(fragments[i], args, keys.slice(0)); | |
336 } | |
337 } | |
338 | |
339 return fragments.join(args[key]); | |
340 }; | |
341 | |
342 /** | |
343 * Translates strings to the page language, or a given language. | |
344 * | |
345 * See the documentation of the server-side t() function for further details. | |
346 * | |
347 * @param {string} str | |
348 * A string containing the English text to translate. | |
349 * @param {Object.<string, string>} [args] | |
350 * An object of replacements pairs to make after translation. Incidences | |
351 * of any key in this array are replaced with the corresponding value. | |
352 * See {@link Drupal.formatString}. | |
353 * @param {object} [options] | |
354 * Additional options for translation. | |
355 * @param {string} [options.context=''] | |
356 * The context the source string belongs to. | |
357 * | |
358 * @return {string} | |
359 * The formatted string. | |
360 * The translated string. | |
361 */ | |
362 Drupal.t = function (str, args, options) { | |
363 options = options || {}; | |
364 options.context = options.context || ''; | |
365 | |
366 // Fetch the localized version of the string. | |
367 if (typeof drupalTranslations !== 'undefined' && drupalTranslations.strings && drupalTranslations.strings[options.context] && drupalTranslations.strings[options.context][str]) { | |
368 str = drupalTranslations.strings[options.context][str]; | |
369 } | |
370 | |
371 if (args) { | |
372 str = Drupal.formatString(str, args); | |
373 } | |
374 return str; | |
375 }; | |
376 | |
377 /** | |
378 * Returns the URL to a Drupal page. | |
379 * | |
380 * @param {string} path | |
381 * Drupal path to transform to URL. | |
382 * | |
383 * @return {string} | |
384 * The full URL. | |
385 */ | |
386 Drupal.url = function (path) { | |
387 return drupalSettings.path.baseUrl + drupalSettings.path.pathPrefix + path; | |
388 }; | |
389 | |
390 /** | |
391 * Returns the passed in URL as an absolute URL. | |
392 * | |
393 * @param {string} url | |
394 * The URL string to be normalized to an absolute URL. | |
395 * | |
396 * @return {string} | |
397 * The normalized, absolute URL. | |
398 * | |
399 * @see https://github.com/angular/angular.js/blob/v1.4.4/src/ng/urlUtils.js | |
400 * @see https://grack.com/blog/2009/11/17/absolutizing-url-in-javascript | |
401 * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L53 | |
402 */ | |
403 Drupal.url.toAbsolute = function (url) { | |
404 const urlParsingNode = document.createElement('a'); | |
405 | |
406 // Decode the URL first; this is required by IE <= 6. Decoding non-UTF-8 | |
407 // strings may throw an exception. | |
408 try { | |
409 url = decodeURIComponent(url); | |
410 } | |
411 catch (e) { | |
412 // Empty. | |
413 } | |
414 | |
415 urlParsingNode.setAttribute('href', url); | |
416 | |
417 // IE <= 7 normalizes the URL when assigned to the anchor node similar to | |
418 // the other browsers. | |
419 return urlParsingNode.cloneNode(false).href; | |
420 }; | |
421 | |
422 /** | |
423 * Returns true if the URL is within Drupal's base path. | |
424 * | |
425 * @param {string} url | |
426 * The URL string to be tested. | |
427 * | |
428 * @return {bool} | |
429 * `true` if local. | |
430 * | |
431 * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L58 | |
432 */ | |
433 Drupal.url.isLocal = function (url) { | |
434 // Always use browser-derived absolute URLs in the comparison, to avoid | |
435 // attempts to break out of the base path using directory traversal. | |
436 let absoluteUrl = Drupal.url.toAbsolute(url); | |
437 let protocol = location.protocol; | |
438 | |
439 // Consider URLs that match this site's base URL but use HTTPS instead of HTTP | |
440 // as local as well. | |
441 if (protocol === 'http:' && absoluteUrl.indexOf('https:') === 0) { | |
442 protocol = 'https:'; | |
443 } | |
444 let baseUrl = `${protocol}//${location.host}${drupalSettings.path.baseUrl.slice(0, -1)}`; | |
445 | |
446 // Decoding non-UTF-8 strings may throw an exception. | |
447 try { | |
448 absoluteUrl = decodeURIComponent(absoluteUrl); | |
449 } | |
450 catch (e) { | |
451 // Empty. | |
452 } | |
453 try { | |
454 baseUrl = decodeURIComponent(baseUrl); | |
455 } | |
456 catch (e) { | |
457 // Empty. | |
458 } | |
459 | |
460 // The given URL matches the site's base URL, or has a path under the site's | |
461 // base URL. | |
462 return absoluteUrl === baseUrl || absoluteUrl.indexOf(`${baseUrl}/`) === 0; | |
463 }; | |
464 | |
465 /** | |
466 * Formats a string containing a count of items. | |
467 * | |
468 * This function ensures that the string is pluralized correctly. Since | |
469 * {@link Drupal.t} is called by this function, make sure not to pass | |
470 * already-localized strings to it. | |
471 * | |
472 * See the documentation of the server-side | |
473 * \Drupal\Core\StringTranslation\TranslationInterface::formatPlural() | |
474 * function for more details. | |
475 * | |
476 * @param {number} count | |
477 * The item count to display. | |
478 * @param {string} singular | |
479 * The string for the singular case. Please make sure it is clear this is | |
480 * singular, to ease translation (e.g. use "1 new comment" instead of "1 | |
481 * new"). Do not use @count in the singular string. | |
482 * @param {string} plural | |
483 * The string for the plural case. Please make sure it is clear this is | |
484 * plural, to ease translation. Use @count in place of the item count, as in | |
485 * "@count new comments". | |
486 * @param {object} [args] | |
487 * An object of replacements pairs to make after translation. Incidences | |
488 * of any key in this array are replaced with the corresponding value. | |
489 * See {@link Drupal.formatString}. | |
490 * Note that you do not need to include @count in this array. | |
491 * This replacement is done automatically for the plural case. | |
492 * @param {object} [options] | |
493 * The options to pass to the {@link Drupal.t} function. | |
494 * | |
495 * @return {string} | |
496 * A translated string. | |
497 */ | |
498 Drupal.formatPlural = function (count, singular, plural, args, options) { | |
499 args = args || {}; | |
500 args['@count'] = count; | |
501 | |
502 const pluralDelimiter = drupalSettings.pluralDelimiter; | |
503 const translations = Drupal.t(singular + pluralDelimiter + plural, args, options).split(pluralDelimiter); | |
504 let index = 0; | |
505 | |
506 // Determine the index of the plural form. | |
507 if (typeof drupalTranslations !== 'undefined' && drupalTranslations.pluralFormula) { | |
508 index = count in drupalTranslations.pluralFormula ? drupalTranslations.pluralFormula[count] : drupalTranslations.pluralFormula.default; | |
509 } | |
510 else if (args['@count'] !== 1) { | |
511 index = 1; | |
512 } | |
513 | |
514 return translations[index]; | |
515 }; | |
516 | |
517 /** | |
518 * Encodes a Drupal path for use in a URL. | |
519 * | |
520 * For aesthetic reasons slashes are not escaped. | |
521 * | |
522 * @param {string} item | |
523 * Unencoded path. | |
524 * | |
525 * @return {string} | |
526 * The encoded path. | |
527 */ | |
528 Drupal.encodePath = function (item) { | |
529 return window.encodeURIComponent(item).replace(/%2F/g, '/'); | |
530 }; | |
531 | |
532 /** | |
533 * Generates the themed representation of a Drupal object. | |
534 * | |
535 * All requests for themed output must go through this function. It examines | |
536 * the request and routes it to the appropriate theme function. If the current | |
537 * theme does not provide an override function, the generic theme function is | |
538 * called. | |
539 * | |
540 * @example | |
541 * <caption>To retrieve the HTML for text that should be emphasized and | |
542 * displayed as a placeholder inside a sentence.</caption> | |
543 * Drupal.theme('placeholder', text); | |
544 * | |
545 * @namespace | |
546 * | |
547 * @param {function} func | |
548 * The name of the theme function to call. | |
549 * @param {...args} | |
550 * Additional arguments to pass along to the theme function. | |
551 * | |
552 * @return {string|object|HTMLElement|jQuery} | |
553 * Any data the theme function returns. This could be a plain HTML string, | |
554 * but also a complex object. | |
555 */ | |
556 Drupal.theme = function (func, ...args) { | |
557 if (func in Drupal.theme) { | |
558 return Drupal.theme[func](...args); | |
559 } | |
560 }; | |
561 | |
562 /** | |
563 * Formats text for emphasized display in a placeholder inside a sentence. | |
564 * | |
565 * @param {string} str | |
566 * The text to format (plain-text). | |
567 * | |
568 * @return {string} | |
569 * The formatted text (html). | |
570 */ | |
571 Drupal.theme.placeholder = function (str) { | |
572 return `<em class="placeholder">${Drupal.checkPlain(str)}</em>`; | |
573 }; | |
574 }(Drupal, window.drupalSettings, window.drupalTranslations)); |