|
1 |
|
2 var Drupal = Drupal || { 'settings': {}, 'behaviors': {}, 'locale': {} }; |
|
3 |
|
4 // Allow other JavaScript libraries to use $. |
|
5 jQuery.noConflict(); |
|
6 |
|
7 (function ($) { |
|
8 |
|
9 /** |
|
10 * Override jQuery.fn.init to guard against XSS attacks. |
|
11 * |
|
12 * See http://bugs.jquery.com/ticket/9521 |
|
13 */ |
|
14 var jquery_init = $.fn.init; |
|
15 $.fn.init = function (selector, context, rootjQuery) { |
|
16 // If the string contains a "#" before a "<", treat it as invalid HTML. |
|
17 if (selector && typeof selector === 'string') { |
|
18 var hash_position = selector.indexOf('#'); |
|
19 if (hash_position >= 0) { |
|
20 var bracket_position = selector.indexOf('<'); |
|
21 if (bracket_position > hash_position) { |
|
22 throw 'Syntax error, unrecognized expression: ' + selector; |
|
23 } |
|
24 } |
|
25 } |
|
26 return jquery_init.call(this, selector, context, rootjQuery); |
|
27 }; |
|
28 $.fn.init.prototype = jquery_init.prototype; |
|
29 |
|
30 /** |
|
31 * Attach all registered behaviors to a page element. |
|
32 * |
|
33 * Behaviors are event-triggered actions that attach to page elements, enhancing |
|
34 * default non-JavaScript UIs. Behaviors are registered in the Drupal.behaviors |
|
35 * object using the method 'attach' and optionally also 'detach' as follows: |
|
36 * @code |
|
37 * Drupal.behaviors.behaviorName = { |
|
38 * attach: function (context, settings) { |
|
39 * ... |
|
40 * }, |
|
41 * detach: function (context, settings, trigger) { |
|
42 * ... |
|
43 * } |
|
44 * }; |
|
45 * @endcode |
|
46 * |
|
47 * Drupal.attachBehaviors is added below to the jQuery ready event and so |
|
48 * runs on initial page load. Developers implementing AHAH/Ajax in their |
|
49 * solutions should also call this function after new page content has been |
|
50 * loaded, feeding in an element to be processed, in order to attach all |
|
51 * behaviors to the new content. |
|
52 * |
|
53 * Behaviors should use |
|
54 * @code |
|
55 * $(selector).once('behavior-name', function () { |
|
56 * ... |
|
57 * }); |
|
58 * @endcode |
|
59 * to ensure the behavior is attached only once to a given element. (Doing so |
|
60 * enables the reprocessing of given elements, which may be needed on occasion |
|
61 * despite the ability to limit behavior attachment to a particular element.) |
|
62 * |
|
63 * @param context |
|
64 * An element to attach behaviors to. If none is given, the document element |
|
65 * is used. |
|
66 * @param settings |
|
67 * An object containing settings for the current context. If none given, the |
|
68 * global Drupal.settings object is used. |
|
69 */ |
|
70 Drupal.attachBehaviors = function (context, settings) { |
|
71 context = context || document; |
|
72 settings = settings || Drupal.settings; |
|
73 // Execute all of them. |
|
74 $.each(Drupal.behaviors, function () { |
|
75 if ($.isFunction(this.attach)) { |
|
76 this.attach(context, settings); |
|
77 } |
|
78 }); |
|
79 }; |
|
80 |
|
81 /** |
|
82 * Detach registered behaviors from a page element. |
|
83 * |
|
84 * Developers implementing AHAH/Ajax in their solutions should call this |
|
85 * function before page content is about to be removed, feeding in an element |
|
86 * to be processed, in order to allow special behaviors to detach from the |
|
87 * content. |
|
88 * |
|
89 * Such implementations should look for the class name that was added in their |
|
90 * corresponding Drupal.behaviors.behaviorName.attach implementation, i.e. |
|
91 * behaviorName-processed, to ensure the behavior is detached only from |
|
92 * previously processed elements. |
|
93 * |
|
94 * @param context |
|
95 * An element to detach behaviors from. If none is given, the document element |
|
96 * is used. |
|
97 * @param settings |
|
98 * An object containing settings for the current context. If none given, the |
|
99 * global Drupal.settings object is used. |
|
100 * @param trigger |
|
101 * A string containing what's causing the behaviors to be detached. The |
|
102 * possible triggers are: |
|
103 * - unload: (default) The context element is being removed from the DOM. |
|
104 * - move: The element is about to be moved within the DOM (for example, |
|
105 * during a tabledrag row swap). After the move is completed, |
|
106 * Drupal.attachBehaviors() is called, so that the behavior can undo |
|
107 * whatever it did in response to the move. Many behaviors won't need to |
|
108 * do anything simply in response to the element being moved, but because |
|
109 * IFRAME elements reload their "src" when being moved within the DOM, |
|
110 * behaviors bound to IFRAME elements (like WYSIWYG editors) may need to |
|
111 * take some action. |
|
112 * - serialize: When an Ajax form is submitted, this is called with the |
|
113 * form as the context. This provides every behavior within the form an |
|
114 * opportunity to ensure that the field elements have correct content |
|
115 * in them before the form is serialized. The canonical use-case is so |
|
116 * that WYSIWYG editors can update the hidden textarea to which they are |
|
117 * bound. |
|
118 * |
|
119 * @see Drupal.attachBehaviors |
|
120 */ |
|
121 Drupal.detachBehaviors = function (context, settings, trigger) { |
|
122 context = context || document; |
|
123 settings = settings || Drupal.settings; |
|
124 trigger = trigger || 'unload'; |
|
125 // Execute all of them. |
|
126 $.each(Drupal.behaviors, function () { |
|
127 if ($.isFunction(this.detach)) { |
|
128 this.detach(context, settings, trigger); |
|
129 } |
|
130 }); |
|
131 }; |
|
132 |
|
133 /** |
|
134 * Encode special characters in a plain-text string for display as HTML. |
|
135 * |
|
136 * @ingroup sanitization |
|
137 */ |
|
138 Drupal.checkPlain = function (str) { |
|
139 var character, regex, |
|
140 replace = { '&': '&', '"': '"', '<': '<', '>': '>' }; |
|
141 str = String(str); |
|
142 for (character in replace) { |
|
143 if (replace.hasOwnProperty(character)) { |
|
144 regex = new RegExp(character, 'g'); |
|
145 str = str.replace(regex, replace[character]); |
|
146 } |
|
147 } |
|
148 return str; |
|
149 }; |
|
150 |
|
151 /** |
|
152 * Replace placeholders with sanitized values in a string. |
|
153 * |
|
154 * @param str |
|
155 * A string with placeholders. |
|
156 * @param args |
|
157 * An object of replacements pairs to make. Incidences of any key in this |
|
158 * array are replaced with the corresponding value. Based on the first |
|
159 * character of the key, the value is escaped and/or themed: |
|
160 * - !variable: inserted as is |
|
161 * - @variable: escape plain text to HTML (Drupal.checkPlain) |
|
162 * - %variable: escape text and theme as a placeholder for user-submitted |
|
163 * content (checkPlain + Drupal.theme('placeholder')) |
|
164 * |
|
165 * @see Drupal.t() |
|
166 * @ingroup sanitization |
|
167 */ |
|
168 Drupal.formatString = function(str, args) { |
|
169 // Transform arguments before inserting them. |
|
170 for (var key in args) { |
|
171 if (args.hasOwnProperty(key)) { |
|
172 switch (key.charAt(0)) { |
|
173 // Escaped only. |
|
174 case '@': |
|
175 args[key] = Drupal.checkPlain(args[key]); |
|
176 break; |
|
177 // Pass-through. |
|
178 case '!': |
|
179 break; |
|
180 // Escaped and placeholder. |
|
181 default: |
|
182 args[key] = Drupal.theme('placeholder', args[key]); |
|
183 break; |
|
184 } |
|
185 } |
|
186 } |
|
187 |
|
188 return Drupal.stringReplace(str, args, null); |
|
189 }; |
|
190 |
|
191 /** |
|
192 * Replace substring. |
|
193 * |
|
194 * The longest keys will be tried first. Once a substring has been replaced, |
|
195 * its new value will not be searched again. |
|
196 * |
|
197 * @param {String} str |
|
198 * A string with placeholders. |
|
199 * @param {Object} args |
|
200 * Key-value pairs. |
|
201 * @param {Array|null} keys |
|
202 * Array of keys from the "args". Internal use only. |
|
203 * |
|
204 * @return {String} |
|
205 * Returns the replaced string. |
|
206 */ |
|
207 Drupal.stringReplace = function (str, args, keys) { |
|
208 if (str.length === 0) { |
|
209 return str; |
|
210 } |
|
211 |
|
212 // If the array of keys is not passed then collect the keys from the args. |
|
213 if (!$.isArray(keys)) { |
|
214 keys = []; |
|
215 for (var k in args) { |
|
216 if (args.hasOwnProperty(k)) { |
|
217 keys.push(k); |
|
218 } |
|
219 } |
|
220 |
|
221 // Order the keys by the character length. The shortest one is the first. |
|
222 keys.sort(function (a, b) { return a.length - b.length; }); |
|
223 } |
|
224 |
|
225 if (keys.length === 0) { |
|
226 return str; |
|
227 } |
|
228 |
|
229 // Take next longest one from the end. |
|
230 var key = keys.pop(); |
|
231 var fragments = str.split(key); |
|
232 |
|
233 if (keys.length) { |
|
234 for (var i = 0; i < fragments.length; i++) { |
|
235 // Process each fragment with a copy of remaining keys. |
|
236 fragments[i] = Drupal.stringReplace(fragments[i], args, keys.slice(0)); |
|
237 } |
|
238 } |
|
239 |
|
240 return fragments.join(args[key]); |
|
241 }; |
|
242 |
|
243 /** |
|
244 * Translate strings to the page language or a given language. |
|
245 * |
|
246 * See the documentation of the server-side t() function for further details. |
|
247 * |
|
248 * @param str |
|
249 * A string containing the English string to translate. |
|
250 * @param args |
|
251 * An object of replacements pairs to make after translation. Incidences |
|
252 * of any key in this array are replaced with the corresponding value. |
|
253 * See Drupal.formatString(). |
|
254 * |
|
255 * @param options |
|
256 * - 'context' (defaults to the empty context): The context the source string |
|
257 * belongs to. |
|
258 * |
|
259 * @return |
|
260 * The translated string. |
|
261 */ |
|
262 Drupal.t = function (str, args, options) { |
|
263 options = options || {}; |
|
264 options.context = options.context || ''; |
|
265 |
|
266 // Fetch the localized version of the string. |
|
267 if (Drupal.locale.strings && Drupal.locale.strings[options.context] && Drupal.locale.strings[options.context][str]) { |
|
268 str = Drupal.locale.strings[options.context][str]; |
|
269 } |
|
270 |
|
271 if (args) { |
|
272 str = Drupal.formatString(str, args); |
|
273 } |
|
274 return str; |
|
275 }; |
|
276 |
|
277 /** |
|
278 * Format a string containing a count of items. |
|
279 * |
|
280 * This function ensures that the string is pluralized correctly. Since Drupal.t() is |
|
281 * called by this function, make sure not to pass already-localized strings to it. |
|
282 * |
|
283 * See the documentation of the server-side format_plural() function for further details. |
|
284 * |
|
285 * @param count |
|
286 * The item count to display. |
|
287 * @param singular |
|
288 * The string for the singular case. Please make sure it is clear this is |
|
289 * singular, to ease translation (e.g. use "1 new comment" instead of "1 new"). |
|
290 * Do not use @count in the singular string. |
|
291 * @param plural |
|
292 * The string for the plural case. Please make sure it is clear this is plural, |
|
293 * to ease translation. Use @count in place of the item count, as in "@count |
|
294 * new comments". |
|
295 * @param args |
|
296 * An object of replacements pairs to make after translation. Incidences |
|
297 * of any key in this array are replaced with the corresponding value. |
|
298 * See Drupal.formatString(). |
|
299 * Note that you do not need to include @count in this array. |
|
300 * This replacement is done automatically for the plural case. |
|
301 * @param options |
|
302 * The options to pass to the Drupal.t() function. |
|
303 * @return |
|
304 * A translated string. |
|
305 */ |
|
306 Drupal.formatPlural = function (count, singular, plural, args, options) { |
|
307 args = args || {}; |
|
308 args['@count'] = count; |
|
309 // Determine the index of the plural form. |
|
310 var index = Drupal.locale.pluralFormula ? Drupal.locale.pluralFormula(args['@count']) : ((args['@count'] == 1) ? 0 : 1); |
|
311 |
|
312 if (index == 0) { |
|
313 return Drupal.t(singular, args, options); |
|
314 } |
|
315 else if (index == 1) { |
|
316 return Drupal.t(plural, args, options); |
|
317 } |
|
318 else { |
|
319 args['@count[' + index + ']'] = args['@count']; |
|
320 delete args['@count']; |
|
321 return Drupal.t(plural.replace('@count', '@count[' + index + ']'), args, options); |
|
322 } |
|
323 }; |
|
324 |
|
325 /** |
|
326 * Returns the passed in URL as an absolute URL. |
|
327 * |
|
328 * @param url |
|
329 * The URL string to be normalized to an absolute URL. |
|
330 * |
|
331 * @return |
|
332 * The normalized, absolute URL. |
|
333 * |
|
334 * @see https://github.com/angular/angular.js/blob/v1.4.4/src/ng/urlUtils.js |
|
335 * @see https://grack.com/blog/2009/11/17/absolutizing-url-in-javascript |
|
336 * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L53 |
|
337 */ |
|
338 Drupal.absoluteUrl = function (url) { |
|
339 var urlParsingNode = document.createElement('a'); |
|
340 |
|
341 // Decode the URL first; this is required by IE <= 6. Decoding non-UTF-8 |
|
342 // strings may throw an exception. |
|
343 try { |
|
344 url = decodeURIComponent(url); |
|
345 } catch (e) {} |
|
346 |
|
347 urlParsingNode.setAttribute('href', url); |
|
348 |
|
349 // IE <= 7 normalizes the URL when assigned to the anchor node similar to |
|
350 // the other browsers. |
|
351 return urlParsingNode.cloneNode(false).href; |
|
352 }; |
|
353 |
|
354 /** |
|
355 * Returns true if the URL is within Drupal's base path. |
|
356 * |
|
357 * @param url |
|
358 * The URL string to be tested. |
|
359 * |
|
360 * @return |
|
361 * Boolean true if local. |
|
362 * |
|
363 * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L58 |
|
364 */ |
|
365 Drupal.urlIsLocal = function (url) { |
|
366 // Always use browser-derived absolute URLs in the comparison, to avoid |
|
367 // attempts to break out of the base path using directory traversal. |
|
368 var absoluteUrl = Drupal.absoluteUrl(url); |
|
369 var protocol = location.protocol; |
|
370 |
|
371 // Consider URLs that match this site's base URL but use HTTPS instead of HTTP |
|
372 // as local as well. |
|
373 if (protocol === 'http:' && absoluteUrl.indexOf('https:') === 0) { |
|
374 protocol = 'https:'; |
|
375 } |
|
376 var baseUrl = protocol + '//' + location.host + Drupal.settings.basePath.slice(0, -1); |
|
377 |
|
378 // Decoding non-UTF-8 strings may throw an exception. |
|
379 try { |
|
380 absoluteUrl = decodeURIComponent(absoluteUrl); |
|
381 } catch (e) {} |
|
382 try { |
|
383 baseUrl = decodeURIComponent(baseUrl); |
|
384 } catch (e) {} |
|
385 |
|
386 // The given URL matches the site's base URL, or has a path under the site's |
|
387 // base URL. |
|
388 return absoluteUrl === baseUrl || absoluteUrl.indexOf(baseUrl + '/') === 0; |
|
389 }; |
|
390 |
|
391 /** |
|
392 * Generate the themed representation of a Drupal object. |
|
393 * |
|
394 * All requests for themed output must go through this function. It examines |
|
395 * the request and routes it to the appropriate theme function. If the current |
|
396 * theme does not provide an override function, the generic theme function is |
|
397 * called. |
|
398 * |
|
399 * For example, to retrieve the HTML for text that should be emphasized and |
|
400 * displayed as a placeholder inside a sentence, call |
|
401 * Drupal.theme('placeholder', text). |
|
402 * |
|
403 * @param func |
|
404 * The name of the theme function to call. |
|
405 * @param ... |
|
406 * Additional arguments to pass along to the theme function. |
|
407 * @return |
|
408 * Any data the theme function returns. This could be a plain HTML string, |
|
409 * but also a complex object. |
|
410 */ |
|
411 Drupal.theme = function (func) { |
|
412 var args = Array.prototype.slice.apply(arguments, [1]); |
|
413 |
|
414 return (Drupal.theme[func] || Drupal.theme.prototype[func]).apply(this, args); |
|
415 }; |
|
416 |
|
417 /** |
|
418 * Freeze the current body height (as minimum height). Used to prevent |
|
419 * unnecessary upwards scrolling when doing DOM manipulations. |
|
420 */ |
|
421 Drupal.freezeHeight = function () { |
|
422 Drupal.unfreezeHeight(); |
|
423 $('<div id="freeze-height"></div>').css({ |
|
424 position: 'absolute', |
|
425 top: '0px', |
|
426 left: '0px', |
|
427 width: '1px', |
|
428 height: $('body').css('height') |
|
429 }).appendTo('body'); |
|
430 }; |
|
431 |
|
432 /** |
|
433 * Unfreeze the body height. |
|
434 */ |
|
435 Drupal.unfreezeHeight = function () { |
|
436 $('#freeze-height').remove(); |
|
437 }; |
|
438 |
|
439 /** |
|
440 * Encodes a Drupal path for use in a URL. |
|
441 * |
|
442 * For aesthetic reasons slashes are not escaped. |
|
443 */ |
|
444 Drupal.encodePath = function (item, uri) { |
|
445 uri = uri || location.href; |
|
446 return encodeURIComponent(item).replace(/%2F/g, '/'); |
|
447 }; |
|
448 |
|
449 /** |
|
450 * Get the text selection in a textarea. |
|
451 */ |
|
452 Drupal.getSelection = function (element) { |
|
453 if (typeof element.selectionStart != 'number' && document.selection) { |
|
454 // The current selection. |
|
455 var range1 = document.selection.createRange(); |
|
456 var range2 = range1.duplicate(); |
|
457 // Select all text. |
|
458 range2.moveToElementText(element); |
|
459 // Now move 'dummy' end point to end point of original range. |
|
460 range2.setEndPoint('EndToEnd', range1); |
|
461 // Now we can calculate start and end points. |
|
462 var start = range2.text.length - range1.text.length; |
|
463 var end = start + range1.text.length; |
|
464 return { 'start': start, 'end': end }; |
|
465 } |
|
466 return { 'start': element.selectionStart, 'end': element.selectionEnd }; |
|
467 }; |
|
468 |
|
469 /** |
|
470 * Add a global variable which determines if the window is being unloaded. |
|
471 * |
|
472 * This is primarily used by Drupal.displayAjaxError(). |
|
473 */ |
|
474 Drupal.beforeUnloadCalled = false; |
|
475 $(window).bind('beforeunload pagehide', function () { |
|
476 Drupal.beforeUnloadCalled = true; |
|
477 }); |
|
478 |
|
479 /** |
|
480 * Displays a JavaScript error from an Ajax response when appropriate to do so. |
|
481 */ |
|
482 Drupal.displayAjaxError = function (message) { |
|
483 // Skip displaying the message if the user deliberately aborted (for example, |
|
484 // by reloading the page or navigating to a different page) while the Ajax |
|
485 // request was still ongoing. See, for example, the discussion at |
|
486 // http://stackoverflow.com/questions/699941/handle-ajax-error-when-a-user-clicks-refresh. |
|
487 if (!Drupal.beforeUnloadCalled) { |
|
488 alert(message); |
|
489 } |
|
490 }; |
|
491 |
|
492 /** |
|
493 * Build an error message from an Ajax response. |
|
494 */ |
|
495 Drupal.ajaxError = function (xmlhttp, uri, customMessage) { |
|
496 var statusCode, statusText, pathText, responseText, readyStateText, message; |
|
497 if (xmlhttp.status) { |
|
498 statusCode = "\n" + Drupal.t("An AJAX HTTP error occurred.") + "\n" + Drupal.t("HTTP Result Code: !status", {'!status': xmlhttp.status}); |
|
499 } |
|
500 else { |
|
501 statusCode = "\n" + Drupal.t("An AJAX HTTP request terminated abnormally."); |
|
502 } |
|
503 statusCode += "\n" + Drupal.t("Debugging information follows."); |
|
504 pathText = "\n" + Drupal.t("Path: !uri", {'!uri': uri} ); |
|
505 statusText = ''; |
|
506 // In some cases, when statusCode == 0, xmlhttp.statusText may not be defined. |
|
507 // Unfortunately, testing for it with typeof, etc, doesn't seem to catch that |
|
508 // and the test causes an exception. So we need to catch the exception here. |
|
509 try { |
|
510 statusText = "\n" + Drupal.t("StatusText: !statusText", {'!statusText': $.trim(xmlhttp.statusText)}); |
|
511 } |
|
512 catch (e) {} |
|
513 |
|
514 responseText = ''; |
|
515 // Again, we don't have a way to know for sure whether accessing |
|
516 // xmlhttp.responseText is going to throw an exception. So we'll catch it. |
|
517 try { |
|
518 responseText = "\n" + Drupal.t("ResponseText: !responseText", {'!responseText': $.trim(xmlhttp.responseText) } ); |
|
519 } catch (e) {} |
|
520 |
|
521 // Make the responseText more readable by stripping HTML tags and newlines. |
|
522 responseText = responseText.replace(/<("[^"]*"|'[^']*'|[^'">])*>/gi,""); |
|
523 responseText = responseText.replace(/[\n]+\s+/g,"\n"); |
|
524 |
|
525 // We don't need readyState except for status == 0. |
|
526 readyStateText = xmlhttp.status == 0 ? ("\n" + Drupal.t("ReadyState: !readyState", {'!readyState': xmlhttp.readyState})) : ""; |
|
527 |
|
528 // Additional message beyond what the xmlhttp object provides. |
|
529 customMessage = customMessage ? ("\n" + Drupal.t("CustomMessage: !customMessage", {'!customMessage': customMessage})) : ""; |
|
530 |
|
531 message = statusCode + pathText + statusText + customMessage + responseText + readyStateText; |
|
532 return message; |
|
533 }; |
|
534 |
|
535 // Class indicating that JS is enabled; used for styling purpose. |
|
536 $('html').addClass('js'); |
|
537 |
|
538 // 'js enabled' cookie. |
|
539 document.cookie = 'has_js=1; path=/'; |
|
540 |
|
541 /** |
|
542 * Additions to jQuery.support. |
|
543 */ |
|
544 $(function () { |
|
545 /** |
|
546 * Boolean indicating whether or not position:fixed is supported. |
|
547 */ |
|
548 if (jQuery.support.positionFixed === undefined) { |
|
549 var el = $('<div style="position:fixed; top:10px" />').appendTo(document.body); |
|
550 jQuery.support.positionFixed = el[0].offsetTop === 10; |
|
551 el.remove(); |
|
552 } |
|
553 }); |
|
554 |
|
555 //Attach all behaviors. |
|
556 $(function () { |
|
557 Drupal.attachBehaviors(document, Drupal.settings); |
|
558 }); |
|
559 |
|
560 /** |
|
561 * The default themes. |
|
562 */ |
|
563 Drupal.theme.prototype = { |
|
564 |
|
565 /** |
|
566 * Formats text for emphasized display in a placeholder inside a sentence. |
|
567 * |
|
568 * @param str |
|
569 * The text to format (plain-text). |
|
570 * @return |
|
571 * The formatted text (html). |
|
572 */ |
|
573 placeholder: function (str) { |
|
574 return '<em class="placeholder">' + Drupal.checkPlain(str) + '</em>'; |
|
575 } |
|
576 }; |
|
577 |
|
578 })(jQuery); |