diff --git a/core/.eslintignore b/core/.eslintignore index a15282a..0bb8ca5 100644 --- a/core/.eslintignore +++ b/core/.eslintignore @@ -1,4 +1,4 @@ assets/vendor/**/* -modules/locale/tests/locale_test.js +modules/locale/tests/locale_test.es6.js node_modules/**/* **/js_test_files/**/* diff --git a/core/misc/active-link.es6.js b/core/misc/active-link.es6.js new file mode 100644 index 0000000..9cf55b4 --- /dev/null +++ b/core/misc/active-link.es6.js @@ -0,0 +1,68 @@ +/** + * @file + * Attaches behaviors for Drupal's active link marking. + */ + +(function (Drupal, drupalSettings) { + + 'use strict'; + + /** + * Append is-active class. + * + * The link is only active if its path corresponds to the current path, the + * language of the linked path is equal to the current language, and if the + * query parameters of the link equal those of the current request, since the + * same request with different query parameters may yield a different page + * (e.g. pagers, exposed View filters). + * + * Does not discriminate based on element type, so allows you to set the + * is-active class on any element: a, li… + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.activeLinks = { + attach: function (context) { + // Start by finding all potentially active links. + var path = drupalSettings.path; + var queryString = JSON.stringify(path.currentQuery); + var querySelector = path.currentQuery ? "[data-drupal-link-query='" + queryString + "']" : ':not([data-drupal-link-query])'; + var originalSelectors = ['[data-drupal-link-system-path="' + path.currentPath + '"]']; + var selectors; + + // If this is the front page, we have to check for the path as + // well. + if (path.isFront) { + originalSelectors.push('[data-drupal-link-system-path=""]'); + } + + // Add language filtering. + selectors = [].concat( + // Links without any hreflang attributes (most of them). + originalSelectors.map(function (selector) { return selector + ':not([hreflang])'; }), + // Links with hreflang equals to the current language. + originalSelectors.map(function (selector) { return selector + '[hreflang="' + path.currentLanguage + '"]'; }) + ); + + // Add query string selector for pagers, exposed filters. + selectors = selectors.map(function (current) { return current + querySelector; }); + + // Query the DOM. + var activeLinks = context.querySelectorAll(selectors.join(',')); + var il = activeLinks.length; + for (var i = 0; i < il; i++) { + activeLinks[i].classList.add('is-active'); + } + }, + detach: function (context, settings, trigger) { + if (trigger === 'unload') { + var activeLinks = context.querySelectorAll('[data-drupal-link-system-path].is-active'); + var il = activeLinks.length; + for (var i = 0; i < il; i++) { + activeLinks[i].classList.remove('is-active'); + } + } + } + }; + +})(Drupal, drupalSettings); diff --git a/core/misc/active-link.js b/core/misc/active-link.js index 9cf55b4..36e69d1 100644 --- a/core/misc/active-link.js +++ b/core/misc/active-link.js @@ -1,60 +1,38 @@ -/** - * @file - * Attaches behaviors for Drupal's active link marking. - */ +"use strict"; (function (Drupal, drupalSettings) { 'use strict'; - /** - * Append is-active class. - * - * The link is only active if its path corresponds to the current path, the - * language of the linked path is equal to the current language, and if the - * query parameters of the link equal those of the current request, since the - * same request with different query parameters may yield a different page - * (e.g. pagers, exposed View filters). - * - * Does not discriminate based on element type, so allows you to set the - * is-active class on any element: a, li… - * - * @type {Drupal~behavior} - */ Drupal.behaviors.activeLinks = { - attach: function (context) { - // Start by finding all potentially active links. + attach: function attach(context) { var path = drupalSettings.path; var queryString = JSON.stringify(path.currentQuery); var querySelector = path.currentQuery ? "[data-drupal-link-query='" + queryString + "']" : ':not([data-drupal-link-query])'; var originalSelectors = ['[data-drupal-link-system-path="' + path.currentPath + '"]']; var selectors; - // If this is the front page, we have to check for the path as - // well. if (path.isFront) { originalSelectors.push('[data-drupal-link-system-path=""]'); } - // Add language filtering. - selectors = [].concat( - // Links without any hreflang attributes (most of them). - originalSelectors.map(function (selector) { return selector + ':not([hreflang])'; }), - // Links with hreflang equals to the current language. - originalSelectors.map(function (selector) { return selector + '[hreflang="' + path.currentLanguage + '"]'; }) - ); + selectors = [].concat(originalSelectors.map(function (selector) { + return selector + ':not([hreflang])'; + }), originalSelectors.map(function (selector) { + return selector + '[hreflang="' + path.currentLanguage + '"]'; + })); - // Add query string selector for pagers, exposed filters. - selectors = selectors.map(function (current) { return current + querySelector; }); + selectors = selectors.map(function (current) { + return current + querySelector; + }); - // Query the DOM. var activeLinks = context.querySelectorAll(selectors.join(',')); var il = activeLinks.length; for (var i = 0; i < il; i++) { activeLinks[i].classList.add('is-active'); } }, - detach: function (context, settings, trigger) { + detach: function detach(context, settings, trigger) { if (trigger === 'unload') { var activeLinks = context.querySelectorAll('[data-drupal-link-system-path].is-active'); var il = activeLinks.length; @@ -64,5 +42,6 @@ } } }; - })(Drupal, drupalSettings); + +//# sourceMappingURL=active-link.js.map \ No newline at end of file diff --git a/core/misc/active-link.js.map b/core/misc/active-link.js.map new file mode 100644 index 0000000..2aedfe8 --- /dev/null +++ b/core/misc/active-link.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["active-link.es6.js"],"names":["Drupal","drupalSettings","behaviors","activeLinks","attach","context","path","queryString","JSON","stringify","currentQuery","querySelector","originalSelectors","currentPath","selectors","isFront","push","concat","map","selector","currentLanguage","current","querySelectorAll","join","il","length","i","classList","add","detach","settings","trigger","remove"],"mappings":";;AAKA,CAAC,UAAUA,MAAV,EAAkBC,cAAlB,EAAkC;;AAEjC;;AAgBAD,SAAOE,SAAP,CAAiBC,WAAjB,GAA+B;AAC7BC,YAAQ,gBAAUC,OAAV,EAAmB;AAEzB,UAAIC,OAAOL,eAAeK,IAA1B;AACA,UAAIC,cAAcC,KAAKC,SAAL,CAAeH,KAAKI,YAApB,CAAlB;AACA,UAAIC,gBAAgBL,KAAKI,YAAL,GAAoB,8BAA8BH,WAA9B,GAA4C,IAAhE,GAAuE,gCAA3F;AACA,UAAIK,oBAAoB,CAAC,oCAAoCN,KAAKO,WAAzC,GAAuD,IAAxD,CAAxB;AACA,UAAIC,SAAJ;;AAIA,UAAIR,KAAKS,OAAT,EAAkB;AAChBH,0BAAkBI,IAAlB,CAAuB,0CAAvB;AACD;;AAGDF,kBAAY,GAAGG,MAAH,CAEVL,kBAAkBM,GAAlB,CAAsB,UAAUC,QAAV,EAAoB;AAAE,eAAOA,WAAW,kBAAlB;AAAuC,OAAnF,CAFU,EAIVP,kBAAkBM,GAAlB,CAAsB,UAAUC,QAAV,EAAoB;AAAE,eAAOA,WAAW,aAAX,GAA2Bb,KAAKc,eAAhC,GAAkD,IAAzD;AAAgE,OAA5G,CAJU,CAAZ;;AAQAN,kBAAYA,UAAUI,GAAV,CAAc,UAAUG,OAAV,EAAmB;AAAE,eAAOA,UAAUV,aAAjB;AAAiC,OAApE,CAAZ;;AAGA,UAAIR,cAAcE,QAAQiB,gBAAR,CAAyBR,UAAUS,IAAV,CAAe,GAAf,CAAzB,CAAlB;AACA,UAAIC,KAAKrB,YAAYsB,MAArB;AACA,WAAK,IAAIC,IAAI,CAAb,EAAgBA,IAAIF,EAApB,EAAwBE,GAAxB,EAA6B;AAC3BvB,oBAAYuB,CAAZ,EAAeC,SAAf,CAAyBC,GAAzB,CAA6B,WAA7B;AACD;AACF,KAhC4B;AAiC7BC,YAAQ,gBAAUxB,OAAV,EAAmByB,QAAnB,EAA6BC,OAA7B,EAAsC;AAC5C,UAAIA,YAAY,QAAhB,EAA0B;AACxB,YAAI5B,cAAcE,QAAQiB,gBAAR,CAAyB,0CAAzB,CAAlB;AACA,YAAIE,KAAKrB,YAAYsB,MAArB;AACA,aAAK,IAAIC,IAAI,CAAb,EAAgBA,IAAIF,EAApB,EAAwBE,GAAxB,EAA6B;AAC3BvB,sBAAYuB,CAAZ,EAAeC,SAAf,CAAyBK,MAAzB,CAAgC,WAAhC;AACD;AACF;AACF;AAzC4B,GAA/B;AA4CD,CA9DD,EA8DGhC,MA9DH,EA8DWC,cA9DX","file":"active-link.es6.js","sourcesContent":["/**\n * @file\n * Attaches behaviors for Drupal's active link marking.\n */\n\n(function (Drupal, drupalSettings) {\n\n 'use strict';\n\n /**\n * Append is-active class.\n *\n * The link is only active if its path corresponds to the current path, the\n * language of the linked path is equal to the current language, and if the\n * query parameters of the link equal those of the current request, since the\n * same request with different query parameters may yield a different page\n * (e.g. pagers, exposed View filters).\n *\n * Does not discriminate based on element type, so allows you to set the\n * is-active class on any element: a, li…\n *\n * @type {Drupal~behavior}\n */\n Drupal.behaviors.activeLinks = {\n attach: function (context) {\n // Start by finding all potentially active links.\n var path = drupalSettings.path;\n var queryString = JSON.stringify(path.currentQuery);\n var querySelector = path.currentQuery ? \"[data-drupal-link-query='\" + queryString + \"']\" : ':not([data-drupal-link-query])';\n var originalSelectors = ['[data-drupal-link-system-path=\"' + path.currentPath + '\"]'];\n var selectors;\n\n // If this is the front page, we have to check for the path as\n // well.\n if (path.isFront) {\n originalSelectors.push('[data-drupal-link-system-path=\"\"]');\n }\n\n // Add language filtering.\n selectors = [].concat(\n // Links without any hreflang attributes (most of them).\n originalSelectors.map(function (selector) { return selector + ':not([hreflang])'; }),\n // Links with hreflang equals to the current language.\n originalSelectors.map(function (selector) { return selector + '[hreflang=\"' + path.currentLanguage + '\"]'; })\n );\n\n // Add query string selector for pagers, exposed filters.\n selectors = selectors.map(function (current) { return current + querySelector; });\n\n // Query the DOM.\n var activeLinks = context.querySelectorAll(selectors.join(','));\n var il = activeLinks.length;\n for (var i = 0; i < il; i++) {\n activeLinks[i].classList.add('is-active');\n }\n },\n detach: function (context, settings, trigger) {\n if (trigger === 'unload') {\n var activeLinks = context.querySelectorAll('[data-drupal-link-system-path].is-active');\n var il = activeLinks.length;\n for (var i = 0; i < il; i++) {\n activeLinks[i].classList.remove('is-active');\n }\n }\n }\n };\n\n})(Drupal, drupalSettings);\n"]} \ No newline at end of file diff --git a/core/misc/ajax.es6.js b/core/misc/ajax.es6.js new file mode 100644 index 0000000..fefe9f3 --- /dev/null +++ b/core/misc/ajax.es6.js @@ -0,0 +1,1344 @@ +/** + * @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) { + + 'use strict'; + + /** + * Attaches the Ajax behavior to each Ajax form element. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Initialize all {@link Drupal.Ajax} objects declared in + * `drupalSettings.ajax` or initialize {@link Drupal.Ajax} objects from + * DOM elements having the `use-ajax-submit` or `use-ajax` css class. + * @prop {Drupal~behaviorDetach} detach + * During `unload` remove all {@link Drupal.Ajax} objects related to + * the removed content. + */ + 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. + element_settings.progress = {type: 'throbber'}; + + // For anchor tags, these will go to the target of the anchor rather + // than the usual location. + var href = $(this).attr('href'); + if (href) { + element_settings.url = 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. + element_settings.progress = {type: 'throbber'}; + element_settings.base = $(this).attr('id'); + element_settings.element = this; + + Drupal.ajax(element_settings); + }); + }, + + detach: function (context, settings, trigger) { + if (trigger === 'unload') { + Drupal.ajax.expired().forEach(function (instance) { + // Set this to null and allow garbage collection to reclaim + // the memory. + Drupal.ajax.instances[instance.instanceIndex] = null; + }); + } + } + }; + + /** + * 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. + * @param {string} customMessage + * The custom message. + */ + Drupal.AjaxError = function (xmlhttp, uri, customMessage) { + + var statusCode; + var statusText; + var pathText; + var responseText; + var readyStateText; + if (xmlhttp.status) { + statusCode = '\n' + Drupal.t('An AJAX HTTP error occurred.') + '\n' + Drupal.t('HTTP Result Code: !status', {'!status': xmlhttp.status}); + } + else { + statusCode = '\n' + Drupal.t('An AJAX HTTP request terminated abnormally.'); + } + statusCode += '\n' + Drupal.t('Debugging information follows.'); + pathText = '\n' + Drupal.t('Path: !uri', {'!uri': uri}); + 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 { + statusText = '\n' + Drupal.t('StatusText: !statusText', {'!statusText': $.trim(xmlhttp.statusText)}); + } + 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 { + responseText = '\n' + Drupal.t('ResponseText: !responseText', {'!responseText': $.trim(xmlhttp.responseText)}); + } + catch (e) { + // Empty. + } + + // Make the responseText more readable by stripping HTML tags and newlines. + responseText = responseText.replace(/<("[^"]*"|'[^']*'|[^'">])*>/gi, ''); + responseText = responseText.replace(/[\n]+\s+/g, '\n'); + + // We don't need readyState except for status == 0. + readyStateText = xmlhttp.status === 0 ? ('\n' + Drupal.t('ReadyState: !readyState', {'!readyState': xmlhttp.readyState})) : ''; + + customMessage = customMessage ? ('\n' + Drupal.t('CustomMessage: !customMessage', {'!customMessage': customMessage})) : ''; + + /** + * Formatted and translated error message. + * + * @type {string} + */ + this.message = statusCode + pathText + statusText + customMessage + responseText + readyStateText; + + /** + * 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} + * The created Ajax object. + * + * @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.instances = []; + + /** + * List all objects where the associated element is not in the DOM + * + * This method ignores {@link Drupal.Ajax} objects not bound to DOM elements + * when created with {@link Drupal.ajax}. + * + * @return {Array.} + * The list of expired {@link Drupal.Ajax} objects. + */ + Drupal.ajax.expired = function () { + return Drupal.ajax.instances.filter(function (instance) { + return instance && instance.element !== false && !document.body.contains(instance.element); + }); + }; + + /** + * Settings for an Ajax object. + * + * @typedef {object} Drupal.Ajax~element_settings + * + * @prop {string} url + * Target of the Ajax request. + * @prop {?string} [event] + * Event bound to settings.element which will trigger the Ajax request. + * @prop {bool} [keypress=true] + * Triggers a request on keypress events. + * @prop {?string} selector + * jQuery selector targeting the element to bind events to or used with + * {@link Drupal.AjaxCommands}. + * @prop {string} [effect='none'] + * Name of the jQuery method to use for displaying new Ajax content. + * @prop {string|number} [speed='none'] + * Speed with which to apply the effect. + * @prop {string} [method] + * Name of the jQuery method used to insert new content in the targeted + * element. + * @prop {object} [progress] + * Settings for the display of a user-friendly loader. + * @prop {string} [progress.type='throbber'] + * Type of progress element, core provides `'bar'`, `'throbber'` and + * `'fullscreen'`. + * @prop {string} [progress.message=Drupal.t('Please wait...')] + * Custom message to be used with the bar indicator. + * @prop {object} [submit] + * Extra data to be sent with the Ajax request. + * @prop {bool} [submit.js=true] + * Allows the PHP side to know this comes from an Ajax request. + * @prop {object} [dialog] + * Options for {@link Drupal.dialog}. + * @prop {string} [dialogType] + * One of `'modal'` or `'dialog'`. + * @prop {string} [prevent] + * List of events on which to stop default action and stop propagation. + */ + + /** + * 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 {Drupal.Ajax~element_settings} element_settings + * Settings for this Ajax object. + */ + 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: { + js: true + } + }; + + $.extend(this, defaults, element_settings); + + /** + * @type {Drupal.AjaxCommands} + */ + this.commands = new Drupal.AjaxCommands(); + + /** + * @type {bool|number} + */ + 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 {Drupal.Ajax~element_settings} + */ + 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). + var originalUrl = this.url; + + /** + * Processed Ajax URL. + * + * @type {string} + */ + this.url = this.url.replace(/\/nojs(\/|$|\?|#)/g, '/ajax$1'); + // If the 'nojs' version of the URL is trusted, also trust the 'ajax' + // version. + if (drupalSettings.ajaxTrustedUrl[originalUrl]) { + drupalSettings.ajaxTrustedUrl[this.url] = true; + } + + // Set the options for the ajaxSubmit function. + // The 'this' variable will not persist inside of the options object. + var ajax = this; + + /** + * Options for the jQuery.ajax function. + * + * @name Drupal.Ajax#options + * + * @type {object} + * + * @prop {string} url + * Ajax URL to be called. + * @prop {object} data + * Ajax payload. + * @prop {function} beforeSerialize + * Implement jQuery beforeSerialize function to call + * {@link Drupal.Ajax#beforeSerialize}. + * @prop {function} beforeSubmit + * Implement jQuery beforeSubmit function to call + * {@link Drupal.Ajax#beforeSubmit}. + * @prop {function} beforeSend + * Implement jQuery beforeSend function to call + * {@link Drupal.Ajax#beforeSend}. + * @prop {function} success + * Implement jQuery success function to call + * {@link Drupal.Ajax#success}. + * @prop {function} complete + * Implement jQuery success function to clean up ajax state and trigger an + * error if needed. + * @prop {string} dataType='json' + * Type of the response expected. + * @prop {string} type='POST' + * HTTP method to use for the Ajax request. + */ + 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); + }, + success: function (response, status, xmlhttprequest) { + // 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); + } + + // 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') { + var customMessage = Drupal.t('The response failed verification so will not be processed.'); + return ajax.error(xmlhttprequest, ajax.url, customMessage); + } + } + + return ajax.success(response, status); + }, + complete: function (xmlhttprequest, status) { + ajax.ajaxing = false; + if (status === 'error' || status === 'parsererror') { + return ajax.error(xmlhttprequest, ajax.url); + } + }, + 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) { + 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})); + } + 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} + * + * @default + */ + Drupal.ajax.WRAPPER_FORMAT = '_wrapper_format'; + + /** + * Request parameter to indicate that a request is a Drupal Ajax request. + * + * @const {string} + * + * @default + */ + 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. + * + * @return {object} + * Returns the jQuery.Deferred object underlying the Ajax request. If + * pre-serialization fails, the Deferred will be returned in the rejected + * state. + */ + 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); + // Return the jqXHR so that external code can hook into the Deferred API. + return $.ajax(this.options); + } + catch (e) { + // Unset the ajax.ajaxing flag here because it won't be unset during + // the complete response. + this.ajaxing = false; + window.alert('An error occurred while attempting to process ' + this.options.url + ': ' + e.message); + // For consistency, return a rejected Deferred (i.e., jqXHR's superclass) + // so that calling code can take appropriate action. + return $.Deferred().reject(); + } + }; + + /** + * 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 + * Element the event was triggered on. + * @param {jQuery.Event} event + * Triggered 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 + * Element the event was triggered on. + * @param {jQuery.Event} event + * Triggered 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; + window.alert('An error occurred while attempting to process ' + ajax.options.url + ': ' + e.message); + } + }; + + /** + * Handler for the form serialization. + * + * Runs before the beforeSend() handler (see below), and unlike that one, runs + * before field data is collected. + * + * @param {object} [element] + * Ajax object's `element_settings`. + * @param {object} options + * jQuery.ajax options. + */ + 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 {Array.} form_values + * Processed form values. + * @param {jQuery} element + * The form node as a jQuery object. + * @param {object} options + * jQuery.ajax 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 + * Native Ajax object. + * @param {object} options + * jQuery.ajax options. + */ + 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 = $('
 
'); + if (this.progress.message) { + this.progress.element.find('.throbber').after('
' + this.progress.message + '
'); + } + $(this.element).after(this.progress.element); + }; + + /** + * Sets the fullscreen progress indicator. + */ + Drupal.Ajax.prototype.setProgressIndicatorFullscreen = function () { + this.progress.element = $('
 
'); + $('body').after(this.progress.element); + }; + + /** + * Handler for the form redirection completion. + * + * @param {Array.} response + * Drupal Ajax response. + * @param {number} status + * XMLHttpRequest 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); + + // 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
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; + 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); + 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'); + } + } + + // 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 + * Drupal Ajax response. + * @param {string} [response.effect] + * Override the default value of {@link Drupal.Ajax#element_settings}. + * @param {string|number} [response.speed] + * Override the default value of {@link Drupal.Ajax#element_settings}. + * + * @return {object} + * Returns an object with `showEffect`, `hideEffect` and `showSpeed` + * properties. + */ + 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. + * + * @param {object} xmlhttprequest + * Native XMLHttpRequest object. + * @param {string} uri + * Ajax Request URI. + * @param {string} [customMessage] + * Extra message to print with the Ajax error. + */ + Drupal.Ajax.prototype.error = function (xmlhttprequest, uri, customMessage) { + // 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) { + var settings = this.settings || drupalSettings; + Drupal.attachBehaviors(this.$form.get(0), settings); + } + throw new Drupal.AjaxError(xmlhttprequest, uri, customMessage); + }; + + /** + * @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 + * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. + * @param {object} response + * The response from the Ajax request. + * @param {string} response.data + * The data to use with the jQuery method. + * @param {string} [response.method] + * The jQuery DOM manipulation method to be used. + * @param {string} [response.selector] + * A optional jQuery selector string. + * @param {object} [response.settings] + * An optional array of settings that will be used. + * @param {number} [status] + * The XMLHttpRequest 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 $('
').replaceWith(response.data). + var $new_content_wrapped = $('
').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
+ // elements are not allowed (e.g., within , , and + // parents), we check if the new content satisfies the requirement + // of a single top-level element, and only use the container
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] + * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. + * @param {object} response + * The response from the Ajax request. + * @param {string} response.selector + * A jQuery selector string. + * @param {object} [response.settings] + * An optional array of settings that will be used. + * @param {number} [status] + * The XMLHttpRequest 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] + * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. + * @param {object} response + * The JSON response object from the Ajax request. + * @param {string} response.selector + * A jQuery selector string. + * @param {bool} [response.asterisk] + * An optional CSS selector. If specified, an asterisk will be + * appended to the HTML inside the provided selector. + * @param {number} [status] + * The request status. + */ + changed: function (ajax, response, status) { + var $element = $(response.selector); + if (!$element.hasClass('ajax-changed')) { + $element.addClass('ajax-changed'); + if (response.asterisk) { + $element.find(response.asterisk).append(' * '); + } + } + }, + + /** + * Command to provide an alert. + * + * @param {Drupal.Ajax} [ajax] + * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. + * @param {object} response + * The JSON response from the Ajax request. + * @param {string} response.text + * The text that will be displayed in an alert dialog. + * @param {number} [status] + * The XMLHttpRequest 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] + * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. + * @param {object} response + * The response from the Ajax request. + * @param {string} response.url + * The URL to redirect to. + * @param {number} [status] + * The XMLHttpRequest status. + */ + redirect: function (ajax, response, status) { + window.location = response.url; + }, + + /** + * Command to provide the jQuery css() function. + * + * @param {Drupal.Ajax} [ajax] + * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. + * @param {object} response + * The response from the Ajax request. + * @param {string} response.selector + * A jQuery selector string. + * @param {object} response.argument + * An array of key/value pairs to set in the CSS for the selector. + * @param {number} [status] + * The XMLHttpRequest status. + */ + css: function (ajax, response, status) { + $(response.selector).css(response.argument); + }, + + /** + * Command to set the settings used for other commands in this response. + * + * This method will also remove expired `drupalSettings.ajax` settings. + * + * @param {Drupal.Ajax} [ajax] + * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. + * @param {object} response + * The response from the Ajax request. + * @param {bool} response.merge + * Determines whether the additional settings should be merged to the + * global settings. + * @param {object} response.settings + * Contains additional settings to add to the global settings. + * @param {number} [status] + * The XMLHttpRequest status. + */ + settings: function (ajax, response, status) { + var ajaxSettings = drupalSettings.ajax; + + // Clean up drupalSettings.ajax. + if (ajaxSettings) { + Drupal.ajax.expired().forEach(function (instance) { + // If the Ajax object has been created through drupalSettings.ajax + // it will have a selector. When there is no selector the object + // has been initialized with a special class name picked up by the + // Ajax behavior. + + if (instance.selector) { + var selector = instance.selector.replace('#', ''); + if (selector in ajaxSettings) { + delete ajaxSettings[selector]; + } + } + }); + } + + 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] + * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. + * @param {object} response + * The response from the Ajax request. + * @param {string} response.name + * The name or key (in the key value pair) of the data attached to this + * selector. + * @param {string} response.selector + * A jQuery selector string. + * @param {string|object} response.value + * The value of to be attached. + * @param {number} [status] + * The XMLHttpRequest status. + */ + data: function (ajax, response, status) { + $(response.selector).data(response.name, response.value); + }, + + /** + * Command to apply a jQuery method. + * + * @param {Drupal.Ajax} [ajax] + * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. + * @param {object} response + * The response from the Ajax request. + * @param {Array} response.args + * An array of arguments to the jQuery method, if any. + * @param {string} response.method + * The jQuery method to invoke. + * @param {string} response.selector + * A jQuery selector string. + * @param {number} [status] + * The XMLHttpRequest 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] + * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. + * @param {object} response + * The response from the Ajax request. + * @param {string} response.selector + * A jQuery selector string. + * @param {number} [status] + * The XMLHttpRequest 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] + * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. + * @param {object} response + * The response from the Ajax request. + * @param {string} response.old + * The old form build ID. + * @param {string} response.new + * The new form build ID. + * @param {number} [status] + * The XMLHttpRequest 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] + * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. + * @param {object} response + * The response from the Ajax request. + * @param {string} response.data + * A string that contains the styles to be added. + * @param {number} [status] + * The XMLHttpRequest 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, window, Drupal, drupalSettings); diff --git a/core/misc/ajax.js b/core/misc/ajax.js index fefe9f3..fbae676 100644 --- a/core/misc/ajax.js +++ b/core/misc/ajax.js @@ -1,35 +1,11 @@ -/** - * @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. - */ +'use strict'; (function ($, window, Drupal, drupalSettings) { 'use strict'; - /** - * Attaches the Ajax behavior to each Ajax form element. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Initialize all {@link Drupal.Ajax} objects declared in - * `drupalSettings.ajax` or initialize {@link Drupal.Ajax} objects from - * DOM elements having the `use-ajax-submit` or `use-ajax` css class. - * @prop {Drupal~behaviorDetach} detach - * During `unload` remove all {@link Drupal.Ajax} objects related to - * the removed content. - */ Drupal.behaviors.AJAX = { - attach: function (context, settings) { + attach: function attach(context, settings) { function loadAjaxBehavior(base) { var element_settings = settings.ajax[base]; @@ -43,21 +19,17 @@ }); } - // 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. - element_settings.progress = {type: 'throbber'}; - // For anchor tags, these will go to the target of the anchor rather - // than the usual location. + element_settings.progress = { type: 'throbber' }; + var href = $(this).attr('href'); if (href) { element_settings.url = href; @@ -70,21 +42,16 @@ 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. - element_settings.progress = {type: 'throbber'}; + + element_settings.progress = { type: 'throbber' }; element_settings.base = $(this).attr('id'); element_settings.element = this; @@ -92,31 +59,15 @@ }); }, - detach: function (context, settings, trigger) { + detach: function detach(context, settings, trigger) { if (trigger === 'unload') { Drupal.ajax.expired().forEach(function (instance) { - // Set this to null and allow garbage collection to reclaim - // the memory. Drupal.ajax.instances[instance.instanceIndex] = null; }); } } }; - /** - * 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. - * @param {string} customMessage - * The custom message. - */ Drupal.AjaxError = function (xmlhttp, uri, customMessage) { var statusCode; @@ -125,124 +76,49 @@ var responseText; var readyStateText; if (xmlhttp.status) { - statusCode = '\n' + Drupal.t('An AJAX HTTP error occurred.') + '\n' + Drupal.t('HTTP Result Code: !status', {'!status': xmlhttp.status}); - } - else { + statusCode = '\n' + Drupal.t('An AJAX HTTP error occurred.') + '\n' + Drupal.t('HTTP Result Code: !status', { '!status': xmlhttp.status }); + } else { statusCode = '\n' + Drupal.t('An AJAX HTTP request terminated abnormally.'); } statusCode += '\n' + Drupal.t('Debugging information follows.'); - pathText = '\n' + Drupal.t('Path: !uri', {'!uri': uri}); + pathText = '\n' + Drupal.t('Path: !uri', { '!uri': uri }); 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 { - statusText = '\n' + Drupal.t('StatusText: !statusText', {'!statusText': $.trim(xmlhttp.statusText)}); - } - catch (e) { - // Empty. - } + statusText = '\n' + Drupal.t('StatusText: !statusText', { '!statusText': $.trim(xmlhttp.statusText) }); + } catch (e) {} 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 { - responseText = '\n' + Drupal.t('ResponseText: !responseText', {'!responseText': $.trim(xmlhttp.responseText)}); - } - catch (e) { - // Empty. - } + responseText = '\n' + Drupal.t('ResponseText: !responseText', { '!responseText': $.trim(xmlhttp.responseText) }); + } catch (e) {} - // Make the responseText more readable by stripping HTML tags and newlines. responseText = responseText.replace(/<("[^"]*"|'[^']*'|[^'">])*>/gi, ''); responseText = responseText.replace(/[\n]+\s+/g, '\n'); - // We don't need readyState except for status == 0. - readyStateText = xmlhttp.status === 0 ? ('\n' + Drupal.t('ReadyState: !readyState', {'!readyState': xmlhttp.readyState})) : ''; + readyStateText = xmlhttp.status === 0 ? '\n' + Drupal.t('ReadyState: !readyState', { '!readyState': xmlhttp.readyState }) : ''; - customMessage = customMessage ? ('\n' + Drupal.t('CustomMessage: !customMessage', {'!customMessage': customMessage})) : ''; + customMessage = customMessage ? '\n' + Drupal.t('CustomMessage: !customMessage', { '!customMessage': customMessage }) : ''; - /** - * Formatted and translated error message. - * - * @type {string} - */ this.message = statusCode + pathText + statusText + customMessage + responseText + readyStateText; - /** - * 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} - * The created Ajax object. - * - * @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; } @@ -254,88 +130,14 @@ return ajax; }; - /** - * Contains all created Ajax objects. - * - * @type {Array.} - */ Drupal.ajax.instances = []; - /** - * List all objects where the associated element is not in the DOM - * - * This method ignores {@link Drupal.Ajax} objects not bound to DOM elements - * when created with {@link Drupal.ajax}. - * - * @return {Array.} - * The list of expired {@link Drupal.Ajax} objects. - */ Drupal.ajax.expired = function () { return Drupal.ajax.instances.filter(function (instance) { return instance && instance.element !== false && !document.body.contains(instance.element); }); }; - /** - * Settings for an Ajax object. - * - * @typedef {object} Drupal.Ajax~element_settings - * - * @prop {string} url - * Target of the Ajax request. - * @prop {?string} [event] - * Event bound to settings.element which will trigger the Ajax request. - * @prop {bool} [keypress=true] - * Triggers a request on keypress events. - * @prop {?string} selector - * jQuery selector targeting the element to bind events to or used with - * {@link Drupal.AjaxCommands}. - * @prop {string} [effect='none'] - * Name of the jQuery method to use for displaying new Ajax content. - * @prop {string|number} [speed='none'] - * Speed with which to apply the effect. - * @prop {string} [method] - * Name of the jQuery method used to insert new content in the targeted - * element. - * @prop {object} [progress] - * Settings for the display of a user-friendly loader. - * @prop {string} [progress.type='throbber'] - * Type of progress element, core provides `'bar'`, `'throbber'` and - * `'fullscreen'`. - * @prop {string} [progress.message=Drupal.t('Please wait...')] - * Custom message to be used with the bar indicator. - * @prop {object} [submit] - * Extra data to be sent with the Ajax request. - * @prop {bool} [submit.js=true] - * Allows the PHP side to know this comes from an Ajax request. - * @prop {object} [dialog] - * Options for {@link Drupal.dialog}. - * @prop {string} [dialogType] - * One of `'modal'` or `'dialog'`. - * @prop {string} [prevent] - * List of events on which to stop default action and stop propagation. - */ - - /** - * 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 {Drupal.Ajax~element_settings} element_settings - * Settings for this Ajax object. - */ Drupal.Ajax = function (base, element, element_settings) { var defaults = { event: element ? 'mousedown' : null, @@ -355,146 +157,60 @@ $.extend(this, defaults, element_settings); - /** - * @type {Drupal.AjaxCommands} - */ this.commands = new Drupal.AjaxCommands(); - /** - * @type {bool|number} - */ 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 {Drupal.Ajax~element_settings} - */ 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) { + } 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). var originalUrl = this.url; - /** - * Processed Ajax URL. - * - * @type {string} - */ this.url = this.url.replace(/\/nojs(\/|$|\?|#)/g, '/ajax$1'); - // If the 'nojs' version of the URL is trusted, also trust the 'ajax' - // version. + if (drupalSettings.ajaxTrustedUrl[originalUrl]) { drupalSettings.ajaxTrustedUrl[this.url] = true; } - // Set the options for the ajaxSubmit function. - // The 'this' variable will not persist inside of the options object. var ajax = this; - /** - * Options for the jQuery.ajax function. - * - * @name Drupal.Ajax#options - * - * @type {object} - * - * @prop {string} url - * Ajax URL to be called. - * @prop {object} data - * Ajax payload. - * @prop {function} beforeSerialize - * Implement jQuery beforeSerialize function to call - * {@link Drupal.Ajax#beforeSerialize}. - * @prop {function} beforeSubmit - * Implement jQuery beforeSubmit function to call - * {@link Drupal.Ajax#beforeSubmit}. - * @prop {function} beforeSend - * Implement jQuery beforeSend function to call - * {@link Drupal.Ajax#beforeSend}. - * @prop {function} success - * Implement jQuery success function to call - * {@link Drupal.Ajax#success}. - * @prop {function} complete - * Implement jQuery success function to clean up ajax state and trigger an - * error if needed. - * @prop {string} dataType='json' - * Type of the response expected. - * @prop {string} type='POST' - * HTTP method to use for the Ajax request. - */ ajax.options = { url: ajax.url, data: ajax.submit, - beforeSerialize: function (element_settings, options) { + beforeSerialize: function beforeSerialize(element_settings, options) { return ajax.beforeSerialize(element_settings, options); }, - beforeSubmit: function (form_values, element_settings, options) { + beforeSubmit: function beforeSubmit(form_values, element_settings, options) { ajax.ajaxing = true; return ajax.beforeSubmit(form_values, element_settings, options); }, - beforeSend: function (xmlhttprequest, options) { + beforeSend: function beforeSend(xmlhttprequest, options) { ajax.ajaxing = true; return ajax.beforeSend(xmlhttprequest, options); }, - success: function (response, status, xmlhttprequest) { - // Sanity check for browser support (object expected). - // When using iFrame uploads, responses must be returned as a string. + success: function success(response, status, xmlhttprequest) { if (typeof response === 'string') { response = $.parseJSON(response); } - // 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') { var customMessage = Drupal.t('The response failed verification so will not be processed.'); @@ -504,7 +220,7 @@ return ajax.success(response, status); }, - complete: function (xmlhttprequest, status) { + complete: function complete(xmlhttprequest, status) { ajax.ajaxing = false; if (status === 'error' || status === 'parsererror') { return ajax.error(xmlhttprequest, ajax.url); @@ -518,288 +234,129 @@ 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 { + } 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) { 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})); + throw new Error(Drupal.t('The callback URL is not local and not trusted: !url', { '!url': ajax.url })); } 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} - * - * @default - */ Drupal.ajax.WRAPPER_FORMAT = '_wrapper_format'; - /** - * Request parameter to indicate that a request is a Drupal Ajax request. - * - * @const {string} - * - * @default - */ 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. - * - * @return {object} - * Returns the jQuery.Deferred object underlying the Ajax request. If - * pre-serialization fails, the Deferred will be returned in the rejected - * state. - */ 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); - // Return the jqXHR so that external code can hook into the Deferred API. + return $.ajax(this.options); - } - catch (e) { - // Unset the ajax.ajaxing flag here because it won't be unset during - // the complete response. + } catch (e) { this.ajaxing = false; window.alert('An error occurred while attempting to process ' + this.options.url + ': ' + e.message); - // For consistency, return a rejected Deferred (i.e., jqXHR's superclass) - // so that calling code can take appropriate action. + return $.Deferred().reject(); } }; - /** - * 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 - * Element the event was triggered on. - * @param {jQuery.Event} event - * Triggered 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')) { + 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 - * Element the event was triggered on. - * @param {jQuery.Event} event - * Triggered 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 { + } 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. + } catch (e) { ajax.ajaxing = false; window.alert('An error occurred while attempting to process ' + ajax.options.url + ': ' + e.message); } }; - /** - * Handler for the form serialization. - * - * Runs before the beforeSend() handler (see below), and unlike that one, runs - * before field data is collected. - * - * @param {object} [element] - * Ajax object's `element_settings`. - * @param {object} options - * jQuery.ajax options. - */ 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 {Array.} form_values - * Processed form values. - * @param {jQuery} element - * The form node as a jQuery object. - * @param {object} options - * jQuery.ajax 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. - }; + Drupal.Ajax.prototype.beforeSubmit = function (form_values, element, options) {}; - /** - * Prepare the Ajax request before it is sent. - * - * @param {XMLHttpRequest} xmlhttprequest - * Native Ajax object. - * @param {object} options - * jQuery.ajax options. - */ 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) { @@ -813,9 +370,6 @@ $(this.element).after(this.progress.element); }; - /** - * Sets the throbber progress indicator. - */ Drupal.Ajax.prototype.setProgressIndicatorThrobber = function () { this.progress.element = $('
 
'); if (this.progress.message) { @@ -824,24 +378,12 @@ $(this.element).after(this.progress.element); }; - /** - * Sets the fullscreen progress indicator. - */ Drupal.Ajax.prototype.setProgressIndicatorFullscreen = function () { this.progress.element = $('
 
'); $('body').after(this.progress.element); }; - /** - * Handler for the form redirection completion. - * - * @param {Array.} response - * Drupal Ajax response. - * @param {number} status - * XMLHttpRequest status. - */ Drupal.Ajax.prototype.success = function (response, status) { - // Remove the progress element. if (this.progress.element) { $(this.progress.element).remove(); } @@ -850,14 +392,8 @@ } $(this.element).prop('disabled', false); - // 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 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; for (var i in response) { if (response.hasOwnProperty(i) && response[i].command && this.commands[response[i].command]) { @@ -868,9 +404,6 @@ } } - // 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; @@ -883,34 +416,14 @@ } } - // 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 - * Drupal Ajax response. - * @param {string} [response.effect] - * Override the default value of {@link Drupal.Ajax#element_settings}. - * @param {string|number} [response.speed] - * Override the default value of {@link Drupal.Ajax#element_settings}. - * - * @return {object} - * Returns an object with `showEffect`, `hideEffect` and `showSpeed` - * properties. - */ Drupal.Ajax.prototype.getEffect = function (response) { var type = response.effect || this.effect; var speed = response.speed || this.speed; @@ -920,13 +433,11 @@ effect.showEffect = 'show'; effect.hideEffect = 'hide'; effect.showSpeed = ''; - } - else if (type === 'fade') { + } else if (type === 'fade') { effect.showEffect = 'fadeIn'; effect.hideEffect = 'fadeOut'; effect.showSpeed = speed; - } - else { + } else { effect.showEffect = type + 'Toggle'; effect.hideEffect = type + 'Toggle'; effect.showSpeed = speed; @@ -935,29 +446,18 @@ return effect; }; - /** - * Handler for the form redirection error. - * - * @param {object} xmlhttprequest - * Native XMLHttpRequest object. - * @param {string} uri - * Ajax Request URI. - * @param {string} [customMessage] - * Extra message to print with the Ajax error. - */ Drupal.Ajax.prototype.error = function (xmlhttprequest, uri, customMessage) { - // 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) { var settings = this.settings || drupalSettings; Drupal.attachBehaviors(this.$form.get(0), settings); @@ -965,87 +465,21 @@ throw new Drupal.AjaxError(xmlhttprequest, uri, customMessage); }; - /** - * @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 - * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. - * @param {object} response - * The response from the Ajax request. - * @param {string} response.data - * The data to use with the jQuery method. - * @param {string} [response.method] - * The jQuery DOM manipulation method to be used. - * @param {string} [response.selector] - * A optional jQuery selector string. - * @param {object} [response.settings] - * An optional array of settings that will be used. - * @param {number} [status] - * The XMLHttpRequest status. - */ - insert: function (ajax, response, status) { - // Get information from the response. If it is not there, default to - // our presets. + insert: function insert(ajax, response, status) { 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 $('
').replaceWith(response.data). var $new_content_wrapped = $('
').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
- // elements are not allowed (e.g., within
, , and - // parents), we check if the new content satisfies the requirement - // of a single top-level element, and only use the container
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': @@ -1056,73 +490,34 @@ 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') { + } 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] - * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. - * @param {object} response - * The response from the Ajax request. - * @param {string} response.selector - * A jQuery selector string. - * @param {object} [response.settings] - * An optional array of settings that will be used. - * @param {number} [status] - * The XMLHttpRequest status. - */ - remove: function (ajax, response, status) { + remove: function remove(ajax, response, status) { var settings = response.settings || ajax.settings || drupalSettings; $(response.selector).each(function () { Drupal.detachBehaviors(this, settings); - }) - .remove(); + }).remove(); }, - /** - * Command to mark a chunk changed. - * - * @param {Drupal.Ajax} [ajax] - * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. - * @param {object} response - * The JSON response object from the Ajax request. - * @param {string} response.selector - * A jQuery selector string. - * @param {bool} [response.asterisk] - * An optional CSS selector. If specified, an asterisk will be - * appended to the HTML inside the provided selector. - * @param {number} [status] - * The request status. - */ - changed: function (ajax, response, status) { + changed: function changed(ajax, response, status) { var $element = $(response.selector); if (!$element.hasClass('ajax-changed')) { $element.addClass('ajax-changed'); @@ -1132,83 +527,23 @@ } }, - /** - * Command to provide an alert. - * - * @param {Drupal.Ajax} [ajax] - * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. - * @param {object} response - * The JSON response from the Ajax request. - * @param {string} response.text - * The text that will be displayed in an alert dialog. - * @param {number} [status] - * The XMLHttpRequest status. - */ - alert: function (ajax, response, status) { + alert: function alert(ajax, response, status) { window.alert(response.text, response.title); }, - /** - * Command to set the window.location, redirecting the browser. - * - * @param {Drupal.Ajax} [ajax] - * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. - * @param {object} response - * The response from the Ajax request. - * @param {string} response.url - * The URL to redirect to. - * @param {number} [status] - * The XMLHttpRequest status. - */ - redirect: function (ajax, response, status) { + redirect: function redirect(ajax, response, status) { window.location = response.url; }, - /** - * Command to provide the jQuery css() function. - * - * @param {Drupal.Ajax} [ajax] - * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. - * @param {object} response - * The response from the Ajax request. - * @param {string} response.selector - * A jQuery selector string. - * @param {object} response.argument - * An array of key/value pairs to set in the CSS for the selector. - * @param {number} [status] - * The XMLHttpRequest status. - */ - css: function (ajax, response, status) { + css: function css(ajax, response, status) { $(response.selector).css(response.argument); }, - /** - * Command to set the settings used for other commands in this response. - * - * This method will also remove expired `drupalSettings.ajax` settings. - * - * @param {Drupal.Ajax} [ajax] - * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. - * @param {object} response - * The response from the Ajax request. - * @param {bool} response.merge - * Determines whether the additional settings should be merged to the - * global settings. - * @param {object} response.settings - * Contains additional settings to add to the global settings. - * @param {number} [status] - * The XMLHttpRequest status. - */ - settings: function (ajax, response, status) { + settings: function settings(ajax, response, status) { var ajaxSettings = drupalSettings.ajax; - // Clean up drupalSettings.ajax. if (ajaxSettings) { Drupal.ajax.expired().forEach(function (instance) { - // If the Ajax object has been created through drupalSettings.ajax - // it will have a selector. When there is no selector the object - // has been initialized with a special class name picked up by the - // Ajax behavior. if (instance.selector) { var selector = instance.selector.replace('#', ''); @@ -1221,114 +556,31 @@ if (response.merge) { $.extend(true, drupalSettings, response.settings); - } - else { + } else { ajax.settings = response.settings; } }, - /** - * Command to attach data using jQuery's data API. - * - * @param {Drupal.Ajax} [ajax] - * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. - * @param {object} response - * The response from the Ajax request. - * @param {string} response.name - * The name or key (in the key value pair) of the data attached to this - * selector. - * @param {string} response.selector - * A jQuery selector string. - * @param {string|object} response.value - * The value of to be attached. - * @param {number} [status] - * The XMLHttpRequest status. - */ - data: function (ajax, response, status) { + data: function data(ajax, response, status) { $(response.selector).data(response.name, response.value); }, - /** - * Command to apply a jQuery method. - * - * @param {Drupal.Ajax} [ajax] - * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. - * @param {object} response - * The response from the Ajax request. - * @param {Array} response.args - * An array of arguments to the jQuery method, if any. - * @param {string} response.method - * The jQuery method to invoke. - * @param {string} response.selector - * A jQuery selector string. - * @param {number} [status] - * The XMLHttpRequest status. - */ - invoke: function (ajax, response, status) { + invoke: function invoke(ajax, response, status) { var $element = $(response.selector); $element[response.method].apply($element, response.args); }, - /** - * Command to restripe a table. - * - * @param {Drupal.Ajax} [ajax] - * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. - * @param {object} response - * The response from the Ajax request. - * @param {string} response.selector - * A jQuery selector string. - * @param {number} [status] - * The XMLHttpRequest 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'); + restripe: function restripe(ajax, response, status) { + $(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] - * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. - * @param {object} response - * The response from the Ajax request. - * @param {string} response.old - * The old form build ID. - * @param {string} response.new - * The new form build ID. - * @param {number} [status] - * The XMLHttpRequest status. - */ - update_build_id: function (ajax, response, status) { + update_build_id: function update_build_id(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] - * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. - * @param {object} response - * The response from the Ajax request. - * @param {string} response.data - * A string that contains the styles to be added. - * @param {number} [status] - * The XMLHttpRequest status. - */ - add_css: function (ajax, response, status) { - // Add the styles in the normal way. + add_css: function add_css(ajax, response, status) { $('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)) { @@ -1340,5 +592,6 @@ } } }; - })(jQuery, window, Drupal, drupalSettings); + +//# sourceMappingURL=ajax.js.map \ No newline at end of file diff --git a/core/misc/ajax.js.map b/core/misc/ajax.js.map new file mode 100644 index 0000000..b570936 --- /dev/null +++ b/core/misc/ajax.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["ajax.es6.js"],"names":["$","window","Drupal","drupalSettings","behaviors","AJAX","attach","context","settings","loadAjaxBehavior","base","element_settings","ajax","selector","once","each","element","hasOwnProperty","progress","type","href","attr","url","event","dialogType","data","dialog","form","setClick","detach","trigger","expired","forEach","instance","instances","instanceIndex","AjaxError","xmlhttp","uri","customMessage","statusCode","statusText","pathText","responseText","readyStateText","status","t","trim","e","replace","readyState","message","name","prototype","Error","constructor","arguments","length","Ajax","push","filter","document","body","contains","defaults","keypress","effect","speed","method","submit","js","extend","commands","AjaxCommands","wrapper","$form","$element","is","originalUrl","ajaxTrustedUrl","options","beforeSerialize","beforeSubmit","form_values","ajaxing","beforeSend","xmlhttprequest","success","response","parseJSON","getResponseHeader","error","complete","dataType","dialogOptions","indexOf","WRAPPER_FORMAT","on","isLocal","eventResponse","keypressResponse","prevent","AJAX_REQUEST_PARAMETER","execute","alert","Deferred","reject","which","preventDefault","stopPropagation","clk","ajaxSubmit","detachBehaviors","get","pageState","ajaxPageState","theme","theme_token","libraries","extraData","ajax_iframe_upload","v","fieldValue","prop","progressIndicatorMethod","slice","toUpperCase","toLowerCase","call","setProgressIndicatorBar","progressBar","ProgressBar","id","noop","setProgress","startMonitoring","interval","addClass","object","after","setProgressIndicatorThrobber","find","setProgressIndicatorFullscreen","remove","stopMonitoring","elementParents","parents","addBack","toArray","focusChanged","i","command","target","n","querySelector","getAttribute","attachBehaviors","getEffect","showEffect","hideEffect","showSpeed","show","insert","$wrapper","$new_content_wrapped","html","$new_content","contents","nodeType","hide","changed","hasClass","asterisk","append","text","title","redirect","location","css","argument","ajaxSettings","merge","value","invoke","apply","args","restripe","removeClass","end","update_build_id","old","val","new","add_css","prepend","match","importMatch","styleSheets","addImport","test","lastIndex","exec","jQuery"],"mappings":"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","file":"ajax.es6.js","sourcesContent":["/**\n * @file\n * Provides Ajax page updating via jQuery $.ajax.\n *\n * Ajax is a method of making a request via JavaScript while viewing an HTML\n * page. The request returns an array of commands encoded in JSON, which is\n * then executed to make any changes that are necessary to the page.\n *\n * Drupal uses this file to enhance form elements with `#ajax['url']` and\n * `#ajax['wrapper']` properties. If set, this file will automatically be\n * included to provide Ajax capabilities.\n */\n\n(function ($, window, Drupal, drupalSettings) {\n\n 'use strict';\n\n /**\n * Attaches the Ajax behavior to each Ajax form element.\n *\n * @type {Drupal~behavior}\n *\n * @prop {Drupal~behaviorAttach} attach\n * Initialize all {@link Drupal.Ajax} objects declared in\n * `drupalSettings.ajax` or initialize {@link Drupal.Ajax} objects from\n * DOM elements having the `use-ajax-submit` or `use-ajax` css class.\n * @prop {Drupal~behaviorDetach} detach\n * During `unload` remove all {@link Drupal.Ajax} objects related to\n * the removed content.\n */\n Drupal.behaviors.AJAX = {\n attach: function (context, settings) {\n\n function loadAjaxBehavior(base) {\n var element_settings = settings.ajax[base];\n if (typeof element_settings.selector === 'undefined') {\n element_settings.selector = '#' + base;\n }\n $(element_settings.selector).once('drupal-ajax').each(function () {\n element_settings.element = this;\n element_settings.base = base;\n Drupal.ajax(element_settings);\n });\n }\n\n // Load all Ajax behaviors specified in the settings.\n for (var base in settings.ajax) {\n if (settings.ajax.hasOwnProperty(base)) {\n loadAjaxBehavior(base);\n }\n }\n\n // Bind Ajax behaviors to all items showing the class.\n $('.use-ajax').once('ajax').each(function () {\n var element_settings = {};\n // Clicked links look better with the throbber than the progress bar.\n element_settings.progress = {type: 'throbber'};\n\n // For anchor tags, these will go to the target of the anchor rather\n // than the usual location.\n var href = $(this).attr('href');\n if (href) {\n element_settings.url = href;\n element_settings.event = 'click';\n }\n element_settings.dialogType = $(this).data('dialog-type');\n element_settings.dialog = $(this).data('dialog-options');\n element_settings.base = $(this).attr('id');\n element_settings.element = this;\n Drupal.ajax(element_settings);\n });\n\n // This class means to submit the form to the action using Ajax.\n $('.use-ajax-submit').once('ajax').each(function () {\n var element_settings = {};\n\n // Ajax submits specified in this manner automatically submit to the\n // normal form action.\n element_settings.url = $(this.form).attr('action');\n // Form submit button clicks need to tell the form what was clicked so\n // it gets passed in the POST request.\n element_settings.setClick = true;\n // Form buttons use the 'click' event rather than mousedown.\n element_settings.event = 'click';\n // Clicked form buttons look better with the throbber than the progress\n // bar.\n element_settings.progress = {type: 'throbber'};\n element_settings.base = $(this).attr('id');\n element_settings.element = this;\n\n Drupal.ajax(element_settings);\n });\n },\n\n detach: function (context, settings, trigger) {\n if (trigger === 'unload') {\n Drupal.ajax.expired().forEach(function (instance) {\n // Set this to null and allow garbage collection to reclaim\n // the memory.\n Drupal.ajax.instances[instance.instanceIndex] = null;\n });\n }\n }\n };\n\n /**\n * Extends Error to provide handling for Errors in Ajax.\n *\n * @constructor\n *\n * @augments Error\n *\n * @param {XMLHttpRequest} xmlhttp\n * XMLHttpRequest object used for the failed request.\n * @param {string} uri\n * The URI where the error occurred.\n * @param {string} customMessage\n * The custom message.\n */\n Drupal.AjaxError = function (xmlhttp, uri, customMessage) {\n\n var statusCode;\n var statusText;\n var pathText;\n var responseText;\n var readyStateText;\n if (xmlhttp.status) {\n statusCode = '\\n' + Drupal.t('An AJAX HTTP error occurred.') + '\\n' + Drupal.t('HTTP Result Code: !status', {'!status': xmlhttp.status});\n }\n else {\n statusCode = '\\n' + Drupal.t('An AJAX HTTP request terminated abnormally.');\n }\n statusCode += '\\n' + Drupal.t('Debugging information follows.');\n pathText = '\\n' + Drupal.t('Path: !uri', {'!uri': uri});\n statusText = '';\n // In some cases, when statusCode === 0, xmlhttp.statusText may not be\n // defined. Unfortunately, testing for it with typeof, etc, doesn't seem to\n // catch that and the test causes an exception. So we need to catch the\n // exception here.\n try {\n statusText = '\\n' + Drupal.t('StatusText: !statusText', {'!statusText': $.trim(xmlhttp.statusText)});\n }\n catch (e) {\n // Empty.\n }\n\n responseText = '';\n // Again, we don't have a way to know for sure whether accessing\n // xmlhttp.responseText is going to throw an exception. So we'll catch it.\n try {\n responseText = '\\n' + Drupal.t('ResponseText: !responseText', {'!responseText': $.trim(xmlhttp.responseText)});\n }\n catch (e) {\n // Empty.\n }\n\n // Make the responseText more readable by stripping HTML tags and newlines.\n responseText = responseText.replace(/<(\"[^\"]*\"|'[^']*'|[^'\">])*>/gi, '');\n responseText = responseText.replace(/[\\n]+\\s+/g, '\\n');\n\n // We don't need readyState except for status == 0.\n readyStateText = xmlhttp.status === 0 ? ('\\n' + Drupal.t('ReadyState: !readyState', {'!readyState': xmlhttp.readyState})) : '';\n\n customMessage = customMessage ? ('\\n' + Drupal.t('CustomMessage: !customMessage', {'!customMessage': customMessage})) : '';\n\n /**\n * Formatted and translated error message.\n *\n * @type {string}\n */\n this.message = statusCode + pathText + statusText + customMessage + responseText + readyStateText;\n\n /**\n * Used by some browsers to display a more accurate stack trace.\n *\n * @type {string}\n */\n this.name = 'AjaxError';\n };\n\n Drupal.AjaxError.prototype = new Error();\n Drupal.AjaxError.prototype.constructor = Drupal.AjaxError;\n\n /**\n * Provides Ajax page updating via jQuery $.ajax.\n *\n * This function is designed to improve developer experience by wrapping the\n * initialization of {@link Drupal.Ajax} objects and storing all created\n * objects in the {@link Drupal.ajax.instances} array.\n *\n * @example\n * Drupal.behaviors.myCustomAJAXStuff = {\n * attach: function (context, settings) {\n *\n * var ajaxSettings = {\n * url: 'my/url/path',\n * // If the old version of Drupal.ajax() needs to be used those\n * // properties can be added\n * base: 'myBase',\n * element: $(context).find('.someElement')\n * };\n *\n * var myAjaxObject = Drupal.ajax(ajaxSettings);\n *\n * // Declare a new Ajax command specifically for this Ajax object.\n * myAjaxObject.commands.insert = function (ajax, response, status) {\n * $('#my-wrapper').append(response.data);\n * alert('New content was appended to #my-wrapper');\n * };\n *\n * // This command will remove this Ajax object from the page.\n * myAjaxObject.commands.destroyObject = function (ajax, response, status) {\n * Drupal.ajax.instances[this.instanceIndex] = null;\n * };\n *\n * // Programmatically trigger the Ajax request.\n * myAjaxObject.execute();\n * }\n * };\n *\n * @param {object} settings\n * The settings object passed to {@link Drupal.Ajax} constructor.\n * @param {string} [settings.base]\n * Base is passed to {@link Drupal.Ajax} constructor as the 'base'\n * parameter.\n * @param {HTMLElement} [settings.element]\n * Element parameter of {@link Drupal.Ajax} constructor, element on which\n * event listeners will be bound.\n *\n * @return {Drupal.Ajax}\n * The created Ajax object.\n *\n * @see Drupal.AjaxCommands\n */\n Drupal.ajax = function (settings) {\n if (arguments.length !== 1) {\n throw new Error('Drupal.ajax() function must be called with one configuration object only');\n }\n // Map those config keys to variables for the old Drupal.ajax function.\n var base = settings.base || false;\n var element = settings.element || false;\n delete settings.base;\n delete settings.element;\n\n // By default do not display progress for ajax calls without an element.\n if (!settings.progress && !element) {\n settings.progress = false;\n }\n\n var ajax = new Drupal.Ajax(base, element, settings);\n ajax.instanceIndex = Drupal.ajax.instances.length;\n Drupal.ajax.instances.push(ajax);\n\n return ajax;\n };\n\n /**\n * Contains all created Ajax objects.\n *\n * @type {Array.}\n */\n Drupal.ajax.instances = [];\n\n /**\n * List all objects where the associated element is not in the DOM\n *\n * This method ignores {@link Drupal.Ajax} objects not bound to DOM elements\n * when created with {@link Drupal.ajax}.\n *\n * @return {Array.}\n * The list of expired {@link Drupal.Ajax} objects.\n */\n Drupal.ajax.expired = function () {\n return Drupal.ajax.instances.filter(function (instance) {\n return instance && instance.element !== false && !document.body.contains(instance.element);\n });\n };\n\n /**\n * Settings for an Ajax object.\n *\n * @typedef {object} Drupal.Ajax~element_settings\n *\n * @prop {string} url\n * Target of the Ajax request.\n * @prop {?string} [event]\n * Event bound to settings.element which will trigger the Ajax request.\n * @prop {bool} [keypress=true]\n * Triggers a request on keypress events.\n * @prop {?string} selector\n * jQuery selector targeting the element to bind events to or used with\n * {@link Drupal.AjaxCommands}.\n * @prop {string} [effect='none']\n * Name of the jQuery method to use for displaying new Ajax content.\n * @prop {string|number} [speed='none']\n * Speed with which to apply the effect.\n * @prop {string} [method]\n * Name of the jQuery method used to insert new content in the targeted\n * element.\n * @prop {object} [progress]\n * Settings for the display of a user-friendly loader.\n * @prop {string} [progress.type='throbber']\n * Type of progress element, core provides `'bar'`, `'throbber'` and\n * `'fullscreen'`.\n * @prop {string} [progress.message=Drupal.t('Please wait...')]\n * Custom message to be used with the bar indicator.\n * @prop {object} [submit]\n * Extra data to be sent with the Ajax request.\n * @prop {bool} [submit.js=true]\n * Allows the PHP side to know this comes from an Ajax request.\n * @prop {object} [dialog]\n * Options for {@link Drupal.dialog}.\n * @prop {string} [dialogType]\n * One of `'modal'` or `'dialog'`.\n * @prop {string} [prevent]\n * List of events on which to stop default action and stop propagation.\n */\n\n /**\n * Ajax constructor.\n *\n * The Ajax request returns an array of commands encoded in JSON, which is\n * then executed to make any changes that are necessary to the page.\n *\n * Drupal uses this file to enhance form elements with `#ajax['url']` and\n * `#ajax['wrapper']` properties. If set, this file will automatically be\n * included to provide Ajax capabilities.\n *\n * @constructor\n *\n * @param {string} [base]\n * Base parameter of {@link Drupal.Ajax} constructor\n * @param {HTMLElement} [element]\n * Element parameter of {@link Drupal.Ajax} constructor, element on which\n * event listeners will be bound.\n * @param {Drupal.Ajax~element_settings} element_settings\n * Settings for this Ajax object.\n */\n Drupal.Ajax = function (base, element, element_settings) {\n var defaults = {\n event: element ? 'mousedown' : null,\n keypress: true,\n selector: base ? '#' + base : null,\n effect: 'none',\n speed: 'none',\n method: 'replaceWith',\n progress: {\n type: 'throbber',\n message: Drupal.t('Please wait...')\n },\n submit: {\n js: true\n }\n };\n\n $.extend(this, defaults, element_settings);\n\n /**\n * @type {Drupal.AjaxCommands}\n */\n this.commands = new Drupal.AjaxCommands();\n\n /**\n * @type {bool|number}\n */\n this.instanceIndex = false;\n\n // @todo Remove this after refactoring the PHP code to:\n // - Call this 'selector'.\n // - Include the '#' for ID-based selectors.\n // - Support non-ID-based selectors.\n if (this.wrapper) {\n\n /**\n * @type {string}\n */\n this.wrapper = '#' + this.wrapper;\n }\n\n /**\n * @type {HTMLElement}\n */\n this.element = element;\n\n /**\n * @type {Drupal.Ajax~element_settings}\n */\n this.element_settings = element_settings;\n\n // If there isn't a form, jQuery.ajax() will be used instead, allowing us to\n // bind Ajax to links as well.\n if (this.element && this.element.form) {\n\n /**\n * @type {jQuery}\n */\n this.$form = $(this.element.form);\n }\n\n // If no Ajax callback URL was given, use the link href or form action.\n if (!this.url) {\n var $element = $(this.element);\n if ($element.is('a')) {\n this.url = $element.attr('href');\n }\n else if (this.element && element.form) {\n this.url = this.$form.attr('action');\n }\n }\n\n // Replacing 'nojs' with 'ajax' in the URL allows for an easy method to let\n // the server detect when it needs to degrade gracefully.\n // There are four scenarios to check for:\n // 1. /nojs/\n // 2. /nojs$ - The end of a URL string.\n // 3. /nojs? - Followed by a query (e.g. path/nojs?destination=foobar).\n // 4. /nojs# - Followed by a fragment (e.g.: path/nojs#myfragment).\n var originalUrl = this.url;\n\n /**\n * Processed Ajax URL.\n *\n * @type {string}\n */\n this.url = this.url.replace(/\\/nojs(\\/|$|\\?|#)/g, '/ajax$1');\n // If the 'nojs' version of the URL is trusted, also trust the 'ajax'\n // version.\n if (drupalSettings.ajaxTrustedUrl[originalUrl]) {\n drupalSettings.ajaxTrustedUrl[this.url] = true;\n }\n\n // Set the options for the ajaxSubmit function.\n // The 'this' variable will not persist inside of the options object.\n var ajax = this;\n\n /**\n * Options for the jQuery.ajax function.\n *\n * @name Drupal.Ajax#options\n *\n * @type {object}\n *\n * @prop {string} url\n * Ajax URL to be called.\n * @prop {object} data\n * Ajax payload.\n * @prop {function} beforeSerialize\n * Implement jQuery beforeSerialize function to call\n * {@link Drupal.Ajax#beforeSerialize}.\n * @prop {function} beforeSubmit\n * Implement jQuery beforeSubmit function to call\n * {@link Drupal.Ajax#beforeSubmit}.\n * @prop {function} beforeSend\n * Implement jQuery beforeSend function to call\n * {@link Drupal.Ajax#beforeSend}.\n * @prop {function} success\n * Implement jQuery success function to call\n * {@link Drupal.Ajax#success}.\n * @prop {function} complete\n * Implement jQuery success function to clean up ajax state and trigger an\n * error if needed.\n * @prop {string} dataType='json'\n * Type of the response expected.\n * @prop {string} type='POST'\n * HTTP method to use for the Ajax request.\n */\n ajax.options = {\n url: ajax.url,\n data: ajax.submit,\n beforeSerialize: function (element_settings, options) {\n return ajax.beforeSerialize(element_settings, options);\n },\n beforeSubmit: function (form_values, element_settings, options) {\n ajax.ajaxing = true;\n return ajax.beforeSubmit(form_values, element_settings, options);\n },\n beforeSend: function (xmlhttprequest, options) {\n ajax.ajaxing = true;\n return ajax.beforeSend(xmlhttprequest, options);\n },\n success: function (response, status, xmlhttprequest) {\n // Sanity check for browser support (object expected).\n // When using iFrame uploads, responses must be returned as a string.\n if (typeof response === 'string') {\n response = $.parseJSON(response);\n }\n\n // Prior to invoking the response's commands, verify that they can be\n // trusted by checking for a response header. See\n // \\Drupal\\Core\\EventSubscriber\\AjaxResponseSubscriber for details.\n // - Empty responses are harmless so can bypass verification. This\n // avoids an alert message for server-generated no-op responses that\n // skip Ajax rendering.\n // - Ajax objects with trusted URLs (e.g., ones defined server-side via\n // #ajax) can bypass header verification. This is especially useful\n // for Ajax with multipart forms. Because IFRAME transport is used,\n // the response headers cannot be accessed for verification.\n if (response !== null && !drupalSettings.ajaxTrustedUrl[ajax.url]) {\n if (xmlhttprequest.getResponseHeader('X-Drupal-Ajax-Token') !== '1') {\n var customMessage = Drupal.t('The response failed verification so will not be processed.');\n return ajax.error(xmlhttprequest, ajax.url, customMessage);\n }\n }\n\n return ajax.success(response, status);\n },\n complete: function (xmlhttprequest, status) {\n ajax.ajaxing = false;\n if (status === 'error' || status === 'parsererror') {\n return ajax.error(xmlhttprequest, ajax.url);\n }\n },\n dataType: 'json',\n type: 'POST'\n };\n\n if (element_settings.dialog) {\n ajax.options.data.dialogOptions = element_settings.dialog;\n }\n\n // Ensure that we have a valid URL by adding ? when no query parameter is\n // yet available, otherwise append using &.\n if (ajax.options.url.indexOf('?') === -1) {\n ajax.options.url += '?';\n }\n else {\n ajax.options.url += '&';\n }\n ajax.options.url += Drupal.ajax.WRAPPER_FORMAT + '=drupal_' + (element_settings.dialogType || 'ajax');\n\n // Bind the ajaxSubmit function to the element event.\n $(ajax.element).on(element_settings.event, function (event) {\n if (!drupalSettings.ajaxTrustedUrl[ajax.url] && !Drupal.url.isLocal(ajax.url)) {\n throw new Error(Drupal.t('The callback URL is not local and not trusted: !url', {'!url': ajax.url}));\n }\n return ajax.eventResponse(this, event);\n });\n\n // If necessary, enable keyboard submission so that Ajax behaviors\n // can be triggered through keyboard input as well as e.g. a mousedown\n // action.\n if (element_settings.keypress) {\n $(ajax.element).on('keypress', function (event) {\n return ajax.keypressResponse(this, event);\n });\n }\n\n // If necessary, prevent the browser default action of an additional event.\n // For example, prevent the browser default action of a click, even if the\n // Ajax behavior binds to mousedown.\n if (element_settings.prevent) {\n $(ajax.element).on(element_settings.prevent, false);\n }\n };\n\n /**\n * URL query attribute to indicate the wrapper used to render a request.\n *\n * The wrapper format determines how the HTML is wrapped, for example in a\n * modal dialog.\n *\n * @const {string}\n *\n * @default\n */\n Drupal.ajax.WRAPPER_FORMAT = '_wrapper_format';\n\n /**\n * Request parameter to indicate that a request is a Drupal Ajax request.\n *\n * @const {string}\n *\n * @default\n */\n Drupal.Ajax.AJAX_REQUEST_PARAMETER = '_drupal_ajax';\n\n /**\n * Execute the ajax request.\n *\n * Allows developers to execute an Ajax request manually without specifying\n * an event to respond to.\n *\n * @return {object}\n * Returns the jQuery.Deferred object underlying the Ajax request. If\n * pre-serialization fails, the Deferred will be returned in the rejected\n * state.\n */\n Drupal.Ajax.prototype.execute = function () {\n // Do not perform another ajax command if one is already in progress.\n if (this.ajaxing) {\n return;\n }\n\n try {\n this.beforeSerialize(this.element, this.options);\n // Return the jqXHR so that external code can hook into the Deferred API.\n return $.ajax(this.options);\n }\n catch (e) {\n // Unset the ajax.ajaxing flag here because it won't be unset during\n // the complete response.\n this.ajaxing = false;\n window.alert('An error occurred while attempting to process ' + this.options.url + ': ' + e.message);\n // For consistency, return a rejected Deferred (i.e., jqXHR's superclass)\n // so that calling code can take appropriate action.\n return $.Deferred().reject();\n }\n };\n\n /**\n * Handle a key press.\n *\n * The Ajax object will, if instructed, bind to a key press response. This\n * will test to see if the key press is valid to trigger this event and\n * if it is, trigger it for us and prevent other keypresses from triggering.\n * In this case we're handling RETURN and SPACEBAR keypresses (event codes 13\n * and 32. RETURN is often used to submit a form when in a textfield, and\n * SPACE is often used to activate an element without submitting.\n *\n * @param {HTMLElement} element\n * Element the event was triggered on.\n * @param {jQuery.Event} event\n * Triggered event.\n */\n Drupal.Ajax.prototype.keypressResponse = function (element, event) {\n // Create a synonym for this to reduce code confusion.\n var ajax = this;\n\n // Detect enter key and space bar and allow the standard response for them,\n // except for form elements of type 'text', 'tel', 'number' and 'textarea',\n // where the spacebar activation causes inappropriate activation if\n // #ajax['keypress'] is TRUE. On a text-type widget a space should always\n // be a space.\n if (event.which === 13 || (event.which === 32 && element.type !== 'text' &&\n element.type !== 'textarea' && element.type !== 'tel' && element.type !== 'number')) {\n event.preventDefault();\n event.stopPropagation();\n $(ajax.element_settings.element).trigger(ajax.element_settings.event);\n }\n };\n\n /**\n * Handle an event that triggers an Ajax response.\n *\n * When an event that triggers an Ajax response happens, this method will\n * perform the actual Ajax call. It is bound to the event using\n * bind() in the constructor, and it uses the options specified on the\n * Ajax object.\n *\n * @param {HTMLElement} element\n * Element the event was triggered on.\n * @param {jQuery.Event} event\n * Triggered event.\n */\n Drupal.Ajax.prototype.eventResponse = function (element, event) {\n event.preventDefault();\n event.stopPropagation();\n\n // Create a synonym for this to reduce code confusion.\n var ajax = this;\n\n // Do not perform another Ajax command if one is already in progress.\n if (ajax.ajaxing) {\n return;\n }\n\n try {\n if (ajax.$form) {\n // If setClick is set, we must set this to ensure that the button's\n // value is passed.\n if (ajax.setClick) {\n // Mark the clicked button. 'form.clk' is a special variable for\n // ajaxSubmit that tells the system which element got clicked to\n // trigger the submit. Without it there would be no 'op' or\n // equivalent.\n element.form.clk = element;\n }\n\n ajax.$form.ajaxSubmit(ajax.options);\n }\n else {\n ajax.beforeSerialize(ajax.element, ajax.options);\n $.ajax(ajax.options);\n }\n }\n catch (e) {\n // Unset the ajax.ajaxing flag here because it won't be unset during\n // the complete response.\n ajax.ajaxing = false;\n window.alert('An error occurred while attempting to process ' + ajax.options.url + ': ' + e.message);\n }\n };\n\n /**\n * Handler for the form serialization.\n *\n * Runs before the beforeSend() handler (see below), and unlike that one, runs\n * before field data is collected.\n *\n * @param {object} [element]\n * Ajax object's `element_settings`.\n * @param {object} options\n * jQuery.ajax options.\n */\n Drupal.Ajax.prototype.beforeSerialize = function (element, options) {\n // Allow detaching behaviors to update field values before collecting them.\n // This is only needed when field values are added to the POST data, so only\n // when there is a form such that this.$form.ajaxSubmit() is used instead of\n // $.ajax(). When there is no form and $.ajax() is used, beforeSerialize()\n // isn't called, but don't rely on that: explicitly check this.$form.\n if (this.$form) {\n var settings = this.settings || drupalSettings;\n Drupal.detachBehaviors(this.$form.get(0), settings, 'serialize');\n }\n\n // Inform Drupal that this is an AJAX request.\n options.data[Drupal.Ajax.AJAX_REQUEST_PARAMETER] = 1;\n\n // Allow Drupal to return new JavaScript and CSS files to load without\n // returning the ones already loaded.\n // @see \\Drupal\\Core\\Theme\\AjaxBasePageNegotiator\n // @see \\Drupal\\Core\\Asset\\LibraryDependencyResolverInterface::getMinimalRepresentativeSubset()\n // @see system_js_settings_alter()\n var pageState = drupalSettings.ajaxPageState;\n options.data['ajax_page_state[theme]'] = pageState.theme;\n options.data['ajax_page_state[theme_token]'] = pageState.theme_token;\n options.data['ajax_page_state[libraries]'] = pageState.libraries;\n };\n\n /**\n * Modify form values prior to form submission.\n *\n * @param {Array.} form_values\n * Processed form values.\n * @param {jQuery} element\n * The form node as a jQuery object.\n * @param {object} options\n * jQuery.ajax options.\n */\n Drupal.Ajax.prototype.beforeSubmit = function (form_values, element, options) {\n // This function is left empty to make it simple to override for modules\n // that wish to add functionality here.\n };\n\n /**\n * Prepare the Ajax request before it is sent.\n *\n * @param {XMLHttpRequest} xmlhttprequest\n * Native Ajax object.\n * @param {object} options\n * jQuery.ajax options.\n */\n Drupal.Ajax.prototype.beforeSend = function (xmlhttprequest, options) {\n // For forms without file inputs, the jQuery Form plugin serializes the\n // form values, and then calls jQuery's $.ajax() function, which invokes\n // this handler. In this circumstance, options.extraData is never used. For\n // forms with file inputs, the jQuery Form plugin uses the browser's normal\n // form submission mechanism, but captures the response in a hidden IFRAME.\n // In this circumstance, it calls this handler first, and then appends\n // hidden fields to the form to submit the values in options.extraData.\n // There is no simple way to know which submission mechanism will be used,\n // so we add to extraData regardless, and allow it to be ignored in the\n // former case.\n if (this.$form) {\n options.extraData = options.extraData || {};\n\n // Let the server know when the IFRAME submission mechanism is used. The\n // server can use this information to wrap the JSON response in a\n // TEXTAREA, as per http://jquery.malsup.com/form/#file-upload.\n options.extraData.ajax_iframe_upload = '1';\n\n // The triggering element is about to be disabled (see below), but if it\n // contains a value (e.g., a checkbox, textfield, select, etc.), ensure\n // that value is included in the submission. As per above, submissions\n // that use $.ajax() are already serialized prior to the element being\n // disabled, so this is only needed for IFRAME submissions.\n var v = $.fieldValue(this.element);\n if (v !== null) {\n options.extraData[this.element.name] = v;\n }\n }\n\n // Disable the element that received the change to prevent user interface\n // interaction while the Ajax request is in progress. ajax.ajaxing prevents\n // the element from triggering a new request, but does not prevent the user\n // from changing its value.\n $(this.element).prop('disabled', true);\n\n if (!this.progress || !this.progress.type) {\n return;\n }\n\n // Insert progress indicator.\n var progressIndicatorMethod = 'setProgressIndicator' + this.progress.type.slice(0, 1).toUpperCase() + this.progress.type.slice(1).toLowerCase();\n if (progressIndicatorMethod in this && typeof this[progressIndicatorMethod] === 'function') {\n this[progressIndicatorMethod].call(this);\n }\n };\n\n /**\n * Sets the progress bar progress indicator.\n */\n Drupal.Ajax.prototype.setProgressIndicatorBar = function () {\n var progressBar = new Drupal.ProgressBar('ajax-progress-' + this.element.id, $.noop, this.progress.method, $.noop);\n if (this.progress.message) {\n progressBar.setProgress(-1, this.progress.message);\n }\n if (this.progress.url) {\n progressBar.startMonitoring(this.progress.url, this.progress.interval || 1500);\n }\n this.progress.element = $(progressBar.element).addClass('ajax-progress ajax-progress-bar');\n this.progress.object = progressBar;\n $(this.element).after(this.progress.element);\n };\n\n /**\n * Sets the throbber progress indicator.\n */\n Drupal.Ajax.prototype.setProgressIndicatorThrobber = function () {\n this.progress.element = $('
 
');\n if (this.progress.message) {\n this.progress.element.find('.throbber').after('
' + this.progress.message + '
');\n }\n $(this.element).after(this.progress.element);\n };\n\n /**\n * Sets the fullscreen progress indicator.\n */\n Drupal.Ajax.prototype.setProgressIndicatorFullscreen = function () {\n this.progress.element = $('
 
');\n $('body').after(this.progress.element);\n };\n\n /**\n * Handler for the form redirection completion.\n *\n * @param {Array.} response\n * Drupal Ajax response.\n * @param {number} status\n * XMLHttpRequest status.\n */\n Drupal.Ajax.prototype.success = function (response, status) {\n // Remove the progress element.\n if (this.progress.element) {\n $(this.progress.element).remove();\n }\n if (this.progress.object) {\n this.progress.object.stopMonitoring();\n }\n $(this.element).prop('disabled', false);\n\n // Save element's ancestors tree so if the element is removed from the dom\n // we can try to refocus one of its parents. Using addBack reverse the\n // result array, meaning that index 0 is the highest parent in the hierarchy\n // in this situation it is usually a element.\n var elementParents = $(this.element).parents('[data-drupal-selector]').addBack().toArray();\n\n // Track if any command is altering the focus so we can avoid changing the\n // focus set by the Ajax command.\n var focusChanged = false;\n for (var i in response) {\n if (response.hasOwnProperty(i) && response[i].command && this.commands[response[i].command]) {\n this.commands[response[i].command](this, response[i], status);\n if (response[i].command === 'invoke' && response[i].method === 'focus') {\n focusChanged = true;\n }\n }\n }\n\n // If the focus hasn't be changed by the ajax commands, try to refocus the\n // triggering element or one of its parents if that element does not exist\n // anymore.\n if (!focusChanged && this.element && !$(this.element).data('disable-refocus')) {\n var target = false;\n\n for (var n = elementParents.length - 1; !target && n > 0; n--) {\n target = document.querySelector('[data-drupal-selector=\"' + elementParents[n].getAttribute('data-drupal-selector') + '\"]');\n }\n\n if (target) {\n $(target).trigger('focus');\n }\n }\n\n // Reattach behaviors, if they were detached in beforeSerialize(). The\n // attachBehaviors() called on the new content from processing the response\n // commands is not sufficient, because behaviors from the entire form need\n // to be reattached.\n if (this.$form) {\n var settings = this.settings || drupalSettings;\n Drupal.attachBehaviors(this.$form.get(0), settings);\n }\n\n // Remove any response-specific settings so they don't get used on the next\n // call by mistake.\n this.settings = null;\n };\n\n /**\n * Build an effect object to apply an effect when adding new HTML.\n *\n * @param {object} response\n * Drupal Ajax response.\n * @param {string} [response.effect]\n * Override the default value of {@link Drupal.Ajax#element_settings}.\n * @param {string|number} [response.speed]\n * Override the default value of {@link Drupal.Ajax#element_settings}.\n *\n * @return {object}\n * Returns an object with `showEffect`, `hideEffect` and `showSpeed`\n * properties.\n */\n Drupal.Ajax.prototype.getEffect = function (response) {\n var type = response.effect || this.effect;\n var speed = response.speed || this.speed;\n\n var effect = {};\n if (type === 'none') {\n effect.showEffect = 'show';\n effect.hideEffect = 'hide';\n effect.showSpeed = '';\n }\n else if (type === 'fade') {\n effect.showEffect = 'fadeIn';\n effect.hideEffect = 'fadeOut';\n effect.showSpeed = speed;\n }\n else {\n effect.showEffect = type + 'Toggle';\n effect.hideEffect = type + 'Toggle';\n effect.showSpeed = speed;\n }\n\n return effect;\n };\n\n /**\n * Handler for the form redirection error.\n *\n * @param {object} xmlhttprequest\n * Native XMLHttpRequest object.\n * @param {string} uri\n * Ajax Request URI.\n * @param {string} [customMessage]\n * Extra message to print with the Ajax error.\n */\n Drupal.Ajax.prototype.error = function (xmlhttprequest, uri, customMessage) {\n // Remove the progress element.\n if (this.progress.element) {\n $(this.progress.element).remove();\n }\n if (this.progress.object) {\n this.progress.object.stopMonitoring();\n }\n // Undo hide.\n $(this.wrapper).show();\n // Re-enable the element.\n $(this.element).prop('disabled', false);\n // Reattach behaviors, if they were detached in beforeSerialize().\n if (this.$form) {\n var settings = this.settings || drupalSettings;\n Drupal.attachBehaviors(this.$form.get(0), settings);\n }\n throw new Drupal.AjaxError(xmlhttprequest, uri, customMessage);\n };\n\n /**\n * @typedef {object} Drupal.AjaxCommands~commandDefinition\n *\n * @prop {string} command\n * @prop {string} [method]\n * @prop {string} [selector]\n * @prop {string} [data]\n * @prop {object} [settings]\n * @prop {bool} [asterisk]\n * @prop {string} [text]\n * @prop {string} [title]\n * @prop {string} [url]\n * @prop {object} [argument]\n * @prop {string} [name]\n * @prop {string} [value]\n * @prop {string} [old]\n * @prop {string} [new]\n * @prop {bool} [merge]\n * @prop {Array} [args]\n *\n * @see Drupal.AjaxCommands\n */\n\n /**\n * Provide a series of commands that the client will perform.\n *\n * @constructor\n */\n Drupal.AjaxCommands = function () {};\n Drupal.AjaxCommands.prototype = {\n\n /**\n * Command to insert new content into the DOM.\n *\n * @param {Drupal.Ajax} ajax\n * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.\n * @param {object} response\n * The response from the Ajax request.\n * @param {string} response.data\n * The data to use with the jQuery method.\n * @param {string} [response.method]\n * The jQuery DOM manipulation method to be used.\n * @param {string} [response.selector]\n * A optional jQuery selector string.\n * @param {object} [response.settings]\n * An optional array of settings that will be used.\n * @param {number} [status]\n * The XMLHttpRequest status.\n */\n insert: function (ajax, response, status) {\n // Get information from the response. If it is not there, default to\n // our presets.\n var $wrapper = response.selector ? $(response.selector) : $(ajax.wrapper);\n var method = response.method || ajax.method;\n var effect = ajax.getEffect(response);\n var settings;\n\n // We don't know what response.data contains: it might be a string of text\n // without HTML, so don't rely on jQuery correctly interpreting\n // $(response.data) as new HTML rather than a CSS selector. Also, if\n // response.data contains top-level text nodes, they get lost with either\n // $(response.data) or $('
').replaceWith(response.data).\n var $new_content_wrapped = $('
').html(response.data);\n var $new_content = $new_content_wrapped.contents();\n\n // For legacy reasons, the effects processing code assumes that\n // $new_content consists of a single top-level element. Also, it has not\n // been sufficiently tested whether attachBehaviors() can be successfully\n // called with a context object that includes top-level text nodes.\n // However, to give developers full control of the HTML appearing in the\n // page, and to enable Ajax content to be inserted in places where
\n // elements are not allowed (e.g., within
, , and \n // parents), we check if the new content satisfies the requirement\n // of a single top-level element, and only use the container
created\n // above when it doesn't. For more information, please see\n // https://www.drupal.org/node/736066.\n if ($new_content.length !== 1 || $new_content.get(0).nodeType !== 1) {\n $new_content = $new_content_wrapped;\n }\n\n // If removing content from the wrapper, detach behaviors first.\n switch (method) {\n case 'html':\n case 'replaceWith':\n case 'replaceAll':\n case 'empty':\n case 'remove':\n settings = response.settings || ajax.settings || drupalSettings;\n Drupal.detachBehaviors($wrapper.get(0), settings);\n }\n\n // Add the new content to the page.\n $wrapper[method]($new_content);\n\n // Immediately hide the new content if we're using any effects.\n if (effect.showEffect !== 'show') {\n $new_content.hide();\n }\n\n // Determine which effect to use and what content will receive the\n // effect, then show the new content.\n if ($new_content.find('.ajax-new-content').length > 0) {\n $new_content.find('.ajax-new-content').hide();\n $new_content.show();\n $new_content.find('.ajax-new-content')[effect.showEffect](effect.showSpeed);\n }\n else if (effect.showEffect !== 'show') {\n $new_content[effect.showEffect](effect.showSpeed);\n }\n\n // Attach all JavaScript behaviors to the new content, if it was\n // successfully added to the page, this if statement allows\n // `#ajax['wrapper']` to be optional.\n if ($new_content.parents('html').length > 0) {\n // Apply any settings from the returned JSON if available.\n settings = response.settings || ajax.settings || drupalSettings;\n Drupal.attachBehaviors($new_content.get(0), settings);\n }\n },\n\n /**\n * Command to remove a chunk from the page.\n *\n * @param {Drupal.Ajax} [ajax]\n * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.\n * @param {object} response\n * The response from the Ajax request.\n * @param {string} response.selector\n * A jQuery selector string.\n * @param {object} [response.settings]\n * An optional array of settings that will be used.\n * @param {number} [status]\n * The XMLHttpRequest status.\n */\n remove: function (ajax, response, status) {\n var settings = response.settings || ajax.settings || drupalSettings;\n $(response.selector).each(function () {\n Drupal.detachBehaviors(this, settings);\n })\n .remove();\n },\n\n /**\n * Command to mark a chunk changed.\n *\n * @param {Drupal.Ajax} [ajax]\n * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.\n * @param {object} response\n * The JSON response object from the Ajax request.\n * @param {string} response.selector\n * A jQuery selector string.\n * @param {bool} [response.asterisk]\n * An optional CSS selector. If specified, an asterisk will be\n * appended to the HTML inside the provided selector.\n * @param {number} [status]\n * The request status.\n */\n changed: function (ajax, response, status) {\n var $element = $(response.selector);\n if (!$element.hasClass('ajax-changed')) {\n $element.addClass('ajax-changed');\n if (response.asterisk) {\n $element.find(response.asterisk).append(' * ');\n }\n }\n },\n\n /**\n * Command to provide an alert.\n *\n * @param {Drupal.Ajax} [ajax]\n * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.\n * @param {object} response\n * The JSON response from the Ajax request.\n * @param {string} response.text\n * The text that will be displayed in an alert dialog.\n * @param {number} [status]\n * The XMLHttpRequest status.\n */\n alert: function (ajax, response, status) {\n window.alert(response.text, response.title);\n },\n\n /**\n * Command to set the window.location, redirecting the browser.\n *\n * @param {Drupal.Ajax} [ajax]\n * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.\n * @param {object} response\n * The response from the Ajax request.\n * @param {string} response.url\n * The URL to redirect to.\n * @param {number} [status]\n * The XMLHttpRequest status.\n */\n redirect: function (ajax, response, status) {\n window.location = response.url;\n },\n\n /**\n * Command to provide the jQuery css() function.\n *\n * @param {Drupal.Ajax} [ajax]\n * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.\n * @param {object} response\n * The response from the Ajax request.\n * @param {string} response.selector\n * A jQuery selector string.\n * @param {object} response.argument\n * An array of key/value pairs to set in the CSS for the selector.\n * @param {number} [status]\n * The XMLHttpRequest status.\n */\n css: function (ajax, response, status) {\n $(response.selector).css(response.argument);\n },\n\n /**\n * Command to set the settings used for other commands in this response.\n *\n * This method will also remove expired `drupalSettings.ajax` settings.\n *\n * @param {Drupal.Ajax} [ajax]\n * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.\n * @param {object} response\n * The response from the Ajax request.\n * @param {bool} response.merge\n * Determines whether the additional settings should be merged to the\n * global settings.\n * @param {object} response.settings\n * Contains additional settings to add to the global settings.\n * @param {number} [status]\n * The XMLHttpRequest status.\n */\n settings: function (ajax, response, status) {\n var ajaxSettings = drupalSettings.ajax;\n\n // Clean up drupalSettings.ajax.\n if (ajaxSettings) {\n Drupal.ajax.expired().forEach(function (instance) {\n // If the Ajax object has been created through drupalSettings.ajax\n // it will have a selector. When there is no selector the object\n // has been initialized with a special class name picked up by the\n // Ajax behavior.\n\n if (instance.selector) {\n var selector = instance.selector.replace('#', '');\n if (selector in ajaxSettings) {\n delete ajaxSettings[selector];\n }\n }\n });\n }\n\n if (response.merge) {\n $.extend(true, drupalSettings, response.settings);\n }\n else {\n ajax.settings = response.settings;\n }\n },\n\n /**\n * Command to attach data using jQuery's data API.\n *\n * @param {Drupal.Ajax} [ajax]\n * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.\n * @param {object} response\n * The response from the Ajax request.\n * @param {string} response.name\n * The name or key (in the key value pair) of the data attached to this\n * selector.\n * @param {string} response.selector\n * A jQuery selector string.\n * @param {string|object} response.value\n * The value of to be attached.\n * @param {number} [status]\n * The XMLHttpRequest status.\n */\n data: function (ajax, response, status) {\n $(response.selector).data(response.name, response.value);\n },\n\n /**\n * Command to apply a jQuery method.\n *\n * @param {Drupal.Ajax} [ajax]\n * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.\n * @param {object} response\n * The response from the Ajax request.\n * @param {Array} response.args\n * An array of arguments to the jQuery method, if any.\n * @param {string} response.method\n * The jQuery method to invoke.\n * @param {string} response.selector\n * A jQuery selector string.\n * @param {number} [status]\n * The XMLHttpRequest status.\n */\n invoke: function (ajax, response, status) {\n var $element = $(response.selector);\n $element[response.method].apply($element, response.args);\n },\n\n /**\n * Command to restripe a table.\n *\n * @param {Drupal.Ajax} [ajax]\n * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.\n * @param {object} response\n * The response from the Ajax request.\n * @param {string} response.selector\n * A jQuery selector string.\n * @param {number} [status]\n * The XMLHttpRequest status.\n */\n restripe: function (ajax, response, status) {\n // :even and :odd are reversed because jQuery counts from 0 and\n // we count from 1, so we're out of sync.\n // Match immediate children of the parent element to allow nesting.\n $(response.selector).find('> tbody > tr:visible, > tr:visible')\n .removeClass('odd even')\n .filter(':even').addClass('odd').end()\n .filter(':odd').addClass('even');\n },\n\n /**\n * Command to update a form's build ID.\n *\n * @param {Drupal.Ajax} [ajax]\n * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.\n * @param {object} response\n * The response from the Ajax request.\n * @param {string} response.old\n * The old form build ID.\n * @param {string} response.new\n * The new form build ID.\n * @param {number} [status]\n * The XMLHttpRequest status.\n */\n update_build_id: function (ajax, response, status) {\n $('input[name=\"form_build_id\"][value=\"' + response.old + '\"]').val(response.new);\n },\n\n /**\n * Command to add css.\n *\n * Uses the proprietary addImport method if available as browsers which\n * support that method ignore @import statements in dynamically added\n * stylesheets.\n *\n * @param {Drupal.Ajax} [ajax]\n * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.\n * @param {object} response\n * The response from the Ajax request.\n * @param {string} response.data\n * A string that contains the styles to be added.\n * @param {number} [status]\n * The XMLHttpRequest status.\n */\n add_css: function (ajax, response, status) {\n // Add the styles in the normal way.\n $('head').prepend(response.data);\n // Add imports in the styles using the addImport method if available.\n var match;\n var importMatch = /^@import url\\(\"(.*)\"\\);$/igm;\n if (document.styleSheets[0].addImport && importMatch.test(response.data)) {\n importMatch.lastIndex = 0;\n do {\n match = importMatch.exec(response.data);\n document.styleSheets[0].addImport(match[1]);\n } while (match);\n }\n }\n };\n\n})(jQuery, window, Drupal, drupalSettings);\n"]} \ No newline at end of file diff --git a/core/misc/announce.es6.js b/core/misc/announce.es6.js new file mode 100644 index 0000000..acf850a --- /dev/null +++ b/core/misc/announce.es6.js @@ -0,0 +1,120 @@ +/** + * @file + * Adds an HTML element and method to trigger audio UAs to read system messages. + * + * Use {@link Drupal.announce} to indicate to screen reader users that an + * element on the page has changed state. For instance, if clicking a link + * loads 10 more items into a list, one might announce the change like this. + * + * @example + * $('#search-list') + * .on('itemInsert', function (event, data) { + * // Insert the new items. + * $(data.container.el).append(data.items.el); + * // Announce the change to the page contents. + * Drupal.announce(Drupal.t('@count items added to @container', + * {'@count': data.items.length, '@container': data.container.title} + * )); + * }); + */ + +(function (Drupal, debounce) { + + 'use strict'; + + var liveElement; + var announcements = []; + + /** + * Builds a div element with the aria-live attribute and add it to the DOM. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for drupalAnnouce. + */ + Drupal.behaviors.drupalAnnounce = { + attach: function (context) { + // Create only one aria-live element. + if (!liveElement) { + liveElement = document.createElement('div'); + liveElement.id = 'drupal-live-announce'; + liveElement.className = 'visually-hidden'; + liveElement.setAttribute('aria-live', 'polite'); + liveElement.setAttribute('aria-busy', 'false'); + document.body.appendChild(liveElement); + } + } + }; + + /** + * Concatenates announcements to a single string; appends to the live region. + */ + function announce() { + var text = []; + var priority = 'polite'; + var announcement; + + // Create an array of announcement strings to be joined and appended to the + // aria live region. + var il = announcements.length; + for (var i = 0; i < il; i++) { + announcement = announcements.pop(); + text.unshift(announcement.text); + // If any of the announcements has a priority of assertive then the group + // of joined announcements will have this priority. + if (announcement.priority === 'assertive') { + priority = 'assertive'; + } + } + + if (text.length) { + // Clear the liveElement so that repeated strings will be read. + liveElement.innerHTML = ''; + // Set the busy state to true until the node changes are complete. + liveElement.setAttribute('aria-busy', 'true'); + // Set the priority to assertive, or default to polite. + liveElement.setAttribute('aria-live', priority); + // Print the text to the live region. Text should be run through + // Drupal.t() before being passed to Drupal.announce(). + liveElement.innerHTML = text.join('\n'); + // The live text area is updated. Allow the AT to announce the text. + liveElement.setAttribute('aria-busy', 'false'); + } + } + + /** + * Triggers audio UAs to read the supplied text. + * + * The aria-live region will only read the text that currently populates its + * text node. Replacing text quickly in rapid calls to announce results in + * only the text from the most recent call to {@link Drupal.announce} being + * read. By wrapping the call to announce in a debounce function, we allow for + * time for multiple calls to {@link Drupal.announce} to queue up their + * messages. These messages are then joined and append to the aria-live region + * as one text node. + * + * @param {string} text + * A string to be read by the UA. + * @param {string} [priority='polite'] + * A string to indicate the priority of the message. Can be either + * 'polite' or 'assertive'. + * + * @return {function} + * The return of the call to debounce. + * + * @see http://www.w3.org/WAI/PF/aria-practices/#liveprops + */ + Drupal.announce = function (text, priority) { + // Save the text and priority into a closure variable. Multiple simultaneous + // announcements will be concatenated and read in sequence. + announcements.push({ + text: text, + priority: priority + }); + // Immediately invoke the function that debounce returns. 200 ms is right at + // the cusp where humans notice a pause, so we will wait + // at most this much time before the set of queued announcements is read. + return (debounce(announce, 200)()); + }; +}(Drupal, Drupal.debounce)); diff --git a/core/misc/announce.js b/core/misc/announce.js index acf850a..bebea3b 100644 --- a/core/misc/announce.js +++ b/core/misc/announce.js @@ -1,22 +1,4 @@ -/** - * @file - * Adds an HTML element and method to trigger audio UAs to read system messages. - * - * Use {@link Drupal.announce} to indicate to screen reader users that an - * element on the page has changed state. For instance, if clicking a link - * loads 10 more items into a list, one might announce the change like this. - * - * @example - * $('#search-list') - * .on('itemInsert', function (event, data) { - * // Insert the new items. - * $(data.container.el).append(data.items.el); - * // Announce the change to the page contents. - * Drupal.announce(Drupal.t('@count items added to @container', - * {'@count': data.items.length, '@container': data.container.title} - * )); - * }); - */ +'use strict'; (function (Drupal, debounce) { @@ -25,17 +7,8 @@ var liveElement; var announcements = []; - /** - * Builds a div element with the aria-live attribute and add it to the DOM. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches the behavior for drupalAnnouce. - */ Drupal.behaviors.drupalAnnounce = { - attach: function (context) { - // Create only one aria-live element. + attach: function attach(context) { if (!liveElement) { liveElement = document.createElement('div'); liveElement.id = 'drupal-live-announce'; @@ -47,74 +20,42 @@ } }; - /** - * Concatenates announcements to a single string; appends to the live region. - */ function announce() { var text = []; var priority = 'polite'; var announcement; - // Create an array of announcement strings to be joined and appended to the - // aria live region. var il = announcements.length; for (var i = 0; i < il; i++) { announcement = announcements.pop(); text.unshift(announcement.text); - // If any of the announcements has a priority of assertive then the group - // of joined announcements will have this priority. + if (announcement.priority === 'assertive') { priority = 'assertive'; } } if (text.length) { - // Clear the liveElement so that repeated strings will be read. liveElement.innerHTML = ''; - // Set the busy state to true until the node changes are complete. + liveElement.setAttribute('aria-busy', 'true'); - // Set the priority to assertive, or default to polite. + liveElement.setAttribute('aria-live', priority); - // Print the text to the live region. Text should be run through - // Drupal.t() before being passed to Drupal.announce(). + liveElement.innerHTML = text.join('\n'); - // The live text area is updated. Allow the AT to announce the text. + liveElement.setAttribute('aria-busy', 'false'); } } - /** - * Triggers audio UAs to read the supplied text. - * - * The aria-live region will only read the text that currently populates its - * text node. Replacing text quickly in rapid calls to announce results in - * only the text from the most recent call to {@link Drupal.announce} being - * read. By wrapping the call to announce in a debounce function, we allow for - * time for multiple calls to {@link Drupal.announce} to queue up their - * messages. These messages are then joined and append to the aria-live region - * as one text node. - * - * @param {string} text - * A string to be read by the UA. - * @param {string} [priority='polite'] - * A string to indicate the priority of the message. Can be either - * 'polite' or 'assertive'. - * - * @return {function} - * The return of the call to debounce. - * - * @see http://www.w3.org/WAI/PF/aria-practices/#liveprops - */ Drupal.announce = function (text, priority) { - // Save the text and priority into a closure variable. Multiple simultaneous - // announcements will be concatenated and read in sequence. announcements.push({ text: text, priority: priority }); - // Immediately invoke the function that debounce returns. 200 ms is right at - // the cusp where humans notice a pause, so we will wait - // at most this much time before the set of queued announcements is read. - return (debounce(announce, 200)()); + + return debounce(announce, 200)(); }; -}(Drupal, Drupal.debounce)); +})(Drupal, Drupal.debounce); + +//# sourceMappingURL=announce.js.map \ No newline at end of file diff --git a/core/misc/announce.js.map b/core/misc/announce.js.map new file mode 100644 index 0000000..d769951 --- /dev/null +++ b/core/misc/announce.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["announce.es6.js"],"names":["Drupal","debounce","liveElement","announcements","behaviors","drupalAnnounce","attach","context","document","createElement","id","className","setAttribute","body","appendChild","announce","text","priority","announcement","il","length","i","pop","unshift","innerHTML","join","push"],"mappings":";;AAoBC,WAAUA,MAAV,EAAkBC,QAAlB,EAA4B;;AAE3B;;AAEA,MAAIC,WAAJ;AACA,MAAIC,gBAAgB,EAApB;;AAUAH,SAAOI,SAAP,CAAiBC,cAAjB,GAAkC;AAChCC,YAAQ,gBAAUC,OAAV,EAAmB;AAEzB,UAAI,CAACL,WAAL,EAAkB;AAChBA,sBAAcM,SAASC,aAAT,CAAuB,KAAvB,CAAd;AACAP,oBAAYQ,EAAZ,GAAiB,sBAAjB;AACAR,oBAAYS,SAAZ,GAAwB,iBAAxB;AACAT,oBAAYU,YAAZ,CAAyB,WAAzB,EAAsC,QAAtC;AACAV,oBAAYU,YAAZ,CAAyB,WAAzB,EAAsC,OAAtC;AACAJ,iBAASK,IAAT,CAAcC,WAAd,CAA0BZ,WAA1B;AACD;AACF;AAX+B,GAAlC;;AAiBA,WAASa,QAAT,GAAoB;AAClB,QAAIC,OAAO,EAAX;AACA,QAAIC,WAAW,QAAf;AACA,QAAIC,YAAJ;;AAIA,QAAIC,KAAKhB,cAAciB,MAAvB;AACA,SAAK,IAAIC,IAAI,CAAb,EAAgBA,IAAIF,EAApB,EAAwBE,GAAxB,EAA6B;AAC3BH,qBAAef,cAAcmB,GAAd,EAAf;AACAN,WAAKO,OAAL,CAAaL,aAAaF,IAA1B;;AAGA,UAAIE,aAAaD,QAAb,KAA0B,WAA9B,EAA2C;AACzCA,mBAAW,WAAX;AACD;AACF;;AAED,QAAID,KAAKI,MAAT,EAAiB;AAEflB,kBAAYsB,SAAZ,GAAwB,EAAxB;;AAEAtB,kBAAYU,YAAZ,CAAyB,WAAzB,EAAsC,MAAtC;;AAEAV,kBAAYU,YAAZ,CAAyB,WAAzB,EAAsCK,QAAtC;;AAGAf,kBAAYsB,SAAZ,GAAwBR,KAAKS,IAAL,CAAU,IAAV,CAAxB;;AAEAvB,kBAAYU,YAAZ,CAAyB,WAAzB,EAAsC,OAAtC;AACD;AACF;;AAwBDZ,SAAOe,QAAP,GAAkB,UAAUC,IAAV,EAAgBC,QAAhB,EAA0B;AAG1Cd,kBAAcuB,IAAd,CAAmB;AACjBV,YAAMA,IADW;AAEjBC,gBAAUA;AAFO,KAAnB;;AAOA,WAAQhB,SAASc,QAAT,EAAmB,GAAnB,GAAR;AACD,GAXD;AAYD,CAnGA,EAmGCf,MAnGD,EAmGSA,OAAOC,QAnGhB,CAAD","file":"announce.es6.js","sourcesContent":["/**\n * @file\n * Adds an HTML element and method to trigger audio UAs to read system messages.\n *\n * Use {@link Drupal.announce} to indicate to screen reader users that an\n * element on the page has changed state. For instance, if clicking a link\n * loads 10 more items into a list, one might announce the change like this.\n *\n * @example\n * $('#search-list')\n * .on('itemInsert', function (event, data) {\n * // Insert the new items.\n * $(data.container.el).append(data.items.el);\n * // Announce the change to the page contents.\n * Drupal.announce(Drupal.t('@count items added to @container',\n * {'@count': data.items.length, '@container': data.container.title}\n * ));\n * });\n */\n\n(function (Drupal, debounce) {\n\n 'use strict';\n\n var liveElement;\n var announcements = [];\n\n /**\n * Builds a div element with the aria-live attribute and add it to the DOM.\n *\n * @type {Drupal~behavior}\n *\n * @prop {Drupal~behaviorAttach} attach\n * Attaches the behavior for drupalAnnouce.\n */\n Drupal.behaviors.drupalAnnounce = {\n attach: function (context) {\n // Create only one aria-live element.\n if (!liveElement) {\n liveElement = document.createElement('div');\n liveElement.id = 'drupal-live-announce';\n liveElement.className = 'visually-hidden';\n liveElement.setAttribute('aria-live', 'polite');\n liveElement.setAttribute('aria-busy', 'false');\n document.body.appendChild(liveElement);\n }\n }\n };\n\n /**\n * Concatenates announcements to a single string; appends to the live region.\n */\n function announce() {\n var text = [];\n var priority = 'polite';\n var announcement;\n\n // Create an array of announcement strings to be joined and appended to the\n // aria live region.\n var il = announcements.length;\n for (var i = 0; i < il; i++) {\n announcement = announcements.pop();\n text.unshift(announcement.text);\n // If any of the announcements has a priority of assertive then the group\n // of joined announcements will have this priority.\n if (announcement.priority === 'assertive') {\n priority = 'assertive';\n }\n }\n\n if (text.length) {\n // Clear the liveElement so that repeated strings will be read.\n liveElement.innerHTML = '';\n // Set the busy state to true until the node changes are complete.\n liveElement.setAttribute('aria-busy', 'true');\n // Set the priority to assertive, or default to polite.\n liveElement.setAttribute('aria-live', priority);\n // Print the text to the live region. Text should be run through\n // Drupal.t() before being passed to Drupal.announce().\n liveElement.innerHTML = text.join('\\n');\n // The live text area is updated. Allow the AT to announce the text.\n liveElement.setAttribute('aria-busy', 'false');\n }\n }\n\n /**\n * Triggers audio UAs to read the supplied text.\n *\n * The aria-live region will only read the text that currently populates its\n * text node. Replacing text quickly in rapid calls to announce results in\n * only the text from the most recent call to {@link Drupal.announce} being\n * read. By wrapping the call to announce in a debounce function, we allow for\n * time for multiple calls to {@link Drupal.announce} to queue up their\n * messages. These messages are then joined and append to the aria-live region\n * as one text node.\n *\n * @param {string} text\n * A string to be read by the UA.\n * @param {string} [priority='polite']\n * A string to indicate the priority of the message. Can be either\n * 'polite' or 'assertive'.\n *\n * @return {function}\n * The return of the call to debounce.\n *\n * @see http://www.w3.org/WAI/PF/aria-practices/#liveprops\n */\n Drupal.announce = function (text, priority) {\n // Save the text and priority into a closure variable. Multiple simultaneous\n // announcements will be concatenated and read in sequence.\n announcements.push({\n text: text,\n priority: priority\n });\n // Immediately invoke the function that debounce returns. 200 ms is right at\n // the cusp where humans notice a pause, so we will wait\n // at most this much time before the set of queued announcements is read.\n return (debounce(announce, 200)());\n };\n}(Drupal, Drupal.debounce));\n"]} \ No newline at end of file diff --git a/core/misc/autocomplete.es6.js b/core/misc/autocomplete.es6.js new file mode 100644 index 0000000..254e7e5 --- /dev/null +++ b/core/misc/autocomplete.es6.js @@ -0,0 +1,273 @@ +/** + * @file + * Autocomplete based on jQuery UI. + */ + +(function ($, Drupal) { + + 'use strict'; + + var autocomplete; + + /** + * Helper splitting terms from the autocomplete value. + * + * @function Drupal.autocomplete.splitValues + * + * @param {string} value + * The value being entered by the user. + * + * @return {Array} + * Array of values, split by comma. + */ + function autocompleteSplitValues(value) { + // We will match the value against comma-separated terms. + var result = []; + var quote = false; + var current = ''; + var valueLength = value.length; + var character; + + for (var i = 0; i < valueLength; i++) { + character = value.charAt(i); + if (character === '"') { + current += character; + quote = !quote; + } + else if (character === ',' && !quote) { + result.push(current.trim()); + current = ''; + } + else { + current += character; + } + } + if (value.length > 0) { + result.push($.trim(current)); + } + + return result; + } + + /** + * Returns the last value of an multi-value textfield. + * + * @function Drupal.autocomplete.extractLastTerm + * + * @param {string} terms + * The value of the field. + * + * @return {string} + * The last value of the input field. + */ + function extractLastTerm(terms) { + return autocomplete.splitValues(terms).pop(); + } + + /** + * The search handler is called before a search is performed. + * + * @function Drupal.autocomplete.options.search + * + * @param {object} event + * The event triggered. + * + * @return {bool} + * Whether to perform a search or not. + */ + function searchHandler(event) { + var options = autocomplete.options; + var term = autocomplete.extractLastTerm(event.target.value); + // Abort search if the first character is in firstCharacterBlacklist. + if (term.length > 0 && options.firstCharacterBlacklist.indexOf(term[0]) !== -1) { + return false; + } + // Only search when the term is at least the minimum length. + return term.length >= options.minLength; + } + + /** + * JQuery UI autocomplete source callback. + * + * @param {object} request + * The request object. + * @param {function} response + * The function to call with the response. + */ + function sourceData(request, response) { + var elementId = this.element.attr('id'); + + if (!(elementId in autocomplete.cache)) { + autocomplete.cache[elementId] = {}; + } + + /** + * Filter through the suggestions removing all terms already tagged and + * display the available terms to the user. + * + * @param {object} suggestions + * Suggestions returned by the server. + */ + function showSuggestions(suggestions) { + var tagged = autocomplete.splitValues(request.term); + var il = tagged.length; + for (var i = 0; i < il; i++) { + var index = suggestions.indexOf(tagged[i]); + if (index >= 0) { + suggestions.splice(index, 1); + } + } + response(suggestions); + } + + /** + * Transforms the data object into an array and update autocomplete results. + * + * @param {object} data + * The data sent back from the server. + */ + function sourceCallbackHandler(data) { + autocomplete.cache[elementId][term] = data; + + // Send the new string array of terms to the jQuery UI list. + showSuggestions(data); + } + + // Get the desired term and construct the autocomplete URL for it. + var term = autocomplete.extractLastTerm(request.term); + + // Check if the term is already cached. + if (autocomplete.cache[elementId].hasOwnProperty(term)) { + showSuggestions(autocomplete.cache[elementId][term]); + } + else { + var options = $.extend({success: sourceCallbackHandler, data: {q: term}}, autocomplete.ajax); + $.ajax(this.element.attr('data-autocomplete-path'), options); + } + } + + /** + * Handles an autocompletefocus event. + * + * @return {bool} + * Always returns false. + */ + function focusHandler() { + return false; + } + + /** + * Handles an autocompleteselect event. + * + * @param {jQuery.Event} event + * The event triggered. + * @param {object} ui + * The jQuery UI settings object. + * + * @return {bool} + * Returns false to indicate the event status. + */ + function selectHandler(event, ui) { + var terms = autocomplete.splitValues(event.target.value); + // Remove the current input. + terms.pop(); + // Add the selected item. + if (ui.item.value.search(',') > 0) { + terms.push('"' + ui.item.value + '"'); + } + else { + terms.push(ui.item.value); + } + event.target.value = terms.join(', '); + // Return false to tell jQuery UI that we've filled in the value already. + return false; + } + + /** + * Override jQuery UI _renderItem function to output HTML by default. + * + * @param {jQuery} ul + * jQuery collection of the ul element. + * @param {object} item + * The list item to append. + * + * @return {jQuery} + * jQuery collection of the ul element. + */ + function renderItem(ul, item) { + return $('
  • ') + .append($('').html(item.label)) + .appendTo(ul); + } + + /** + * Attaches the autocomplete behavior to all required fields. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the autocomplete behaviors. + * @prop {Drupal~behaviorDetach} detach + * Detaches the autocomplete behaviors. + */ + Drupal.behaviors.autocomplete = { + attach: function (context) { + // Act on textfields with the "form-autocomplete" class. + var $autocomplete = $(context).find('input.form-autocomplete').once('autocomplete'); + if ($autocomplete.length) { + // Allow options to be overriden per instance. + var blacklist = $autocomplete.attr('data-autocomplete-first-character-blacklist'); + $.extend(autocomplete.options, { + firstCharacterBlacklist: (blacklist) ? blacklist : '' + }); + // Use jQuery UI Autocomplete on the textfield. + $autocomplete.autocomplete(autocomplete.options) + .each(function () { + $(this).data('ui-autocomplete')._renderItem = autocomplete.options.renderItem; + }); + } + }, + detach: function (context, settings, trigger) { + if (trigger === 'unload') { + $(context).find('input.form-autocomplete') + .removeOnce('autocomplete') + .autocomplete('destroy'); + } + } + }; + + /** + * Autocomplete object implementation. + * + * @namespace Drupal.autocomplete + */ + autocomplete = { + cache: {}, + // Exposes options to allow overriding by contrib. + splitValues: autocompleteSplitValues, + extractLastTerm: extractLastTerm, + // jQuery UI autocomplete options. + + /** + * JQuery UI option object. + * + * @name Drupal.autocomplete.options + */ + options: { + source: sourceData, + focus: focusHandler, + search: searchHandler, + select: selectHandler, + renderItem: renderItem, + minLength: 1, + // Custom options, used by Drupal.autocomplete. + firstCharacterBlacklist: '' + }, + ajax: { + dataType: 'json' + } + }; + + Drupal.autocomplete = autocomplete; + +})(jQuery, Drupal); diff --git a/core/misc/autocomplete.js b/core/misc/autocomplete.js index 254e7e5..f8a1dd0 100644 --- a/core/misc/autocomplete.js +++ b/core/misc/autocomplete.js @@ -1,7 +1,4 @@ -/** - * @file - * Autocomplete based on jQuery UI. - */ +'use strict'; (function ($, Drupal) { @@ -9,19 +6,7 @@ var autocomplete; - /** - * Helper splitting terms from the autocomplete value. - * - * @function Drupal.autocomplete.splitValues - * - * @param {string} value - * The value being entered by the user. - * - * @return {Array} - * Array of values, split by comma. - */ function autocompleteSplitValues(value) { - // We will match the value against comma-separated terms. var result = []; var quote = false; var current = ''; @@ -33,12 +18,10 @@ if (character === '"') { current += character; quote = !quote; - } - else if (character === ',' && !quote) { + } else if (character === ',' && !quote) { result.push(current.trim()); current = ''; - } - else { + } else { current += character; } } @@ -49,51 +32,21 @@ return result; } - /** - * Returns the last value of an multi-value textfield. - * - * @function Drupal.autocomplete.extractLastTerm - * - * @param {string} terms - * The value of the field. - * - * @return {string} - * The last value of the input field. - */ function extractLastTerm(terms) { return autocomplete.splitValues(terms).pop(); } - /** - * The search handler is called before a search is performed. - * - * @function Drupal.autocomplete.options.search - * - * @param {object} event - * The event triggered. - * - * @return {bool} - * Whether to perform a search or not. - */ function searchHandler(event) { var options = autocomplete.options; var term = autocomplete.extractLastTerm(event.target.value); - // Abort search if the first character is in firstCharacterBlacklist. + if (term.length > 0 && options.firstCharacterBlacklist.indexOf(term[0]) !== -1) { return false; } - // Only search when the term is at least the minimum length. + return term.length >= options.minLength; } - /** - * JQuery UI autocomplete source callback. - * - * @param {object} request - * The request object. - * @param {function} response - * The function to call with the response. - */ function sourceData(request, response) { var elementId = this.element.attr('id'); @@ -101,13 +54,6 @@ autocomplete.cache[elementId] = {}; } - /** - * Filter through the suggestions removing all terms already tagged and - * display the available terms to the user. - * - * @param {object} suggestions - * Suggestions returned by the server. - */ function showSuggestions(suggestions) { var tagged = autocomplete.splitValues(request.term); var il = tagged.length; @@ -120,139 +66,72 @@ response(suggestions); } - /** - * Transforms the data object into an array and update autocomplete results. - * - * @param {object} data - * The data sent back from the server. - */ function sourceCallbackHandler(data) { autocomplete.cache[elementId][term] = data; - // Send the new string array of terms to the jQuery UI list. showSuggestions(data); } - // Get the desired term and construct the autocomplete URL for it. var term = autocomplete.extractLastTerm(request.term); - // Check if the term is already cached. if (autocomplete.cache[elementId].hasOwnProperty(term)) { showSuggestions(autocomplete.cache[elementId][term]); - } - else { - var options = $.extend({success: sourceCallbackHandler, data: {q: term}}, autocomplete.ajax); + } else { + var options = $.extend({ success: sourceCallbackHandler, data: { q: term } }, autocomplete.ajax); $.ajax(this.element.attr('data-autocomplete-path'), options); } } - /** - * Handles an autocompletefocus event. - * - * @return {bool} - * Always returns false. - */ function focusHandler() { return false; } - /** - * Handles an autocompleteselect event. - * - * @param {jQuery.Event} event - * The event triggered. - * @param {object} ui - * The jQuery UI settings object. - * - * @return {bool} - * Returns false to indicate the event status. - */ function selectHandler(event, ui) { var terms = autocomplete.splitValues(event.target.value); - // Remove the current input. + terms.pop(); - // Add the selected item. + if (ui.item.value.search(',') > 0) { terms.push('"' + ui.item.value + '"'); - } - else { + } else { terms.push(ui.item.value); } event.target.value = terms.join(', '); - // Return false to tell jQuery UI that we've filled in the value already. + return false; } - /** - * Override jQuery UI _renderItem function to output HTML by default. - * - * @param {jQuery} ul - * jQuery collection of the ul element. - * @param {object} item - * The list item to append. - * - * @return {jQuery} - * jQuery collection of the ul element. - */ function renderItem(ul, item) { - return $('
  • ') - .append($('').html(item.label)) - .appendTo(ul); + return $('
  • ').append($('').html(item.label)).appendTo(ul); } - /** - * Attaches the autocomplete behavior to all required fields. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches the autocomplete behaviors. - * @prop {Drupal~behaviorDetach} detach - * Detaches the autocomplete behaviors. - */ Drupal.behaviors.autocomplete = { - attach: function (context) { - // Act on textfields with the "form-autocomplete" class. + attach: function attach(context) { var $autocomplete = $(context).find('input.form-autocomplete').once('autocomplete'); if ($autocomplete.length) { - // Allow options to be overriden per instance. var blacklist = $autocomplete.attr('data-autocomplete-first-character-blacklist'); $.extend(autocomplete.options, { - firstCharacterBlacklist: (blacklist) ? blacklist : '' + firstCharacterBlacklist: blacklist ? blacklist : '' + }); + + $autocomplete.autocomplete(autocomplete.options).each(function () { + $(this).data('ui-autocomplete')._renderItem = autocomplete.options.renderItem; }); - // Use jQuery UI Autocomplete on the textfield. - $autocomplete.autocomplete(autocomplete.options) - .each(function () { - $(this).data('ui-autocomplete')._renderItem = autocomplete.options.renderItem; - }); } }, - detach: function (context, settings, trigger) { + detach: function detach(context, settings, trigger) { if (trigger === 'unload') { - $(context).find('input.form-autocomplete') - .removeOnce('autocomplete') - .autocomplete('destroy'); + $(context).find('input.form-autocomplete').removeOnce('autocomplete').autocomplete('destroy'); } } }; - /** - * Autocomplete object implementation. - * - * @namespace Drupal.autocomplete - */ autocomplete = { cache: {}, - // Exposes options to allow overriding by contrib. + splitValues: autocompleteSplitValues, extractLastTerm: extractLastTerm, - // jQuery UI autocomplete options. - /** - * JQuery UI option object. - * - * @name Drupal.autocomplete.options - */ options: { source: sourceData, focus: focusHandler, @@ -260,7 +139,7 @@ select: selectHandler, renderItem: renderItem, minLength: 1, - // Custom options, used by Drupal.autocomplete. + firstCharacterBlacklist: '' }, ajax: { @@ -269,5 +148,6 @@ }; Drupal.autocomplete = autocomplete; - })(jQuery, Drupal); + +//# sourceMappingURL=autocomplete.js.map \ No newline at end of file diff --git a/core/misc/autocomplete.js.map b/core/misc/autocomplete.js.map new file mode 100644 index 0000000..8bf5413 --- /dev/null +++ b/core/misc/autocomplete.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["autocomplete.es6.js"],"names":["$","Drupal","autocomplete","autocompleteSplitValues","value","result","quote","current","valueLength","length","character","i","charAt","push","trim","extractLastTerm","terms","splitValues","pop","searchHandler","event","options","term","target","firstCharacterBlacklist","indexOf","minLength","sourceData","request","response","elementId","element","attr","cache","showSuggestions","suggestions","tagged","il","index","splice","sourceCallbackHandler","data","hasOwnProperty","extend","success","q","ajax","focusHandler","selectHandler","ui","item","search","join","renderItem","ul","append","html","label","appendTo","behaviors","attach","context","$autocomplete","find","once","blacklist","each","_renderItem","detach","settings","trigger","removeOnce","source","focus","select","dataType","jQuery"],"mappings":"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","file":"autocomplete.es6.js","sourcesContent":["/**\n * @file\n * Autocomplete based on jQuery UI.\n */\n\n(function ($, Drupal) {\n\n 'use strict';\n\n var autocomplete;\n\n /**\n * Helper splitting terms from the autocomplete value.\n *\n * @function Drupal.autocomplete.splitValues\n *\n * @param {string} value\n * The value being entered by the user.\n *\n * @return {Array}\n * Array of values, split by comma.\n */\n function autocompleteSplitValues(value) {\n // We will match the value against comma-separated terms.\n var result = [];\n var quote = false;\n var current = '';\n var valueLength = value.length;\n var character;\n\n for (var i = 0; i < valueLength; i++) {\n character = value.charAt(i);\n if (character === '\"') {\n current += character;\n quote = !quote;\n }\n else if (character === ',' && !quote) {\n result.push(current.trim());\n current = '';\n }\n else {\n current += character;\n }\n }\n if (value.length > 0) {\n result.push($.trim(current));\n }\n\n return result;\n }\n\n /**\n * Returns the last value of an multi-value textfield.\n *\n * @function Drupal.autocomplete.extractLastTerm\n *\n * @param {string} terms\n * The value of the field.\n *\n * @return {string}\n * The last value of the input field.\n */\n function extractLastTerm(terms) {\n return autocomplete.splitValues(terms).pop();\n }\n\n /**\n * The search handler is called before a search is performed.\n *\n * @function Drupal.autocomplete.options.search\n *\n * @param {object} event\n * The event triggered.\n *\n * @return {bool}\n * Whether to perform a search or not.\n */\n function searchHandler(event) {\n var options = autocomplete.options;\n var term = autocomplete.extractLastTerm(event.target.value);\n // Abort search if the first character is in firstCharacterBlacklist.\n if (term.length > 0 && options.firstCharacterBlacklist.indexOf(term[0]) !== -1) {\n return false;\n }\n // Only search when the term is at least the minimum length.\n return term.length >= options.minLength;\n }\n\n /**\n * JQuery UI autocomplete source callback.\n *\n * @param {object} request\n * The request object.\n * @param {function} response\n * The function to call with the response.\n */\n function sourceData(request, response) {\n var elementId = this.element.attr('id');\n\n if (!(elementId in autocomplete.cache)) {\n autocomplete.cache[elementId] = {};\n }\n\n /**\n * Filter through the suggestions removing all terms already tagged and\n * display the available terms to the user.\n *\n * @param {object} suggestions\n * Suggestions returned by the server.\n */\n function showSuggestions(suggestions) {\n var tagged = autocomplete.splitValues(request.term);\n var il = tagged.length;\n for (var i = 0; i < il; i++) {\n var index = suggestions.indexOf(tagged[i]);\n if (index >= 0) {\n suggestions.splice(index, 1);\n }\n }\n response(suggestions);\n }\n\n /**\n * Transforms the data object into an array and update autocomplete results.\n *\n * @param {object} data\n * The data sent back from the server.\n */\n function sourceCallbackHandler(data) {\n autocomplete.cache[elementId][term] = data;\n\n // Send the new string array of terms to the jQuery UI list.\n showSuggestions(data);\n }\n\n // Get the desired term and construct the autocomplete URL for it.\n var term = autocomplete.extractLastTerm(request.term);\n\n // Check if the term is already cached.\n if (autocomplete.cache[elementId].hasOwnProperty(term)) {\n showSuggestions(autocomplete.cache[elementId][term]);\n }\n else {\n var options = $.extend({success: sourceCallbackHandler, data: {q: term}}, autocomplete.ajax);\n $.ajax(this.element.attr('data-autocomplete-path'), options);\n }\n }\n\n /**\n * Handles an autocompletefocus event.\n *\n * @return {bool}\n * Always returns false.\n */\n function focusHandler() {\n return false;\n }\n\n /**\n * Handles an autocompleteselect event.\n *\n * @param {jQuery.Event} event\n * The event triggered.\n * @param {object} ui\n * The jQuery UI settings object.\n *\n * @return {bool}\n * Returns false to indicate the event status.\n */\n function selectHandler(event, ui) {\n var terms = autocomplete.splitValues(event.target.value);\n // Remove the current input.\n terms.pop();\n // Add the selected item.\n if (ui.item.value.search(',') > 0) {\n terms.push('\"' + ui.item.value + '\"');\n }\n else {\n terms.push(ui.item.value);\n }\n event.target.value = terms.join(', ');\n // Return false to tell jQuery UI that we've filled in the value already.\n return false;\n }\n\n /**\n * Override jQuery UI _renderItem function to output HTML by default.\n *\n * @param {jQuery} ul\n * jQuery collection of the ul element.\n * @param {object} item\n * The list item to append.\n *\n * @return {jQuery}\n * jQuery collection of the ul element.\n */\n function renderItem(ul, item) {\n return $('
  • ')\n .append($('').html(item.label))\n .appendTo(ul);\n }\n\n /**\n * Attaches the autocomplete behavior to all required fields.\n *\n * @type {Drupal~behavior}\n *\n * @prop {Drupal~behaviorAttach} attach\n * Attaches the autocomplete behaviors.\n * @prop {Drupal~behaviorDetach} detach\n * Detaches the autocomplete behaviors.\n */\n Drupal.behaviors.autocomplete = {\n attach: function (context) {\n // Act on textfields with the \"form-autocomplete\" class.\n var $autocomplete = $(context).find('input.form-autocomplete').once('autocomplete');\n if ($autocomplete.length) {\n // Allow options to be overriden per instance.\n var blacklist = $autocomplete.attr('data-autocomplete-first-character-blacklist');\n $.extend(autocomplete.options, {\n firstCharacterBlacklist: (blacklist) ? blacklist : ''\n });\n // Use jQuery UI Autocomplete on the textfield.\n $autocomplete.autocomplete(autocomplete.options)\n .each(function () {\n $(this).data('ui-autocomplete')._renderItem = autocomplete.options.renderItem;\n });\n }\n },\n detach: function (context, settings, trigger) {\n if (trigger === 'unload') {\n $(context).find('input.form-autocomplete')\n .removeOnce('autocomplete')\n .autocomplete('destroy');\n }\n }\n };\n\n /**\n * Autocomplete object implementation.\n *\n * @namespace Drupal.autocomplete\n */\n autocomplete = {\n cache: {},\n // Exposes options to allow overriding by contrib.\n splitValues: autocompleteSplitValues,\n extractLastTerm: extractLastTerm,\n // jQuery UI autocomplete options.\n\n /**\n * JQuery UI option object.\n *\n * @name Drupal.autocomplete.options\n */\n options: {\n source: sourceData,\n focus: focusHandler,\n search: searchHandler,\n select: selectHandler,\n renderItem: renderItem,\n minLength: 1,\n // Custom options, used by Drupal.autocomplete.\n firstCharacterBlacklist: ''\n },\n ajax: {\n dataType: 'json'\n }\n };\n\n Drupal.autocomplete = autocomplete;\n\n})(jQuery, Drupal);\n"]} \ No newline at end of file diff --git a/core/misc/batch.es6.js b/core/misc/batch.es6.js new file mode 100644 index 0000000..411badb --- /dev/null +++ b/core/misc/batch.es6.js @@ -0,0 +1,46 @@ +/** + * @file + * Drupal's batch API. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Attaches the batch behavior to progress bars. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.batch = { + attach: function (context, settings) { + var batch = settings.batch; + var $progress = $('[data-drupal-progress]').once('batch'); + var progressBar; + + // Success: redirect to the summary. + function updateCallback(progress, status, pb) { + if (progress === '100') { + pb.stopMonitoring(); + window.location = batch.uri + '&op=finished'; + } + } + + function errorCallback(pb) { + $progress.prepend($('

    ').html(batch.errorMessage)); + $('#wait').hide(); + } + + if ($progress.length) { + progressBar = new Drupal.ProgressBar('updateprogress', updateCallback, 'POST', errorCallback); + progressBar.setProgress(-1, batch.initMessage); + progressBar.startMonitoring(batch.uri + '&op=do', 10); + // Remove HTML from no-js progress bar. + $progress.empty(); + // Append the JS progressbar element. + $progress.append(progressBar.element); + } + } + }; + +})(jQuery, Drupal); diff --git a/core/misc/batch.js b/core/misc/batch.js index 411badb..ef80c31 100644 --- a/core/misc/batch.js +++ b/core/misc/batch.js @@ -1,24 +1,15 @@ -/** - * @file - * Drupal's batch API. - */ +'use strict'; (function ($, Drupal) { 'use strict'; - /** - * Attaches the batch behavior to progress bars. - * - * @type {Drupal~behavior} - */ Drupal.behaviors.batch = { - attach: function (context, settings) { + attach: function attach(context, settings) { var batch = settings.batch; var $progress = $('[data-drupal-progress]').once('batch'); var progressBar; - // Success: redirect to the summary. function updateCallback(progress, status, pb) { if (progress === '100') { pb.stopMonitoring(); @@ -35,12 +26,13 @@ progressBar = new Drupal.ProgressBar('updateprogress', updateCallback, 'POST', errorCallback); progressBar.setProgress(-1, batch.initMessage); progressBar.startMonitoring(batch.uri + '&op=do', 10); - // Remove HTML from no-js progress bar. + $progress.empty(); - // Append the JS progressbar element. + $progress.append(progressBar.element); } } }; - })(jQuery, Drupal); + +//# sourceMappingURL=batch.js.map \ No newline at end of file diff --git a/core/misc/batch.js.map b/core/misc/batch.js.map new file mode 100644 index 0000000..ee11c6e --- /dev/null +++ b/core/misc/batch.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["batch.es6.js"],"names":["$","Drupal","behaviors","batch","attach","context","settings","$progress","once","progressBar","updateCallback","progress","status","pb","stopMonitoring","window","location","uri","errorCallback","prepend","html","errorMessage","hide","length","ProgressBar","setProgress","initMessage","startMonitoring","empty","append","element","jQuery"],"mappings":";;AAKA,CAAC,UAAUA,CAAV,EAAaC,MAAb,EAAqB;;AAEpB;;AAOAA,SAAOC,SAAP,CAAiBC,KAAjB,GAAyB;AACvBC,YAAQ,gBAAUC,OAAV,EAAmBC,QAAnB,EAA6B;AACnC,UAAIH,QAAQG,SAASH,KAArB;AACA,UAAII,YAAYP,EAAE,wBAAF,EAA4BQ,IAA5B,CAAiC,OAAjC,CAAhB;AACA,UAAIC,WAAJ;;AAGA,eAASC,cAAT,CAAwBC,QAAxB,EAAkCC,MAAlC,EAA0CC,EAA1C,EAA8C;AAC5C,YAAIF,aAAa,KAAjB,EAAwB;AACtBE,aAAGC,cAAH;AACAC,iBAAOC,QAAP,GAAkBb,MAAMc,GAAN,GAAY,cAA9B;AACD;AACF;;AAED,eAASC,aAAT,CAAuBL,EAAvB,EAA2B;AACzBN,kBAAUY,OAAV,CAAkBnB,EAAE,uBAAF,EAA2BoB,IAA3B,CAAgCjB,MAAMkB,YAAtC,CAAlB;AACArB,UAAE,OAAF,EAAWsB,IAAX;AACD;;AAED,UAAIf,UAAUgB,MAAd,EAAsB;AACpBd,sBAAc,IAAIR,OAAOuB,WAAX,CAAuB,gBAAvB,EAAyCd,cAAzC,EAAyD,MAAzD,EAAiEQ,aAAjE,CAAd;AACAT,oBAAYgB,WAAZ,CAAwB,CAAC,CAAzB,EAA4BtB,MAAMuB,WAAlC;AACAjB,oBAAYkB,eAAZ,CAA4BxB,MAAMc,GAAN,GAAY,QAAxC,EAAkD,EAAlD;;AAEAV,kBAAUqB,KAAV;;AAEArB,kBAAUsB,MAAV,CAAiBpB,YAAYqB,OAA7B;AACD;AACF;AA5BsB,GAAzB;AA+BD,CAxCD,EAwCGC,MAxCH,EAwCW9B,MAxCX","file":"batch.es6.js","sourcesContent":["/**\n * @file\n * Drupal's batch API.\n */\n\n(function ($, Drupal) {\n\n 'use strict';\n\n /**\n * Attaches the batch behavior to progress bars.\n *\n * @type {Drupal~behavior}\n */\n Drupal.behaviors.batch = {\n attach: function (context, settings) {\n var batch = settings.batch;\n var $progress = $('[data-drupal-progress]').once('batch');\n var progressBar;\n\n // Success: redirect to the summary.\n function updateCallback(progress, status, pb) {\n if (progress === '100') {\n pb.stopMonitoring();\n window.location = batch.uri + '&op=finished';\n }\n }\n\n function errorCallback(pb) {\n $progress.prepend($('

    ').html(batch.errorMessage));\n $('#wait').hide();\n }\n\n if ($progress.length) {\n progressBar = new Drupal.ProgressBar('updateprogress', updateCallback, 'POST', errorCallback);\n progressBar.setProgress(-1, batch.initMessage);\n progressBar.startMonitoring(batch.uri + '&op=do', 10);\n // Remove HTML from no-js progress bar.\n $progress.empty();\n // Append the JS progressbar element.\n $progress.append(progressBar.element);\n }\n }\n };\n\n})(jQuery, Drupal);\n"]} \ No newline at end of file diff --git a/core/misc/collapse.es6.js b/core/misc/collapse.es6.js new file mode 100644 index 0000000..767325e --- /dev/null +++ b/core/misc/collapse.es6.js @@ -0,0 +1,146 @@ +/** + * @file + * Polyfill for HTML5 details elements. + */ + +(function ($, Modernizr, Drupal) { + + 'use strict'; + + /** + * The collapsible details object represents a single details element. + * + * @constructor Drupal.CollapsibleDetails + * + * @param {HTMLElement} node + * The details element. + */ + function CollapsibleDetails(node) { + this.$node = $(node); + this.$node.data('details', this); + // Expand details if there are errors inside, or if it contains an + // element that is targeted by the URI fragment identifier. + var anchor = location.hash && location.hash !== '#' ? ', ' + location.hash : ''; + if (this.$node.find('.error' + anchor).length) { + this.$node.attr('open', true); + } + // Initialize and setup the summary, + this.setupSummary(); + // Initialize and setup the legend. + this.setupLegend(); + } + + $.extend(CollapsibleDetails, /** @lends Drupal.CollapsibleDetails */{ + + /** + * Holds references to instantiated CollapsibleDetails objects. + * + * @type {Array.} + */ + instances: [] + }); + + $.extend(CollapsibleDetails.prototype, /** @lends Drupal.CollapsibleDetails# */{ + + /** + * Initialize and setup summary events and markup. + * + * @fires event:summaryUpdated + * + * @listens event:summaryUpdated + */ + setupSummary: function () { + this.$summary = $(''); + this.$node + .on('summaryUpdated', $.proxy(this.onSummaryUpdated, this)) + .trigger('summaryUpdated'); + }, + + /** + * Initialize and setup legend markup. + */ + setupLegend: function () { + // Turn the summary into a clickable link. + var $legend = this.$node.find('> summary'); + + $('') + .append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show')) + .prependTo($legend) + .after(document.createTextNode(' ')); + + // .wrapInner() does not retain bound events. + $('
    ') + .attr('href', '#' + this.$node.attr('id')) + .prepend($legend.contents()) + .appendTo($legend); + + $legend + .append(this.$summary) + .on('click', $.proxy(this.onLegendClick, this)); + }, + + /** + * Handle legend clicks. + * + * @param {jQuery.Event} e + * The event triggered. + */ + onLegendClick: function (e) { + this.toggle(); + e.preventDefault(); + }, + + /** + * Update summary. + */ + onSummaryUpdated: function () { + var text = $.trim(this.$node.drupalGetSummary()); + this.$summary.html(text ? ' (' + text + ')' : ''); + }, + + /** + * Toggle the visibility of a details element using smooth animations. + */ + toggle: function () { + var isOpen = !!this.$node.attr('open'); + var $summaryPrefix = this.$node.find('> summary span.details-summary-prefix'); + if (isOpen) { + $summaryPrefix.html(Drupal.t('Show')); + } + else { + $summaryPrefix.html(Drupal.t('Hide')); + } + // Delay setting the attribute to emulate chrome behavior and make + // details-aria.js work as expected with this polyfill. + setTimeout(function () { + this.$node.attr('open', !isOpen); + }.bind(this), 0); + } + }); + + /** + * Polyfill HTML5 details element. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches behavior for the details element. + */ + Drupal.behaviors.collapse = { + attach: function (context) { + if (Modernizr.details) { + return; + } + var $collapsibleDetails = $(context).find('details').once('collapse').addClass('collapse-processed'); + if ($collapsibleDetails.length) { + for (var i = 0; i < $collapsibleDetails.length; i++) { + CollapsibleDetails.instances.push(new CollapsibleDetails($collapsibleDetails[i])); + } + } + } + }; + + // Expose constructor in the public space. + Drupal.CollapsibleDetails = CollapsibleDetails; + +})(jQuery, Modernizr, Drupal); diff --git a/core/misc/collapse.js b/core/misc/collapse.js index 767325e..3c3e088 100644 --- a/core/misc/collapse.js +++ b/core/misc/collapse.js @@ -1,133 +1,70 @@ -/** - * @file - * Polyfill for HTML5 details elements. - */ +'use strict'; (function ($, Modernizr, Drupal) { 'use strict'; - /** - * The collapsible details object represents a single details element. - * - * @constructor Drupal.CollapsibleDetails - * - * @param {HTMLElement} node - * The details element. - */ function CollapsibleDetails(node) { this.$node = $(node); this.$node.data('details', this); - // Expand details if there are errors inside, or if it contains an - // element that is targeted by the URI fragment identifier. + var anchor = location.hash && location.hash !== '#' ? ', ' + location.hash : ''; if (this.$node.find('.error' + anchor).length) { this.$node.attr('open', true); } - // Initialize and setup the summary, + this.setupSummary(); - // Initialize and setup the legend. + this.setupLegend(); } - $.extend(CollapsibleDetails, /** @lends Drupal.CollapsibleDetails */{ - - /** - * Holds references to instantiated CollapsibleDetails objects. - * - * @type {Array.} - */ + $.extend(CollapsibleDetails, { instances: [] }); - $.extend(CollapsibleDetails.prototype, /** @lends Drupal.CollapsibleDetails# */{ - - /** - * Initialize and setup summary events and markup. - * - * @fires event:summaryUpdated - * - * @listens event:summaryUpdated - */ - setupSummary: function () { + $.extend(CollapsibleDetails.prototype, { + setupSummary: function setupSummary() { this.$summary = $(''); - this.$node - .on('summaryUpdated', $.proxy(this.onSummaryUpdated, this)) - .trigger('summaryUpdated'); + this.$node.on('summaryUpdated', $.proxy(this.onSummaryUpdated, this)).trigger('summaryUpdated'); }, - /** - * Initialize and setup legend markup. - */ - setupLegend: function () { - // Turn the summary into a clickable link. + setupLegend: function setupLegend() { var $legend = this.$node.find('> summary'); - $('') - .append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show')) - .prependTo($legend) - .after(document.createTextNode(' ')); + $('').append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show')).prependTo($legend).after(document.createTextNode(' ')); - // .wrapInner() does not retain bound events. - $('') - .attr('href', '#' + this.$node.attr('id')) - .prepend($legend.contents()) - .appendTo($legend); + $('').attr('href', '#' + this.$node.attr('id')).prepend($legend.contents()).appendTo($legend); - $legend - .append(this.$summary) - .on('click', $.proxy(this.onLegendClick, this)); + $legend.append(this.$summary).on('click', $.proxy(this.onLegendClick, this)); }, - /** - * Handle legend clicks. - * - * @param {jQuery.Event} e - * The event triggered. - */ - onLegendClick: function (e) { + onLegendClick: function onLegendClick(e) { this.toggle(); e.preventDefault(); }, - /** - * Update summary. - */ - onSummaryUpdated: function () { + onSummaryUpdated: function onSummaryUpdated() { var text = $.trim(this.$node.drupalGetSummary()); this.$summary.html(text ? ' (' + text + ')' : ''); }, - /** - * Toggle the visibility of a details element using smooth animations. - */ - toggle: function () { + toggle: function toggle() { var isOpen = !!this.$node.attr('open'); var $summaryPrefix = this.$node.find('> summary span.details-summary-prefix'); if (isOpen) { $summaryPrefix.html(Drupal.t('Show')); - } - else { + } else { $summaryPrefix.html(Drupal.t('Hide')); } - // Delay setting the attribute to emulate chrome behavior and make - // details-aria.js work as expected with this polyfill. + setTimeout(function () { this.$node.attr('open', !isOpen); }.bind(this), 0); } }); - /** - * Polyfill HTML5 details element. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches behavior for the details element. - */ Drupal.behaviors.collapse = { - attach: function (context) { + attach: function attach(context) { if (Modernizr.details) { return; } @@ -140,7 +77,7 @@ } }; - // Expose constructor in the public space. Drupal.CollapsibleDetails = CollapsibleDetails; - })(jQuery, Modernizr, Drupal); + +//# sourceMappingURL=collapse.js.map \ No newline at end of file diff --git a/core/misc/collapse.js.map b/core/misc/collapse.js.map new file mode 100644 index 0000000..0a0daa3 --- /dev/null +++ b/core/misc/collapse.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["collapse.es6.js"],"names":["$","Modernizr","Drupal","CollapsibleDetails","node","$node","data","anchor","location","hash","find","length","attr","setupSummary","setupLegend","extend","instances","prototype","$summary","on","proxy","onSummaryUpdated","trigger","$legend","append","t","prependTo","after","document","createTextNode","prepend","contents","appendTo","onLegendClick","e","toggle","preventDefault","text","trim","drupalGetSummary","html","isOpen","$summaryPrefix","setTimeout","bind","behaviors","collapse","attach","context","details","$collapsibleDetails","once","addClass","i","push","jQuery"],"mappings":"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","file":"collapse.es6.js","sourcesContent":["/**\n * @file\n * Polyfill for HTML5 details elements.\n */\n\n(function ($, Modernizr, Drupal) {\n\n 'use strict';\n\n /**\n * The collapsible details object represents a single details element.\n *\n * @constructor Drupal.CollapsibleDetails\n *\n * @param {HTMLElement} node\n * The details element.\n */\n function CollapsibleDetails(node) {\n this.$node = $(node);\n this.$node.data('details', this);\n // Expand details if there are errors inside, or if it contains an\n // element that is targeted by the URI fragment identifier.\n var anchor = location.hash && location.hash !== '#' ? ', ' + location.hash : '';\n if (this.$node.find('.error' + anchor).length) {\n this.$node.attr('open', true);\n }\n // Initialize and setup the summary,\n this.setupSummary();\n // Initialize and setup the legend.\n this.setupLegend();\n }\n\n $.extend(CollapsibleDetails, /** @lends Drupal.CollapsibleDetails */{\n\n /**\n * Holds references to instantiated CollapsibleDetails objects.\n *\n * @type {Array.}\n */\n instances: []\n });\n\n $.extend(CollapsibleDetails.prototype, /** @lends Drupal.CollapsibleDetails# */{\n\n /**\n * Initialize and setup summary events and markup.\n *\n * @fires event:summaryUpdated\n *\n * @listens event:summaryUpdated\n */\n setupSummary: function () {\n this.$summary = $('');\n this.$node\n .on('summaryUpdated', $.proxy(this.onSummaryUpdated, this))\n .trigger('summaryUpdated');\n },\n\n /**\n * Initialize and setup legend markup.\n */\n setupLegend: function () {\n // Turn the summary into a clickable link.\n var $legend = this.$node.find('> summary');\n\n $('')\n .append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show'))\n .prependTo($legend)\n .after(document.createTextNode(' '));\n\n // .wrapInner() does not retain bound events.\n $('')\n .attr('href', '#' + this.$node.attr('id'))\n .prepend($legend.contents())\n .appendTo($legend);\n\n $legend\n .append(this.$summary)\n .on('click', $.proxy(this.onLegendClick, this));\n },\n\n /**\n * Handle legend clicks.\n *\n * @param {jQuery.Event} e\n * The event triggered.\n */\n onLegendClick: function (e) {\n this.toggle();\n e.preventDefault();\n },\n\n /**\n * Update summary.\n */\n onSummaryUpdated: function () {\n var text = $.trim(this.$node.drupalGetSummary());\n this.$summary.html(text ? ' (' + text + ')' : '');\n },\n\n /**\n * Toggle the visibility of a details element using smooth animations.\n */\n toggle: function () {\n var isOpen = !!this.$node.attr('open');\n var $summaryPrefix = this.$node.find('> summary span.details-summary-prefix');\n if (isOpen) {\n $summaryPrefix.html(Drupal.t('Show'));\n }\n else {\n $summaryPrefix.html(Drupal.t('Hide'));\n }\n // Delay setting the attribute to emulate chrome behavior and make\n // details-aria.js work as expected with this polyfill.\n setTimeout(function () {\n this.$node.attr('open', !isOpen);\n }.bind(this), 0);\n }\n });\n\n /**\n * Polyfill HTML5 details element.\n *\n * @type {Drupal~behavior}\n *\n * @prop {Drupal~behaviorAttach} attach\n * Attaches behavior for the details element.\n */\n Drupal.behaviors.collapse = {\n attach: function (context) {\n if (Modernizr.details) {\n return;\n }\n var $collapsibleDetails = $(context).find('details').once('collapse').addClass('collapse-processed');\n if ($collapsibleDetails.length) {\n for (var i = 0; i < $collapsibleDetails.length; i++) {\n CollapsibleDetails.instances.push(new CollapsibleDetails($collapsibleDetails[i]));\n }\n }\n }\n };\n\n // Expose constructor in the public space.\n Drupal.CollapsibleDetails = CollapsibleDetails;\n\n})(jQuery, Modernizr, Drupal);\n"]} \ No newline at end of file diff --git a/core/misc/date.es6.js b/core/misc/date.es6.js new file mode 100644 index 0000000..8b6b71c --- /dev/null +++ b/core/misc/date.es6.js @@ -0,0 +1,56 @@ +/** + * @file + * Polyfill for HTML5 date input. + */ + +(function ($, Modernizr, Drupal) { + + 'use strict'; + + /** + * Attach datepicker fallback on date elements. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior. Accepts in `settings.date` an object listing + * elements to process, keyed by the HTML ID of the form element containing + * the human-readable value. Each element is an datepicker settings object. + * @prop {Drupal~behaviorDetach} detach + * Detach the behavior destroying datepickers on effected elements. + */ + Drupal.behaviors.date = { + attach: function (context, settings) { + var $context = $(context); + // Skip if date are supported by the browser. + if (Modernizr.inputtypes.date === true) { + return; + } + $context.find('input[data-drupal-date-format]').once('datePicker').each(function () { + var $input = $(this); + var datepickerSettings = {}; + var dateFormat = $input.data('drupalDateFormat'); + // The date format is saved in PHP style, we need to convert to jQuery + // datepicker. + datepickerSettings.dateFormat = dateFormat + .replace('Y', 'yy') + .replace('m', 'mm') + .replace('d', 'dd'); + // Add min and max date if set on the input. + if ($input.attr('min')) { + datepickerSettings.minDate = $input.attr('min'); + } + if ($input.attr('max')) { + datepickerSettings.maxDate = $input.attr('max'); + } + $input.datepicker(datepickerSettings); + }); + }, + detach: function (context, settings, trigger) { + if (trigger === 'unload') { + $(context).find('input[data-drupal-date-format]').findOnce('datePicker').datepicker('destroy'); + } + } + }; + +})(jQuery, Modernizr, Drupal); diff --git a/core/misc/date.js b/core/misc/date.js index 8b6b71c..4702567 100644 --- a/core/misc/date.js +++ b/core/misc/date.js @@ -1,28 +1,13 @@ -/** - * @file - * Polyfill for HTML5 date input. - */ +'use strict'; (function ($, Modernizr, Drupal) { 'use strict'; - /** - * Attach datepicker fallback on date elements. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches the behavior. Accepts in `settings.date` an object listing - * elements to process, keyed by the HTML ID of the form element containing - * the human-readable value. Each element is an datepicker settings object. - * @prop {Drupal~behaviorDetach} detach - * Detach the behavior destroying datepickers on effected elements. - */ Drupal.behaviors.date = { - attach: function (context, settings) { + attach: function attach(context, settings) { var $context = $(context); - // Skip if date are supported by the browser. + if (Modernizr.inputtypes.date === true) { return; } @@ -30,13 +15,9 @@ var $input = $(this); var datepickerSettings = {}; var dateFormat = $input.data('drupalDateFormat'); - // The date format is saved in PHP style, we need to convert to jQuery - // datepicker. - datepickerSettings.dateFormat = dateFormat - .replace('Y', 'yy') - .replace('m', 'mm') - .replace('d', 'dd'); - // Add min and max date if set on the input. + + datepickerSettings.dateFormat = dateFormat.replace('Y', 'yy').replace('m', 'mm').replace('d', 'dd'); + if ($input.attr('min')) { datepickerSettings.minDate = $input.attr('min'); } @@ -46,11 +27,12 @@ $input.datepicker(datepickerSettings); }); }, - detach: function (context, settings, trigger) { + detach: function detach(context, settings, trigger) { if (trigger === 'unload') { $(context).find('input[data-drupal-date-format]').findOnce('datePicker').datepicker('destroy'); } } }; - })(jQuery, Modernizr, Drupal); + +//# sourceMappingURL=date.js.map \ No newline at end of file diff --git a/core/misc/date.js.map b/core/misc/date.js.map new file mode 100644 index 0000000..230181f --- /dev/null +++ b/core/misc/date.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["date.es6.js"],"names":["$","Modernizr","Drupal","behaviors","date","attach","context","settings","$context","inputtypes","find","once","each","$input","datepickerSettings","dateFormat","data","replace","attr","minDate","maxDate","datepicker","detach","trigger","findOnce","jQuery"],"mappings":";;AAKA,CAAC,UAAUA,CAAV,EAAaC,SAAb,EAAwBC,MAAxB,EAAgC;;AAE/B;;AAcAA,SAAOC,SAAP,CAAiBC,IAAjB,GAAwB;AACtBC,YAAQ,gBAAUC,OAAV,EAAmBC,QAAnB,EAA6B;AACnC,UAAIC,WAAWR,EAAEM,OAAF,CAAf;;AAEA,UAAIL,UAAUQ,UAAV,CAAqBL,IAArB,KAA8B,IAAlC,EAAwC;AACtC;AACD;AACDI,eAASE,IAAT,CAAc,gCAAd,EAAgDC,IAAhD,CAAqD,YAArD,EAAmEC,IAAnE,CAAwE,YAAY;AAClF,YAAIC,SAASb,EAAE,IAAF,CAAb;AACA,YAAIc,qBAAqB,EAAzB;AACA,YAAIC,aAAaF,OAAOG,IAAP,CAAY,kBAAZ,CAAjB;;AAGAF,2BAAmBC,UAAnB,GAAgCA,WAC7BE,OAD6B,CACrB,GADqB,EAChB,IADgB,EAE7BA,OAF6B,CAErB,GAFqB,EAEhB,IAFgB,EAG7BA,OAH6B,CAGrB,GAHqB,EAGhB,IAHgB,CAAhC;;AAKA,YAAIJ,OAAOK,IAAP,CAAY,KAAZ,CAAJ,EAAwB;AACtBJ,6BAAmBK,OAAnB,GAA6BN,OAAOK,IAAP,CAAY,KAAZ,CAA7B;AACD;AACD,YAAIL,OAAOK,IAAP,CAAY,KAAZ,CAAJ,EAAwB;AACtBJ,6BAAmBM,OAAnB,GAA6BP,OAAOK,IAAP,CAAY,KAAZ,CAA7B;AACD;AACDL,eAAOQ,UAAP,CAAkBP,kBAAlB;AACD,OAlBD;AAmBD,KA1BqB;AA2BtBQ,YAAQ,gBAAUhB,OAAV,EAAmBC,QAAnB,EAA6BgB,OAA7B,EAAsC;AAC5C,UAAIA,YAAY,QAAhB,EAA0B;AACxBvB,UAAEM,OAAF,EAAWI,IAAX,CAAgB,gCAAhB,EAAkDc,QAAlD,CAA2D,YAA3D,EAAyEH,UAAzE,CAAoF,SAApF;AACD;AACF;AA/BqB,GAAxB;AAkCD,CAlDD,EAkDGI,MAlDH,EAkDWxB,SAlDX,EAkDsBC,MAlDtB","file":"date.es6.js","sourcesContent":["/**\n * @file\n * Polyfill for HTML5 date input.\n */\n\n(function ($, Modernizr, Drupal) {\n\n 'use strict';\n\n /**\n * Attach datepicker fallback on date elements.\n *\n * @type {Drupal~behavior}\n *\n * @prop {Drupal~behaviorAttach} attach\n * Attaches the behavior. Accepts in `settings.date` an object listing\n * elements to process, keyed by the HTML ID of the form element containing\n * the human-readable value. Each element is an datepicker settings object.\n * @prop {Drupal~behaviorDetach} detach\n * Detach the behavior destroying datepickers on effected elements.\n */\n Drupal.behaviors.date = {\n attach: function (context, settings) {\n var $context = $(context);\n // Skip if date are supported by the browser.\n if (Modernizr.inputtypes.date === true) {\n return;\n }\n $context.find('input[data-drupal-date-format]').once('datePicker').each(function () {\n var $input = $(this);\n var datepickerSettings = {};\n var dateFormat = $input.data('drupalDateFormat');\n // The date format is saved in PHP style, we need to convert to jQuery\n // datepicker.\n datepickerSettings.dateFormat = dateFormat\n .replace('Y', 'yy')\n .replace('m', 'mm')\n .replace('d', 'dd');\n // Add min and max date if set on the input.\n if ($input.attr('min')) {\n datepickerSettings.minDate = $input.attr('min');\n }\n if ($input.attr('max')) {\n datepickerSettings.maxDate = $input.attr('max');\n }\n $input.datepicker(datepickerSettings);\n });\n },\n detach: function (context, settings, trigger) {\n if (trigger === 'unload') {\n $(context).find('input[data-drupal-date-format]').findOnce('datePicker').datepicker('destroy');\n }\n }\n };\n\n})(jQuery, Modernizr, Drupal);\n"]} \ No newline at end of file diff --git a/core/misc/debounce.es6.js b/core/misc/debounce.es6.js new file mode 100644 index 0000000..995e3d7 --- /dev/null +++ b/core/misc/debounce.es6.js @@ -0,0 +1,52 @@ +/** + * @file + * Adapted from underscore.js with the addition Drupal namespace. + */ + +/** + * Limits the invocations of a function in a given time frame. + * + * The debounce function wrapper should be used sparingly. One clear use case + * is limiting the invocation of a callback attached to the window resize event. + * + * Before using the debounce function wrapper, consider first whether the + * callback could be attached to an event that fires less frequently or if the + * function can be written in such a way that it is only invoked under specific + * conditions. + * + * @param {function} func + * The function to be invoked. + * @param {number} wait + * The time period within which the callback function should only be + * invoked once. For example if the wait period is 250ms, then the callback + * will only be called at most 4 times per second. + * @param {bool} immediate + * Whether we wait at the beginning or end to execute the function. + * + * @return {function} + * The debounced function. + */ +Drupal.debounce = function (func, wait, immediate) { + + 'use strict'; + + var timeout; + var result; + return function () { + var context = this; + var args = arguments; + var later = function () { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + } + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + } + return result; + }; +}; diff --git a/core/misc/debounce.js b/core/misc/debounce.js index 995e3d7..4ed07db 100644 --- a/core/misc/debounce.js +++ b/core/misc/debounce.js @@ -1,31 +1,5 @@ -/** - * @file - * Adapted from underscore.js with the addition Drupal namespace. - */ +'use strict'; -/** - * Limits the invocations of a function in a given time frame. - * - * The debounce function wrapper should be used sparingly. One clear use case - * is limiting the invocation of a callback attached to the window resize event. - * - * Before using the debounce function wrapper, consider first whether the - * callback could be attached to an event that fires less frequently or if the - * function can be written in such a way that it is only invoked under specific - * conditions. - * - * @param {function} func - * The function to be invoked. - * @param {number} wait - * The time period within which the callback function should only be - * invoked once. For example if the wait period is 250ms, then the callback - * will only be called at most 4 times per second. - * @param {bool} immediate - * Whether we wait at the beginning or end to execute the function. - * - * @return {function} - * The debounced function. - */ Drupal.debounce = function (func, wait, immediate) { 'use strict'; @@ -35,7 +9,7 @@ Drupal.debounce = function (func, wait, immediate) { return function () { var context = this; var args = arguments; - var later = function () { + var later = function later() { timeout = null; if (!immediate) { result = func.apply(context, args); @@ -50,3 +24,5 @@ Drupal.debounce = function (func, wait, immediate) { return result; }; }; + +//# sourceMappingURL=debounce.js.map \ No newline at end of file diff --git a/core/misc/debounce.js.map b/core/misc/debounce.js.map new file mode 100644 index 0000000..2ae0ffb --- /dev/null +++ b/core/misc/debounce.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["debounce.es6.js"],"names":["Drupal","debounce","func","wait","immediate","timeout","result","context","args","arguments","later","apply","callNow","clearTimeout","setTimeout"],"mappings":";;AA4BAA,OAAOC,QAAP,GAAkB,UAAUC,IAAV,EAAgBC,IAAhB,EAAsBC,SAAtB,EAAiC;;AAEjD;;AAEA,MAAIC,OAAJ;AACA,MAAIC,MAAJ;AACA,SAAO,YAAY;AACjB,QAAIC,UAAU,IAAd;AACA,QAAIC,OAAOC,SAAX;AACA,QAAIC,QAAQ,SAARA,KAAQ,GAAY;AACtBL,gBAAU,IAAV;AACA,UAAI,CAACD,SAAL,EAAgB;AACdE,iBAASJ,KAAKS,KAAL,CAAWJ,OAAX,EAAoBC,IAApB,CAAT;AACD;AACF,KALD;AAMA,QAAII,UAAUR,aAAa,CAACC,OAA5B;AACAQ,iBAAaR,OAAb;AACAA,cAAUS,WAAWJ,KAAX,EAAkBP,IAAlB,CAAV;AACA,QAAIS,OAAJ,EAAa;AACXN,eAASJ,KAAKS,KAAL,CAAWJ,OAAX,EAAoBC,IAApB,CAAT;AACD;AACD,WAAOF,MAAP;AACD,GAhBD;AAiBD,CAvBD","file":"debounce.es6.js","sourcesContent":["/**\n * @file\n * Adapted from underscore.js with the addition Drupal namespace.\n */\n\n/**\n * Limits the invocations of a function in a given time frame.\n *\n * The debounce function wrapper should be used sparingly. One clear use case\n * is limiting the invocation of a callback attached to the window resize event.\n *\n * Before using the debounce function wrapper, consider first whether the\n * callback could be attached to an event that fires less frequently or if the\n * function can be written in such a way that it is only invoked under specific\n * conditions.\n *\n * @param {function} func\n * The function to be invoked.\n * @param {number} wait\n * The time period within which the callback function should only be\n * invoked once. For example if the wait period is 250ms, then the callback\n * will only be called at most 4 times per second.\n * @param {bool} immediate\n * Whether we wait at the beginning or end to execute the function.\n *\n * @return {function}\n * The debounced function.\n */\nDrupal.debounce = function (func, wait, immediate) {\n\n 'use strict';\n\n var timeout;\n var result;\n return function () {\n var context = this;\n var args = arguments;\n var later = function () {\n timeout = null;\n if (!immediate) {\n result = func.apply(context, args);\n }\n };\n var callNow = immediate && !timeout;\n clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n if (callNow) {\n result = func.apply(context, args);\n }\n return result;\n };\n};\n"]} \ No newline at end of file diff --git a/core/misc/details-aria.es6.js b/core/misc/details-aria.es6.js new file mode 100644 index 0000000..d341422 --- /dev/null +++ b/core/misc/details-aria.es6.js @@ -0,0 +1,29 @@ +/** + * @file + * Add aria attribute handling for details and summary elements. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Handles `aria-expanded` and `aria-pressed` attributes on details elements. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.detailsAria = { + attach: function () { + $('body').once('detailsAria').on('click.detailsAria', 'summary', function (event) { + var $summary = $(event.currentTarget); + var open = $(event.currentTarget.parentNode).attr('open') === 'open' ? 'false' : 'true'; + + $summary.attr({ + 'aria-expanded': open, + 'aria-pressed': open + }); + }); + } + }; + +})(jQuery, Drupal); diff --git a/core/misc/details-aria.js b/core/misc/details-aria.js index d341422..076fecf 100644 --- a/core/misc/details-aria.js +++ b/core/misc/details-aria.js @@ -1,19 +1,11 @@ -/** - * @file - * Add aria attribute handling for details and summary elements. - */ +'use strict'; (function ($, Drupal) { 'use strict'; - /** - * Handles `aria-expanded` and `aria-pressed` attributes on details elements. - * - * @type {Drupal~behavior} - */ Drupal.behaviors.detailsAria = { - attach: function () { + attach: function attach() { $('body').once('detailsAria').on('click.detailsAria', 'summary', function (event) { var $summary = $(event.currentTarget); var open = $(event.currentTarget.parentNode).attr('open') === 'open' ? 'false' : 'true'; @@ -25,5 +17,6 @@ }); } }; - })(jQuery, Drupal); + +//# sourceMappingURL=details-aria.js.map \ No newline at end of file diff --git a/core/misc/details-aria.js.map b/core/misc/details-aria.js.map new file mode 100644 index 0000000..4a64016 --- /dev/null +++ b/core/misc/details-aria.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["details-aria.es6.js"],"names":["$","Drupal","behaviors","detailsAria","attach","once","on","event","$summary","currentTarget","open","parentNode","attr","jQuery"],"mappings":";;AAKA,CAAC,UAAUA,CAAV,EAAaC,MAAb,EAAqB;;AAEpB;;AAOAA,SAAOC,SAAP,CAAiBC,WAAjB,GAA+B;AAC7BC,YAAQ,kBAAY;AAClBJ,QAAE,MAAF,EAAUK,IAAV,CAAe,aAAf,EAA8BC,EAA9B,CAAiC,mBAAjC,EAAsD,SAAtD,EAAiE,UAAUC,KAAV,EAAiB;AAChF,YAAIC,WAAWR,EAAEO,MAAME,aAAR,CAAf;AACA,YAAIC,OAAOV,EAAEO,MAAME,aAAN,CAAoBE,UAAtB,EAAkCC,IAAlC,CAAuC,MAAvC,MAAmD,MAAnD,GAA4D,OAA5D,GAAsE,MAAjF;;AAEAJ,iBAASI,IAAT,CAAc;AACZ,2BAAiBF,IADL;AAEZ,0BAAgBA;AAFJ,SAAd;AAID,OARD;AASD;AAX4B,GAA/B;AAcD,CAvBD,EAuBGG,MAvBH,EAuBWZ,MAvBX","file":"details-aria.es6.js","sourcesContent":["/**\n * @file\n * Add aria attribute handling for details and summary elements.\n */\n\n(function ($, Drupal) {\n\n 'use strict';\n\n /**\n * Handles `aria-expanded` and `aria-pressed` attributes on details elements.\n *\n * @type {Drupal~behavior}\n */\n Drupal.behaviors.detailsAria = {\n attach: function () {\n $('body').once('detailsAria').on('click.detailsAria', 'summary', function (event) {\n var $summary = $(event.currentTarget);\n var open = $(event.currentTarget.parentNode).attr('open') === 'open' ? 'false' : 'true';\n\n $summary.attr({\n 'aria-expanded': open,\n 'aria-pressed': open\n });\n });\n }\n };\n\n})(jQuery, Drupal);\n"]} \ No newline at end of file diff --git a/core/misc/dialog/dialog.ajax.es6.js b/core/misc/dialog/dialog.ajax.es6.js new file mode 100644 index 0000000..3f1b0c2 --- /dev/null +++ b/core/misc/dialog/dialog.ajax.es6.js @@ -0,0 +1,246 @@ +/** + * @file + * Extends the Drupal AJAX functionality to integrate the dialog API. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Initialize dialogs for Ajax purposes. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behaviors for dialog ajax functionality. + */ + Drupal.behaviors.dialog = { + attach: function (context, settings) { + var $context = $(context); + + // Provide a known 'drupal-modal' DOM element for Drupal-based modal + // dialogs. Non-modal dialogs are responsible for creating their own + // elements, since there can be multiple non-modal dialogs at a time. + if (!$('#drupal-modal').length) { + // Add 'ui-front' jQuery UI class so jQuery UI widgets like autocomplete + // sit on top of dialogs. For more information see + // http://api.jqueryui.com/theming/stacking-elements/. + $('
    ').hide().appendTo('body'); + } + + // Special behaviors specific when attaching content within a dialog. + // These behaviors usually fire after a validation error inside a dialog. + var $dialog = $context.closest('.ui-dialog-content'); + if ($dialog.length) { + // Remove and replace the dialog buttons with those from the new form. + if ($dialog.dialog('option', 'drupalAutoButtons')) { + // Trigger an event to detect/sync changes to buttons. + $dialog.trigger('dialogButtonsChange'); + } + + // Force focus on the modal when the behavior is run. + $dialog.dialog('widget').trigger('focus'); + } + + var originalClose = settings.dialog.close; + // Overwrite the close method to remove the dialog on closing. + settings.dialog.close = function (event) { + originalClose.apply(settings.dialog, arguments); + $(event.target).remove(); + }; + }, + + /** + * Scan a dialog for any primary buttons and move them to the button area. + * + * @param {jQuery} $dialog + * An jQuery object containing the element that is the dialog target. + * + * @return {Array} + * An array of buttons that need to be added to the button area. + */ + prepareDialogButtons: function ($dialog) { + var buttons = []; + var $buttons = $dialog.find('.form-actions input[type=submit], .form-actions a.button'); + $buttons.each(function () { + // Hidden form buttons need special attention. For browser consistency, + // the button needs to be "visible" in order to have the enter key fire + // the form submit event. So instead of a simple "hide" or + // "display: none", we set its dimensions to zero. + // See http://mattsnider.com/how-forms-submit-when-pressing-enter/ + var $originalButton = $(this).css({ + display: 'block', + width: 0, + height: 0, + padding: 0, + border: 0, + overflow: 'hidden' + }); + buttons.push({ + text: $originalButton.html() || $originalButton.attr('value'), + class: $originalButton.attr('class'), + click: function (e) { + // If the original button is an anchor tag, triggering the "click" + // event will not simulate a click. Use the click method instead. + if ($originalButton.is('a')) { + $originalButton[0].click(); + } + else { + $originalButton.trigger('mousedown').trigger('mouseup').trigger('click'); + e.preventDefault(); + } + } + }); + }); + return buttons; + } + }; + + /** + * Command to open a dialog. + * + * @param {Drupal.Ajax} ajax + * The Drupal Ajax object. + * @param {object} response + * Object holding the server response. + * @param {number} [status] + * The HTTP status code. + * + * @return {bool|undefined} + * Returns false if there was no selector property in the response object. + */ + Drupal.AjaxCommands.prototype.openDialog = function (ajax, response, status) { + if (!response.selector) { + return false; + } + var $dialog = $(response.selector); + if (!$dialog.length) { + // Create the element if needed. + $dialog = $('
    ').appendTo('body'); + } + // Set up the wrapper, if there isn't one. + if (!ajax.wrapper) { + ajax.wrapper = $dialog.attr('id'); + } + + // Use the ajax.js insert command to populate the dialog contents. + response.command = 'insert'; + response.method = 'html'; + ajax.commands.insert(ajax, response, status); + + // Move the buttons to the jQuery UI dialog buttons area. + if (!response.dialogOptions.buttons) { + response.dialogOptions.drupalAutoButtons = true; + response.dialogOptions.buttons = Drupal.behaviors.dialog.prepareDialogButtons($dialog); + } + + // Bind dialogButtonsChange. + $dialog.on('dialogButtonsChange', function () { + var buttons = Drupal.behaviors.dialog.prepareDialogButtons($dialog); + $dialog.dialog('option', 'buttons', buttons); + }); + + // Open the dialog itself. + response.dialogOptions = response.dialogOptions || {}; + var dialog = Drupal.dialog($dialog.get(0), response.dialogOptions); + if (response.dialogOptions.modal) { + dialog.showModal(); + } + else { + dialog.show(); + } + + // Add the standard Drupal class for buttons for style consistency. + $dialog.parent().find('.ui-dialog-buttonset').addClass('form-actions'); + }; + + /** + * Command to close a dialog. + * + * If no selector is given, it defaults to trying to close the modal. + * + * @param {Drupal.Ajax} [ajax] + * The ajax object. + * @param {object} response + * Object holding the server response. + * @param {string} response.selector + * The selector of the dialog. + * @param {bool} response.persist + * Whether to persist the dialog element or not. + * @param {number} [status] + * The HTTP status code. + */ + Drupal.AjaxCommands.prototype.closeDialog = function (ajax, response, status) { + var $dialog = $(response.selector); + if ($dialog.length) { + Drupal.dialog($dialog.get(0)).close(); + if (!response.persist) { + $dialog.remove(); + } + } + + // Unbind dialogButtonsChange. + $dialog.off('dialogButtonsChange'); + }; + + /** + * Command to set a dialog property. + * + * JQuery UI specific way of setting dialog options. + * + * @param {Drupal.Ajax} [ajax] + * The Drupal Ajax object. + * @param {object} response + * Object holding the server response. + * @param {string} response.selector + * Selector for the dialog element. + * @param {string} response.optionsName + * Name of a key to set. + * @param {string} response.optionValue + * Value to set. + * @param {number} [status] + * The HTTP status code. + */ + Drupal.AjaxCommands.prototype.setDialogOption = function (ajax, response, status) { + var $dialog = $(response.selector); + if ($dialog.length) { + $dialog.dialog('option', response.optionName, response.optionValue); + } + }; + + /** + * Binds a listener on dialog creation to handle the cancel link. + * + * @param {jQuery.Event} e + * The event triggered. + * @param {Drupal.dialog~dialogDefinition} dialog + * The dialog instance. + * @param {jQuery} $element + * The jQuery collection of the dialog element. + * @param {object} [settings] + * Dialog settings. + */ + $(window).on('dialog:aftercreate', function (e, dialog, $element, settings) { + $element.on('click.dialog', '.dialog-cancel', function (e) { + dialog.close('cancel'); + e.preventDefault(); + e.stopPropagation(); + }); + }); + + /** + * Removes all 'dialog' listeners. + * + * @param {jQuery.Event} e + * The event triggered. + * @param {Drupal.dialog~dialogDefinition} dialog + * The dialog instance. + * @param {jQuery} $element + * jQuery collection of the dialog element. + */ + $(window).on('dialog:beforeclose', function (e, dialog, $element) { + $element.off('.dialog'); + }); + +})(jQuery, Drupal); diff --git a/core/misc/dialog/dialog.ajax.js b/core/misc/dialog/dialog.ajax.js index 3f1b0c2..7149ecd 100644 --- a/core/misc/dialog/dialog.ajax.js +++ b/core/misc/dialog/dialog.ajax.js @@ -1,74 +1,38 @@ -/** - * @file - * Extends the Drupal AJAX functionality to integrate the dialog API. - */ +'use strict'; (function ($, Drupal) { 'use strict'; - /** - * Initialize dialogs for Ajax purposes. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches the behaviors for dialog ajax functionality. - */ Drupal.behaviors.dialog = { - attach: function (context, settings) { + attach: function attach(context, settings) { var $context = $(context); - // Provide a known 'drupal-modal' DOM element for Drupal-based modal - // dialogs. Non-modal dialogs are responsible for creating their own - // elements, since there can be multiple non-modal dialogs at a time. if (!$('#drupal-modal').length) { - // Add 'ui-front' jQuery UI class so jQuery UI widgets like autocomplete - // sit on top of dialogs. For more information see - // http://api.jqueryui.com/theming/stacking-elements/. $('
    ').hide().appendTo('body'); } - // Special behaviors specific when attaching content within a dialog. - // These behaviors usually fire after a validation error inside a dialog. var $dialog = $context.closest('.ui-dialog-content'); if ($dialog.length) { - // Remove and replace the dialog buttons with those from the new form. if ($dialog.dialog('option', 'drupalAutoButtons')) { - // Trigger an event to detect/sync changes to buttons. $dialog.trigger('dialogButtonsChange'); } - // Force focus on the modal when the behavior is run. $dialog.dialog('widget').trigger('focus'); } var originalClose = settings.dialog.close; - // Overwrite the close method to remove the dialog on closing. + settings.dialog.close = function (event) { originalClose.apply(settings.dialog, arguments); $(event.target).remove(); }; }, - /** - * Scan a dialog for any primary buttons and move them to the button area. - * - * @param {jQuery} $dialog - * An jQuery object containing the element that is the dialog target. - * - * @return {Array} - * An array of buttons that need to be added to the button area. - */ - prepareDialogButtons: function ($dialog) { + prepareDialogButtons: function prepareDialogButtons($dialog) { var buttons = []; var $buttons = $dialog.find('.form-actions input[type=submit], .form-actions a.button'); $buttons.each(function () { - // Hidden form buttons need special attention. For browser consistency, - // the button needs to be "visible" in order to have the enter key fire - // the form submit event. So instead of a simple "hide" or - // "display: none", we set its dimensions to zero. - // See http://mattsnider.com/how-forms-submit-when-pressing-enter/ var $originalButton = $(this).css({ display: 'block', width: 0, @@ -80,13 +44,10 @@ buttons.push({ text: $originalButton.html() || $originalButton.attr('value'), class: $originalButton.attr('class'), - click: function (e) { - // If the original button is an anchor tag, triggering the "click" - // event will not simulate a click. Use the click method instead. + click: function click(e) { if ($originalButton.is('a')) { $originalButton[0].click(); - } - else { + } else { $originalButton.trigger('mousedown').trigger('mouseup').trigger('click'); e.preventDefault(); } @@ -97,80 +58,44 @@ } }; - /** - * Command to open a dialog. - * - * @param {Drupal.Ajax} ajax - * The Drupal Ajax object. - * @param {object} response - * Object holding the server response. - * @param {number} [status] - * The HTTP status code. - * - * @return {bool|undefined} - * Returns false if there was no selector property in the response object. - */ Drupal.AjaxCommands.prototype.openDialog = function (ajax, response, status) { if (!response.selector) { return false; } var $dialog = $(response.selector); if (!$dialog.length) { - // Create the element if needed. $dialog = $('
    ').appendTo('body'); } - // Set up the wrapper, if there isn't one. + if (!ajax.wrapper) { ajax.wrapper = $dialog.attr('id'); } - // Use the ajax.js insert command to populate the dialog contents. response.command = 'insert'; response.method = 'html'; ajax.commands.insert(ajax, response, status); - // Move the buttons to the jQuery UI dialog buttons area. if (!response.dialogOptions.buttons) { response.dialogOptions.drupalAutoButtons = true; response.dialogOptions.buttons = Drupal.behaviors.dialog.prepareDialogButtons($dialog); } - // Bind dialogButtonsChange. $dialog.on('dialogButtonsChange', function () { var buttons = Drupal.behaviors.dialog.prepareDialogButtons($dialog); $dialog.dialog('option', 'buttons', buttons); }); - // Open the dialog itself. response.dialogOptions = response.dialogOptions || {}; var dialog = Drupal.dialog($dialog.get(0), response.dialogOptions); if (response.dialogOptions.modal) { dialog.showModal(); - } - else { + } else { dialog.show(); } - // Add the standard Drupal class for buttons for style consistency. $dialog.parent().find('.ui-dialog-buttonset').addClass('form-actions'); }; - /** - * Command to close a dialog. - * - * If no selector is given, it defaults to trying to close the modal. - * - * @param {Drupal.Ajax} [ajax] - * The ajax object. - * @param {object} response - * Object holding the server response. - * @param {string} response.selector - * The selector of the dialog. - * @param {bool} response.persist - * Whether to persist the dialog element or not. - * @param {number} [status] - * The HTTP status code. - */ Drupal.AjaxCommands.prototype.closeDialog = function (ajax, response, status) { var $dialog = $(response.selector); if ($dialog.length) { @@ -180,28 +105,9 @@ } } - // Unbind dialogButtonsChange. $dialog.off('dialogButtonsChange'); }; - /** - * Command to set a dialog property. - * - * JQuery UI specific way of setting dialog options. - * - * @param {Drupal.Ajax} [ajax] - * The Drupal Ajax object. - * @param {object} response - * Object holding the server response. - * @param {string} response.selector - * Selector for the dialog element. - * @param {string} response.optionsName - * Name of a key to set. - * @param {string} response.optionValue - * Value to set. - * @param {number} [status] - * The HTTP status code. - */ Drupal.AjaxCommands.prototype.setDialogOption = function (ajax, response, status) { var $dialog = $(response.selector); if ($dialog.length) { @@ -209,18 +115,6 @@ } }; - /** - * Binds a listener on dialog creation to handle the cancel link. - * - * @param {jQuery.Event} e - * The event triggered. - * @param {Drupal.dialog~dialogDefinition} dialog - * The dialog instance. - * @param {jQuery} $element - * The jQuery collection of the dialog element. - * @param {object} [settings] - * Dialog settings. - */ $(window).on('dialog:aftercreate', function (e, dialog, $element, settings) { $element.on('click.dialog', '.dialog-cancel', function (e) { dialog.close('cancel'); @@ -229,18 +123,9 @@ }); }); - /** - * Removes all 'dialog' listeners. - * - * @param {jQuery.Event} e - * The event triggered. - * @param {Drupal.dialog~dialogDefinition} dialog - * The dialog instance. - * @param {jQuery} $element - * jQuery collection of the dialog element. - */ $(window).on('dialog:beforeclose', function (e, dialog, $element) { $element.off('.dialog'); }); - })(jQuery, Drupal); + +//# sourceMappingURL=dialog.ajax.js.map \ No newline at end of file diff --git a/core/misc/dialog/dialog.ajax.js.map b/core/misc/dialog/dialog.ajax.js.map new file mode 100644 index 0000000..cd18674 --- /dev/null +++ b/core/misc/dialog/dialog.ajax.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["dialog.ajax.es6.js"],"names":["$","Drupal","behaviors","dialog","attach","context","settings","$context","length","hide","appendTo","$dialog","closest","trigger","originalClose","close","event","apply","arguments","target","remove","prepareDialogButtons","buttons","$buttons","find","each","$originalButton","css","display","width","height","padding","border","overflow","push","text","html","attr","class","click","e","is","preventDefault","AjaxCommands","prototype","openDialog","ajax","response","status","selector","replace","wrapper","command","method","commands","insert","dialogOptions","drupalAutoButtons","on","get","modal","showModal","show","parent","addClass","closeDialog","persist","off","setDialogOption","optionName","optionValue","window","$element","stopPropagation","jQuery"],"mappings":"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","file":"dialog.ajax.es6.js","sourcesContent":["/**\n * @file\n * Extends the Drupal AJAX functionality to integrate the dialog API.\n */\n\n(function ($, Drupal) {\n\n 'use strict';\n\n /**\n * Initialize dialogs for Ajax purposes.\n *\n * @type {Drupal~behavior}\n *\n * @prop {Drupal~behaviorAttach} attach\n * Attaches the behaviors for dialog ajax functionality.\n */\n Drupal.behaviors.dialog = {\n attach: function (context, settings) {\n var $context = $(context);\n\n // Provide a known 'drupal-modal' DOM element for Drupal-based modal\n // dialogs. Non-modal dialogs are responsible for creating their own\n // elements, since there can be multiple non-modal dialogs at a time.\n if (!$('#drupal-modal').length) {\n // Add 'ui-front' jQuery UI class so jQuery UI widgets like autocomplete\n // sit on top of dialogs. For more information see\n // http://api.jqueryui.com/theming/stacking-elements/.\n $('
    ').hide().appendTo('body');\n }\n\n // Special behaviors specific when attaching content within a dialog.\n // These behaviors usually fire after a validation error inside a dialog.\n var $dialog = $context.closest('.ui-dialog-content');\n if ($dialog.length) {\n // Remove and replace the dialog buttons with those from the new form.\n if ($dialog.dialog('option', 'drupalAutoButtons')) {\n // Trigger an event to detect/sync changes to buttons.\n $dialog.trigger('dialogButtonsChange');\n }\n\n // Force focus on the modal when the behavior is run.\n $dialog.dialog('widget').trigger('focus');\n }\n\n var originalClose = settings.dialog.close;\n // Overwrite the close method to remove the dialog on closing.\n settings.dialog.close = function (event) {\n originalClose.apply(settings.dialog, arguments);\n $(event.target).remove();\n };\n },\n\n /**\n * Scan a dialog for any primary buttons and move them to the button area.\n *\n * @param {jQuery} $dialog\n * An jQuery object containing the element that is the dialog target.\n *\n * @return {Array}\n * An array of buttons that need to be added to the button area.\n */\n prepareDialogButtons: function ($dialog) {\n var buttons = [];\n var $buttons = $dialog.find('.form-actions input[type=submit], .form-actions a.button');\n $buttons.each(function () {\n // Hidden form buttons need special attention. For browser consistency,\n // the button needs to be \"visible\" in order to have the enter key fire\n // the form submit event. So instead of a simple \"hide\" or\n // \"display: none\", we set its dimensions to zero.\n // See http://mattsnider.com/how-forms-submit-when-pressing-enter/\n var $originalButton = $(this).css({\n display: 'block',\n width: 0,\n height: 0,\n padding: 0,\n border: 0,\n overflow: 'hidden'\n });\n buttons.push({\n text: $originalButton.html() || $originalButton.attr('value'),\n class: $originalButton.attr('class'),\n click: function (e) {\n // If the original button is an anchor tag, triggering the \"click\"\n // event will not simulate a click. Use the click method instead.\n if ($originalButton.is('a')) {\n $originalButton[0].click();\n }\n else {\n $originalButton.trigger('mousedown').trigger('mouseup').trigger('click');\n e.preventDefault();\n }\n }\n });\n });\n return buttons;\n }\n };\n\n /**\n * Command to open a dialog.\n *\n * @param {Drupal.Ajax} ajax\n * The Drupal Ajax object.\n * @param {object} response\n * Object holding the server response.\n * @param {number} [status]\n * The HTTP status code.\n *\n * @return {bool|undefined}\n * Returns false if there was no selector property in the response object.\n */\n Drupal.AjaxCommands.prototype.openDialog = function (ajax, response, status) {\n if (!response.selector) {\n return false;\n }\n var $dialog = $(response.selector);\n if (!$dialog.length) {\n // Create the element if needed.\n $dialog = $('
    ').appendTo('body');\n }\n // Set up the wrapper, if there isn't one.\n if (!ajax.wrapper) {\n ajax.wrapper = $dialog.attr('id');\n }\n\n // Use the ajax.js insert command to populate the dialog contents.\n response.command = 'insert';\n response.method = 'html';\n ajax.commands.insert(ajax, response, status);\n\n // Move the buttons to the jQuery UI dialog buttons area.\n if (!response.dialogOptions.buttons) {\n response.dialogOptions.drupalAutoButtons = true;\n response.dialogOptions.buttons = Drupal.behaviors.dialog.prepareDialogButtons($dialog);\n }\n\n // Bind dialogButtonsChange.\n $dialog.on('dialogButtonsChange', function () {\n var buttons = Drupal.behaviors.dialog.prepareDialogButtons($dialog);\n $dialog.dialog('option', 'buttons', buttons);\n });\n\n // Open the dialog itself.\n response.dialogOptions = response.dialogOptions || {};\n var dialog = Drupal.dialog($dialog.get(0), response.dialogOptions);\n if (response.dialogOptions.modal) {\n dialog.showModal();\n }\n else {\n dialog.show();\n }\n\n // Add the standard Drupal class for buttons for style consistency.\n $dialog.parent().find('.ui-dialog-buttonset').addClass('form-actions');\n };\n\n /**\n * Command to close a dialog.\n *\n * If no selector is given, it defaults to trying to close the modal.\n *\n * @param {Drupal.Ajax} [ajax]\n * The ajax object.\n * @param {object} response\n * Object holding the server response.\n * @param {string} response.selector\n * The selector of the dialog.\n * @param {bool} response.persist\n * Whether to persist the dialog element or not.\n * @param {number} [status]\n * The HTTP status code.\n */\n Drupal.AjaxCommands.prototype.closeDialog = function (ajax, response, status) {\n var $dialog = $(response.selector);\n if ($dialog.length) {\n Drupal.dialog($dialog.get(0)).close();\n if (!response.persist) {\n $dialog.remove();\n }\n }\n\n // Unbind dialogButtonsChange.\n $dialog.off('dialogButtonsChange');\n };\n\n /**\n * Command to set a dialog property.\n *\n * JQuery UI specific way of setting dialog options.\n *\n * @param {Drupal.Ajax} [ajax]\n * The Drupal Ajax object.\n * @param {object} response\n * Object holding the server response.\n * @param {string} response.selector\n * Selector for the dialog element.\n * @param {string} response.optionsName\n * Name of a key to set.\n * @param {string} response.optionValue\n * Value to set.\n * @param {number} [status]\n * The HTTP status code.\n */\n Drupal.AjaxCommands.prototype.setDialogOption = function (ajax, response, status) {\n var $dialog = $(response.selector);\n if ($dialog.length) {\n $dialog.dialog('option', response.optionName, response.optionValue);\n }\n };\n\n /**\n * Binds a listener on dialog creation to handle the cancel link.\n *\n * @param {jQuery.Event} e\n * The event triggered.\n * @param {Drupal.dialog~dialogDefinition} dialog\n * The dialog instance.\n * @param {jQuery} $element\n * The jQuery collection of the dialog element.\n * @param {object} [settings]\n * Dialog settings.\n */\n $(window).on('dialog:aftercreate', function (e, dialog, $element, settings) {\n $element.on('click.dialog', '.dialog-cancel', function (e) {\n dialog.close('cancel');\n e.preventDefault();\n e.stopPropagation();\n });\n });\n\n /**\n * Removes all 'dialog' listeners.\n *\n * @param {jQuery.Event} e\n * The event triggered.\n * @param {Drupal.dialog~dialogDefinition} dialog\n * The dialog instance.\n * @param {jQuery} $element\n * jQuery collection of the dialog element.\n */\n $(window).on('dialog:beforeclose', function (e, dialog, $element) {\n $element.off('.dialog');\n });\n\n})(jQuery, Drupal);\n"]} \ No newline at end of file diff --git a/core/misc/dialog/dialog.es6.js b/core/misc/dialog/dialog.es6.js new file mode 100644 index 0000000..ea1e52c --- /dev/null +++ b/core/misc/dialog/dialog.es6.js @@ -0,0 +1,100 @@ +/** + * @file + * Dialog API inspired by HTML5 dialog element. + * + * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#the-dialog-element + */ + +(function ($, Drupal, drupalSettings) { + + 'use strict'; + + /** + * Default dialog options. + * + * @type {object} + * + * @prop {bool} [autoOpen=true] + * @prop {string} [dialogClass=''] + * @prop {string} [buttonClass='button'] + * @prop {string} [buttonPrimaryClass='button--primary'] + * @prop {function} close + */ + drupalSettings.dialog = { + autoOpen: true, + dialogClass: '', + // Drupal-specific extensions: see dialog.jquery-ui.js. + buttonClass: 'button', + buttonPrimaryClass: 'button--primary', + // When using this API directly (when generating dialogs on the client + // side), you may want to override this method and do + // `jQuery(event.target).remove()` as well, to remove the dialog on + // closing. + close: function (event) { + Drupal.dialog(event.target).close(); + Drupal.detachBehaviors(event.target, null, 'unload'); + } + }; + + /** + * @typedef {object} Drupal.dialog~dialogDefinition + * + * @prop {boolean} open + * Is the dialog open or not. + * @prop {*} returnValue + * Return value of the dialog. + * @prop {function} show + * Method to display the dialog on the page. + * @prop {function} showModal + * Method to display the dialog as a modal on the page. + * @prop {function} close + * Method to hide the dialog from the page. + */ + + /** + * Polyfill HTML5 dialog element with jQueryUI. + * + * @param {HTMLElement} element + * The element that holds the dialog. + * @param {object} options + * jQuery UI options to be passed to the dialog. + * + * @return {Drupal.dialog~dialogDefinition} + * The dialog instance. + */ + Drupal.dialog = function (element, options) { + var undef; + var $element = $(element); + var dialog = { + open: false, + returnValue: undef, + show: function () { + openDialog({modal: false}); + }, + showModal: function () { + openDialog({modal: true}); + }, + close: closeDialog + }; + + function openDialog(settings) { + settings = $.extend({}, drupalSettings.dialog, options, settings); + // Trigger a global event to allow scripts to bind events to the dialog. + $(window).trigger('dialog:beforecreate', [dialog, $element, settings]); + $element.dialog(settings); + dialog.open = true; + $(window).trigger('dialog:aftercreate', [dialog, $element, settings]); + } + + function closeDialog(value) { + $(window).trigger('dialog:beforeclose', [dialog, $element]); + $element.dialog('close'); + dialog.returnValue = value; + dialog.open = false; + $(window).trigger('dialog:afterclose', [dialog, $element]); + } + + return dialog; + }; + +})(jQuery, Drupal, drupalSettings); diff --git a/core/misc/dialog/dialog.jquery-ui.es6.js b/core/misc/dialog/dialog.jquery-ui.es6.js new file mode 100644 index 0000000..2526e30 --- /dev/null +++ b/core/misc/dialog/dialog.jquery-ui.es6.js @@ -0,0 +1,36 @@ +/** + * @file + * Adds default classes to buttons for styling purposes. + */ + +(function ($) { + + 'use strict'; + + $.widget('ui.dialog', $.ui.dialog, { + options: { + buttonClass: 'button', + buttonPrimaryClass: 'button--primary' + }, + _createButtons: function () { + var opts = this.options; + var primaryIndex; + var $buttons; + var index; + var il = opts.buttons.length; + for (index = 0; index < il; index++) { + if (opts.buttons[index].primary && opts.buttons[index].primary === true) { + primaryIndex = index; + delete opts.buttons[index].primary; + break; + } + } + this._super(); + $buttons = this.uiButtonSet.children().addClass(opts.buttonClass); + if (typeof primaryIndex !== 'undefined') { + $buttons.eq(index).addClass(opts.buttonPrimaryClass); + } + } + }); + +})(jQuery); diff --git a/core/misc/dialog/dialog.jquery-ui.js b/core/misc/dialog/dialog.jquery-ui.js index 2526e30..10c5456 100644 --- a/core/misc/dialog/dialog.jquery-ui.js +++ b/core/misc/dialog/dialog.jquery-ui.js @@ -1,7 +1,4 @@ -/** - * @file - * Adds default classes to buttons for styling purposes. - */ +'use strict'; (function ($) { @@ -12,7 +9,7 @@ buttonClass: 'button', buttonPrimaryClass: 'button--primary' }, - _createButtons: function () { + _createButtons: function _createButtons() { var opts = this.options; var primaryIndex; var $buttons; @@ -32,5 +29,6 @@ } } }); - })(jQuery); + +//# sourceMappingURL=dialog.jquery-ui.js.map \ No newline at end of file diff --git a/core/misc/dialog/dialog.jquery-ui.js.map b/core/misc/dialog/dialog.jquery-ui.js.map new file mode 100644 index 0000000..14d0f4e --- /dev/null +++ b/core/misc/dialog/dialog.jquery-ui.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["dialog.jquery-ui.es6.js"],"names":["$","widget","ui","dialog","options","buttonClass","buttonPrimaryClass","_createButtons","opts","primaryIndex","$buttons","index","il","buttons","length","primary","_super","uiButtonSet","children","addClass","eq","jQuery"],"mappings":";;AAKA,CAAC,UAAUA,CAAV,EAAa;;AAEZ;;AAEAA,IAAEC,MAAF,CAAS,WAAT,EAAsBD,EAAEE,EAAF,CAAKC,MAA3B,EAAmC;AACjCC,aAAS;AACPC,mBAAa,QADN;AAEPC,0BAAoB;AAFb,KADwB;AAKjCC,oBAAgB,0BAAY;AAC1B,UAAIC,OAAO,KAAKJ,OAAhB;AACA,UAAIK,YAAJ;AACA,UAAIC,QAAJ;AACA,UAAIC,KAAJ;AACA,UAAIC,KAAKJ,KAAKK,OAAL,CAAaC,MAAtB;AACA,WAAKH,QAAQ,CAAb,EAAgBA,QAAQC,EAAxB,EAA4BD,OAA5B,EAAqC;AACnC,YAAIH,KAAKK,OAAL,CAAaF,KAAb,EAAoBI,OAApB,IAA+BP,KAAKK,OAAL,CAAaF,KAAb,EAAoBI,OAApB,KAAgC,IAAnE,EAAyE;AACvEN,yBAAeE,KAAf;AACA,iBAAOH,KAAKK,OAAL,CAAaF,KAAb,EAAoBI,OAA3B;AACA;AACD;AACF;AACD,WAAKC,MAAL;AACAN,iBAAW,KAAKO,WAAL,CAAiBC,QAAjB,GAA4BC,QAA5B,CAAqCX,KAAKH,WAA1C,CAAX;AACA,UAAI,OAAOI,YAAP,KAAwB,WAA5B,EAAyC;AACvCC,iBAASU,EAAT,CAAYT,KAAZ,EAAmBQ,QAAnB,CAA4BX,KAAKF,kBAAjC;AACD;AACF;AAvBgC,GAAnC;AA0BD,CA9BD,EA8BGe,MA9BH","file":"dialog.jquery-ui.es6.js","sourcesContent":["/**\n * @file\n * Adds default classes to buttons for styling purposes.\n */\n\n(function ($) {\n\n 'use strict';\n\n $.widget('ui.dialog', $.ui.dialog, {\n options: {\n buttonClass: 'button',\n buttonPrimaryClass: 'button--primary'\n },\n _createButtons: function () {\n var opts = this.options;\n var primaryIndex;\n var $buttons;\n var index;\n var il = opts.buttons.length;\n for (index = 0; index < il; index++) {\n if (opts.buttons[index].primary && opts.buttons[index].primary === true) {\n primaryIndex = index;\n delete opts.buttons[index].primary;\n break;\n }\n }\n this._super();\n $buttons = this.uiButtonSet.children().addClass(opts.buttonClass);\n if (typeof primaryIndex !== 'undefined') {\n $buttons.eq(index).addClass(opts.buttonPrimaryClass);\n }\n }\n });\n\n})(jQuery);\n"]} \ No newline at end of file diff --git a/core/misc/dialog/dialog.js b/core/misc/dialog/dialog.js index ea1e52c..27f7e2e 100644 --- a/core/misc/dialog/dialog.js +++ b/core/misc/dialog/dialog.js @@ -1,85 +1,40 @@ -/** - * @file - * Dialog API inspired by HTML5 dialog element. - * - * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#the-dialog-element - */ +'use strict'; (function ($, Drupal, drupalSettings) { 'use strict'; - /** - * Default dialog options. - * - * @type {object} - * - * @prop {bool} [autoOpen=true] - * @prop {string} [dialogClass=''] - * @prop {string} [buttonClass='button'] - * @prop {string} [buttonPrimaryClass='button--primary'] - * @prop {function} close - */ drupalSettings.dialog = { autoOpen: true, dialogClass: '', - // Drupal-specific extensions: see dialog.jquery-ui.js. + buttonClass: 'button', buttonPrimaryClass: 'button--primary', - // When using this API directly (when generating dialogs on the client - // side), you may want to override this method and do - // `jQuery(event.target).remove()` as well, to remove the dialog on - // closing. - close: function (event) { + + close: function close(event) { Drupal.dialog(event.target).close(); Drupal.detachBehaviors(event.target, null, 'unload'); } }; - /** - * @typedef {object} Drupal.dialog~dialogDefinition - * - * @prop {boolean} open - * Is the dialog open or not. - * @prop {*} returnValue - * Return value of the dialog. - * @prop {function} show - * Method to display the dialog on the page. - * @prop {function} showModal - * Method to display the dialog as a modal on the page. - * @prop {function} close - * Method to hide the dialog from the page. - */ - - /** - * Polyfill HTML5 dialog element with jQueryUI. - * - * @param {HTMLElement} element - * The element that holds the dialog. - * @param {object} options - * jQuery UI options to be passed to the dialog. - * - * @return {Drupal.dialog~dialogDefinition} - * The dialog instance. - */ Drupal.dialog = function (element, options) { var undef; var $element = $(element); var dialog = { open: false, returnValue: undef, - show: function () { - openDialog({modal: false}); + show: function show() { + openDialog({ modal: false }); }, - showModal: function () { - openDialog({modal: true}); + showModal: function showModal() { + openDialog({ modal: true }); }, close: closeDialog }; function openDialog(settings) { settings = $.extend({}, drupalSettings.dialog, options, settings); - // Trigger a global event to allow scripts to bind events to the dialog. + $(window).trigger('dialog:beforecreate', [dialog, $element, settings]); $element.dialog(settings); dialog.open = true; @@ -96,5 +51,6 @@ return dialog; }; - })(jQuery, Drupal, drupalSettings); + +//# sourceMappingURL=dialog.js.map \ No newline at end of file diff --git a/core/misc/dialog/dialog.js.map b/core/misc/dialog/dialog.js.map new file mode 100644 index 0000000..7ab9fc3 --- /dev/null +++ b/core/misc/dialog/dialog.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["dialog.es6.js"],"names":["$","Drupal","drupalSettings","dialog","autoOpen","dialogClass","buttonClass","buttonPrimaryClass","close","event","target","detachBehaviors","element","options","undef","$element","open","returnValue","show","openDialog","modal","showModal","closeDialog","settings","extend","window","trigger","value","jQuery"],"mappings":";;AAOA,CAAC,UAAUA,CAAV,EAAaC,MAAb,EAAqBC,cAArB,EAAqC;;AAEpC;;AAaAA,iBAAeC,MAAf,GAAwB;AACtBC,cAAU,IADY;AAEtBC,iBAAa,EAFS;;AAItBC,iBAAa,QAJS;AAKtBC,wBAAoB,iBALE;;AAUtBC,WAAO,eAAUC,KAAV,EAAiB;AACtBR,aAAOE,MAAP,CAAcM,MAAMC,MAApB,EAA4BF,KAA5B;AACAP,aAAOU,eAAP,CAAuBF,MAAMC,MAA7B,EAAqC,IAArC,EAA2C,QAA3C;AACD;AAbqB,GAAxB;;AA0CAT,SAAOE,MAAP,GAAgB,UAAUS,OAAV,EAAmBC,OAAnB,EAA4B;AAC1C,QAAIC,KAAJ;AACA,QAAIC,WAAWf,EAAEY,OAAF,CAAf;AACA,QAAIT,SAAS;AACXa,YAAM,KADK;AAEXC,mBAAaH,KAFF;AAGXI,YAAM,gBAAY;AAChBC,mBAAW,EAACC,OAAO,KAAR,EAAX;AACD,OALU;AAMXC,iBAAW,qBAAY;AACrBF,mBAAW,EAACC,OAAO,IAAR,EAAX;AACD,OARU;AASXZ,aAAOc;AATI,KAAb;;AAYA,aAASH,UAAT,CAAoBI,QAApB,EAA8B;AAC5BA,iBAAWvB,EAAEwB,MAAF,CAAS,EAAT,EAAatB,eAAeC,MAA5B,EAAoCU,OAApC,EAA6CU,QAA7C,CAAX;;AAEAvB,QAAEyB,MAAF,EAAUC,OAAV,CAAkB,qBAAlB,EAAyC,CAACvB,MAAD,EAASY,QAAT,EAAmBQ,QAAnB,CAAzC;AACAR,eAASZ,MAAT,CAAgBoB,QAAhB;AACApB,aAAOa,IAAP,GAAc,IAAd;AACAhB,QAAEyB,MAAF,EAAUC,OAAV,CAAkB,oBAAlB,EAAwC,CAACvB,MAAD,EAASY,QAAT,EAAmBQ,QAAnB,CAAxC;AACD;;AAED,aAASD,WAAT,CAAqBK,KAArB,EAA4B;AAC1B3B,QAAEyB,MAAF,EAAUC,OAAV,CAAkB,oBAAlB,EAAwC,CAACvB,MAAD,EAASY,QAAT,CAAxC;AACAA,eAASZ,MAAT,CAAgB,OAAhB;AACAA,aAAOc,WAAP,GAAqBU,KAArB;AACAxB,aAAOa,IAAP,GAAc,KAAd;AACAhB,QAAEyB,MAAF,EAAUC,OAAV,CAAkB,mBAAlB,EAAuC,CAACvB,MAAD,EAASY,QAAT,CAAvC;AACD;;AAED,WAAOZ,MAAP;AACD,GAjCD;AAmCD,CA5FD,EA4FGyB,MA5FH,EA4FW3B,MA5FX,EA4FmBC,cA5FnB","file":"dialog.es6.js","sourcesContent":["/**\n * @file\n * Dialog API inspired by HTML5 dialog element.\n *\n * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#the-dialog-element\n */\n\n(function ($, Drupal, drupalSettings) {\n\n 'use strict';\n\n /**\n * Default dialog options.\n *\n * @type {object}\n *\n * @prop {bool} [autoOpen=true]\n * @prop {string} [dialogClass='']\n * @prop {string} [buttonClass='button']\n * @prop {string} [buttonPrimaryClass='button--primary']\n * @prop {function} close\n */\n drupalSettings.dialog = {\n autoOpen: true,\n dialogClass: '',\n // Drupal-specific extensions: see dialog.jquery-ui.js.\n buttonClass: 'button',\n buttonPrimaryClass: 'button--primary',\n // When using this API directly (when generating dialogs on the client\n // side), you may want to override this method and do\n // `jQuery(event.target).remove()` as well, to remove the dialog on\n // closing.\n close: function (event) {\n Drupal.dialog(event.target).close();\n Drupal.detachBehaviors(event.target, null, 'unload');\n }\n };\n\n /**\n * @typedef {object} Drupal.dialog~dialogDefinition\n *\n * @prop {boolean} open\n * Is the dialog open or not.\n * @prop {*} returnValue\n * Return value of the dialog.\n * @prop {function} show\n * Method to display the dialog on the page.\n * @prop {function} showModal\n * Method to display the dialog as a modal on the page.\n * @prop {function} close\n * Method to hide the dialog from the page.\n */\n\n /**\n * Polyfill HTML5 dialog element with jQueryUI.\n *\n * @param {HTMLElement} element\n * The element that holds the dialog.\n * @param {object} options\n * jQuery UI options to be passed to the dialog.\n *\n * @return {Drupal.dialog~dialogDefinition}\n * The dialog instance.\n */\n Drupal.dialog = function (element, options) {\n var undef;\n var $element = $(element);\n var dialog = {\n open: false,\n returnValue: undef,\n show: function () {\n openDialog({modal: false});\n },\n showModal: function () {\n openDialog({modal: true});\n },\n close: closeDialog\n };\n\n function openDialog(settings) {\n settings = $.extend({}, drupalSettings.dialog, options, settings);\n // Trigger a global event to allow scripts to bind events to the dialog.\n $(window).trigger('dialog:beforecreate', [dialog, $element, settings]);\n $element.dialog(settings);\n dialog.open = true;\n $(window).trigger('dialog:aftercreate', [dialog, $element, settings]);\n }\n\n function closeDialog(value) {\n $(window).trigger('dialog:beforeclose', [dialog, $element]);\n $element.dialog('close');\n dialog.returnValue = value;\n dialog.open = false;\n $(window).trigger('dialog:afterclose', [dialog, $element]);\n }\n\n return dialog;\n };\n\n})(jQuery, Drupal, drupalSettings);\n"]} \ No newline at end of file diff --git a/core/misc/dialog/dialog.position.es6.js b/core/misc/dialog/dialog.position.es6.js new file mode 100644 index 0000000..40bb5b6 --- /dev/null +++ b/core/misc/dialog/dialog.position.es6.js @@ -0,0 +1,111 @@ +/** + * @file + * Positioning extensions for dialogs. + */ + +/** + * Triggers when content inside a dialog changes. + * + * @event dialogContentResize + */ + +(function ($, Drupal, drupalSettings, debounce, displace) { + + 'use strict'; + + // autoResize option will turn off resizable and draggable. + drupalSettings.dialog = $.extend({autoResize: true, maxHeight: '95%'}, drupalSettings.dialog); + + /** + * Resets the current options for positioning. + * + * This is used as a window resize and scroll callback to reposition the + * jQuery UI dialog. Although not a built-in jQuery UI option, this can + * be disabled by setting autoResize: false in the options array when creating + * a new {@link Drupal.dialog}. + * + * @function Drupal.dialog~resetSize + * + * @param {jQuery.Event} event + * The event triggered. + * + * @fires event:dialogContentResize + */ + function resetSize(event) { + var positionOptions = ['width', 'height', 'minWidth', 'minHeight', 'maxHeight', 'maxWidth', 'position']; + var adjustedOptions = {}; + var windowHeight = $(window).height(); + var option; + var optionValue; + var adjustedValue; + for (var n = 0; n < positionOptions.length; n++) { + option = positionOptions[n]; + optionValue = event.data.settings[option]; + if (optionValue) { + // jQuery UI does not support percentages on heights, convert to pixels. + if (typeof optionValue === 'string' && /%$/.test(optionValue) && /height/i.test(option)) { + // Take offsets in account. + windowHeight -= displace.offsets.top + displace.offsets.bottom; + adjustedValue = parseInt(0.01 * parseInt(optionValue, 10) * windowHeight, 10); + // Don't force the dialog to be bigger vertically than needed. + if (option === 'height' && event.data.$element.parent().outerHeight() < adjustedValue) { + adjustedValue = 'auto'; + } + adjustedOptions[option] = adjustedValue; + } + } + } + // Offset the dialog center to be at the center of Drupal.displace.offsets. + if (!event.data.settings.modal) { + adjustedOptions = resetPosition(adjustedOptions); + } + event.data.$element + .dialog('option', adjustedOptions) + .trigger('dialogContentResize'); + } + + /** + * Position the dialog's center at the center of displace.offsets boundaries. + * + * @function Drupal.dialog~resetPosition + * + * @param {object} options + * Options object. + * + * @return {object} + * Altered options object. + */ + function resetPosition(options) { + var offsets = displace.offsets; + var left = offsets.left - offsets.right; + var top = offsets.top - offsets.bottom; + + var leftString = (left > 0 ? '+' : '-') + Math.abs(Math.round(left / 2)) + 'px'; + var topString = (top > 0 ? '+' : '-') + Math.abs(Math.round(top / 2)) + 'px'; + options.position = { + my: 'center' + (left !== 0 ? leftString : '') + ' center' + (top !== 0 ? topString : ''), + of: window + }; + return options; + } + + $(window).on({ + 'dialog:aftercreate': function (event, dialog, $element, settings) { + var autoResize = debounce(resetSize, 20); + var eventData = {settings: settings, $element: $element}; + if (settings.autoResize === true || settings.autoResize === 'true') { + $element + .dialog('option', {resizable: false, draggable: false}) + .dialog('widget').css('position', 'fixed'); + $(window) + .on('resize.dialogResize scroll.dialogResize', eventData, autoResize) + .trigger('resize.dialogResize'); + $(document).on('drupalViewportOffsetChange', eventData, autoResize); + } + }, + 'dialog:beforeclose': function (event, dialog, $element) { + $(window).off('.dialogResize'); + } + }); + +})(jQuery, Drupal, drupalSettings, Drupal.debounce, Drupal.displace); diff --git a/core/misc/dialog/dialog.position.js b/core/misc/dialog/dialog.position.js index 40bb5b6..5fab42d 100644 --- a/core/misc/dialog/dialog.position.js +++ b/core/misc/dialog/dialog.position.js @@ -1,36 +1,11 @@ -/** - * @file - * Positioning extensions for dialogs. - */ - -/** - * Triggers when content inside a dialog changes. - * - * @event dialogContentResize - */ +'use strict'; (function ($, Drupal, drupalSettings, debounce, displace) { 'use strict'; - // autoResize option will turn off resizable and draggable. - drupalSettings.dialog = $.extend({autoResize: true, maxHeight: '95%'}, drupalSettings.dialog); + drupalSettings.dialog = $.extend({ autoResize: true, maxHeight: '95%' }, drupalSettings.dialog); - /** - * Resets the current options for positioning. - * - * This is used as a window resize and scroll callback to reposition the - * jQuery UI dialog. Although not a built-in jQuery UI option, this can - * be disabled by setting autoResize: false in the options array when creating - * a new {@link Drupal.dialog}. - * - * @function Drupal.dialog~resetSize - * - * @param {jQuery.Event} event - * The event triggered. - * - * @fires event:dialogContentResize - */ function resetSize(event) { var positionOptions = ['width', 'height', 'minWidth', 'minHeight', 'maxHeight', 'maxWidth', 'position']; var adjustedOptions = {}; @@ -42,12 +17,10 @@ option = positionOptions[n]; optionValue = event.data.settings[option]; if (optionValue) { - // jQuery UI does not support percentages on heights, convert to pixels. if (typeof optionValue === 'string' && /%$/.test(optionValue) && /height/i.test(option)) { - // Take offsets in account. windowHeight -= displace.offsets.top + displace.offsets.bottom; adjustedValue = parseInt(0.01 * parseInt(optionValue, 10) * windowHeight, 10); - // Don't force the dialog to be bigger vertically than needed. + if (option === 'height' && event.data.$element.parent().outerHeight() < adjustedValue) { adjustedValue = 'auto'; } @@ -55,26 +28,13 @@ } } } - // Offset the dialog center to be at the center of Drupal.displace.offsets. + if (!event.data.settings.modal) { adjustedOptions = resetPosition(adjustedOptions); } - event.data.$element - .dialog('option', adjustedOptions) - .trigger('dialogContentResize'); + event.data.$element.dialog('option', adjustedOptions).trigger('dialogContentResize'); } - /** - * Position the dialog's center at the center of displace.offsets boundaries. - * - * @function Drupal.dialog~resetPosition - * - * @param {object} options - * Options object. - * - * @return {object} - * Altered options object. - */ function resetPosition(options) { var offsets = displace.offsets; var left = offsets.left - offsets.right; @@ -90,22 +50,19 @@ } $(window).on({ - 'dialog:aftercreate': function (event, dialog, $element, settings) { + 'dialog:aftercreate': function dialogAftercreate(event, dialog, $element, settings) { var autoResize = debounce(resetSize, 20); - var eventData = {settings: settings, $element: $element}; + var eventData = { settings: settings, $element: $element }; if (settings.autoResize === true || settings.autoResize === 'true') { - $element - .dialog('option', {resizable: false, draggable: false}) - .dialog('widget').css('position', 'fixed'); - $(window) - .on('resize.dialogResize scroll.dialogResize', eventData, autoResize) - .trigger('resize.dialogResize'); + $element.dialog('option', { resizable: false, draggable: false }).dialog('widget').css('position', 'fixed'); + $(window).on('resize.dialogResize scroll.dialogResize', eventData, autoResize).trigger('resize.dialogResize'); $(document).on('drupalViewportOffsetChange', eventData, autoResize); } }, - 'dialog:beforeclose': function (event, dialog, $element) { + 'dialog:beforeclose': function dialogBeforeclose(event, dialog, $element) { $(window).off('.dialogResize'); } }); - })(jQuery, Drupal, drupalSettings, Drupal.debounce, Drupal.displace); + +//# sourceMappingURL=dialog.position.js.map \ No newline at end of file diff --git a/core/misc/dialog/dialog.position.js.map b/core/misc/dialog/dialog.position.js.map new file mode 100644 index 0000000..c2826c1 --- /dev/null +++ b/core/misc/dialog/dialog.position.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["dialog.position.es6.js"],"names":["$","Drupal","drupalSettings","debounce","displace","dialog","extend","autoResize","maxHeight","resetSize","event","positionOptions","adjustedOptions","windowHeight","window","height","option","optionValue","adjustedValue","n","length","data","settings","test","offsets","top","bottom","parseInt","$element","parent","outerHeight","modal","resetPosition","trigger","options","left","right","leftString","Math","abs","round","topString","position","my","of","on","eventData","resizable","draggable","css","document","off","jQuery"],"mappings":"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","file":"dialog.position.es6.js","sourcesContent":["/**\n * @file\n * Positioning extensions for dialogs.\n */\n\n/**\n * Triggers when content inside a dialog changes.\n *\n * @event dialogContentResize\n */\n\n(function ($, Drupal, drupalSettings, debounce, displace) {\n\n 'use strict';\n\n // autoResize option will turn off resizable and draggable.\n drupalSettings.dialog = $.extend({autoResize: true, maxHeight: '95%'}, drupalSettings.dialog);\n\n /**\n * Resets the current options for positioning.\n *\n * This is used as a window resize and scroll callback to reposition the\n * jQuery UI dialog. Although not a built-in jQuery UI option, this can\n * be disabled by setting autoResize: false in the options array when creating\n * a new {@link Drupal.dialog}.\n *\n * @function Drupal.dialog~resetSize\n *\n * @param {jQuery.Event} event\n * The event triggered.\n *\n * @fires event:dialogContentResize\n */\n function resetSize(event) {\n var positionOptions = ['width', 'height', 'minWidth', 'minHeight', 'maxHeight', 'maxWidth', 'position'];\n var adjustedOptions = {};\n var windowHeight = $(window).height();\n var option;\n var optionValue;\n var adjustedValue;\n for (var n = 0; n < positionOptions.length; n++) {\n option = positionOptions[n];\n optionValue = event.data.settings[option];\n if (optionValue) {\n // jQuery UI does not support percentages on heights, convert to pixels.\n if (typeof optionValue === 'string' && /%$/.test(optionValue) && /height/i.test(option)) {\n // Take offsets in account.\n windowHeight -= displace.offsets.top + displace.offsets.bottom;\n adjustedValue = parseInt(0.01 * parseInt(optionValue, 10) * windowHeight, 10);\n // Don't force the dialog to be bigger vertically than needed.\n if (option === 'height' && event.data.$element.parent().outerHeight() < adjustedValue) {\n adjustedValue = 'auto';\n }\n adjustedOptions[option] = adjustedValue;\n }\n }\n }\n // Offset the dialog center to be at the center of Drupal.displace.offsets.\n if (!event.data.settings.modal) {\n adjustedOptions = resetPosition(adjustedOptions);\n }\n event.data.$element\n .dialog('option', adjustedOptions)\n .trigger('dialogContentResize');\n }\n\n /**\n * Position the dialog's center at the center of displace.offsets boundaries.\n *\n * @function Drupal.dialog~resetPosition\n *\n * @param {object} options\n * Options object.\n *\n * @return {object}\n * Altered options object.\n */\n function resetPosition(options) {\n var offsets = displace.offsets;\n var left = offsets.left - offsets.right;\n var top = offsets.top - offsets.bottom;\n\n var leftString = (left > 0 ? '+' : '-') + Math.abs(Math.round(left / 2)) + 'px';\n var topString = (top > 0 ? '+' : '-') + Math.abs(Math.round(top / 2)) + 'px';\n options.position = {\n my: 'center' + (left !== 0 ? leftString : '') + ' center' + (top !== 0 ? topString : ''),\n of: window\n };\n return options;\n }\n\n $(window).on({\n 'dialog:aftercreate': function (event, dialog, $element, settings) {\n var autoResize = debounce(resetSize, 20);\n var eventData = {settings: settings, $element: $element};\n if (settings.autoResize === true || settings.autoResize === 'true') {\n $element\n .dialog('option', {resizable: false, draggable: false})\n .dialog('widget').css('position', 'fixed');\n $(window)\n .on('resize.dialogResize scroll.dialogResize', eventData, autoResize)\n .trigger('resize.dialogResize');\n $(document).on('drupalViewportOffsetChange', eventData, autoResize);\n }\n },\n 'dialog:beforeclose': function (event, dialog, $element) {\n $(window).off('.dialogResize');\n }\n });\n\n})(jQuery, Drupal, drupalSettings, Drupal.debounce, Drupal.displace);\n"]} \ No newline at end of file diff --git a/core/misc/displace.es6.js b/core/misc/displace.es6.js new file mode 100644 index 0000000..3e89c56 --- /dev/null +++ b/core/misc/displace.es6.js @@ -0,0 +1,222 @@ +/** + * @file + * Manages elements that can offset the size of the viewport. + * + * Measures and reports viewport offset dimensions from elements like the + * toolbar that can potentially displace the positioning of other elements. + */ + +/** + * @typedef {object} Drupal~displaceOffset + * + * @prop {number} top + * @prop {number} left + * @prop {number} right + * @prop {number} bottom + */ + +/** + * Triggers when layout of the page changes. + * + * This is used to position fixed element on the page during page resize and + * Toolbar toggling. + * + * @event drupalViewportOffsetChange + */ + +(function ($, Drupal, debounce) { + + 'use strict'; + + /** + * @name Drupal.displace.offsets + * + * @type {Drupal~displaceOffset} + */ + var offsets = { + top: 0, + right: 0, + bottom: 0, + left: 0 + }; + + /** + * Registers a resize handler on the window. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.drupalDisplace = { + attach: function () { + // Mark this behavior as processed on the first pass. + if (this.displaceProcessed) { + return; + } + this.displaceProcessed = true; + + $(window).on('resize.drupalDisplace', debounce(displace, 200)); + } + }; + + /** + * Informs listeners of the current offset dimensions. + * + * @function Drupal.displace + * + * @prop {Drupal~displaceOffset} offsets + * + * @param {bool} [broadcast] + * When true or undefined, causes the recalculated offsets values to be + * broadcast to listeners. + * + * @return {Drupal~displaceOffset} + * An object whose keys are the for sides an element -- top, right, bottom + * and left. The value of each key is the viewport displacement distance for + * that edge. + * + * @fires event:drupalViewportOffsetChange + */ + function displace(broadcast) { + offsets = Drupal.displace.offsets = calculateOffsets(); + if (typeof broadcast === 'undefined' || broadcast) { + $(document).trigger('drupalViewportOffsetChange', offsets); + } + return offsets; + } + + /** + * Determines the viewport offsets. + * + * @return {Drupal~displaceOffset} + * An object whose keys are the for sides an element -- top, right, bottom + * and left. The value of each key is the viewport displacement distance for + * that edge. + */ + function calculateOffsets() { + return { + top: calculateOffset('top'), + right: calculateOffset('right'), + bottom: calculateOffset('bottom'), + left: calculateOffset('left') + }; + } + + /** + * Gets a specific edge's offset. + * + * Any element with the attribute data-offset-{edge} e.g. data-offset-top will + * be considered in the viewport offset calculations. If the attribute has a + * numeric value, that value will be used. If no value is provided, one will + * be calculated using the element's dimensions and placement. + * + * @function Drupal.displace.calculateOffset + * + * @param {string} edge + * The name of the edge to calculate. Can be 'top', 'right', + * 'bottom' or 'left'. + * + * @return {number} + * The viewport displacement distance for the requested edge. + */ + function calculateOffset(edge) { + var edgeOffset = 0; + var displacingElements = document.querySelectorAll('[data-offset-' + edge + ']'); + var n = displacingElements.length; + for (var i = 0; i < n; i++) { + var el = displacingElements[i]; + // If the element is not visible, do consider its dimensions. + if (el.style.display === 'none') { + continue; + } + // If the offset data attribute contains a displacing value, use it. + var displacement = parseInt(el.getAttribute('data-offset-' + edge), 10); + // If the element's offset data attribute exits + // but is not a valid number then get the displacement + // dimensions directly from the element. + if (isNaN(displacement)) { + displacement = getRawOffset(el, edge); + } + // If the displacement value is larger than the current value for this + // edge, use the displacement value. + edgeOffset = Math.max(edgeOffset, displacement); + } + + return edgeOffset; + } + + /** + * Calculates displacement for element based on its dimensions and placement. + * + * @param {HTMLElement} el + * The jQuery element whose dimensions and placement will be measured. + * + * @param {string} edge + * The name of the edge of the viewport that the element is associated + * with. + * + * @return {number} + * The viewport displacement distance for the requested edge. + */ + function getRawOffset(el, edge) { + var $el = $(el); + var documentElement = document.documentElement; + var displacement = 0; + var horizontal = (edge === 'left' || edge === 'right'); + // Get the offset of the element itself. + var placement = $el.offset()[horizontal ? 'left' : 'top']; + // Subtract scroll distance from placement to get the distance + // to the edge of the viewport. + placement -= window['scroll' + (horizontal ? 'X' : 'Y')] || document.documentElement['scroll' + (horizontal ? 'Left' : 'Top')] || 0; + // Find the displacement value according to the edge. + switch (edge) { + // Left and top elements displace as a sum of their own offset value + // plus their size. + case 'top': + // Total displacement is the sum of the elements placement and size. + displacement = placement + $el.outerHeight(); + break; + + case 'left': + // Total displacement is the sum of the elements placement and size. + displacement = placement + $el.outerWidth(); + break; + + // Right and bottom elements displace according to their left and + // top offset. Their size isn't important. + case 'bottom': + displacement = documentElement.clientHeight - placement; + break; + + case 'right': + displacement = documentElement.clientWidth - placement; + break; + + default: + displacement = 0; + } + return displacement; + } + + /** + * Assign the displace function to a property of the Drupal global object. + * + * @ignore + */ + Drupal.displace = displace; + $.extend(Drupal.displace, { + + /** + * Expose offsets to other scripts to avoid having to recalculate offsets. + * + * @ignore + */ + offsets: offsets, + + /** + * Expose method to compute a single edge offsets. + * + * @ignore + */ + calculateOffset: calculateOffset + }); + +})(jQuery, Drupal, Drupal.debounce); diff --git a/core/misc/displace.js b/core/misc/displace.js index 3e89c56..6fe4927 100644 --- a/core/misc/displace.js +++ b/core/misc/displace.js @@ -1,38 +1,9 @@ -/** - * @file - * Manages elements that can offset the size of the viewport. - * - * Measures and reports viewport offset dimensions from elements like the - * toolbar that can potentially displace the positioning of other elements. - */ - -/** - * @typedef {object} Drupal~displaceOffset - * - * @prop {number} top - * @prop {number} left - * @prop {number} right - * @prop {number} bottom - */ - -/** - * Triggers when layout of the page changes. - * - * This is used to position fixed element on the page during page resize and - * Toolbar toggling. - * - * @event drupalViewportOffsetChange - */ +'use strict'; (function ($, Drupal, debounce) { 'use strict'; - /** - * @name Drupal.displace.offsets - * - * @type {Drupal~displaceOffset} - */ var offsets = { top: 0, right: 0, @@ -40,14 +11,8 @@ left: 0 }; - /** - * Registers a resize handler on the window. - * - * @type {Drupal~behavior} - */ Drupal.behaviors.drupalDisplace = { - attach: function () { - // Mark this behavior as processed on the first pass. + attach: function attach() { if (this.displaceProcessed) { return; } @@ -57,24 +22,6 @@ } }; - /** - * Informs listeners of the current offset dimensions. - * - * @function Drupal.displace - * - * @prop {Drupal~displaceOffset} offsets - * - * @param {bool} [broadcast] - * When true or undefined, causes the recalculated offsets values to be - * broadcast to listeners. - * - * @return {Drupal~displaceOffset} - * An object whose keys are the for sides an element -- top, right, bottom - * and left. The value of each key is the viewport displacement distance for - * that edge. - * - * @fires event:drupalViewportOffsetChange - */ function displace(broadcast) { offsets = Drupal.displace.offsets = calculateOffsets(); if (typeof broadcast === 'undefined' || broadcast) { @@ -83,14 +30,6 @@ return offsets; } - /** - * Determines the viewport offsets. - * - * @return {Drupal~displaceOffset} - * An object whose keys are the for sides an element -- top, right, bottom - * and left. The value of each key is the viewport displacement distance for - * that edge. - */ function calculateOffsets() { return { top: calculateOffset('top'), @@ -100,88 +39,48 @@ }; } - /** - * Gets a specific edge's offset. - * - * Any element with the attribute data-offset-{edge} e.g. data-offset-top will - * be considered in the viewport offset calculations. If the attribute has a - * numeric value, that value will be used. If no value is provided, one will - * be calculated using the element's dimensions and placement. - * - * @function Drupal.displace.calculateOffset - * - * @param {string} edge - * The name of the edge to calculate. Can be 'top', 'right', - * 'bottom' or 'left'. - * - * @return {number} - * The viewport displacement distance for the requested edge. - */ function calculateOffset(edge) { var edgeOffset = 0; var displacingElements = document.querySelectorAll('[data-offset-' + edge + ']'); var n = displacingElements.length; for (var i = 0; i < n; i++) { var el = displacingElements[i]; - // If the element is not visible, do consider its dimensions. + if (el.style.display === 'none') { continue; } - // If the offset data attribute contains a displacing value, use it. + var displacement = parseInt(el.getAttribute('data-offset-' + edge), 10); - // If the element's offset data attribute exits - // but is not a valid number then get the displacement - // dimensions directly from the element. + if (isNaN(displacement)) { displacement = getRawOffset(el, edge); } - // If the displacement value is larger than the current value for this - // edge, use the displacement value. + edgeOffset = Math.max(edgeOffset, displacement); } return edgeOffset; } - /** - * Calculates displacement for element based on its dimensions and placement. - * - * @param {HTMLElement} el - * The jQuery element whose dimensions and placement will be measured. - * - * @param {string} edge - * The name of the edge of the viewport that the element is associated - * with. - * - * @return {number} - * The viewport displacement distance for the requested edge. - */ function getRawOffset(el, edge) { var $el = $(el); var documentElement = document.documentElement; var displacement = 0; - var horizontal = (edge === 'left' || edge === 'right'); - // Get the offset of the element itself. + var horizontal = edge === 'left' || edge === 'right'; + var placement = $el.offset()[horizontal ? 'left' : 'top']; - // Subtract scroll distance from placement to get the distance - // to the edge of the viewport. + placement -= window['scroll' + (horizontal ? 'X' : 'Y')] || document.documentElement['scroll' + (horizontal ? 'Left' : 'Top')] || 0; - // Find the displacement value according to the edge. + switch (edge) { - // Left and top elements displace as a sum of their own offset value - // plus their size. case 'top': - // Total displacement is the sum of the elements placement and size. displacement = placement + $el.outerHeight(); break; case 'left': - // Total displacement is the sum of the elements placement and size. displacement = placement + $el.outerWidth(); break; - // Right and bottom elements displace according to their left and - // top offset. Their size isn't important. case 'bottom': displacement = documentElement.clientHeight - placement; break; @@ -196,27 +95,12 @@ return displacement; } - /** - * Assign the displace function to a property of the Drupal global object. - * - * @ignore - */ Drupal.displace = displace; $.extend(Drupal.displace, { - - /** - * Expose offsets to other scripts to avoid having to recalculate offsets. - * - * @ignore - */ offsets: offsets, - /** - * Expose method to compute a single edge offsets. - * - * @ignore - */ calculateOffset: calculateOffset }); - })(jQuery, Drupal, Drupal.debounce); + +//# sourceMappingURL=displace.js.map \ No newline at end of file diff --git a/core/misc/displace.js.map b/core/misc/displace.js.map new file mode 100644 index 0000000..cb7712f --- /dev/null +++ b/core/misc/displace.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["displace.es6.js"],"names":["$","Drupal","debounce","offsets","top","right","bottom","left","behaviors","drupalDisplace","attach","displaceProcessed","window","on","displace","broadcast","calculateOffsets","document","trigger","calculateOffset","edge","edgeOffset","displacingElements","querySelectorAll","n","length","i","el","style","display","displacement","parseInt","getAttribute","isNaN","getRawOffset","Math","max","$el","documentElement","horizontal","placement","offset","outerHeight","outerWidth","clientHeight","clientWidth","extend","jQuery"],"mappings":"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","file":"displace.es6.js","sourcesContent":["/**\n * @file\n * Manages elements that can offset the size of the viewport.\n *\n * Measures and reports viewport offset dimensions from elements like the\n * toolbar that can potentially displace the positioning of other elements.\n */\n\n/**\n * @typedef {object} Drupal~displaceOffset\n *\n * @prop {number} top\n * @prop {number} left\n * @prop {number} right\n * @prop {number} bottom\n */\n\n/**\n * Triggers when layout of the page changes.\n *\n * This is used to position fixed element on the page during page resize and\n * Toolbar toggling.\n *\n * @event drupalViewportOffsetChange\n */\n\n(function ($, Drupal, debounce) {\n\n 'use strict';\n\n /**\n * @name Drupal.displace.offsets\n *\n * @type {Drupal~displaceOffset}\n */\n var offsets = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0\n };\n\n /**\n * Registers a resize handler on the window.\n *\n * @type {Drupal~behavior}\n */\n Drupal.behaviors.drupalDisplace = {\n attach: function () {\n // Mark this behavior as processed on the first pass.\n if (this.displaceProcessed) {\n return;\n }\n this.displaceProcessed = true;\n\n $(window).on('resize.drupalDisplace', debounce(displace, 200));\n }\n };\n\n /**\n * Informs listeners of the current offset dimensions.\n *\n * @function Drupal.displace\n *\n * @prop {Drupal~displaceOffset} offsets\n *\n * @param {bool} [broadcast]\n * When true or undefined, causes the recalculated offsets values to be\n * broadcast to listeners.\n *\n * @return {Drupal~displaceOffset}\n * An object whose keys are the for sides an element -- top, right, bottom\n * and left. The value of each key is the viewport displacement distance for\n * that edge.\n *\n * @fires event:drupalViewportOffsetChange\n */\n function displace(broadcast) {\n offsets = Drupal.displace.offsets = calculateOffsets();\n if (typeof broadcast === 'undefined' || broadcast) {\n $(document).trigger('drupalViewportOffsetChange', offsets);\n }\n return offsets;\n }\n\n /**\n * Determines the viewport offsets.\n *\n * @return {Drupal~displaceOffset}\n * An object whose keys are the for sides an element -- top, right, bottom\n * and left. The value of each key is the viewport displacement distance for\n * that edge.\n */\n function calculateOffsets() {\n return {\n top: calculateOffset('top'),\n right: calculateOffset('right'),\n bottom: calculateOffset('bottom'),\n left: calculateOffset('left')\n };\n }\n\n /**\n * Gets a specific edge's offset.\n *\n * Any element with the attribute data-offset-{edge} e.g. data-offset-top will\n * be considered in the viewport offset calculations. If the attribute has a\n * numeric value, that value will be used. If no value is provided, one will\n * be calculated using the element's dimensions and placement.\n *\n * @function Drupal.displace.calculateOffset\n *\n * @param {string} edge\n * The name of the edge to calculate. Can be 'top', 'right',\n * 'bottom' or 'left'.\n *\n * @return {number}\n * The viewport displacement distance for the requested edge.\n */\n function calculateOffset(edge) {\n var edgeOffset = 0;\n var displacingElements = document.querySelectorAll('[data-offset-' + edge + ']');\n var n = displacingElements.length;\n for (var i = 0; i < n; i++) {\n var el = displacingElements[i];\n // If the element is not visible, do consider its dimensions.\n if (el.style.display === 'none') {\n continue;\n }\n // If the offset data attribute contains a displacing value, use it.\n var displacement = parseInt(el.getAttribute('data-offset-' + edge), 10);\n // If the element's offset data attribute exits\n // but is not a valid number then get the displacement\n // dimensions directly from the element.\n if (isNaN(displacement)) {\n displacement = getRawOffset(el, edge);\n }\n // If the displacement value is larger than the current value for this\n // edge, use the displacement value.\n edgeOffset = Math.max(edgeOffset, displacement);\n }\n\n return edgeOffset;\n }\n\n /**\n * Calculates displacement for element based on its dimensions and placement.\n *\n * @param {HTMLElement} el\n * The jQuery element whose dimensions and placement will be measured.\n *\n * @param {string} edge\n * The name of the edge of the viewport that the element is associated\n * with.\n *\n * @return {number}\n * The viewport displacement distance for the requested edge.\n */\n function getRawOffset(el, edge) {\n var $el = $(el);\n var documentElement = document.documentElement;\n var displacement = 0;\n var horizontal = (edge === 'left' || edge === 'right');\n // Get the offset of the element itself.\n var placement = $el.offset()[horizontal ? 'left' : 'top'];\n // Subtract scroll distance from placement to get the distance\n // to the edge of the viewport.\n placement -= window['scroll' + (horizontal ? 'X' : 'Y')] || document.documentElement['scroll' + (horizontal ? 'Left' : 'Top')] || 0;\n // Find the displacement value according to the edge.\n switch (edge) {\n // Left and top elements displace as a sum of their own offset value\n // plus their size.\n case 'top':\n // Total displacement is the sum of the elements placement and size.\n displacement = placement + $el.outerHeight();\n break;\n\n case 'left':\n // Total displacement is the sum of the elements placement and size.\n displacement = placement + $el.outerWidth();\n break;\n\n // Right and bottom elements displace according to their left and\n // top offset. Their size isn't important.\n case 'bottom':\n displacement = documentElement.clientHeight - placement;\n break;\n\n case 'right':\n displacement = documentElement.clientWidth - placement;\n break;\n\n default:\n displacement = 0;\n }\n return displacement;\n }\n\n /**\n * Assign the displace function to a property of the Drupal global object.\n *\n * @ignore\n */\n Drupal.displace = displace;\n $.extend(Drupal.displace, {\n\n /**\n * Expose offsets to other scripts to avoid having to recalculate offsets.\n *\n * @ignore\n */\n offsets: offsets,\n\n /**\n * Expose method to compute a single edge offsets.\n *\n * @ignore\n */\n calculateOffset: calculateOffset\n });\n\n})(jQuery, Drupal, Drupal.debounce);\n"]} \ No newline at end of file diff --git a/core/misc/dropbutton/dropbutton.es6.js b/core/misc/dropbutton/dropbutton.es6.js new file mode 100644 index 0000000..04f9491a --- /dev/null +++ b/core/misc/dropbutton/dropbutton.es6.js @@ -0,0 +1,233 @@ +/** + * @file + * Dropbutton feature. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Process elements with the .dropbutton class on page load. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches dropButton behaviors. + */ + Drupal.behaviors.dropButton = { + attach: function (context, settings) { + var $dropbuttons = $(context).find('.dropbutton-wrapper').once('dropbutton'); + if ($dropbuttons.length) { + // Adds the delegated handler that will toggle dropdowns on click. + var $body = $('body').once('dropbutton-click'); + if ($body.length) { + $body.on('click', '.dropbutton-toggle', dropbuttonClickHandler); + } + // Initialize all buttons. + var il = $dropbuttons.length; + for (var i = 0; i < il; i++) { + DropButton.dropbuttons.push(new DropButton($dropbuttons[i], settings.dropbutton)); + } + } + } + }; + + /** + * Delegated callback for opening and closing dropbutton secondary actions. + * + * @function Drupal.DropButton~dropbuttonClickHandler + * + * @param {jQuery.Event} e + * The event triggered. + */ + function dropbuttonClickHandler(e) { + e.preventDefault(); + $(e.target).closest('.dropbutton-wrapper').toggleClass('open'); + } + + /** + * A DropButton presents an HTML list as a button with a primary action. + * + * All secondary actions beyond the first in the list are presented in a + * dropdown list accessible through a toggle arrow associated with the button. + * + * @constructor Drupal.DropButton + * + * @param {HTMLElement} dropbutton + * A DOM element. + * @param {object} settings + * A list of options including: + * @param {string} settings.title + * The text inside the toggle link element. This text is hidden + * from visual UAs. + */ + function DropButton(dropbutton, settings) { + // Merge defaults with settings. + var options = $.extend({title: Drupal.t('List additional actions')}, settings); + var $dropbutton = $(dropbutton); + + /** + * @type {jQuery} + */ + this.$dropbutton = $dropbutton; + + /** + * @type {jQuery} + */ + this.$list = $dropbutton.find('.dropbutton'); + + /** + * Find actions and mark them. + * + * @type {jQuery} + */ + this.$actions = this.$list.find('li').addClass('dropbutton-action'); + + // Add the special dropdown only if there are hidden actions. + if (this.$actions.length > 1) { + // Identify the first element of the collection. + var $primary = this.$actions.slice(0, 1); + // Identify the secondary actions. + var $secondary = this.$actions.slice(1); + $secondary.addClass('secondary-action'); + // Add toggle link. + $primary.after(Drupal.theme('dropbuttonToggle', options)); + // Bind mouse events. + this.$dropbutton + .addClass('dropbutton-multiple') + .on({ + + /** + * Adds a timeout to close the dropdown on mouseleave. + * + * @ignore + */ + 'mouseleave.dropbutton': $.proxy(this.hoverOut, this), + + /** + * Clears timeout when mouseout of the dropdown. + * + * @ignore + */ + 'mouseenter.dropbutton': $.proxy(this.hoverIn, this), + + /** + * Similar to mouseleave/mouseenter, but for keyboard navigation. + * + * @ignore + */ + 'focusout.dropbutton': $.proxy(this.focusOut, this), + + /** + * @ignore + */ + 'focusin.dropbutton': $.proxy(this.focusIn, this) + }); + } + else { + this.$dropbutton.addClass('dropbutton-single'); + } + } + + /** + * Extend the DropButton constructor. + */ + $.extend(DropButton, /** @lends Drupal.DropButton */{ + /** + * Store all processed DropButtons. + * + * @type {Array.} + */ + dropbuttons: [] + }); + + /** + * Extend the DropButton prototype. + */ + $.extend(DropButton.prototype, /** @lends Drupal.DropButton# */{ + + /** + * Toggle the dropbutton open and closed. + * + * @param {bool} [show] + * Force the dropbutton to open by passing true or to close by + * passing false. + */ + toggle: function (show) { + var isBool = typeof show === 'boolean'; + show = isBool ? show : !this.$dropbutton.hasClass('open'); + this.$dropbutton.toggleClass('open', show); + }, + + /** + * @method + */ + hoverIn: function () { + // Clear any previous timer we were using. + if (this.timerID) { + window.clearTimeout(this.timerID); + } + }, + + /** + * @method + */ + hoverOut: function () { + // Wait half a second before closing. + this.timerID = window.setTimeout($.proxy(this, 'close'), 500); + }, + + /** + * @method + */ + open: function () { + this.toggle(true); + }, + + /** + * @method + */ + close: function () { + this.toggle(false); + }, + + /** + * @param {jQuery.Event} e + * The event triggered. + */ + focusOut: function (e) { + this.hoverOut.call(this, e); + }, + + /** + * @param {jQuery.Event} e + * The event triggered. + */ + focusIn: function (e) { + this.hoverIn.call(this, e); + } + }); + + $.extend(Drupal.theme, /** @lends Drupal.theme */{ + + /** + * A toggle is an interactive element often bound to a click handler. + * + * @param {object} options + * Options object. + * @param {string} [options.title] + * The HTML anchor title attribute and text for the inner span element. + * + * @return {string} + * A string representing a DOM fragment. + */ + dropbuttonToggle: function (options) { + return '
  • '; + } + }); + + // Expose constructor in the public space. + Drupal.DropButton = DropButton; + +})(jQuery, Drupal); diff --git a/core/misc/dropbutton/dropbutton.js b/core/misc/dropbutton/dropbutton.js index 04f9491a..f251e1e 100644 --- a/core/misc/dropbutton/dropbutton.js +++ b/core/misc/dropbutton/dropbutton.js @@ -1,30 +1,18 @@ -/** - * @file - * Dropbutton feature. - */ +'use strict'; (function ($, Drupal) { 'use strict'; - /** - * Process elements with the .dropbutton class on page load. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches dropButton behaviors. - */ Drupal.behaviors.dropButton = { - attach: function (context, settings) { + attach: function attach(context, settings) { var $dropbuttons = $(context).find('.dropbutton-wrapper').once('dropbutton'); if ($dropbuttons.length) { - // Adds the delegated handler that will toggle dropdowns on click. var $body = $('body').once('dropbutton-click'); if ($body.length) { $body.on('click', '.dropbutton-toggle', dropbuttonClickHandler); } - // Initialize all buttons. + var il = $dropbuttons.length; for (var i = 0; i < il; i++) { DropButton.dropbuttons.push(new DropButton($dropbuttons[i], settings.dropbutton)); @@ -33,201 +21,88 @@ } }; - /** - * Delegated callback for opening and closing dropbutton secondary actions. - * - * @function Drupal.DropButton~dropbuttonClickHandler - * - * @param {jQuery.Event} e - * The event triggered. - */ function dropbuttonClickHandler(e) { e.preventDefault(); $(e.target).closest('.dropbutton-wrapper').toggleClass('open'); } - /** - * A DropButton presents an HTML list as a button with a primary action. - * - * All secondary actions beyond the first in the list are presented in a - * dropdown list accessible through a toggle arrow associated with the button. - * - * @constructor Drupal.DropButton - * - * @param {HTMLElement} dropbutton - * A DOM element. - * @param {object} settings - * A list of options including: - * @param {string} settings.title - * The text inside the toggle link element. This text is hidden - * from visual UAs. - */ function DropButton(dropbutton, settings) { - // Merge defaults with settings. - var options = $.extend({title: Drupal.t('List additional actions')}, settings); + var options = $.extend({ title: Drupal.t('List additional actions') }, settings); var $dropbutton = $(dropbutton); - /** - * @type {jQuery} - */ this.$dropbutton = $dropbutton; - /** - * @type {jQuery} - */ this.$list = $dropbutton.find('.dropbutton'); - /** - * Find actions and mark them. - * - * @type {jQuery} - */ this.$actions = this.$list.find('li').addClass('dropbutton-action'); - // Add the special dropdown only if there are hidden actions. if (this.$actions.length > 1) { - // Identify the first element of the collection. var $primary = this.$actions.slice(0, 1); - // Identify the secondary actions. + var $secondary = this.$actions.slice(1); $secondary.addClass('secondary-action'); - // Add toggle link. + $primary.after(Drupal.theme('dropbuttonToggle', options)); - // Bind mouse events. - this.$dropbutton - .addClass('dropbutton-multiple') - .on({ - - /** - * Adds a timeout to close the dropdown on mouseleave. - * - * @ignore - */ - 'mouseleave.dropbutton': $.proxy(this.hoverOut, this), - - /** - * Clears timeout when mouseout of the dropdown. - * - * @ignore - */ - 'mouseenter.dropbutton': $.proxy(this.hoverIn, this), - - /** - * Similar to mouseleave/mouseenter, but for keyboard navigation. - * - * @ignore - */ - 'focusout.dropbutton': $.proxy(this.focusOut, this), - - /** - * @ignore - */ - 'focusin.dropbutton': $.proxy(this.focusIn, this) - }); - } - else { + + this.$dropbutton.addClass('dropbutton-multiple').on({ + 'mouseleave.dropbutton': $.proxy(this.hoverOut, this), + + 'mouseenter.dropbutton': $.proxy(this.hoverIn, this), + + 'focusout.dropbutton': $.proxy(this.focusOut, this), + + 'focusin.dropbutton': $.proxy(this.focusIn, this) + }); + } else { this.$dropbutton.addClass('dropbutton-single'); } } - /** - * Extend the DropButton constructor. - */ - $.extend(DropButton, /** @lends Drupal.DropButton */{ - /** - * Store all processed DropButtons. - * - * @type {Array.} - */ + $.extend(DropButton, { dropbuttons: [] }); - /** - * Extend the DropButton prototype. - */ - $.extend(DropButton.prototype, /** @lends Drupal.DropButton# */{ - - /** - * Toggle the dropbutton open and closed. - * - * @param {bool} [show] - * Force the dropbutton to open by passing true or to close by - * passing false. - */ - toggle: function (show) { + $.extend(DropButton.prototype, { + toggle: function toggle(show) { var isBool = typeof show === 'boolean'; show = isBool ? show : !this.$dropbutton.hasClass('open'); this.$dropbutton.toggleClass('open', show); }, - /** - * @method - */ - hoverIn: function () { - // Clear any previous timer we were using. + hoverIn: function hoverIn() { if (this.timerID) { window.clearTimeout(this.timerID); } }, - /** - * @method - */ - hoverOut: function () { - // Wait half a second before closing. + hoverOut: function hoverOut() { this.timerID = window.setTimeout($.proxy(this, 'close'), 500); }, - /** - * @method - */ - open: function () { + open: function open() { this.toggle(true); }, - /** - * @method - */ - close: function () { + close: function close() { this.toggle(false); }, - /** - * @param {jQuery.Event} e - * The event triggered. - */ - focusOut: function (e) { + focusOut: function focusOut(e) { this.hoverOut.call(this, e); }, - /** - * @param {jQuery.Event} e - * The event triggered. - */ - focusIn: function (e) { + focusIn: function focusIn(e) { this.hoverIn.call(this, e); } }); - $.extend(Drupal.theme, /** @lends Drupal.theme */{ - - /** - * A toggle is an interactive element often bound to a click handler. - * - * @param {object} options - * Options object. - * @param {string} [options.title] - * The HTML anchor title attribute and text for the inner span element. - * - * @return {string} - * A string representing a DOM fragment. - */ - dropbuttonToggle: function (options) { + $.extend(Drupal.theme, { + dropbuttonToggle: function dropbuttonToggle(options) { return '
  • '; } }); - // Expose constructor in the public space. Drupal.DropButton = DropButton; - })(jQuery, Drupal); + +//# sourceMappingURL=dropbutton.js.map \ No newline at end of file diff --git a/core/misc/dropbutton/dropbutton.js.map b/core/misc/dropbutton/dropbutton.js.map new file mode 100644 index 0000000..e853efd --- /dev/null +++ b/core/misc/dropbutton/dropbutton.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["dropbutton.es6.js"],"names":["$","Drupal","behaviors","dropButton","attach","context","settings","$dropbuttons","find","once","length","$body","on","dropbuttonClickHandler","il","i","DropButton","dropbuttons","push","dropbutton","e","preventDefault","target","closest","toggleClass","options","extend","title","t","$dropbutton","$list","$actions","addClass","$primary","slice","$secondary","after","theme","proxy","hoverOut","hoverIn","focusOut","focusIn","prototype","toggle","show","isBool","hasClass","timerID","window","clearTimeout","setTimeout","open","close","call","dropbuttonToggle","jQuery"],"mappings":"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","file":"dropbutton.es6.js","sourcesContent":["/**\n * @file\n * Dropbutton feature.\n */\n\n(function ($, Drupal) {\n\n 'use strict';\n\n /**\n * Process elements with the .dropbutton class on page load.\n *\n * @type {Drupal~behavior}\n *\n * @prop {Drupal~behaviorAttach} attach\n * Attaches dropButton behaviors.\n */\n Drupal.behaviors.dropButton = {\n attach: function (context, settings) {\n var $dropbuttons = $(context).find('.dropbutton-wrapper').once('dropbutton');\n if ($dropbuttons.length) {\n // Adds the delegated handler that will toggle dropdowns on click.\n var $body = $('body').once('dropbutton-click');\n if ($body.length) {\n $body.on('click', '.dropbutton-toggle', dropbuttonClickHandler);\n }\n // Initialize all buttons.\n var il = $dropbuttons.length;\n for (var i = 0; i < il; i++) {\n DropButton.dropbuttons.push(new DropButton($dropbuttons[i], settings.dropbutton));\n }\n }\n }\n };\n\n /**\n * Delegated callback for opening and closing dropbutton secondary actions.\n *\n * @function Drupal.DropButton~dropbuttonClickHandler\n *\n * @param {jQuery.Event} e\n * The event triggered.\n */\n function dropbuttonClickHandler(e) {\n e.preventDefault();\n $(e.target).closest('.dropbutton-wrapper').toggleClass('open');\n }\n\n /**\n * A DropButton presents an HTML list as a button with a primary action.\n *\n * All secondary actions beyond the first in the list are presented in a\n * dropdown list accessible through a toggle arrow associated with the button.\n *\n * @constructor Drupal.DropButton\n *\n * @param {HTMLElement} dropbutton\n * A DOM element.\n * @param {object} settings\n * A list of options including:\n * @param {string} settings.title\n * The text inside the toggle link element. This text is hidden\n * from visual UAs.\n */\n function DropButton(dropbutton, settings) {\n // Merge defaults with settings.\n var options = $.extend({title: Drupal.t('List additional actions')}, settings);\n var $dropbutton = $(dropbutton);\n\n /**\n * @type {jQuery}\n */\n this.$dropbutton = $dropbutton;\n\n /**\n * @type {jQuery}\n */\n this.$list = $dropbutton.find('.dropbutton');\n\n /**\n * Find actions and mark them.\n *\n * @type {jQuery}\n */\n this.$actions = this.$list.find('li').addClass('dropbutton-action');\n\n // Add the special dropdown only if there are hidden actions.\n if (this.$actions.length > 1) {\n // Identify the first element of the collection.\n var $primary = this.$actions.slice(0, 1);\n // Identify the secondary actions.\n var $secondary = this.$actions.slice(1);\n $secondary.addClass('secondary-action');\n // Add toggle link.\n $primary.after(Drupal.theme('dropbuttonToggle', options));\n // Bind mouse events.\n this.$dropbutton\n .addClass('dropbutton-multiple')\n .on({\n\n /**\n * Adds a timeout to close the dropdown on mouseleave.\n *\n * @ignore\n */\n 'mouseleave.dropbutton': $.proxy(this.hoverOut, this),\n\n /**\n * Clears timeout when mouseout of the dropdown.\n *\n * @ignore\n */\n 'mouseenter.dropbutton': $.proxy(this.hoverIn, this),\n\n /**\n * Similar to mouseleave/mouseenter, but for keyboard navigation.\n *\n * @ignore\n */\n 'focusout.dropbutton': $.proxy(this.focusOut, this),\n\n /**\n * @ignore\n */\n 'focusin.dropbutton': $.proxy(this.focusIn, this)\n });\n }\n else {\n this.$dropbutton.addClass('dropbutton-single');\n }\n }\n\n /**\n * Extend the DropButton constructor.\n */\n $.extend(DropButton, /** @lends Drupal.DropButton */{\n /**\n * Store all processed DropButtons.\n *\n * @type {Array.}\n */\n dropbuttons: []\n });\n\n /**\n * Extend the DropButton prototype.\n */\n $.extend(DropButton.prototype, /** @lends Drupal.DropButton# */{\n\n /**\n * Toggle the dropbutton open and closed.\n *\n * @param {bool} [show]\n * Force the dropbutton to open by passing true or to close by\n * passing false.\n */\n toggle: function (show) {\n var isBool = typeof show === 'boolean';\n show = isBool ? show : !this.$dropbutton.hasClass('open');\n this.$dropbutton.toggleClass('open', show);\n },\n\n /**\n * @method\n */\n hoverIn: function () {\n // Clear any previous timer we were using.\n if (this.timerID) {\n window.clearTimeout(this.timerID);\n }\n },\n\n /**\n * @method\n */\n hoverOut: function () {\n // Wait half a second before closing.\n this.timerID = window.setTimeout($.proxy(this, 'close'), 500);\n },\n\n /**\n * @method\n */\n open: function () {\n this.toggle(true);\n },\n\n /**\n * @method\n */\n close: function () {\n this.toggle(false);\n },\n\n /**\n * @param {jQuery.Event} e\n * The event triggered.\n */\n focusOut: function (e) {\n this.hoverOut.call(this, e);\n },\n\n /**\n * @param {jQuery.Event} e\n * The event triggered.\n */\n focusIn: function (e) {\n this.hoverIn.call(this, e);\n }\n });\n\n $.extend(Drupal.theme, /** @lends Drupal.theme */{\n\n /**\n * A toggle is an interactive element often bound to a click handler.\n *\n * @param {object} options\n * Options object.\n * @param {string} [options.title]\n * The HTML anchor title attribute and text for the inner span element.\n *\n * @return {string}\n * A string representing a DOM fragment.\n */\n dropbuttonToggle: function (options) {\n return '
  • ';\n }\n });\n\n // Expose constructor in the public space.\n Drupal.DropButton = DropButton;\n\n})(jQuery, Drupal);\n"]} \ No newline at end of file diff --git a/core/misc/drupal.es6.js b/core/misc/drupal.es6.js new file mode 100644 index 0000000..750d908 --- /dev/null +++ b/core/misc/drupal.es6.js @@ -0,0 +1,594 @@ +/** + * @file + * Defines the Drupal JavaScript API. + */ + +/** + * A jQuery object, typically the return value from a `$(selector)` call. + * + * Holds an HTMLElement or a collection of HTMLElements. + * + * @typedef {object} jQuery + * + * @prop {number} length=0 + * Number of elements contained in the jQuery object. + */ + +/** + * Variable generated by Drupal that holds all translated strings from PHP. + * + * 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. + * + * This variable is used to pass data from the backend to the frontend. Data + * contained in `drupalSettings` is used during behavior initialization. + * + * @global + * + * @var {object} drupalTranslations + */ + +/** + * Global Drupal object. + * + * All Drupal JavaScript APIs are contained in this namespace. + * + * @global + * + * @namespace + */ +window.Drupal = {behaviors: {}, locale: {}}; + +// Class indicating that JavaScript is enabled; used for styling purpose. +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) { + + 'use strict'; + + /** + * Helper to rethrow errors asynchronously. + * + * This way Errors bubbles up outside of the original callstack, making it + * easier to debug errors in the browser. + * + * @param {Error|string} error + * The error to be thrown. + */ + Drupal.throwError = function (error) { + setTimeout(function () { throw error; }, 0); + }; + + /** + * Custom error thrown after attach/detach if one or more behaviors failed. + * Initializes the JavaScript behaviors for page loads and Ajax requests. + * + * @callback Drupal~behaviorAttach + * + * @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. + * + * @see Drupal.attachBehaviors + */ + + /** + * Reverts and cleans up JavaScript behavior initialization. + * + * @callback Drupal~behaviorDetach + * + * @param {HTMLDocument|HTMLElement} context + * An element to attach behaviors to. + * @param {object} settings + * An object containing settings for the current context. + * @param {string} trigger + * One of `'unload'`, `'move'`, or `'serialize'`. + * + * @see Drupal.detachBehaviors + */ + + /** + * @typedef {object} Drupal~behavior + * + * @prop {Drupal~behaviorAttach} attach + * Function run on page load and after an Ajax call. + * @prop {Drupal~behaviorDetach} detach + * Function run when content is serialized or removed from the page. + */ + + /** + * Holds all initialization methods. + * + * @namespace Drupal.behaviors + * + * @type {Object.} + */ + + /** + * Defines a behavior to be run during attach and detach phases. + * + * Attaches all registered behaviors to a page element. + * + * 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 + * also 'detach'. + * + * {@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 + * behaviors to the new content. + * + * 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.) + * + * @example + * Drupal.behaviors.behaviorName = { + * attach: function (context, settings) { + * // ... + * }, + * detach: function (context, settings, trigger) { + * // ... + * } + * }; + * + * @param {HTMLDocument|HTMLElement} [context=document] + * An element to attach behaviors to. + * @param {object} [settings=drupalSettings] + * An object containing settings for the current context. If none is given, + * the global {@link drupalSettings} object is used. + * + * @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) { + Drupal.throwError(e); + } + } + } + }; + + // Attach all behaviors. + domready(function () { Drupal.attachBehaviors(document, drupalSettings); }); + + /** + * Detaches registered behaviors from a page element. + * + * 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. + * + * 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 + * is detached only from previously processed elements. + * + * @param {HTMLDocument|HTMLElement} [context=document] + * An element to detach behaviors from. + * @param {object} [settings=drupalSettings] + * An object containing settings for the current context. If none given, + * the global {@link drupalSettings} object is used. + * @param {string} [trigger='unload'] + * A string containing what's causing the behaviors to be detached. The + * possible triggers are: + * - `'unload'`: The context element is being removed from the DOM. + * - `'move'`: The element is about to be moved within the DOM (for example, + * 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. + * - `'serialize'`: When an Ajax form is submitted, this is called with the + * 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) { + Drupal.throwError(e); + } + } + } + }; + + /** + * Encodes special characters in a plain-text string for display as HTML. + * + * @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, '>'); + return str; + }; + + /** + * Replaces placeholders with sanitized values in a string. + * + * @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: + * - `'!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')`). + * + * @return {string} + * The formatted 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); + }; + + /** + * Replaces substring. + * + * 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 + * Array of keys from `args`. Internal use only. + * + * @return {string} + * The replaced string. + */ + 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]); + }; + + /** + * Translates strings to the page language, or a given language. + * + * See the documentation of the server-side t() function for further details. + * + * @param {string} str + * A string containing the English text to translate. + * @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}. + * @param {object} [options] + * Additional options for translation. + * @param {string} [options.context=''] + * The context the source string belongs to. + * + * @return {string} + * The formatted string. + * 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} + * The full URL. + */ + Drupal.url = function (path) { + return drupalSettings.path.baseUrl + drupalSettings.path.pathPrefix + path; + }; + + /** + * 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. + * + * 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} + * The encoded path. + */ + Drupal.encodePath = function (item) { + return window.encodeURIComponent(item).replace(/%2F/g, '/'); + }; + + /** + * Generates the themed representation of a Drupal object. + * + * 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. + * + * @example + *
    + * Drupal.theme('placeholder', text); + * + * @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 '' + Drupal.checkPlain(str) + ''; + }; + +})(domready, Drupal, window.drupalSettings, window.drupalTranslations); diff --git a/core/misc/drupal.js b/core/misc/drupal.js index 750d908..5574cb6 100644 --- a/core/misc/drupal.js +++ b/core/misc/drupal.js @@ -1,300 +1,79 @@ -/** - * @file - * Defines the Drupal JavaScript API. - */ - -/** - * A jQuery object, typically the return value from a `$(selector)` call. - * - * Holds an HTMLElement or a collection of HTMLElements. - * - * @typedef {object} jQuery - * - * @prop {number} length=0 - * Number of elements contained in the jQuery object. - */ - -/** - * Variable generated by Drupal that holds all translated strings from PHP. - * - * 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. - * - * This variable is used to pass data from the backend to the frontend. Data - * contained in `drupalSettings` is used during behavior initialization. - * - * @global - * - * @var {object} drupalTranslations - */ - -/** - * Global Drupal object. - * - * All Drupal JavaScript APIs are contained in this namespace. - * - * @global - * - * @namespace - */ -window.Drupal = {behaviors: {}, locale: {}}; - -// Class indicating that JavaScript is enabled; used for styling purpose. +'use strict'; + +window.Drupal = { behaviors: {}, locale: {} }; + 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) { 'use strict'; - /** - * Helper to rethrow errors asynchronously. - * - * This way Errors bubbles up outside of the original callstack, making it - * easier to debug errors in the browser. - * - * @param {Error|string} error - * The error to be thrown. - */ Drupal.throwError = function (error) { - setTimeout(function () { throw error; }, 0); + setTimeout(function () { + throw error; + }, 0); }; - /** - * Custom error thrown after attach/detach if one or more behaviors failed. - * Initializes the JavaScript behaviors for page loads and Ajax requests. - * - * @callback Drupal~behaviorAttach - * - * @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. - * - * @see Drupal.attachBehaviors - */ - - /** - * Reverts and cleans up JavaScript behavior initialization. - * - * @callback Drupal~behaviorDetach - * - * @param {HTMLDocument|HTMLElement} context - * An element to attach behaviors to. - * @param {object} settings - * An object containing settings for the current context. - * @param {string} trigger - * One of `'unload'`, `'move'`, or `'serialize'`. - * - * @see Drupal.detachBehaviors - */ - - /** - * @typedef {object} Drupal~behavior - * - * @prop {Drupal~behaviorAttach} attach - * Function run on page load and after an Ajax call. - * @prop {Drupal~behaviorDetach} detach - * Function run when content is serialized or removed from the page. - */ - - /** - * Holds all initialization methods. - * - * @namespace Drupal.behaviors - * - * @type {Object.} - */ - - /** - * Defines a behavior to be run during attach and detach phases. - * - * Attaches all registered behaviors to a page element. - * - * 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 - * also 'detach'. - * - * {@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 - * behaviors to the new content. - * - * 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.) - * - * @example - * Drupal.behaviors.behaviorName = { - * attach: function (context, settings) { - * // ... - * }, - * detach: function (context, settings, trigger) { - * // ... - * } - * }; - * - * @param {HTMLDocument|HTMLElement} [context=document] - * An element to attach behaviors to. - * @param {object} [settings=drupalSettings] - * An object containing settings for the current context. If none is given, - * the global {@link drupalSettings} object is used. - * - * @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) { + } catch (e) { Drupal.throwError(e); } } } }; - // Attach all behaviors. - domready(function () { Drupal.attachBehaviors(document, drupalSettings); }); - - /** - * Detaches registered behaviors from a page element. - * - * 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. - * - * 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 - * is detached only from previously processed elements. - * - * @param {HTMLDocument|HTMLElement} [context=document] - * An element to detach behaviors from. - * @param {object} [settings=drupalSettings] - * An object containing settings for the current context. If none given, - * the global {@link drupalSettings} object is used. - * @param {string} [trigger='unload'] - * A string containing what's causing the behaviors to be detached. The - * possible triggers are: - * - `'unload'`: The context element is being removed from the DOM. - * - `'move'`: The element is about to be moved within the DOM (for example, - * 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. - * - `'serialize'`: When an Ajax form is submitted, this is called with the - * 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 - */ + domready(function () { + Drupal.attachBehaviors(document, drupalSettings); + }); + 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) { + } catch (e) { Drupal.throwError(e); } } } }; - /** - * Encodes special characters in a plain-text string for display as HTML. - * - * @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, '>'); + str = str.toString().replace(/&/g, '&').replace(/"/g, '"').replace(//g, '>'); return str; }; - /** - * Replaces placeholders with sanitized values in a string. - * - * @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: - * - `'!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')`). - * - * @return {string} - * The formatted 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; @@ -305,28 +84,11 @@ if (window.jQuery) { return Drupal.stringReplace(str, processedArgs, null); }; - /** - * Replaces substring. - * - * 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 - * Array of keys from `args`. Internal use only. - * - * @return {string} - * The replaced string. - */ 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) { @@ -335,21 +97,20 @@ if (window.jQuery) { } } - // Order the keys by the character length. The shortest one is the first. - keys.sort(function (a, b) { return a.length - b.length; }); + 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)); } } @@ -357,31 +118,10 @@ if (window.jQuery) { return fragments.join(args[key]); }; - /** - * Translates strings to the page language, or a given language. - * - * See the documentation of the server-side t() function for further details. - * - * @param {string} str - * A string containing the English text to translate. - * @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}. - * @param {object} [options] - * Additional options for translation. - * @param {string} [options.context=''] - * The context the source string belongs to. - * - * @return {string} - * The formatted string. - * 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]; } @@ -392,127 +132,41 @@ if (window.jQuery) { return str; }; - /** - * Returns the URL to a Drupal page. - * - * @param {string} path - * Drupal path to transform to URL. - * - * @return {string} - * The full URL. - */ Drupal.url = function (path) { return drupalSettings.path.baseUrl + drupalSettings.path.pathPrefix + path; }; - /** - * 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. - } + } catch (e) {} 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. - } + } catch (e) {} try { baseUrl = decodeURIComponent(baseUrl); - } - catch (e) { - // Empty. - } + } catch (e) {} - // 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. - * - * 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; @@ -521,56 +175,19 @@ if (window.jQuery) { 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) { + } 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} - * The encoded path. - */ Drupal.encodePath = function (item) { return window.encodeURIComponent(item).replace(/%2F/g, '/'); }; - /** - * Generates the themed representation of a Drupal object. - * - * 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. - * - * @example - * - * Drupal.theme('placeholder', text); - * - * @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) { @@ -578,17 +195,9 @@ if (window.jQuery) { } }; - /** - * 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 '' + Drupal.checkPlain(str) + ''; }; - })(domready, Drupal, window.drupalSettings, window.drupalTranslations); + +//# sourceMappingURL=drupal.js.map \ No newline at end of file diff --git a/core/misc/drupal.js.map b/core/misc/drupal.js.map new file mode 100644 index 0000000..8c2a2fc --- /dev/null +++ b/core/misc/drupal.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["drupal.es6.js"],"names":["window","Drupal","behaviors","locale","document","documentElement","className","jQuery","noConflict","domready","drupalSettings","drupalTranslations","throwError","error","setTimeout","attachBehaviors","context","settings","i","hasOwnProperty","attach","e","detachBehaviors","trigger","detach","checkPlain","str","toString","replace","formatString","args","processedArgs","key","charAt","theme","stringReplace","keys","length","Array","isArray","k","push","sort","a","b","pop","fragments","split","slice","join","t","options","strings","url","path","baseUrl","pathPrefix","toAbsolute","urlParsingNode","createElement","decodeURIComponent","setAttribute","cloneNode","href","isLocal","absoluteUrl","protocol","location","indexOf","host","formatPlural","count","singular","plural","pluralDelimiter","translations","index","pluralFormula","encodePath","item","encodeURIComponent","func","prototype","apply","arguments","placeholder"],"mappings":"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","file":"drupal.es6.js","sourcesContent":["/**\n * @file\n * Defines the Drupal JavaScript API.\n */\n\n/**\n * A jQuery object, typically the return value from a `$(selector)` call.\n *\n * Holds an HTMLElement or a collection of HTMLElements.\n *\n * @typedef {object} jQuery\n *\n * @prop {number} length=0\n * Number of elements contained in the jQuery object.\n */\n\n/**\n * Variable generated by Drupal that holds all translated strings from PHP.\n *\n * Content of this variable is automatically created by Drupal when using the\n * Interface Translation module. It holds the translation of strings used on\n * the page.\n *\n * This variable is used to pass data from the backend to the frontend. Data\n * contained in `drupalSettings` is used during behavior initialization.\n *\n * @global\n *\n * @var {object} drupalTranslations\n */\n\n/**\n * Global Drupal object.\n *\n * All Drupal JavaScript APIs are contained in this namespace.\n *\n * @global\n *\n * @namespace\n */\nwindow.Drupal = {behaviors: {}, locale: {}};\n\n// Class indicating that JavaScript is enabled; used for styling purpose.\ndocument.documentElement.className += ' js';\n\n// Allow other JavaScript libraries to use $.\nif (window.jQuery) {\n jQuery.noConflict();\n}\n\n// JavaScript should be made compatible with libraries other than jQuery by\n// wrapping it in an anonymous closure.\n(function (domready, Drupal, drupalSettings, drupalTranslations) {\n\n 'use strict';\n\n /**\n * Helper to rethrow errors asynchronously.\n *\n * This way Errors bubbles up outside of the original callstack, making it\n * easier to debug errors in the browser.\n *\n * @param {Error|string} error\n * The error to be thrown.\n */\n Drupal.throwError = function (error) {\n setTimeout(function () { throw error; }, 0);\n };\n\n /**\n * Custom error thrown after attach/detach if one or more behaviors failed.\n * Initializes the JavaScript behaviors for page loads and Ajax requests.\n *\n * @callback Drupal~behaviorAttach\n *\n * @param {HTMLDocument|HTMLElement} context\n * An element to detach behaviors from.\n * @param {?object} settings\n * An object containing settings for the current context. It is rarely used.\n *\n * @see Drupal.attachBehaviors\n */\n\n /**\n * Reverts and cleans up JavaScript behavior initialization.\n *\n * @callback Drupal~behaviorDetach\n *\n * @param {HTMLDocument|HTMLElement} context\n * An element to attach behaviors to.\n * @param {object} settings\n * An object containing settings for the current context.\n * @param {string} trigger\n * One of `'unload'`, `'move'`, or `'serialize'`.\n *\n * @see Drupal.detachBehaviors\n */\n\n /**\n * @typedef {object} Drupal~behavior\n *\n * @prop {Drupal~behaviorAttach} attach\n * Function run on page load and after an Ajax call.\n * @prop {Drupal~behaviorDetach} detach\n * Function run when content is serialized or removed from the page.\n */\n\n /**\n * Holds all initialization methods.\n *\n * @namespace Drupal.behaviors\n *\n * @type {Object.}\n */\n\n /**\n * Defines a behavior to be run during attach and detach phases.\n *\n * Attaches all registered behaviors to a page element.\n *\n * Behaviors are event-triggered actions that attach to page elements,\n * enhancing default non-JavaScript UIs. Behaviors are registered in the\n * {@link Drupal.behaviors} object using the method 'attach' and optionally\n * also 'detach'.\n *\n * {@link Drupal.attachBehaviors} is added below to the `jQuery.ready` event\n * and therefore runs on initial page load. Developers implementing Ajax in\n * their solutions should also call this function after new page content has\n * been loaded, feeding in an element to be processed, in order to attach all\n * behaviors to the new content.\n *\n * Behaviors should use `var elements =\n * $(context).find(selector).once('behavior-name');` to ensure the behavior is\n * attached only once to a given element. (Doing so enables the reprocessing\n * of given elements, which may be needed on occasion despite the ability to\n * limit behavior attachment to a particular element.)\n *\n * @example\n * Drupal.behaviors.behaviorName = {\n * attach: function (context, settings) {\n * // ...\n * },\n * detach: function (context, settings, trigger) {\n * // ...\n * }\n * };\n *\n * @param {HTMLDocument|HTMLElement} [context=document]\n * An element to attach behaviors to.\n * @param {object} [settings=drupalSettings]\n * An object containing settings for the current context. If none is given,\n * the global {@link drupalSettings} object is used.\n *\n * @see Drupal~behaviorAttach\n * @see Drupal.detachBehaviors\n *\n * @throws {Drupal~DrupalBehaviorError}\n */\n Drupal.attachBehaviors = function (context, settings) {\n context = context || document;\n settings = settings || drupalSettings;\n var behaviors = Drupal.behaviors;\n // Execute all of them.\n for (var i in behaviors) {\n if (behaviors.hasOwnProperty(i) && typeof behaviors[i].attach === 'function') {\n // Don't stop the execution of behaviors in case of an error.\n try {\n behaviors[i].attach(context, settings);\n }\n catch (e) {\n Drupal.throwError(e);\n }\n }\n }\n };\n\n // Attach all behaviors.\n domready(function () { Drupal.attachBehaviors(document, drupalSettings); });\n\n /**\n * Detaches registered behaviors from a page element.\n *\n * Developers implementing Ajax in their solutions should call this function\n * before page content is about to be removed, feeding in an element to be\n * processed, in order to allow special behaviors to detach from the content.\n *\n * Such implementations should use `.findOnce()` and `.removeOnce()` to find\n * elements with their corresponding `Drupal.behaviors.behaviorName.attach`\n * implementation, i.e. `.removeOnce('behaviorName')`, to ensure the behavior\n * is detached only from previously processed elements.\n *\n * @param {HTMLDocument|HTMLElement} [context=document]\n * An element to detach behaviors from.\n * @param {object} [settings=drupalSettings]\n * An object containing settings for the current context. If none given,\n * the global {@link drupalSettings} object is used.\n * @param {string} [trigger='unload']\n * A string containing what's causing the behaviors to be detached. The\n * possible triggers are:\n * - `'unload'`: The context element is being removed from the DOM.\n * - `'move'`: The element is about to be moved within the DOM (for example,\n * during a tabledrag row swap). After the move is completed,\n * {@link Drupal.attachBehaviors} is called, so that the behavior can undo\n * whatever it did in response to the move. Many behaviors won't need to\n * do anything simply in response to the element being moved, but because\n * IFRAME elements reload their \"src\" when being moved within the DOM,\n * behaviors bound to IFRAME elements (like WYSIWYG editors) may need to\n * take some action.\n * - `'serialize'`: When an Ajax form is submitted, this is called with the\n * form as the context. This provides every behavior within the form an\n * opportunity to ensure that the field elements have correct content\n * in them before the form is serialized. The canonical use-case is so\n * that WYSIWYG editors can update the hidden textarea to which they are\n * bound.\n *\n * @throws {Drupal~DrupalBehaviorError}\n *\n * @see Drupal~behaviorDetach\n * @see Drupal.attachBehaviors\n */\n Drupal.detachBehaviors = function (context, settings, trigger) {\n context = context || document;\n settings = settings || drupalSettings;\n trigger = trigger || 'unload';\n var behaviors = Drupal.behaviors;\n // Execute all of them.\n for (var i in behaviors) {\n if (behaviors.hasOwnProperty(i) && typeof behaviors[i].detach === 'function') {\n // Don't stop the execution of behaviors in case of an error.\n try {\n behaviors[i].detach(context, settings, trigger);\n }\n catch (e) {\n Drupal.throwError(e);\n }\n }\n }\n };\n\n /**\n * Encodes special characters in a plain-text string for display as HTML.\n *\n * @param {string} str\n * The string to be encoded.\n *\n * @return {string}\n * The encoded string.\n *\n * @ingroup sanitization\n */\n Drupal.checkPlain = function (str) {\n str = str.toString()\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(//g, '>');\n return str;\n };\n\n /**\n * Replaces placeholders with sanitized values in a string.\n *\n * @param {string} str\n * A string with placeholders.\n * @param {object} args\n * An object of replacements pairs to make. Incidences of any key in this\n * array are replaced with the corresponding value. Based on the first\n * character of the key, the value is escaped and/or themed:\n * - `'!variable'`: inserted as is.\n * - `'@variable'`: escape plain text to HTML ({@link Drupal.checkPlain}).\n * - `'%variable'`: escape text and theme as a placeholder for user-\n * submitted content ({@link Drupal.checkPlain} +\n * `{@link Drupal.theme}('placeholder')`).\n *\n * @return {string}\n * The formatted string.\n *\n * @see Drupal.t\n */\n Drupal.formatString = function (str, args) {\n // Keep args intact.\n var processedArgs = {};\n // Transform arguments before inserting them.\n for (var key in args) {\n if (args.hasOwnProperty(key)) {\n switch (key.charAt(0)) {\n // Escaped only.\n case '@':\n processedArgs[key] = Drupal.checkPlain(args[key]);\n break;\n\n // Pass-through.\n case '!':\n processedArgs[key] = args[key];\n break;\n\n // Escaped and placeholder.\n default:\n processedArgs[key] = Drupal.theme('placeholder', args[key]);\n break;\n }\n }\n }\n\n return Drupal.stringReplace(str, processedArgs, null);\n };\n\n /**\n * Replaces substring.\n *\n * The longest keys will be tried first. Once a substring has been replaced,\n * its new value will not be searched again.\n *\n * @param {string} str\n * A string with placeholders.\n * @param {object} args\n * Key-value pairs.\n * @param {Array|null} keys\n * Array of keys from `args`. Internal use only.\n *\n * @return {string}\n * The replaced string.\n */\n Drupal.stringReplace = function (str, args, keys) {\n if (str.length === 0) {\n return str;\n }\n\n // If the array of keys is not passed then collect the keys from the args.\n if (!Array.isArray(keys)) {\n keys = [];\n for (var k in args) {\n if (args.hasOwnProperty(k)) {\n keys.push(k);\n }\n }\n\n // Order the keys by the character length. The shortest one is the first.\n keys.sort(function (a, b) { return a.length - b.length; });\n }\n\n if (keys.length === 0) {\n return str;\n }\n\n // Take next longest one from the end.\n var key = keys.pop();\n var fragments = str.split(key);\n\n if (keys.length) {\n for (var i = 0; i < fragments.length; i++) {\n // Process each fragment with a copy of remaining keys.\n fragments[i] = Drupal.stringReplace(fragments[i], args, keys.slice(0));\n }\n }\n\n return fragments.join(args[key]);\n };\n\n /**\n * Translates strings to the page language, or a given language.\n *\n * See the documentation of the server-side t() function for further details.\n *\n * @param {string} str\n * A string containing the English text to translate.\n * @param {Object.} [args]\n * An object of replacements pairs to make after translation. Incidences\n * of any key in this array are replaced with the corresponding value.\n * See {@link Drupal.formatString}.\n * @param {object} [options]\n * Additional options for translation.\n * @param {string} [options.context='']\n * The context the source string belongs to.\n *\n * @return {string}\n * The formatted string.\n * The translated string.\n */\n Drupal.t = function (str, args, options) {\n options = options || {};\n options.context = options.context || '';\n\n // Fetch the localized version of the string.\n if (typeof drupalTranslations !== 'undefined' && drupalTranslations.strings && drupalTranslations.strings[options.context] && drupalTranslations.strings[options.context][str]) {\n str = drupalTranslations.strings[options.context][str];\n }\n\n if (args) {\n str = Drupal.formatString(str, args);\n }\n return str;\n };\n\n /**\n * Returns the URL to a Drupal page.\n *\n * @param {string} path\n * Drupal path to transform to URL.\n *\n * @return {string}\n * The full URL.\n */\n Drupal.url = function (path) {\n return drupalSettings.path.baseUrl + drupalSettings.path.pathPrefix + path;\n };\n\n /**\n * Returns the passed in URL as an absolute URL.\n *\n * @param {string} url\n * The URL string to be normalized to an absolute URL.\n *\n * @return {string}\n * The normalized, absolute URL.\n *\n * @see https://github.com/angular/angular.js/blob/v1.4.4/src/ng/urlUtils.js\n * @see https://grack.com/blog/2009/11/17/absolutizing-url-in-javascript\n * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L53\n */\n Drupal.url.toAbsolute = function (url) {\n var urlParsingNode = document.createElement('a');\n\n // Decode the URL first; this is required by IE <= 6. Decoding non-UTF-8\n // strings may throw an exception.\n try {\n url = decodeURIComponent(url);\n }\n catch (e) {\n // Empty.\n }\n\n urlParsingNode.setAttribute('href', url);\n\n // IE <= 7 normalizes the URL when assigned to the anchor node similar to\n // the other browsers.\n return urlParsingNode.cloneNode(false).href;\n };\n\n /**\n * Returns true if the URL is within Drupal's base path.\n *\n * @param {string} url\n * The URL string to be tested.\n *\n * @return {bool}\n * `true` if local.\n *\n * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L58\n */\n Drupal.url.isLocal = function (url) {\n // Always use browser-derived absolute URLs in the comparison, to avoid\n // attempts to break out of the base path using directory traversal.\n var absoluteUrl = Drupal.url.toAbsolute(url);\n var protocol = location.protocol;\n\n // Consider URLs that match this site's base URL but use HTTPS instead of HTTP\n // as local as well.\n if (protocol === 'http:' && absoluteUrl.indexOf('https:') === 0) {\n protocol = 'https:';\n }\n var baseUrl = protocol + '//' + location.host + drupalSettings.path.baseUrl.slice(0, -1);\n\n // Decoding non-UTF-8 strings may throw an exception.\n try {\n absoluteUrl = decodeURIComponent(absoluteUrl);\n }\n catch (e) {\n // Empty.\n }\n try {\n baseUrl = decodeURIComponent(baseUrl);\n }\n catch (e) {\n // Empty.\n }\n\n // The given URL matches the site's base URL, or has a path under the site's\n // base URL.\n return absoluteUrl === baseUrl || absoluteUrl.indexOf(baseUrl + '/') === 0;\n };\n\n /**\n * Formats a string containing a count of items.\n *\n * This function ensures that the string is pluralized correctly. Since\n * {@link Drupal.t} is called by this function, make sure not to pass\n * already-localized strings to it.\n *\n * See the documentation of the server-side\n * \\Drupal\\Core\\StringTranslation\\TranslationInterface::formatPlural()\n * function for more details.\n *\n * @param {number} count\n * The item count to display.\n * @param {string} singular\n * The string for the singular case. Please make sure it is clear this is\n * singular, to ease translation (e.g. use \"1 new comment\" instead of \"1\n * new\"). Do not use @count in the singular string.\n * @param {string} plural\n * The string for the plural case. Please make sure it is clear this is\n * plural, to ease translation. Use @count in place of the item count, as in\n * \"@count new comments\".\n * @param {object} [args]\n * An object of replacements pairs to make after translation. Incidences\n * of any key in this array are replaced with the corresponding value.\n * See {@link Drupal.formatString}.\n * Note that you do not need to include @count in this array.\n * This replacement is done automatically for the plural case.\n * @param {object} [options]\n * The options to pass to the {@link Drupal.t} function.\n *\n * @return {string}\n * A translated string.\n */\n Drupal.formatPlural = function (count, singular, plural, args, options) {\n args = args || {};\n args['@count'] = count;\n\n var pluralDelimiter = drupalSettings.pluralDelimiter;\n var translations = Drupal.t(singular + pluralDelimiter + plural, args, options).split(pluralDelimiter);\n var index = 0;\n\n // Determine the index of the plural form.\n if (typeof drupalTranslations !== 'undefined' && drupalTranslations.pluralFormula) {\n index = count in drupalTranslations.pluralFormula ? drupalTranslations.pluralFormula[count] : drupalTranslations.pluralFormula['default'];\n }\n else if (args['@count'] !== 1) {\n index = 1;\n }\n\n return translations[index];\n };\n\n /**\n * Encodes a Drupal path for use in a URL.\n *\n * For aesthetic reasons slashes are not escaped.\n *\n * @param {string} item\n * Unencoded path.\n *\n * @return {string}\n * The encoded path.\n */\n Drupal.encodePath = function (item) {\n return window.encodeURIComponent(item).replace(/%2F/g, '/');\n };\n\n /**\n * Generates the themed representation of a Drupal object.\n *\n * All requests for themed output must go through this function. It examines\n * the request and routes it to the appropriate theme function. If the current\n * theme does not provide an override function, the generic theme function is\n * called.\n *\n * @example\n * \n * Drupal.theme('placeholder', text);\n *\n * @namespace\n *\n * @param {function} func\n * The name of the theme function to call.\n * @param {...args}\n * Additional arguments to pass along to the theme function.\n *\n * @return {string|object|HTMLElement|jQuery}\n * Any data the theme function returns. This could be a plain HTML string,\n * but also a complex object.\n */\n Drupal.theme = function (func) {\n var args = Array.prototype.slice.apply(arguments, [1]);\n if (func in Drupal.theme) {\n return Drupal.theme[func].apply(this, args);\n }\n };\n\n /**\n * Formats text for emphasized display in a placeholder inside a sentence.\n *\n * @param {string} str\n * The text to format (plain-text).\n *\n * @return {string}\n * The formatted text (html).\n */\n Drupal.theme.placeholder = function (str) {\n return '' + Drupal.checkPlain(str) + '';\n };\n\n})(domready, Drupal, window.drupalSettings, window.drupalTranslations);\n"]} \ No newline at end of file diff --git a/core/misc/drupalSettingsLoader.es6.js b/core/misc/drupalSettingsLoader.es6.js new file mode 100644 index 0000000..7ff292e --- /dev/null +++ b/core/misc/drupalSettingsLoader.es6.js @@ -0,0 +1,25 @@ +/** + * @file + * Parse inline JSON and initialize the drupalSettings global object. + */ + +(function () { + + 'use strict'; + + // Use direct child elements to harden against XSS exploits when CSP is on. + var settingsElement = document.querySelector('head > script[type="application/json"][data-drupal-selector="drupal-settings-json"], body > script[type="application/json"][data-drupal-selector="drupal-settings-json"]'); + + /** + * Variable generated by Drupal with all the configuration created from PHP. + * + * @global + * + * @type {object} + */ + window.drupalSettings = {}; + + if (settingsElement !== null) { + window.drupalSettings = JSON.parse(settingsElement.textContent); + } +})(); diff --git a/core/misc/drupalSettingsLoader.js b/core/misc/drupalSettingsLoader.js index 7ff292e..e80ef4c 100644 --- a/core/misc/drupalSettingsLoader.js +++ b/core/misc/drupalSettingsLoader.js @@ -1,25 +1,16 @@ -/** - * @file - * Parse inline JSON and initialize the drupalSettings global object. - */ +'use strict'; (function () { 'use strict'; - // Use direct child elements to harden against XSS exploits when CSP is on. var settingsElement = document.querySelector('head > script[type="application/json"][data-drupal-selector="drupal-settings-json"], body > script[type="application/json"][data-drupal-selector="drupal-settings-json"]'); - /** - * Variable generated by Drupal with all the configuration created from PHP. - * - * @global - * - * @type {object} - */ window.drupalSettings = {}; if (settingsElement !== null) { window.drupalSettings = JSON.parse(settingsElement.textContent); } })(); + +//# sourceMappingURL=drupalSettingsLoader.js.map \ No newline at end of file diff --git a/core/misc/drupalSettingsLoader.js.map b/core/misc/drupalSettingsLoader.js.map new file mode 100644 index 0000000..ed04594 --- /dev/null +++ b/core/misc/drupalSettingsLoader.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["drupalSettingsLoader.es6.js"],"names":["settingsElement","document","querySelector","window","drupalSettings","JSON","parse","textContent"],"mappings":";;AAKA,CAAC,YAAY;;AAEX;;AAGA,MAAIA,kBAAkBC,SAASC,aAAT,CAAuB,0KAAvB,CAAtB;;AASAC,SAAOC,cAAP,GAAwB,EAAxB;;AAEA,MAAIJ,oBAAoB,IAAxB,EAA8B;AAC5BG,WAAOC,cAAP,GAAwBC,KAAKC,KAAL,CAAWN,gBAAgBO,WAA3B,CAAxB;AACD;AACF,CAnBD","file":"drupalSettingsLoader.es6.js","sourcesContent":["/**\n * @file\n * Parse inline JSON and initialize the drupalSettings global object.\n */\n\n(function () {\n\n 'use strict';\n\n // Use direct child elements to harden against XSS exploits when CSP is on.\n var settingsElement = document.querySelector('head > script[type=\"application/json\"][data-drupal-selector=\"drupal-settings-json\"], body > script[type=\"application/json\"][data-drupal-selector=\"drupal-settings-json\"]');\n\n /**\n * Variable generated by Drupal with all the configuration created from PHP.\n *\n * @global\n *\n * @type {object}\n */\n window.drupalSettings = {};\n\n if (settingsElement !== null) {\n window.drupalSettings = JSON.parse(settingsElement.textContent);\n }\n})();\n"]} \ No newline at end of file diff --git a/core/misc/form.es6.js b/core/misc/form.es6.js new file mode 100644 index 0000000..7ca64fc --- /dev/null +++ b/core/misc/form.es6.js @@ -0,0 +1,250 @@ +/** + * @file + * Form features. + */ + +/** + * Triggers when a value in the form changed. + * + * The event triggers when content is typed or pasted in a text field, before + * the change event triggers. + * + * @event formUpdated + */ + +(function ($, Drupal, debounce) { + + 'use strict'; + + /** + * Retrieves the summary for the first element. + * + * @return {string} + * The text of the summary. + */ + $.fn.drupalGetSummary = function () { + var callback = this.data('summaryCallback'); + return (this[0] && callback) ? $.trim(callback(this[0])) : ''; + }; + + /** + * Sets the summary for all matched elements. + * + * @param {function} callback + * Either a function that will be called each time the summary is + * retrieved or a string (which is returned each time). + * + * @return {jQuery} + * jQuery collection of the current element. + * + * @fires event:summaryUpdated + * + * @listens event:formUpdated + */ + $.fn.drupalSetSummary = function (callback) { + var self = this; + + // To facilitate things, the callback should always be a function. If it's + // not, we wrap it into an anonymous function which just returns the value. + if (typeof callback !== 'function') { + var val = callback; + callback = function () { return val; }; + } + + return this + .data('summaryCallback', callback) + // To prevent duplicate events, the handlers are first removed and then + // (re-)added. + .off('formUpdated.summary') + .on('formUpdated.summary', function () { + self.trigger('summaryUpdated'); + }) + // The actual summaryUpdated handler doesn't fire when the callback is + // changed, so we have to do this manually. + .trigger('summaryUpdated'); + }; + + /** + * Prevents consecutive form submissions of identical form values. + * + * Repetitive form submissions that would submit the identical form values + * are prevented, unless the form values are different to the previously + * submitted values. + * + * This is a simplified re-implementation of a user-agent behavior that + * should be natively supported by major web browsers, but at this time, only + * Firefox has a built-in protection. + * + * A form value-based approach ensures that the constraint is triggered for + * consecutive, identical form submissions only. Compared to that, a form + * button-based approach would (1) rely on [visible] buttons to exist where + * technically not required and (2) require more complex state management if + * there are multiple buttons in a form. + * + * This implementation is based on form-level submit events only and relies + * on jQuery's serialize() method to determine submitted form values. As such, + * the following limitations exist: + * + * - Event handlers on form buttons that preventDefault() do not receive a + * double-submit protection. That is deemed to be fine, since such button + * events typically trigger reversible client-side or server-side + * operations that are local to the context of a form only. + * - Changed values in advanced form controls, such as file inputs, are not + * part of the form values being compared between consecutive form submits + * (due to limitations of jQuery.serialize()). That is deemed to be + * acceptable, because if the user forgot to attach a file, then the size of + * HTTP payload will most likely be small enough to be fully passed to the + * server endpoint within (milli)seconds. If a user mistakenly attached a + * wrong file and is technically versed enough to cancel the form submission + * (and HTTP payload) in order to attach a different file, then that + * edge-case is not supported here. + * + * Lastly, all forms submitted via HTTP GET are idempotent by definition of + * HTTP standards, so excluded in this implementation. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.formSingleSubmit = { + attach: function () { + function onFormSubmit(e) { + var $form = $(e.currentTarget); + var formValues = $form.serialize(); + var previousValues = $form.attr('data-drupal-form-submit-last'); + if (previousValues === formValues) { + e.preventDefault(); + } + else { + $form.attr('data-drupal-form-submit-last', formValues); + } + } + + $('body').once('form-single-submit') + .on('submit.singleSubmit', 'form:not([method~="GET"])', onFormSubmit); + } + }; + + /** + * Sends a 'formUpdated' event each time a form element is modified. + * + * @param {HTMLElement} element + * The element to trigger a form updated event on. + * + * @fires event:formUpdated + */ + function triggerFormUpdated(element) { + $(element).trigger('formUpdated'); + } + + /** + * Collects the IDs of all form fields in the given form. + * + * @param {HTMLFormElement} form + * The form element to search. + * + * @return {Array} + * Array of IDs for form fields. + */ + function fieldsList(form) { + var $fieldList = $(form).find('[name]').map(function (index, element) { + // We use id to avoid name duplicates on radio fields and filter out + // elements with a name but no id. + return element.getAttribute('id'); + }); + // Return a true array. + return $.makeArray($fieldList); + } + + /** + * Triggers the 'formUpdated' event on form elements when they are modified. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches formUpdated behaviors. + * @prop {Drupal~behaviorDetach} detach + * Detaches formUpdated behaviors. + * + * @fires event:formUpdated + */ + Drupal.behaviors.formUpdated = { + attach: function (context) { + var $context = $(context); + var contextIsForm = $context.is('form'); + var $forms = (contextIsForm ? $context : $context.find('form')).once('form-updated'); + var formFields; + + if ($forms.length) { + // Initialize form behaviors, use $.makeArray to be able to use native + // forEach array method and have the callback parameters in the right + // order. + $.makeArray($forms).forEach(function (form) { + var events = 'change.formUpdated input.formUpdated '; + var eventHandler = debounce(function (event) { triggerFormUpdated(event.target); }, 300); + formFields = fieldsList(form).join(','); + + form.setAttribute('data-drupal-form-fields', formFields); + $(form).on(events, eventHandler); + }); + } + // On ajax requests context is the form element. + if (contextIsForm) { + formFields = fieldsList(context).join(','); + // @todo replace with form.getAttribute() when #1979468 is in. + var currentFields = $(context).attr('data-drupal-form-fields'); + // If there has been a change in the fields or their order, trigger + // formUpdated. + if (formFields !== currentFields) { + triggerFormUpdated(context); + } + } + + }, + detach: function (context, settings, trigger) { + var $context = $(context); + var contextIsForm = $context.is('form'); + if (trigger === 'unload') { + var $forms = (contextIsForm ? $context : $context.find('form')).removeOnce('form-updated'); + if ($forms.length) { + $.makeArray($forms).forEach(function (form) { + form.removeAttribute('data-drupal-form-fields'); + $(form).off('.formUpdated'); + }); + } + } + } + }; + + /** + * Prepopulate form fields with information from the visitor browser. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for filling user info from browser. + */ + Drupal.behaviors.fillUserInfoFromBrowser = { + attach: function (context, settings) { + var userInfo = ['name', 'mail', 'homepage']; + var $forms = $('[data-user-info-from-browser]').once('user-info-from-browser'); + if ($forms.length) { + userInfo.map(function (info) { + var $element = $forms.find('[name=' + info + ']'); + var browserData = localStorage.getItem('Drupal.visitor.' + info); + var emptyOrDefault = ($element.val() === '' || ($element.attr('data-drupal-default-value') === $element.val())); + if ($element.length && emptyOrDefault && browserData) { + $element.val(browserData); + } + }); + } + $forms.on('submit', function () { + userInfo.map(function (info) { + var $element = $forms.find('[name=' + info + ']'); + if ($element.length) { + localStorage.setItem('Drupal.visitor.' + info, $element.val()); + } + }); + }); + } + }; + +})(jQuery, Drupal, Drupal.debounce); diff --git a/core/misc/form.js b/core/misc/form.js index 7ca64fc..8cfe43f 100644 --- a/core/misc/form.js +++ b/core/misc/form.js @@ -1,205 +1,89 @@ -/** - * @file - * Form features. - */ - -/** - * Triggers when a value in the form changed. - * - * The event triggers when content is typed or pasted in a text field, before - * the change event triggers. - * - * @event formUpdated - */ +'use strict'; (function ($, Drupal, debounce) { 'use strict'; - /** - * Retrieves the summary for the first element. - * - * @return {string} - * The text of the summary. - */ $.fn.drupalGetSummary = function () { var callback = this.data('summaryCallback'); - return (this[0] && callback) ? $.trim(callback(this[0])) : ''; + return this[0] && callback ? $.trim(callback(this[0])) : ''; }; - /** - * Sets the summary for all matched elements. - * - * @param {function} callback - * Either a function that will be called each time the summary is - * retrieved or a string (which is returned each time). - * - * @return {jQuery} - * jQuery collection of the current element. - * - * @fires event:summaryUpdated - * - * @listens event:formUpdated - */ $.fn.drupalSetSummary = function (callback) { var self = this; - // To facilitate things, the callback should always be a function. If it's - // not, we wrap it into an anonymous function which just returns the value. if (typeof callback !== 'function') { var val = callback; - callback = function () { return val; }; + callback = function callback() { + return val; + }; } - return this - .data('summaryCallback', callback) - // To prevent duplicate events, the handlers are first removed and then - // (re-)added. - .off('formUpdated.summary') - .on('formUpdated.summary', function () { - self.trigger('summaryUpdated'); - }) - // The actual summaryUpdated handler doesn't fire when the callback is - // changed, so we have to do this manually. - .trigger('summaryUpdated'); + return this.data('summaryCallback', callback).off('formUpdated.summary').on('formUpdated.summary', function () { + self.trigger('summaryUpdated'); + }).trigger('summaryUpdated'); }; - /** - * Prevents consecutive form submissions of identical form values. - * - * Repetitive form submissions that would submit the identical form values - * are prevented, unless the form values are different to the previously - * submitted values. - * - * This is a simplified re-implementation of a user-agent behavior that - * should be natively supported by major web browsers, but at this time, only - * Firefox has a built-in protection. - * - * A form value-based approach ensures that the constraint is triggered for - * consecutive, identical form submissions only. Compared to that, a form - * button-based approach would (1) rely on [visible] buttons to exist where - * technically not required and (2) require more complex state management if - * there are multiple buttons in a form. - * - * This implementation is based on form-level submit events only and relies - * on jQuery's serialize() method to determine submitted form values. As such, - * the following limitations exist: - * - * - Event handlers on form buttons that preventDefault() do not receive a - * double-submit protection. That is deemed to be fine, since such button - * events typically trigger reversible client-side or server-side - * operations that are local to the context of a form only. - * - Changed values in advanced form controls, such as file inputs, are not - * part of the form values being compared between consecutive form submits - * (due to limitations of jQuery.serialize()). That is deemed to be - * acceptable, because if the user forgot to attach a file, then the size of - * HTTP payload will most likely be small enough to be fully passed to the - * server endpoint within (milli)seconds. If a user mistakenly attached a - * wrong file and is technically versed enough to cancel the form submission - * (and HTTP payload) in order to attach a different file, then that - * edge-case is not supported here. - * - * Lastly, all forms submitted via HTTP GET are idempotent by definition of - * HTTP standards, so excluded in this implementation. - * - * @type {Drupal~behavior} - */ Drupal.behaviors.formSingleSubmit = { - attach: function () { + attach: function attach() { function onFormSubmit(e) { var $form = $(e.currentTarget); var formValues = $form.serialize(); var previousValues = $form.attr('data-drupal-form-submit-last'); if (previousValues === formValues) { e.preventDefault(); - } - else { + } else { $form.attr('data-drupal-form-submit-last', formValues); } } - $('body').once('form-single-submit') - .on('submit.singleSubmit', 'form:not([method~="GET"])', onFormSubmit); + $('body').once('form-single-submit').on('submit.singleSubmit', 'form:not([method~="GET"])', onFormSubmit); } }; - /** - * Sends a 'formUpdated' event each time a form element is modified. - * - * @param {HTMLElement} element - * The element to trigger a form updated event on. - * - * @fires event:formUpdated - */ function triggerFormUpdated(element) { $(element).trigger('formUpdated'); } - /** - * Collects the IDs of all form fields in the given form. - * - * @param {HTMLFormElement} form - * The form element to search. - * - * @return {Array} - * Array of IDs for form fields. - */ function fieldsList(form) { var $fieldList = $(form).find('[name]').map(function (index, element) { - // We use id to avoid name duplicates on radio fields and filter out - // elements with a name but no id. return element.getAttribute('id'); }); - // Return a true array. + return $.makeArray($fieldList); } - /** - * Triggers the 'formUpdated' event on form elements when they are modified. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches formUpdated behaviors. - * @prop {Drupal~behaviorDetach} detach - * Detaches formUpdated behaviors. - * - * @fires event:formUpdated - */ Drupal.behaviors.formUpdated = { - attach: function (context) { + attach: function attach(context) { var $context = $(context); var contextIsForm = $context.is('form'); var $forms = (contextIsForm ? $context : $context.find('form')).once('form-updated'); var formFields; if ($forms.length) { - // Initialize form behaviors, use $.makeArray to be able to use native - // forEach array method and have the callback parameters in the right - // order. $.makeArray($forms).forEach(function (form) { var events = 'change.formUpdated input.formUpdated '; - var eventHandler = debounce(function (event) { triggerFormUpdated(event.target); }, 300); + var eventHandler = debounce(function (event) { + triggerFormUpdated(event.target); + }, 300); formFields = fieldsList(form).join(','); form.setAttribute('data-drupal-form-fields', formFields); $(form).on(events, eventHandler); }); } - // On ajax requests context is the form element. + if (contextIsForm) { formFields = fieldsList(context).join(','); - // @todo replace with form.getAttribute() when #1979468 is in. + var currentFields = $(context).attr('data-drupal-form-fields'); - // If there has been a change in the fields or their order, trigger - // formUpdated. + if (formFields !== currentFields) { triggerFormUpdated(context); } } - }, - detach: function (context, settings, trigger) { + detach: function detach(context, settings, trigger) { var $context = $(context); var contextIsForm = $context.is('form'); if (trigger === 'unload') { @@ -214,23 +98,15 @@ } }; - /** - * Prepopulate form fields with information from the visitor browser. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches the behavior for filling user info from browser. - */ Drupal.behaviors.fillUserInfoFromBrowser = { - attach: function (context, settings) { + attach: function attach(context, settings) { var userInfo = ['name', 'mail', 'homepage']; var $forms = $('[data-user-info-from-browser]').once('user-info-from-browser'); if ($forms.length) { userInfo.map(function (info) { var $element = $forms.find('[name=' + info + ']'); var browserData = localStorage.getItem('Drupal.visitor.' + info); - var emptyOrDefault = ($element.val() === '' || ($element.attr('data-drupal-default-value') === $element.val())); + var emptyOrDefault = $element.val() === '' || $element.attr('data-drupal-default-value') === $element.val(); if ($element.length && emptyOrDefault && browserData) { $element.val(browserData); } @@ -246,5 +122,6 @@ }); } }; - })(jQuery, Drupal, Drupal.debounce); + +//# sourceMappingURL=form.js.map \ No newline at end of file diff --git a/core/misc/form.js.map b/core/misc/form.js.map new file mode 100644 index 0000000..5431902 --- /dev/null +++ b/core/misc/form.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["form.es6.js"],"names":["$","Drupal","debounce","fn","drupalGetSummary","callback","data","trim","drupalSetSummary","self","val","off","on","trigger","behaviors","formSingleSubmit","attach","onFormSubmit","e","$form","currentTarget","formValues","serialize","previousValues","attr","preventDefault","once","triggerFormUpdated","element","fieldsList","form","$fieldList","find","map","index","getAttribute","makeArray","formUpdated","context","$context","contextIsForm","is","$forms","formFields","length","forEach","events","eventHandler","event","target","join","setAttribute","currentFields","detach","settings","removeOnce","removeAttribute","fillUserInfoFromBrowser","userInfo","info","$element","browserData","localStorage","getItem","emptyOrDefault","setItem","jQuery"],"mappings":"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","file":"form.es6.js","sourcesContent":["/**\n * @file\n * Form features.\n */\n\n/**\n * Triggers when a value in the form changed.\n *\n * The event triggers when content is typed or pasted in a text field, before\n * the change event triggers.\n *\n * @event formUpdated\n */\n\n(function ($, Drupal, debounce) {\n\n 'use strict';\n\n /**\n * Retrieves the summary for the first element.\n *\n * @return {string}\n * The text of the summary.\n */\n $.fn.drupalGetSummary = function () {\n var callback = this.data('summaryCallback');\n return (this[0] && callback) ? $.trim(callback(this[0])) : '';\n };\n\n /**\n * Sets the summary for all matched elements.\n *\n * @param {function} callback\n * Either a function that will be called each time the summary is\n * retrieved or a string (which is returned each time).\n *\n * @return {jQuery}\n * jQuery collection of the current element.\n *\n * @fires event:summaryUpdated\n *\n * @listens event:formUpdated\n */\n $.fn.drupalSetSummary = function (callback) {\n var self = this;\n\n // To facilitate things, the callback should always be a function. If it's\n // not, we wrap it into an anonymous function which just returns the value.\n if (typeof callback !== 'function') {\n var val = callback;\n callback = function () { return val; };\n }\n\n return this\n .data('summaryCallback', callback)\n // To prevent duplicate events, the handlers are first removed and then\n // (re-)added.\n .off('formUpdated.summary')\n .on('formUpdated.summary', function () {\n self.trigger('summaryUpdated');\n })\n // The actual summaryUpdated handler doesn't fire when the callback is\n // changed, so we have to do this manually.\n .trigger('summaryUpdated');\n };\n\n /**\n * Prevents consecutive form submissions of identical form values.\n *\n * Repetitive form submissions that would submit the identical form values\n * are prevented, unless the form values are different to the previously\n * submitted values.\n *\n * This is a simplified re-implementation of a user-agent behavior that\n * should be natively supported by major web browsers, but at this time, only\n * Firefox has a built-in protection.\n *\n * A form value-based approach ensures that the constraint is triggered for\n * consecutive, identical form submissions only. Compared to that, a form\n * button-based approach would (1) rely on [visible] buttons to exist where\n * technically not required and (2) require more complex state management if\n * there are multiple buttons in a form.\n *\n * This implementation is based on form-level submit events only and relies\n * on jQuery's serialize() method to determine submitted form values. As such,\n * the following limitations exist:\n *\n * - Event handlers on form buttons that preventDefault() do not receive a\n * double-submit protection. That is deemed to be fine, since such button\n * events typically trigger reversible client-side or server-side\n * operations that are local to the context of a form only.\n * - Changed values in advanced form controls, such as file inputs, are not\n * part of the form values being compared between consecutive form submits\n * (due to limitations of jQuery.serialize()). That is deemed to be\n * acceptable, because if the user forgot to attach a file, then the size of\n * HTTP payload will most likely be small enough to be fully passed to the\n * server endpoint within (milli)seconds. If a user mistakenly attached a\n * wrong file and is technically versed enough to cancel the form submission\n * (and HTTP payload) in order to attach a different file, then that\n * edge-case is not supported here.\n *\n * Lastly, all forms submitted via HTTP GET are idempotent by definition of\n * HTTP standards, so excluded in this implementation.\n *\n * @type {Drupal~behavior}\n */\n Drupal.behaviors.formSingleSubmit = {\n attach: function () {\n function onFormSubmit(e) {\n var $form = $(e.currentTarget);\n var formValues = $form.serialize();\n var previousValues = $form.attr('data-drupal-form-submit-last');\n if (previousValues === formValues) {\n e.preventDefault();\n }\n else {\n $form.attr('data-drupal-form-submit-last', formValues);\n }\n }\n\n $('body').once('form-single-submit')\n .on('submit.singleSubmit', 'form:not([method~=\"GET\"])', onFormSubmit);\n }\n };\n\n /**\n * Sends a 'formUpdated' event each time a form element is modified.\n *\n * @param {HTMLElement} element\n * The element to trigger a form updated event on.\n *\n * @fires event:formUpdated\n */\n function triggerFormUpdated(element) {\n $(element).trigger('formUpdated');\n }\n\n /**\n * Collects the IDs of all form fields in the given form.\n *\n * @param {HTMLFormElement} form\n * The form element to search.\n *\n * @return {Array}\n * Array of IDs for form fields.\n */\n function fieldsList(form) {\n var $fieldList = $(form).find('[name]').map(function (index, element) {\n // We use id to avoid name duplicates on radio fields and filter out\n // elements with a name but no id.\n return element.getAttribute('id');\n });\n // Return a true array.\n return $.makeArray($fieldList);\n }\n\n /**\n * Triggers the 'formUpdated' event on form elements when they are modified.\n *\n * @type {Drupal~behavior}\n *\n * @prop {Drupal~behaviorAttach} attach\n * Attaches formUpdated behaviors.\n * @prop {Drupal~behaviorDetach} detach\n * Detaches formUpdated behaviors.\n *\n * @fires event:formUpdated\n */\n Drupal.behaviors.formUpdated = {\n attach: function (context) {\n var $context = $(context);\n var contextIsForm = $context.is('form');\n var $forms = (contextIsForm ? $context : $context.find('form')).once('form-updated');\n var formFields;\n\n if ($forms.length) {\n // Initialize form behaviors, use $.makeArray to be able to use native\n // forEach array method and have the callback parameters in the right\n // order.\n $.makeArray($forms).forEach(function (form) {\n var events = 'change.formUpdated input.formUpdated ';\n var eventHandler = debounce(function (event) { triggerFormUpdated(event.target); }, 300);\n formFields = fieldsList(form).join(',');\n\n form.setAttribute('data-drupal-form-fields', formFields);\n $(form).on(events, eventHandler);\n });\n }\n // On ajax requests context is the form element.\n if (contextIsForm) {\n formFields = fieldsList(context).join(',');\n // @todo replace with form.getAttribute() when #1979468 is in.\n var currentFields = $(context).attr('data-drupal-form-fields');\n // If there has been a change in the fields or their order, trigger\n // formUpdated.\n if (formFields !== currentFields) {\n triggerFormUpdated(context);\n }\n }\n\n },\n detach: function (context, settings, trigger) {\n var $context = $(context);\n var contextIsForm = $context.is('form');\n if (trigger === 'unload') {\n var $forms = (contextIsForm ? $context : $context.find('form')).removeOnce('form-updated');\n if ($forms.length) {\n $.makeArray($forms).forEach(function (form) {\n form.removeAttribute('data-drupal-form-fields');\n $(form).off('.formUpdated');\n });\n }\n }\n }\n };\n\n /**\n * Prepopulate form fields with information from the visitor browser.\n *\n * @type {Drupal~behavior}\n *\n * @prop {Drupal~behaviorAttach} attach\n * Attaches the behavior for filling user info from browser.\n */\n Drupal.behaviors.fillUserInfoFromBrowser = {\n attach: function (context, settings) {\n var userInfo = ['name', 'mail', 'homepage'];\n var $forms = $('[data-user-info-from-browser]').once('user-info-from-browser');\n if ($forms.length) {\n userInfo.map(function (info) {\n var $element = $forms.find('[name=' + info + ']');\n var browserData = localStorage.getItem('Drupal.visitor.' + info);\n var emptyOrDefault = ($element.val() === '' || ($element.attr('data-drupal-default-value') === $element.val()));\n if ($element.length && emptyOrDefault && browserData) {\n $element.val(browserData);\n }\n });\n }\n $forms.on('submit', function () {\n userInfo.map(function (info) {\n var $element = $forms.find('[name=' + info + ']');\n if ($element.length) {\n localStorage.setItem('Drupal.visitor.' + info, $element.val());\n }\n });\n });\n }\n };\n\n})(jQuery, Drupal, Drupal.debounce);\n"]} \ No newline at end of file diff --git a/core/misc/machine-name.es6.js b/core/misc/machine-name.es6.js new file mode 100644 index 0000000..bbad2e3 --- /dev/null +++ b/core/misc/machine-name.es6.js @@ -0,0 +1,208 @@ +/** + * @file + * Machine name functionality. + */ + +(function ($, Drupal, drupalSettings) { + + 'use strict'; + + /** + * Attach the machine-readable name form element behavior. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches machine-name behaviors. + */ + Drupal.behaviors.machineName = { + + /** + * Attaches the behavior. + * + * @param {Element} context + * The context for attaching the behavior. + * @param {object} settings + * Settings object. + * @param {object} settings.machineName + * A list of elements to process, keyed by the HTML ID of the form + * element containing the human-readable value. Each element is an object + * defining the following properties: + * - target: The HTML ID of the machine name form element. + * - suffix: The HTML ID of a container to show the machine name preview + * in (usually a field suffix after the human-readable name + * form element). + * - label: The label to show for the machine name preview. + * - replace_pattern: A regular expression (without modifiers) matching + * disallowed characters in the machine name; e.g., '[^a-z0-9]+'. + * - replace: A character to replace disallowed characters with; e.g., + * '_' or '-'. + * - standalone: Whether the preview should stay in its own element + * rather than the suffix of the source element. + * - field_prefix: The #field_prefix of the form element. + * - field_suffix: The #field_suffix of the form element. + */ + attach: function (context, settings) { + var self = this; + var $context = $(context); + var timeout = null; + var xhr = null; + + function clickEditHandler(e) { + var data = e.data; + data.$wrapper.removeClass('visually-hidden'); + data.$target.trigger('focus'); + data.$suffix.hide(); + data.$source.off('.machineName'); + } + + function machineNameHandler(e) { + var data = e.data; + var options = data.options; + var baseValue = $(e.target).val(); + + var rx = new RegExp(options.replace_pattern, 'g'); + var expected = baseValue.toLowerCase().replace(rx, options.replace).substr(0, options.maxlength); + + // Abort the last pending request because the label has changed and it + // is no longer valid. + if (xhr && xhr.readystate !== 4) { + xhr.abort(); + xhr = null; + } + + // Wait 300 milliseconds since the last event to update the machine name + // i.e., after the user has stopped typing. + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + timeout = setTimeout(function () { + if (baseValue.toLowerCase() !== expected) { + xhr = self.transliterate(baseValue, options).done(function (machine) { + self.showMachineName(machine.substr(0, options.maxlength), data); + }); + } + else { + self.showMachineName(expected, data); + } + }, 300); + } + + Object.keys(settings.machineName).forEach(function (source_id) { + var machine = ''; + var eventData; + var options = settings.machineName[source_id]; + + var $source = $context.find(source_id).addClass('machine-name-source').once('machine-name'); + var $target = $context.find(options.target).addClass('machine-name-target'); + var $suffix = $context.find(options.suffix); + var $wrapper = $target.closest('.js-form-item'); + // All elements have to exist. + if (!$source.length || !$target.length || !$suffix.length || !$wrapper.length) { + return; + } + // Skip processing upon a form validation error on the machine name. + if ($target.hasClass('error')) { + return; + } + // Figure out the maximum length for the machine name. + options.maxlength = $target.attr('maxlength'); + // Hide the form item container of the machine name form element. + $wrapper.addClass('visually-hidden'); + // Determine the initial machine name value. Unless the machine name + // form element is disabled or not empty, the initial default value is + // based on the human-readable form element value. + if ($target.is(':disabled') || $target.val() !== '') { + machine = $target.val(); + } + else if ($source.val() !== '') { + machine = self.transliterate($source.val(), options); + } + // Append the machine name preview to the source field. + var $preview = $('' + options.field_prefix + Drupal.checkPlain(machine) + options.field_suffix + ''); + $suffix.empty(); + if (options.label) { + $suffix.append('' + options.label + ': '); + } + $suffix.append($preview); + + // If the machine name cannot be edited, stop further processing. + if ($target.is(':disabled')) { + return; + } + + eventData = { + $source: $source, + $target: $target, + $suffix: $suffix, + $wrapper: $wrapper, + $preview: $preview, + options: options + }; + // If it is editable, append an edit link. + var $link = $('').on('click', eventData, clickEditHandler); + $suffix.append($link); + + // Preview the machine name in realtime when the human-readable name + // changes, but only if there is no machine name yet; i.e., only upon + // initial creation, not when editing. + if ($target.val() === '') { + $source.on('formUpdated.machineName', eventData, machineNameHandler) + // Initialize machine name preview. + .trigger('formUpdated.machineName'); + } + + // Add a listener for an invalid event on the machine name input + // to show its container and focus it. + $target.on('invalid', eventData, clickEditHandler); + }); + }, + + showMachineName: function (machine, data) { + var settings = data.options; + // Set the machine name to the transliterated value. + if (machine !== '') { + if (machine !== settings.replace) { + data.$target.val(machine); + data.$preview.html(settings.field_prefix + Drupal.checkPlain(machine) + settings.field_suffix); + } + data.$suffix.show(); + } + else { + data.$suffix.hide(); + data.$target.val(machine); + data.$preview.empty(); + } + }, + + /** + * Transliterate a human-readable name to a machine name. + * + * @param {string} source + * A string to transliterate. + * @param {object} settings + * The machine name settings for the corresponding field. + * @param {string} settings.replace_pattern + * A regular expression (without modifiers) matching disallowed characters + * in the machine name; e.g., '[^a-z0-9]+'. + * @param {string} settings.replace + * A character to replace disallowed characters with; e.g., '_' or '-'. + * @param {number} settings.maxlength + * The maximum length of the machine name. + * + * @return {jQuery} + * The transliterated source string. + */ + transliterate: function (source, settings) { + return $.get(Drupal.url('machine_name/transliterate'), { + text: source, + langcode: drupalSettings.langcode, + replace_pattern: settings.replace_pattern, + replace: settings.replace, + lowercase: true + }); + } + }; + +})(jQuery, Drupal, drupalSettings); diff --git a/core/misc/machine-name.js b/core/misc/machine-name.js index bbad2e3..5d89f23 100644 --- a/core/misc/machine-name.js +++ b/core/misc/machine-name.js @@ -1,48 +1,11 @@ -/** - * @file - * Machine name functionality. - */ +'use strict'; (function ($, Drupal, drupalSettings) { 'use strict'; - /** - * Attach the machine-readable name form element behavior. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches machine-name behaviors. - */ Drupal.behaviors.machineName = { - - /** - * Attaches the behavior. - * - * @param {Element} context - * The context for attaching the behavior. - * @param {object} settings - * Settings object. - * @param {object} settings.machineName - * A list of elements to process, keyed by the HTML ID of the form - * element containing the human-readable value. Each element is an object - * defining the following properties: - * - target: The HTML ID of the machine name form element. - * - suffix: The HTML ID of a container to show the machine name preview - * in (usually a field suffix after the human-readable name - * form element). - * - label: The label to show for the machine name preview. - * - replace_pattern: A regular expression (without modifiers) matching - * disallowed characters in the machine name; e.g., '[^a-z0-9]+'. - * - replace: A character to replace disallowed characters with; e.g., - * '_' or '-'. - * - standalone: Whether the preview should stay in its own element - * rather than the suffix of the source element. - * - field_prefix: The #field_prefix of the form element. - * - field_suffix: The #field_suffix of the form element. - */ - attach: function (context, settings) { + attach: function attach(context, settings) { var self = this; var $context = $(context); var timeout = null; @@ -64,15 +27,11 @@ var rx = new RegExp(options.replace_pattern, 'g'); var expected = baseValue.toLowerCase().replace(rx, options.replace).substr(0, options.maxlength); - // Abort the last pending request because the label has changed and it - // is no longer valid. if (xhr && xhr.readystate !== 4) { xhr.abort(); xhr = null; } - // Wait 300 milliseconds since the last event to update the machine name - // i.e., after the user has stopped typing. if (timeout) { clearTimeout(timeout); timeout = null; @@ -82,8 +41,7 @@ xhr = self.transliterate(baseValue, options).done(function (machine) { self.showMachineName(machine.substr(0, options.maxlength), data); }); - } - else { + } else { self.showMachineName(expected, data); } }, 300); @@ -98,28 +56,25 @@ var $target = $context.find(options.target).addClass('machine-name-target'); var $suffix = $context.find(options.suffix); var $wrapper = $target.closest('.js-form-item'); - // All elements have to exist. + if (!$source.length || !$target.length || !$suffix.length || !$wrapper.length) { return; } - // Skip processing upon a form validation error on the machine name. + if ($target.hasClass('error')) { return; } - // Figure out the maximum length for the machine name. + options.maxlength = $target.attr('maxlength'); - // Hide the form item container of the machine name form element. + $wrapper.addClass('visually-hidden'); - // Determine the initial machine name value. Unless the machine name - // form element is disabled or not empty, the initial default value is - // based on the human-readable form element value. + if ($target.is(':disabled') || $target.val() !== '') { machine = $target.val(); - } - else if ($source.val() !== '') { + } else if ($source.val() !== '') { machine = self.transliterate($source.val(), options); } - // Append the machine name preview to the source field. + var $preview = $('' + options.field_prefix + Drupal.checkPlain(machine) + options.field_suffix + ''); $suffix.empty(); if (options.label) { @@ -127,7 +82,6 @@ } $suffix.append($preview); - // If the machine name cannot be edited, stop further processing. if ($target.is(':disabled')) { return; } @@ -140,61 +94,35 @@ $preview: $preview, options: options }; - // If it is editable, append an edit link. + var $link = $('').on('click', eventData, clickEditHandler); $suffix.append($link); - // Preview the machine name in realtime when the human-readable name - // changes, but only if there is no machine name yet; i.e., only upon - // initial creation, not when editing. if ($target.val() === '') { - $source.on('formUpdated.machineName', eventData, machineNameHandler) - // Initialize machine name preview. - .trigger('formUpdated.machineName'); + $source.on('formUpdated.machineName', eventData, machineNameHandler).trigger('formUpdated.machineName'); } - // Add a listener for an invalid event on the machine name input - // to show its container and focus it. $target.on('invalid', eventData, clickEditHandler); }); }, - showMachineName: function (machine, data) { + showMachineName: function showMachineName(machine, data) { var settings = data.options; - // Set the machine name to the transliterated value. + if (machine !== '') { if (machine !== settings.replace) { data.$target.val(machine); data.$preview.html(settings.field_prefix + Drupal.checkPlain(machine) + settings.field_suffix); } data.$suffix.show(); - } - else { + } else { data.$suffix.hide(); data.$target.val(machine); data.$preview.empty(); } }, - /** - * Transliterate a human-readable name to a machine name. - * - * @param {string} source - * A string to transliterate. - * @param {object} settings - * The machine name settings for the corresponding field. - * @param {string} settings.replace_pattern - * A regular expression (without modifiers) matching disallowed characters - * in the machine name; e.g., '[^a-z0-9]+'. - * @param {string} settings.replace - * A character to replace disallowed characters with; e.g., '_' or '-'. - * @param {number} settings.maxlength - * The maximum length of the machine name. - * - * @return {jQuery} - * The transliterated source string. - */ - transliterate: function (source, settings) { + transliterate: function transliterate(source, settings) { return $.get(Drupal.url('machine_name/transliterate'), { text: source, langcode: drupalSettings.langcode, @@ -204,5 +132,6 @@ }); } }; - })(jQuery, Drupal, drupalSettings); + +//# sourceMappingURL=machine-name.js.map \ No newline at end of file diff --git a/core/misc/machine-name.js.map b/core/misc/machine-name.js.map new file mode 100644 index 0000000..129d8bc --- /dev/null +++ b/core/misc/machine-name.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["machine-name.es6.js"],"names":["$","Drupal","drupalSettings","behaviors","machineName","attach","context","settings","self","$context","timeout","xhr","clickEditHandler","e","data","$wrapper","removeClass","$target","trigger","$suffix","hide","$source","off","machineNameHandler","options","baseValue","target","val","rx","RegExp","replace_pattern","expected","toLowerCase","replace","substr","maxlength","readystate","abort","clearTimeout","setTimeout","transliterate","done","machine","showMachineName","Object","keys","forEach","source_id","eventData","find","addClass","once","suffix","closest","length","hasClass","attr","is","$preview","field_prefix","checkPlain","field_suffix","empty","label","append","$link","t","on","html","show","source","get","url","text","langcode","lowercase","jQuery"],"mappings":"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","file":"machine-name.es6.js","sourcesContent":["/**\n * @file\n * Machine name functionality.\n */\n\n(function ($, Drupal, drupalSettings) {\n\n 'use strict';\n\n /**\n * Attach the machine-readable name form element behavior.\n *\n * @type {Drupal~behavior}\n *\n * @prop {Drupal~behaviorAttach} attach\n * Attaches machine-name behaviors.\n */\n Drupal.behaviors.machineName = {\n\n /**\n * Attaches the behavior.\n *\n * @param {Element} context\n * The context for attaching the behavior.\n * @param {object} settings\n * Settings object.\n * @param {object} settings.machineName\n * A list of elements to process, keyed by the HTML ID of the form\n * element containing the human-readable value. Each element is an object\n * defining the following properties:\n * - target: The HTML ID of the machine name form element.\n * - suffix: The HTML ID of a container to show the machine name preview\n * in (usually a field suffix after the human-readable name\n * form element).\n * - label: The label to show for the machine name preview.\n * - replace_pattern: A regular expression (without modifiers) matching\n * disallowed characters in the machine name; e.g., '[^a-z0-9]+'.\n * - replace: A character to replace disallowed characters with; e.g.,\n * '_' or '-'.\n * - standalone: Whether the preview should stay in its own element\n * rather than the suffix of the source element.\n * - field_prefix: The #field_prefix of the form element.\n * - field_suffix: The #field_suffix of the form element.\n */\n attach: function (context, settings) {\n var self = this;\n var $context = $(context);\n var timeout = null;\n var xhr = null;\n\n function clickEditHandler(e) {\n var data = e.data;\n data.$wrapper.removeClass('visually-hidden');\n data.$target.trigger('focus');\n data.$suffix.hide();\n data.$source.off('.machineName');\n }\n\n function machineNameHandler(e) {\n var data = e.data;\n var options = data.options;\n var baseValue = $(e.target).val();\n\n var rx = new RegExp(options.replace_pattern, 'g');\n var expected = baseValue.toLowerCase().replace(rx, options.replace).substr(0, options.maxlength);\n\n // Abort the last pending request because the label has changed and it\n // is no longer valid.\n if (xhr && xhr.readystate !== 4) {\n xhr.abort();\n xhr = null;\n }\n\n // Wait 300 milliseconds since the last event to update the machine name\n // i.e., after the user has stopped typing.\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n timeout = setTimeout(function () {\n if (baseValue.toLowerCase() !== expected) {\n xhr = self.transliterate(baseValue, options).done(function (machine) {\n self.showMachineName(machine.substr(0, options.maxlength), data);\n });\n }\n else {\n self.showMachineName(expected, data);\n }\n }, 300);\n }\n\n Object.keys(settings.machineName).forEach(function (source_id) {\n var machine = '';\n var eventData;\n var options = settings.machineName[source_id];\n\n var $source = $context.find(source_id).addClass('machine-name-source').once('machine-name');\n var $target = $context.find(options.target).addClass('machine-name-target');\n var $suffix = $context.find(options.suffix);\n var $wrapper = $target.closest('.js-form-item');\n // All elements have to exist.\n if (!$source.length || !$target.length || !$suffix.length || !$wrapper.length) {\n return;\n }\n // Skip processing upon a form validation error on the machine name.\n if ($target.hasClass('error')) {\n return;\n }\n // Figure out the maximum length for the machine name.\n options.maxlength = $target.attr('maxlength');\n // Hide the form item container of the machine name form element.\n $wrapper.addClass('visually-hidden');\n // Determine the initial machine name value. Unless the machine name\n // form element is disabled or not empty, the initial default value is\n // based on the human-readable form element value.\n if ($target.is(':disabled') || $target.val() !== '') {\n machine = $target.val();\n }\n else if ($source.val() !== '') {\n machine = self.transliterate($source.val(), options);\n }\n // Append the machine name preview to the source field.\n var $preview = $('' + options.field_prefix + Drupal.checkPlain(machine) + options.field_suffix + '');\n $suffix.empty();\n if (options.label) {\n $suffix.append('' + options.label + ': ');\n }\n $suffix.append($preview);\n\n // If the machine name cannot be edited, stop further processing.\n if ($target.is(':disabled')) {\n return;\n }\n\n eventData = {\n $source: $source,\n $target: $target,\n $suffix: $suffix,\n $wrapper: $wrapper,\n $preview: $preview,\n options: options\n };\n // If it is editable, append an edit link.\n var $link = $('').on('click', eventData, clickEditHandler);\n $suffix.append($link);\n\n // Preview the machine name in realtime when the human-readable name\n // changes, but only if there is no machine name yet; i.e., only upon\n // initial creation, not when editing.\n if ($target.val() === '') {\n $source.on('formUpdated.machineName', eventData, machineNameHandler)\n // Initialize machine name preview.\n .trigger('formUpdated.machineName');\n }\n\n // Add a listener for an invalid event on the machine name input\n // to show its container and focus it.\n $target.on('invalid', eventData, clickEditHandler);\n });\n },\n\n showMachineName: function (machine, data) {\n var settings = data.options;\n // Set the machine name to the transliterated value.\n if (machine !== '') {\n if (machine !== settings.replace) {\n data.$target.val(machine);\n data.$preview.html(settings.field_prefix + Drupal.checkPlain(machine) + settings.field_suffix);\n }\n data.$suffix.show();\n }\n else {\n data.$suffix.hide();\n data.$target.val(machine);\n data.$preview.empty();\n }\n },\n\n /**\n * Transliterate a human-readable name to a machine name.\n *\n * @param {string} source\n * A string to transliterate.\n * @param {object} settings\n * The machine name settings for the corresponding field.\n * @param {string} settings.replace_pattern\n * A regular expression (without modifiers) matching disallowed characters\n * in the machine name; e.g., '[^a-z0-9]+'.\n * @param {string} settings.replace\n * A character to replace disallowed characters with; e.g., '_' or '-'.\n * @param {number} settings.maxlength\n * The maximum length of the machine name.\n *\n * @return {jQuery}\n * The transliterated source string.\n */\n transliterate: function (source, settings) {\n return $.get(Drupal.url('machine_name/transliterate'), {\n text: source,\n langcode: drupalSettings.langcode,\n replace_pattern: settings.replace_pattern,\n replace: settings.replace,\n lowercase: true\n });\n }\n };\n\n})(jQuery, Drupal, drupalSettings);\n"]} \ No newline at end of file diff --git a/core/misc/progress.es6.js b/core/misc/progress.es6.js new file mode 100644 index 0000000..a669489 --- /dev/null +++ b/core/misc/progress.es6.js @@ -0,0 +1,169 @@ +/** + * @file + * Progress bar. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Theme function for the progress bar. + * + * @param {string} id + * The id for the progress bar. + * + * @return {string} + * The HTML for the progress bar. + */ + Drupal.theme.progressBar = function (id) { + return '
    ' + + '
     
    ' + + '
    ' + + '
    ' + + '
     
    ' + + '
    '; + }; + + /** + * A progressbar object. Initialized with the given id. Must be inserted into + * the DOM afterwards through progressBar.element. + * + * Method is the function which will perform the HTTP request to get the + * progress bar state. Either "GET" or "POST". + * + * @example + * pb = new Drupal.ProgressBar('myProgressBar'); + * some_element.appendChild(pb.element); + * + * @constructor + * + * @param {string} id + * The id for the progressbar. + * @param {function} updateCallback + * Callback to run on update. + * @param {string} method + * HTTP method to use. + * @param {function} errorCallback + * Callback to call on error. + */ + Drupal.ProgressBar = function (id, updateCallback, method, errorCallback) { + this.id = id; + this.method = method || 'GET'; + this.updateCallback = updateCallback; + this.errorCallback = errorCallback; + + // The WAI-ARIA setting aria-live="polite" will announce changes after + // users + // have completed their current activity and not interrupt the screen + // reader. + this.element = $(Drupal.theme('progressBar', id)); + }; + + $.extend(Drupal.ProgressBar.prototype, /** @lends Drupal.ProgressBar# */{ + + /** + * Set the percentage and status message for the progressbar. + * + * @param {number} percentage + * The progress percentage. + * @param {string} message + * The message to show the user. + * @param {string} label + * The text for the progressbar label. + */ + setProgress: function (percentage, message, label) { + if (percentage >= 0 && percentage <= 100) { + $(this.element).find('div.progress__bar').css('width', percentage + '%'); + $(this.element).find('div.progress__percentage').html(percentage + '%'); + } + $('div.progress__description', this.element).html(message); + $('div.progress__label', this.element).html(label); + if (this.updateCallback) { + this.updateCallback(percentage, message, this); + } + }, + + /** + * Start monitoring progress via Ajax. + * + * @param {string} uri + * The URI to use for monitoring. + * @param {number} delay + * The delay for calling the monitoring URI. + */ + startMonitoring: function (uri, delay) { + this.delay = delay; + this.uri = uri; + this.sendPing(); + }, + + /** + * Stop monitoring progress via Ajax. + */ + stopMonitoring: function () { + clearTimeout(this.timer); + // This allows monitoring to be stopped from within the callback. + this.uri = null; + }, + + /** + * Request progress data from server. + */ + sendPing: function () { + if (this.timer) { + clearTimeout(this.timer); + } + if (this.uri) { + var pb = this; + // When doing a post request, you need non-null data. Otherwise a + // HTTP 411 or HTTP 406 (with Apache mod_security) error may result. + var uri = this.uri; + if (uri.indexOf('?') === -1) { + uri += '?'; + } + else { + uri += '&'; + } + uri += '_format=json'; + $.ajax({ + type: this.method, + url: uri, + data: '', + dataType: 'json', + success: function (progress) { + // Display errors. + if (progress.status === 0) { + pb.displayError(progress.data); + return; + } + // Update display. + pb.setProgress(progress.percentage, progress.message, progress.label); + // Schedule next timer. + pb.timer = setTimeout(function () { pb.sendPing(); }, pb.delay); + }, + error: function (xmlhttp) { + var e = new Drupal.AjaxError(xmlhttp, pb.uri); + pb.displayError('
    ' + e.message + '
    '); + } + }); + } + }, + + /** + * Display errors on the page. + * + * @param {string} string + * The error message to show the user. + */ + displayError: function (string) { + var error = $('
    ').html(string); + $(this.element).before(error).hide(); + + if (this.errorCallback) { + this.errorCallback(this); + } + } + }); + +})(jQuery, Drupal); diff --git a/core/misc/progress.js b/core/misc/progress.js index a669489..e0bb2ae 100644 --- a/core/misc/progress.js +++ b/core/misc/progress.js @@ -1,78 +1,24 @@ -/** - * @file - * Progress bar. - */ +'use strict'; (function ($, Drupal) { 'use strict'; - /** - * Theme function for the progress bar. - * - * @param {string} id - * The id for the progress bar. - * - * @return {string} - * The HTML for the progress bar. - */ Drupal.theme.progressBar = function (id) { - return '
    ' + - '
     
    ' + - '
    ' + - '
    ' + - '
     
    ' + - '
    '; + return '
    ' + '
     
    ' + '
    ' + '
    ' + '
     
    ' + '
    '; }; - /** - * A progressbar object. Initialized with the given id. Must be inserted into - * the DOM afterwards through progressBar.element. - * - * Method is the function which will perform the HTTP request to get the - * progress bar state. Either "GET" or "POST". - * - * @example - * pb = new Drupal.ProgressBar('myProgressBar'); - * some_element.appendChild(pb.element); - * - * @constructor - * - * @param {string} id - * The id for the progressbar. - * @param {function} updateCallback - * Callback to run on update. - * @param {string} method - * HTTP method to use. - * @param {function} errorCallback - * Callback to call on error. - */ Drupal.ProgressBar = function (id, updateCallback, method, errorCallback) { this.id = id; this.method = method || 'GET'; this.updateCallback = updateCallback; this.errorCallback = errorCallback; - // The WAI-ARIA setting aria-live="polite" will announce changes after - // users - // have completed their current activity and not interrupt the screen - // reader. this.element = $(Drupal.theme('progressBar', id)); }; - $.extend(Drupal.ProgressBar.prototype, /** @lends Drupal.ProgressBar# */{ - - /** - * Set the percentage and status message for the progressbar. - * - * @param {number} percentage - * The progress percentage. - * @param {string} message - * The message to show the user. - * @param {string} label - * The text for the progressbar label. - */ - setProgress: function (percentage, message, label) { + $.extend(Drupal.ProgressBar.prototype, { + setProgress: function setProgress(percentage, message, label) { if (percentage >= 0 && percentage <= 100) { $(this.element).find('div.progress__bar').css('width', percentage + '%'); $(this.element).find('div.progress__percentage').html(percentage + '%'); @@ -84,45 +30,29 @@ } }, - /** - * Start monitoring progress via Ajax. - * - * @param {string} uri - * The URI to use for monitoring. - * @param {number} delay - * The delay for calling the monitoring URI. - */ - startMonitoring: function (uri, delay) { + startMonitoring: function startMonitoring(uri, delay) { this.delay = delay; this.uri = uri; this.sendPing(); }, - /** - * Stop monitoring progress via Ajax. - */ - stopMonitoring: function () { + stopMonitoring: function stopMonitoring() { clearTimeout(this.timer); - // This allows monitoring to be stopped from within the callback. + this.uri = null; }, - /** - * Request progress data from server. - */ - sendPing: function () { + sendPing: function sendPing() { if (this.timer) { clearTimeout(this.timer); } if (this.uri) { var pb = this; - // When doing a post request, you need non-null data. Otherwise a - // HTTP 411 or HTTP 406 (with Apache mod_security) error may result. + var uri = this.uri; if (uri.indexOf('?') === -1) { uri += '?'; - } - else { + } else { uri += '&'; } uri += '_format=json'; @@ -131,18 +61,19 @@ url: uri, data: '', dataType: 'json', - success: function (progress) { - // Display errors. + success: function success(progress) { if (progress.status === 0) { pb.displayError(progress.data); return; } - // Update display. + pb.setProgress(progress.percentage, progress.message, progress.label); - // Schedule next timer. - pb.timer = setTimeout(function () { pb.sendPing(); }, pb.delay); + + pb.timer = setTimeout(function () { + pb.sendPing(); + }, pb.delay); }, - error: function (xmlhttp) { + error: function error(xmlhttp) { var e = new Drupal.AjaxError(xmlhttp, pb.uri); pb.displayError('
    ' + e.message + '
    '); } @@ -150,13 +81,7 @@ } }, - /** - * Display errors on the page. - * - * @param {string} string - * The error message to show the user. - */ - displayError: function (string) { + displayError: function displayError(string) { var error = $('
    ').html(string); $(this.element).before(error).hide(); @@ -165,5 +90,6 @@ } } }); - })(jQuery, Drupal); + +//# sourceMappingURL=progress.js.map \ No newline at end of file diff --git a/core/misc/progress.js.map b/core/misc/progress.js.map new file mode 100644 index 0000000..13358ac --- /dev/null +++ b/core/misc/progress.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["progress.es6.js"],"names":["$","Drupal","theme","progressBar","id","ProgressBar","updateCallback","method","errorCallback","element","extend","prototype","setProgress","percentage","message","label","find","css","html","startMonitoring","uri","delay","sendPing","stopMonitoring","clearTimeout","timer","pb","indexOf","ajax","type","url","data","dataType","success","progress","status","displayError","setTimeout","error","xmlhttp","e","AjaxError","string","before","hide","jQuery"],"mappings":"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","file":"progress.es6.js","sourcesContent":["/**\n * @file\n * Progress bar.\n */\n\n(function ($, Drupal) {\n\n 'use strict';\n\n /**\n * Theme function for the progress bar.\n *\n * @param {string} id\n * The id for the progress bar.\n *\n * @return {string}\n * The HTML for the progress bar.\n */\n Drupal.theme.progressBar = function (id) {\n return '
    ' +\n '
     
    ' +\n '
    ' +\n '
    ' +\n '
     
    ' +\n '
    ';\n };\n\n /**\n * A progressbar object. Initialized with the given id. Must be inserted into\n * the DOM afterwards through progressBar.element.\n *\n * Method is the function which will perform the HTTP request to get the\n * progress bar state. Either \"GET\" or \"POST\".\n *\n * @example\n * pb = new Drupal.ProgressBar('myProgressBar');\n * some_element.appendChild(pb.element);\n *\n * @constructor\n *\n * @param {string} id\n * The id for the progressbar.\n * @param {function} updateCallback\n * Callback to run on update.\n * @param {string} method\n * HTTP method to use.\n * @param {function} errorCallback\n * Callback to call on error.\n */\n Drupal.ProgressBar = function (id, updateCallback, method, errorCallback) {\n this.id = id;\n this.method = method || 'GET';\n this.updateCallback = updateCallback;\n this.errorCallback = errorCallback;\n\n // The WAI-ARIA setting aria-live=\"polite\" will announce changes after\n // users\n // have completed their current activity and not interrupt the screen\n // reader.\n this.element = $(Drupal.theme('progressBar', id));\n };\n\n $.extend(Drupal.ProgressBar.prototype, /** @lends Drupal.ProgressBar# */{\n\n /**\n * Set the percentage and status message for the progressbar.\n *\n * @param {number} percentage\n * The progress percentage.\n * @param {string} message\n * The message to show the user.\n * @param {string} label\n * The text for the progressbar label.\n */\n setProgress: function (percentage, message, label) {\n if (percentage >= 0 && percentage <= 100) {\n $(this.element).find('div.progress__bar').css('width', percentage + '%');\n $(this.element).find('div.progress__percentage').html(percentage + '%');\n }\n $('div.progress__description', this.element).html(message);\n $('div.progress__label', this.element).html(label);\n if (this.updateCallback) {\n this.updateCallback(percentage, message, this);\n }\n },\n\n /**\n * Start monitoring progress via Ajax.\n *\n * @param {string} uri\n * The URI to use for monitoring.\n * @param {number} delay\n * The delay for calling the monitoring URI.\n */\n startMonitoring: function (uri, delay) {\n this.delay = delay;\n this.uri = uri;\n this.sendPing();\n },\n\n /**\n * Stop monitoring progress via Ajax.\n */\n stopMonitoring: function () {\n clearTimeout(this.timer);\n // This allows monitoring to be stopped from within the callback.\n this.uri = null;\n },\n\n /**\n * Request progress data from server.\n */\n sendPing: function () {\n if (this.timer) {\n clearTimeout(this.timer);\n }\n if (this.uri) {\n var pb = this;\n // When doing a post request, you need non-null data. Otherwise a\n // HTTP 411 or HTTP 406 (with Apache mod_security) error may result.\n var uri = this.uri;\n if (uri.indexOf('?') === -1) {\n uri += '?';\n }\n else {\n uri += '&';\n }\n uri += '_format=json';\n $.ajax({\n type: this.method,\n url: uri,\n data: '',\n dataType: 'json',\n success: function (progress) {\n // Display errors.\n if (progress.status === 0) {\n pb.displayError(progress.data);\n return;\n }\n // Update display.\n pb.setProgress(progress.percentage, progress.message, progress.label);\n // Schedule next timer.\n pb.timer = setTimeout(function () { pb.sendPing(); }, pb.delay);\n },\n error: function (xmlhttp) {\n var e = new Drupal.AjaxError(xmlhttp, pb.uri);\n pb.displayError('
    ' + e.message + '
    ');\n }\n });\n }\n },\n\n /**\n * Display errors on the page.\n *\n * @param {string} string\n * The error message to show the user.\n */\n displayError: function (string) {\n var error = $('
    ').html(string);\n $(this.element).before(error).hide();\n\n if (this.errorCallback) {\n this.errorCallback(this);\n }\n }\n });\n\n})(jQuery, Drupal);\n"]} \ No newline at end of file diff --git a/core/misc/states.es6.js b/core/misc/states.es6.js new file mode 100644 index 0000000..24374b6 --- /dev/null +++ b/core/misc/states.es6.js @@ -0,0 +1,724 @@ +/** + * @file + * Drupal's states library. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * The base States namespace. + * + * Having the local states variable allows us to use the States namespace + * without having to always declare "Drupal.states". + * + * @namespace Drupal.states + */ + var states = Drupal.states = { + + /** + * An array of functions that should be postponed. + */ + postponed: [] + }; + + /** + * Attaches the states. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches states behaviors. + */ + Drupal.behaviors.states = { + attach: function (context, settings) { + var $states = $(context).find('[data-drupal-states]'); + var config; + var state; + var il = $states.length; + for (var i = 0; i < il; i++) { + config = JSON.parse($states[i].getAttribute('data-drupal-states')); + for (state in config) { + if (config.hasOwnProperty(state)) { + new states.Dependent({ + element: $($states[i]), + state: states.State.sanitize(state), + constraints: config[state] + }); + } + } + } + + // Execute all postponed functions now. + while (states.postponed.length) { + (states.postponed.shift())(); + } + } + }; + + /** + * Object representing an element that depends on other elements. + * + * @constructor Drupal.states.Dependent + * + * @param {object} args + * Object with the following keys (all of which are required) + * @param {jQuery} args.element + * A jQuery object of the dependent element + * @param {Drupal.states.State} args.state + * A State object describing the state that is dependent + * @param {object} args.constraints + * An object with dependency specifications. Lists all elements that this + * element depends on. It can be nested and can contain + * arbitrary AND and OR clauses. + */ + states.Dependent = function (args) { + $.extend(this, {values: {}, oldValue: null}, args); + + this.dependees = this.getDependees(); + for (var selector in this.dependees) { + if (this.dependees.hasOwnProperty(selector)) { + this.initializeDependee(selector, this.dependees[selector]); + } + } + }; + + /** + * Comparison functions for comparing the value of an element with the + * specification from the dependency settings. If the object type can't be + * found in this list, the === operator is used by default. + * + * @name Drupal.states.Dependent.comparisons + * + * @prop {function} RegExp + * @prop {function} Function + * @prop {function} Number + */ + states.Dependent.comparisons = { + RegExp: function (reference, value) { + return reference.test(value); + }, + Function: function (reference, value) { + // The "reference" variable is a comparison function. + return reference(value); + }, + Number: function (reference, value) { + // If "reference" is a number and "value" is a string, then cast + // reference as a string before applying the strict comparison in + // compare(). + // Otherwise numeric keys in the form's #states array fail to match + // string values returned from jQuery's val(). + return (typeof value === 'string') ? compare(reference.toString(), value) : compare(reference, value); + } + }; + + states.Dependent.prototype = { + + /** + * Initializes one of the elements this dependent depends on. + * + * @memberof Drupal.states.Dependent# + * + * @param {string} selector + * The CSS selector describing the dependee. + * @param {object} dependeeStates + * The list of states that have to be monitored for tracking the + * dependee's compliance status. + */ + initializeDependee: function (selector, dependeeStates) { + var state; + var self = this; + + function stateEventHandler(e) { + self.update(e.data.selector, e.data.state, e.value); + } + + // Cache for the states of this dependee. + this.values[selector] = {}; + + for (var i in dependeeStates) { + if (dependeeStates.hasOwnProperty(i)) { + state = dependeeStates[i]; + // Make sure we're not initializing this selector/state combination + // twice. + if ($.inArray(state, dependeeStates) === -1) { + continue; + } + + state = states.State.sanitize(state); + + // Initialize the value of this state. + this.values[selector][state.name] = null; + + // Monitor state changes of the specified state for this dependee. + $(selector).on('state:' + state, {selector: selector, state: state}, stateEventHandler); + + // Make sure the event we just bound ourselves to is actually fired. + new states.Trigger({selector: selector, state: state}); + } + } + }, + + /** + * Compares a value with a reference value. + * + * @memberof Drupal.states.Dependent# + * + * @param {object} reference + * The value used for reference. + * @param {string} selector + * CSS selector describing the dependee. + * @param {Drupal.states.State} state + * A State object describing the dependee's updated state. + * + * @return {bool} + * true or false. + */ + compare: function (reference, selector, state) { + var value = this.values[selector][state.name]; + if (reference.constructor.name in states.Dependent.comparisons) { + // Use a custom compare function for certain reference value types. + return states.Dependent.comparisons[reference.constructor.name](reference, value); + } + else { + // Do a plain comparison otherwise. + return compare(reference, value); + } + }, + + /** + * Update the value of a dependee's state. + * + * @memberof Drupal.states.Dependent# + * + * @param {string} selector + * CSS selector describing the dependee. + * @param {Drupal.states.state} state + * A State object describing the dependee's updated state. + * @param {string} value + * The new value for the dependee's updated state. + */ + update: function (selector, state, value) { + // Only act when the 'new' value is actually new. + if (value !== this.values[selector][state.name]) { + this.values[selector][state.name] = value; + this.reevaluate(); + } + }, + + /** + * Triggers change events in case a state changed. + * + * @memberof Drupal.states.Dependent# + */ + reevaluate: function () { + // Check whether any constraint for this dependent state is satisfied. + var value = this.verifyConstraints(this.constraints); + + // Only invoke a state change event when the value actually changed. + if (value !== this.oldValue) { + // Store the new value so that we can compare later whether the value + // actually changed. + this.oldValue = value; + + // Normalize the value to match the normalized state name. + value = invert(value, this.state.invert); + + // By adding "trigger: true", we ensure that state changes don't go into + // infinite loops. + this.element.trigger({type: 'state:' + this.state, value: value, trigger: true}); + } + }, + + /** + * Evaluates child constraints to determine if a constraint is satisfied. + * + * @memberof Drupal.states.Dependent# + * + * @param {object|Array} constraints + * A constraint object or an array of constraints. + * @param {string} selector + * The selector for these constraints. If undefined, there isn't yet a + * selector that these constraints apply to. In that case, the keys of the + * object are interpreted as the selector if encountered. + * + * @return {bool} + * true or false, depending on whether these constraints are satisfied. + */ + verifyConstraints: function (constraints, selector) { + var result; + if ($.isArray(constraints)) { + // This constraint is an array (OR or XOR). + var hasXor = $.inArray('xor', constraints) === -1; + var len = constraints.length; + for (var i = 0; i < len; i++) { + if (constraints[i] !== 'xor') { + var constraint = this.checkConstraints(constraints[i], selector, i); + // Return if this is OR and we have a satisfied constraint or if + // this is XOR and we have a second satisfied constraint. + if (constraint && (hasXor || result)) { + return hasXor; + } + result = result || constraint; + } + } + } + // Make sure we don't try to iterate over things other than objects. This + // shouldn't normally occur, but in case the condition definition is + // bogus, we don't want to end up with an infinite loop. + else if ($.isPlainObject(constraints)) { + // This constraint is an object (AND). + for (var n in constraints) { + if (constraints.hasOwnProperty(n)) { + result = ternary(result, this.checkConstraints(constraints[n], selector, n)); + // False and anything else will evaluate to false, so return when + // any false condition is found. + if (result === false) { return false; } + } + } + } + return result; + }, + + /** + * Checks whether the value matches the requirements for this constraint. + * + * @memberof Drupal.states.Dependent# + * + * @param {string|Array|object} value + * Either the value of a state or an array/object of constraints. In the + * latter case, resolving the constraint continues. + * @param {string} [selector] + * The selector for this constraint. If undefined, there isn't yet a + * selector that this constraint applies to. In that case, the state key + * is propagates to a selector and resolving continues. + * @param {Drupal.states.State} [state] + * The state to check for this constraint. If undefined, resolving + * continues. If both selector and state aren't undefined and valid + * non-numeric strings, a lookup for the actual value of that selector's + * state is performed. This parameter is not a State object but a pristine + * state string. + * + * @return {bool} + * true or false, depending on whether this constraint is satisfied. + */ + checkConstraints: function (value, selector, state) { + // Normalize the last parameter. If it's non-numeric, we treat it either + // as a selector (in case there isn't one yet) or as a trigger/state. + if (typeof state !== 'string' || (/[0-9]/).test(state[0])) { + state = null; + } + else if (typeof selector === 'undefined') { + // Propagate the state to the selector when there isn't one yet. + selector = state; + state = null; + } + + if (state !== null) { + // Constraints is the actual constraints of an element to check for. + state = states.State.sanitize(state); + return invert(this.compare(value, selector, state), state.invert); + } + else { + // Resolve this constraint as an AND/OR operator. + return this.verifyConstraints(value, selector); + } + }, + + /** + * Gathers information about all required triggers. + * + * @memberof Drupal.states.Dependent# + * + * @return {object} + * An object describing the required triggers. + */ + getDependees: function () { + var cache = {}; + // Swivel the lookup function so that we can record all available + // selector- state combinations for initialization. + var _compare = this.compare; + this.compare = function (reference, selector, state) { + (cache[selector] || (cache[selector] = [])).push(state.name); + // Return nothing (=== undefined) so that the constraint loops are not + // broken. + }; + + // This call doesn't actually verify anything but uses the resolving + // mechanism to go through the constraints array, trying to look up each + // value. Since we swivelled the compare function, this comparison returns + // undefined and lookup continues until the very end. Instead of lookup up + // the value, we record that combination of selector and state so that we + // can initialize all triggers. + this.verifyConstraints(this.constraints); + // Restore the original function. + this.compare = _compare; + + return cache; + } + }; + + /** + * @constructor Drupal.states.Trigger + * + * @param {object} args + * Trigger arguments. + */ + states.Trigger = function (args) { + $.extend(this, args); + + if (this.state in states.Trigger.states) { + this.element = $(this.selector); + + // Only call the trigger initializer when it wasn't yet attached to this + // element. Otherwise we'd end up with duplicate events. + if (!this.element.data('trigger:' + this.state)) { + this.initialize(); + } + } + }; + + states.Trigger.prototype = { + + /** + * @memberof Drupal.states.Trigger# + */ + initialize: function () { + var trigger = states.Trigger.states[this.state]; + + if (typeof trigger === 'function') { + // We have a custom trigger initialization function. + trigger.call(window, this.element); + } + else { + for (var event in trigger) { + if (trigger.hasOwnProperty(event)) { + this.defaultTrigger(event, trigger[event]); + } + } + } + + // Mark this trigger as initialized for this element. + this.element.data('trigger:' + this.state, true); + }, + + /** + * @memberof Drupal.states.Trigger# + * + * @param {jQuery.Event} event + * The event triggered. + * @param {function} valueFn + * The function to call. + */ + defaultTrigger: function (event, valueFn) { + var oldValue = valueFn.call(this.element); + + // Attach the event callback. + this.element.on(event, $.proxy(function (e) { + var value = valueFn.call(this.element, e); + // Only trigger the event if the value has actually changed. + if (oldValue !== value) { + this.element.trigger({type: 'state:' + this.state, value: value, oldValue: oldValue}); + oldValue = value; + } + }, this)); + + states.postponed.push($.proxy(function () { + // Trigger the event once for initialization purposes. + this.element.trigger({type: 'state:' + this.state, value: oldValue, oldValue: null}); + }, this)); + } + }; + + /** + * This list of states contains functions that are used to monitor the state + * of an element. Whenever an element depends on the state of another element, + * one of these trigger functions is added to the dependee so that the + * dependent element can be updated. + * + * @name Drupal.states.Trigger.states + * + * @prop empty + * @prop checked + * @prop value + * @prop collapsed + */ + states.Trigger.states = { + // 'empty' describes the state to be monitored. + empty: { + // 'keyup' is the (native DOM) event that we watch for. + keyup: function () { + // The function associated with that trigger returns the new value for + // the state. + return this.val() === ''; + } + }, + + checked: { + change: function () { + // prop() and attr() only takes the first element into account. To + // support selectors matching multiple checkboxes, iterate over all and + // return whether any is checked. + var checked = false; + this.each(function () { + // Use prop() here as we want a boolean of the checkbox state. + // @see http://api.jquery.com/prop/ + checked = $(this).prop('checked'); + // Break the each() loop if this is checked. + return !checked; + }); + return checked; + } + }, + + // For radio buttons, only return the value if the radio button is selected. + value: { + keyup: function () { + // Radio buttons share the same :input[name="key"] selector. + if (this.length > 1) { + // Initial checked value of radios is undefined, so we return false. + return this.filter(':checked').val() || false; + } + return this.val(); + }, + change: function () { + // Radio buttons share the same :input[name="key"] selector. + if (this.length > 1) { + // Initial checked value of radios is undefined, so we return false. + return this.filter(':checked').val() || false; + } + return this.val(); + } + }, + + collapsed: { + collapsed: function (e) { + return (typeof e !== 'undefined' && 'value' in e) ? e.value : !this.is('[open]'); + } + } + }; + + /** + * A state object is used for describing the state and performing aliasing. + * + * @constructor Drupal.states.State + * + * @param {string} state + * The name of the state. + */ + states.State = function (state) { + + /** + * Original unresolved name. + */ + this.pristine = this.name = state; + + // Normalize the state name. + var process = true; + do { + // Iteratively remove exclamation marks and invert the value. + while (this.name.charAt(0) === '!') { + this.name = this.name.substring(1); + this.invert = !this.invert; + } + + // Replace the state with its normalized name. + if (this.name in states.State.aliases) { + this.name = states.State.aliases[this.name]; + } + else { + process = false; + } + } while (process); + }; + + /** + * Creates a new State object by sanitizing the passed value. + * + * @name Drupal.states.State.sanitize + * + * @param {string|Drupal.states.State} state + * A state object or the name of a state. + * + * @return {Drupal.states.state} + * A state object. + */ + states.State.sanitize = function (state) { + if (state instanceof states.State) { + return state; + } + else { + return new states.State(state); + } + }; + + /** + * This list of aliases is used to normalize states and associates negated + * names with their respective inverse state. + * + * @name Drupal.states.State.aliases + */ + states.State.aliases = { + enabled: '!disabled', + invisible: '!visible', + invalid: '!valid', + untouched: '!touched', + optional: '!required', + filled: '!empty', + unchecked: '!checked', + irrelevant: '!relevant', + expanded: '!collapsed', + open: '!collapsed', + closed: 'collapsed', + readwrite: '!readonly' + }; + + states.State.prototype = { + + /** + * @memberof Drupal.states.State# + */ + invert: false, + + /** + * Ensures that just using the state object returns the name. + * + * @memberof Drupal.states.State# + * + * @return {string} + * The name of the state. + */ + toString: function () { + return this.name; + } + }; + + /** + * Global state change handlers. These are bound to "document" to cover all + * elements whose state changes. Events sent to elements within the page + * bubble up to these handlers. We use this system so that themes and modules + * can override these state change handlers for particular parts of a page. + */ + + var $document = $(document); + $document.on('state:disabled', function (e) { + // Only act when this change was triggered by a dependency and not by the + // element monitoring itself. + if (e.trigger) { + $(e.target) + .prop('disabled', e.value) + .closest('.js-form-item, .js-form-submit, .js-form-wrapper').toggleClass('form-disabled', e.value) + .find('select, input, textarea').prop('disabled', e.value); + + // Note: WebKit nightlies don't reflect that change correctly. + // See https://bugs.webkit.org/show_bug.cgi?id=23789 + } + }); + + $document.on('state:required', function (e) { + if (e.trigger) { + if (e.value) { + var label = 'label' + (e.target.id ? '[for=' + e.target.id + ']' : ''); + var $label = $(e.target).attr({'required': 'required', 'aria-required': 'aria-required'}).closest('.js-form-item, .js-form-wrapper').find(label); + // Avoids duplicate required markers on initialization. + if (!$label.hasClass('js-form-required').length) { + $label.addClass('js-form-required form-required'); + } + } + else { + $(e.target).removeAttr('required aria-required').closest('.js-form-item, .js-form-wrapper').find('label.js-form-required').removeClass('js-form-required form-required'); + } + } + }); + + $document.on('state:visible', function (e) { + if (e.trigger) { + $(e.target).closest('.js-form-item, .js-form-submit, .js-form-wrapper').toggle(e.value); + } + }); + + $document.on('state:checked', function (e) { + if (e.trigger) { + $(e.target).prop('checked', e.value); + } + }); + + $document.on('state:collapsed', function (e) { + if (e.trigger) { + if ($(e.target).is('[open]') === e.value) { + $(e.target).find('> summary').trigger('click'); + } + } + }); + + /** + * These are helper functions implementing addition "operators" and don't + * implement any logic that is particular to states. + */ + + /** + * Bitwise AND with a third undefined state. + * + * @function Drupal.states~ternary + * + * @param {*} a + * Value a. + * @param {*} b + * Value b + * + * @return {bool} + * The result. + */ + function ternary(a, b) { + if (typeof a === 'undefined') { + return b; + } + else if (typeof b === 'undefined') { + return a; + } + else { + return a && b; + } + } + + /** + * Inverts a (if it's not undefined) when invertState is true. + * + * @function Drupal.states~invert + * + * @param {*} a + * The value to maybe invert. + * @param {bool} invertState + * Whether to invert state or not. + * + * @return {bool} + * The result. + */ + function invert(a, invertState) { + return (invertState && typeof a !== 'undefined') ? !a : a; + } + + /** + * Compares two values while ignoring undefined values. + * + * @function Drupal.states~compare + * + * @param {*} a + * Value a. + * @param {*} b + * Value b. + * + * @return {bool} + * The comparison result. + */ + function compare(a, b) { + if (a === b) { + return typeof a === 'undefined' ? a : true; + } + else { + return typeof a === 'undefined' || typeof b === 'undefined'; + } + } + +})(jQuery, Drupal); diff --git a/core/misc/states.js b/core/misc/states.js index 24374b6..8218936 100644 --- a/core/misc/states.js +++ b/core/misc/states.js @@ -1,38 +1,15 @@ -/** - * @file - * Drupal's states library. - */ +'use strict'; (function ($, Drupal) { 'use strict'; - /** - * The base States namespace. - * - * Having the local states variable allows us to use the States namespace - * without having to always declare "Drupal.states". - * - * @namespace Drupal.states - */ var states = Drupal.states = { - - /** - * An array of functions that should be postponed. - */ postponed: [] }; - /** - * Attaches the states. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches states behaviors. - */ Drupal.behaviors.states = { - attach: function (context, settings) { + attach: function attach(context, settings) { var $states = $(context).find('[data-drupal-states]'); var config; var state; @@ -50,31 +27,14 @@ } } - // Execute all postponed functions now. while (states.postponed.length) { - (states.postponed.shift())(); + states.postponed.shift()(); } } }; - /** - * Object representing an element that depends on other elements. - * - * @constructor Drupal.states.Dependent - * - * @param {object} args - * Object with the following keys (all of which are required) - * @param {jQuery} args.element - * A jQuery object of the dependent element - * @param {Drupal.states.State} args.state - * A State object describing the state that is dependent - * @param {object} args.constraints - * An object with dependency specifications. Lists all elements that this - * element depends on. It can be nested and can contain - * arbitrary AND and OR clauses. - */ states.Dependent = function (args) { - $.extend(this, {values: {}, oldValue: null}, args); + $.extend(this, { values: {}, oldValue: null }, args); this.dependees = this.getDependees(); for (var selector in this.dependees) { @@ -84,49 +44,20 @@ } }; - /** - * Comparison functions for comparing the value of an element with the - * specification from the dependency settings. If the object type can't be - * found in this list, the === operator is used by default. - * - * @name Drupal.states.Dependent.comparisons - * - * @prop {function} RegExp - * @prop {function} Function - * @prop {function} Number - */ states.Dependent.comparisons = { - RegExp: function (reference, value) { + RegExp: function RegExp(reference, value) { return reference.test(value); }, - Function: function (reference, value) { - // The "reference" variable is a comparison function. + Function: function Function(reference, value) { return reference(value); }, - Number: function (reference, value) { - // If "reference" is a number and "value" is a string, then cast - // reference as a string before applying the strict comparison in - // compare(). - // Otherwise numeric keys in the form's #states array fail to match - // string values returned from jQuery's val(). - return (typeof value === 'string') ? compare(reference.toString(), value) : compare(reference, value); + Number: function Number(reference, value) { + return typeof value === 'string' ? _compare2(reference.toString(), value) : _compare2(reference, value); } }; states.Dependent.prototype = { - - /** - * Initializes one of the elements this dependent depends on. - * - * @memberof Drupal.states.Dependent# - * - * @param {string} selector - * The CSS selector describing the dependee. - * @param {object} dependeeStates - * The list of states that have to be monitored for tracking the - * dependee's compliance status. - */ - initializeDependee: function (selector, dependeeStates) { + initializeDependee: function initializeDependee(selector, dependeeStates) { var state; var self = this; @@ -134,245 +65,122 @@ self.update(e.data.selector, e.data.state, e.value); } - // Cache for the states of this dependee. this.values[selector] = {}; for (var i in dependeeStates) { if (dependeeStates.hasOwnProperty(i)) { state = dependeeStates[i]; - // Make sure we're not initializing this selector/state combination - // twice. + if ($.inArray(state, dependeeStates) === -1) { continue; } state = states.State.sanitize(state); - // Initialize the value of this state. this.values[selector][state.name] = null; - // Monitor state changes of the specified state for this dependee. - $(selector).on('state:' + state, {selector: selector, state: state}, stateEventHandler); + $(selector).on('state:' + state, { selector: selector, state: state }, stateEventHandler); - // Make sure the event we just bound ourselves to is actually fired. - new states.Trigger({selector: selector, state: state}); + new states.Trigger({ selector: selector, state: state }); } } }, - /** - * Compares a value with a reference value. - * - * @memberof Drupal.states.Dependent# - * - * @param {object} reference - * The value used for reference. - * @param {string} selector - * CSS selector describing the dependee. - * @param {Drupal.states.State} state - * A State object describing the dependee's updated state. - * - * @return {bool} - * true or false. - */ - compare: function (reference, selector, state) { + compare: function compare(reference, selector, state) { var value = this.values[selector][state.name]; if (reference.constructor.name in states.Dependent.comparisons) { - // Use a custom compare function for certain reference value types. return states.Dependent.comparisons[reference.constructor.name](reference, value); - } - else { - // Do a plain comparison otherwise. - return compare(reference, value); + } else { + return _compare2(reference, value); } }, - /** - * Update the value of a dependee's state. - * - * @memberof Drupal.states.Dependent# - * - * @param {string} selector - * CSS selector describing the dependee. - * @param {Drupal.states.state} state - * A State object describing the dependee's updated state. - * @param {string} value - * The new value for the dependee's updated state. - */ - update: function (selector, state, value) { - // Only act when the 'new' value is actually new. + update: function update(selector, state, value) { if (value !== this.values[selector][state.name]) { this.values[selector][state.name] = value; this.reevaluate(); } }, - /** - * Triggers change events in case a state changed. - * - * @memberof Drupal.states.Dependent# - */ - reevaluate: function () { - // Check whether any constraint for this dependent state is satisfied. + reevaluate: function reevaluate() { var value = this.verifyConstraints(this.constraints); - // Only invoke a state change event when the value actually changed. if (value !== this.oldValue) { - // Store the new value so that we can compare later whether the value - // actually changed. this.oldValue = value; - // Normalize the value to match the normalized state name. value = invert(value, this.state.invert); - // By adding "trigger: true", we ensure that state changes don't go into - // infinite loops. - this.element.trigger({type: 'state:' + this.state, value: value, trigger: true}); + this.element.trigger({ type: 'state:' + this.state, value: value, trigger: true }); } }, - /** - * Evaluates child constraints to determine if a constraint is satisfied. - * - * @memberof Drupal.states.Dependent# - * - * @param {object|Array} constraints - * A constraint object or an array of constraints. - * @param {string} selector - * The selector for these constraints. If undefined, there isn't yet a - * selector that these constraints apply to. In that case, the keys of the - * object are interpreted as the selector if encountered. - * - * @return {bool} - * true or false, depending on whether these constraints are satisfied. - */ - verifyConstraints: function (constraints, selector) { + verifyConstraints: function verifyConstraints(constraints, selector) { var result; if ($.isArray(constraints)) { - // This constraint is an array (OR or XOR). var hasXor = $.inArray('xor', constraints) === -1; var len = constraints.length; for (var i = 0; i < len; i++) { if (constraints[i] !== 'xor') { var constraint = this.checkConstraints(constraints[i], selector, i); - // Return if this is OR and we have a satisfied constraint or if - // this is XOR and we have a second satisfied constraint. + if (constraint && (hasXor || result)) { return hasXor; } result = result || constraint; } } - } - // Make sure we don't try to iterate over things other than objects. This - // shouldn't normally occur, but in case the condition definition is - // bogus, we don't want to end up with an infinite loop. - else if ($.isPlainObject(constraints)) { - // This constraint is an object (AND). - for (var n in constraints) { - if (constraints.hasOwnProperty(n)) { - result = ternary(result, this.checkConstraints(constraints[n], selector, n)); - // False and anything else will evaluate to false, so return when - // any false condition is found. - if (result === false) { return false; } + } else if ($.isPlainObject(constraints)) { + for (var n in constraints) { + if (constraints.hasOwnProperty(n)) { + result = ternary(result, this.checkConstraints(constraints[n], selector, n)); + + if (result === false) { + return false; + } + } } } - } return result; }, - /** - * Checks whether the value matches the requirements for this constraint. - * - * @memberof Drupal.states.Dependent# - * - * @param {string|Array|object} value - * Either the value of a state or an array/object of constraints. In the - * latter case, resolving the constraint continues. - * @param {string} [selector] - * The selector for this constraint. If undefined, there isn't yet a - * selector that this constraint applies to. In that case, the state key - * is propagates to a selector and resolving continues. - * @param {Drupal.states.State} [state] - * The state to check for this constraint. If undefined, resolving - * continues. If both selector and state aren't undefined and valid - * non-numeric strings, a lookup for the actual value of that selector's - * state is performed. This parameter is not a State object but a pristine - * state string. - * - * @return {bool} - * true or false, depending on whether this constraint is satisfied. - */ - checkConstraints: function (value, selector, state) { - // Normalize the last parameter. If it's non-numeric, we treat it either - // as a selector (in case there isn't one yet) or as a trigger/state. - if (typeof state !== 'string' || (/[0-9]/).test(state[0])) { + checkConstraints: function checkConstraints(value, selector, state) { + if (typeof state !== 'string' || /[0-9]/.test(state[0])) { state = null; - } - else if (typeof selector === 'undefined') { - // Propagate the state to the selector when there isn't one yet. + } else if (typeof selector === 'undefined') { selector = state; state = null; } if (state !== null) { - // Constraints is the actual constraints of an element to check for. state = states.State.sanitize(state); return invert(this.compare(value, selector, state), state.invert); - } - else { - // Resolve this constraint as an AND/OR operator. + } else { return this.verifyConstraints(value, selector); } }, - /** - * Gathers information about all required triggers. - * - * @memberof Drupal.states.Dependent# - * - * @return {object} - * An object describing the required triggers. - */ - getDependees: function () { + getDependees: function getDependees() { var cache = {}; - // Swivel the lookup function so that we can record all available - // selector- state combinations for initialization. + var _compare = this.compare; this.compare = function (reference, selector, state) { (cache[selector] || (cache[selector] = [])).push(state.name); - // Return nothing (=== undefined) so that the constraint loops are not - // broken. }; - // This call doesn't actually verify anything but uses the resolving - // mechanism to go through the constraints array, trying to look up each - // value. Since we swivelled the compare function, this comparison returns - // undefined and lookup continues until the very end. Instead of lookup up - // the value, we record that combination of selector and state so that we - // can initialize all triggers. this.verifyConstraints(this.constraints); - // Restore the original function. + this.compare = _compare; return cache; } }; - /** - * @constructor Drupal.states.Trigger - * - * @param {object} args - * Trigger arguments. - */ states.Trigger = function (args) { $.extend(this, args); if (this.state in states.Trigger.states) { this.element = $(this.selector); - // Only call the trigger initializer when it wasn't yet attached to this - // element. Otherwise we'd end up with duplicate events. if (!this.element.data('trigger:' + this.state)) { this.initialize(); } @@ -380,18 +188,12 @@ }; states.Trigger.prototype = { - - /** - * @memberof Drupal.states.Trigger# - */ - initialize: function () { + initialize: function initialize() { var trigger = states.Trigger.states[this.state]; if (typeof trigger === 'function') { - // We have a custom trigger initialization function. trigger.call(window, this.element); - } - else { + } else { for (var event in trigger) { if (trigger.hasOwnProperty(event)) { this.defaultTrigger(event, trigger[event]); @@ -399,93 +201,55 @@ } } - // Mark this trigger as initialized for this element. this.element.data('trigger:' + this.state, true); }, - /** - * @memberof Drupal.states.Trigger# - * - * @param {jQuery.Event} event - * The event triggered. - * @param {function} valueFn - * The function to call. - */ - defaultTrigger: function (event, valueFn) { + defaultTrigger: function defaultTrigger(event, valueFn) { var oldValue = valueFn.call(this.element); - // Attach the event callback. this.element.on(event, $.proxy(function (e) { var value = valueFn.call(this.element, e); - // Only trigger the event if the value has actually changed. + if (oldValue !== value) { - this.element.trigger({type: 'state:' + this.state, value: value, oldValue: oldValue}); + this.element.trigger({ type: 'state:' + this.state, value: value, oldValue: oldValue }); oldValue = value; } }, this)); states.postponed.push($.proxy(function () { - // Trigger the event once for initialization purposes. - this.element.trigger({type: 'state:' + this.state, value: oldValue, oldValue: null}); + this.element.trigger({ type: 'state:' + this.state, value: oldValue, oldValue: null }); }, this)); } }; - /** - * This list of states contains functions that are used to monitor the state - * of an element. Whenever an element depends on the state of another element, - * one of these trigger functions is added to the dependee so that the - * dependent element can be updated. - * - * @name Drupal.states.Trigger.states - * - * @prop empty - * @prop checked - * @prop value - * @prop collapsed - */ states.Trigger.states = { - // 'empty' describes the state to be monitored. empty: { - // 'keyup' is the (native DOM) event that we watch for. - keyup: function () { - // The function associated with that trigger returns the new value for - // the state. + keyup: function keyup() { return this.val() === ''; } }, checked: { - change: function () { - // prop() and attr() only takes the first element into account. To - // support selectors matching multiple checkboxes, iterate over all and - // return whether any is checked. + change: function change() { var checked = false; this.each(function () { - // Use prop() here as we want a boolean of the checkbox state. - // @see http://api.jquery.com/prop/ checked = $(this).prop('checked'); - // Break the each() loop if this is checked. + return !checked; }); return checked; } }, - // For radio buttons, only return the value if the radio button is selected. value: { - keyup: function () { - // Radio buttons share the same :input[name="key"] selector. + keyup: function keyup() { if (this.length > 1) { - // Initial checked value of radios is undefined, so we return false. return this.filter(':checked').val() || false; } return this.val(); }, - change: function () { - // Radio buttons share the same :input[name="key"] selector. + change: function change() { if (this.length > 1) { - // Initial checked value of radios is undefined, so we return false. return this.filter(':checked').val() || false; } return this.val(); @@ -493,72 +257,38 @@ }, collapsed: { - collapsed: function (e) { - return (typeof e !== 'undefined' && 'value' in e) ? e.value : !this.is('[open]'); + collapsed: function collapsed(e) { + return typeof e !== 'undefined' && 'value' in e ? e.value : !this.is('[open]'); } } }; - /** - * A state object is used for describing the state and performing aliasing. - * - * @constructor Drupal.states.State - * - * @param {string} state - * The name of the state. - */ states.State = function (state) { - - /** - * Original unresolved name. - */ this.pristine = this.name = state; - // Normalize the state name. var process = true; do { - // Iteratively remove exclamation marks and invert the value. while (this.name.charAt(0) === '!') { this.name = this.name.substring(1); this.invert = !this.invert; } - // Replace the state with its normalized name. if (this.name in states.State.aliases) { this.name = states.State.aliases[this.name]; - } - else { + } else { process = false; } } while (process); }; - /** - * Creates a new State object by sanitizing the passed value. - * - * @name Drupal.states.State.sanitize - * - * @param {string|Drupal.states.State} state - * A state object or the name of a state. - * - * @return {Drupal.states.state} - * A state object. - */ states.State.sanitize = function (state) { if (state instanceof states.State) { return state; - } - else { + } else { return new states.State(state); } }; - /** - * This list of aliases is used to normalize states and associates negated - * names with their respective inverse state. - * - * @name Drupal.states.State.aliases - */ states.State.aliases = { enabled: '!disabled', invisible: '!visible', @@ -575,44 +305,17 @@ }; states.State.prototype = { - - /** - * @memberof Drupal.states.State# - */ invert: false, - /** - * Ensures that just using the state object returns the name. - * - * @memberof Drupal.states.State# - * - * @return {string} - * The name of the state. - */ - toString: function () { + toString: function toString() { return this.name; } }; - /** - * Global state change handlers. These are bound to "document" to cover all - * elements whose state changes. Events sent to elements within the page - * bubble up to these handlers. We use this system so that themes and modules - * can override these state change handlers for particular parts of a page. - */ - var $document = $(document); $document.on('state:disabled', function (e) { - // Only act when this change was triggered by a dependency and not by the - // element monitoring itself. if (e.trigger) { - $(e.target) - .prop('disabled', e.value) - .closest('.js-form-item, .js-form-submit, .js-form-wrapper').toggleClass('form-disabled', e.value) - .find('select, input, textarea').prop('disabled', e.value); - - // Note: WebKit nightlies don't reflect that change correctly. - // See https://bugs.webkit.org/show_bug.cgi?id=23789 + $(e.target).prop('disabled', e.value).closest('.js-form-item, .js-form-submit, .js-form-wrapper').toggleClass('form-disabled', e.value).find('select, input, textarea').prop('disabled', e.value); } }); @@ -620,13 +323,12 @@ if (e.trigger) { if (e.value) { var label = 'label' + (e.target.id ? '[for=' + e.target.id + ']' : ''); - var $label = $(e.target).attr({'required': 'required', 'aria-required': 'aria-required'}).closest('.js-form-item, .js-form-wrapper').find(label); - // Avoids duplicate required markers on initialization. + var $label = $(e.target).attr({ 'required': 'required', 'aria-required': 'aria-required' }).closest('.js-form-item, .js-form-wrapper').find(label); + if (!$label.hasClass('js-form-required').length) { $label.addClass('js-form-required form-required'); } - } - else { + } else { $(e.target).removeAttr('required aria-required').closest('.js-form-item, .js-form-wrapper').find('label.js-form-required').removeClass('js-form-required form-required'); } } @@ -652,73 +354,27 @@ } }); - /** - * These are helper functions implementing addition "operators" and don't - * implement any logic that is particular to states. - */ - - /** - * Bitwise AND with a third undefined state. - * - * @function Drupal.states~ternary - * - * @param {*} a - * Value a. - * @param {*} b - * Value b - * - * @return {bool} - * The result. - */ function ternary(a, b) { if (typeof a === 'undefined') { return b; - } - else if (typeof b === 'undefined') { + } else if (typeof b === 'undefined') { return a; - } - else { + } else { return a && b; } } - /** - * Inverts a (if it's not undefined) when invertState is true. - * - * @function Drupal.states~invert - * - * @param {*} a - * The value to maybe invert. - * @param {bool} invertState - * Whether to invert state or not. - * - * @return {bool} - * The result. - */ function invert(a, invertState) { - return (invertState && typeof a !== 'undefined') ? !a : a; + return invertState && typeof a !== 'undefined' ? !a : a; } - /** - * Compares two values while ignoring undefined values. - * - * @function Drupal.states~compare - * - * @param {*} a - * Value a. - * @param {*} b - * Value b. - * - * @return {bool} - * The comparison result. - */ - function compare(a, b) { + function _compare2(a, b) { if (a === b) { return typeof a === 'undefined' ? a : true; - } - else { + } else { return typeof a === 'undefined' || typeof b === 'undefined'; } } - })(jQuery, Drupal); + +//# sourceMappingURL=states.js.map \ No newline at end of file diff --git a/core/misc/states.js.map b/core/misc/states.js.map new file mode 100644 index 0000000..fdd4681 --- /dev/null +++ b/core/misc/states.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["states.es6.js"],"names":["$","Drupal","states","postponed","behaviors","attach","context","settings","$states","find","config","state","il","length","i","JSON","parse","getAttribute","hasOwnProperty","Dependent","element","State","sanitize","constraints","shift","args","extend","values","oldValue","dependees","getDependees","selector","initializeDependee","comparisons","RegExp","reference","value","test","Function","Number","compare","toString","prototype","dependeeStates","self","stateEventHandler","e","update","data","inArray","name","on","Trigger","constructor","reevaluate","verifyConstraints","invert","trigger","type","result","isArray","hasXor","len","constraint","checkConstraints","isPlainObject","n","ternary","cache","_compare","push","initialize","call","window","event","defaultTrigger","valueFn","proxy","empty","keyup","val","checked","change","each","prop","filter","collapsed","is","pristine","process","charAt","substring","aliases","enabled","invisible","invalid","untouched","optional","filled","unchecked","irrelevant","expanded","open","closed","readwrite","$document","document","target","closest","toggleClass","label","id","$label","attr","hasClass","addClass","removeAttr","removeClass","toggle","a","b","invertState","jQuery"],"mappings":"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","file":"states.es6.js","sourcesContent":["/**\n * @file\n * Drupal's states library.\n */\n\n(function ($, Drupal) {\n\n 'use strict';\n\n /**\n * The base States namespace.\n *\n * Having the local states variable allows us to use the States namespace\n * without having to always declare \"Drupal.states\".\n *\n * @namespace Drupal.states\n */\n var states = Drupal.states = {\n\n /**\n * An array of functions that should be postponed.\n */\n postponed: []\n };\n\n /**\n * Attaches the states.\n *\n * @type {Drupal~behavior}\n *\n * @prop {Drupal~behaviorAttach} attach\n * Attaches states behaviors.\n */\n Drupal.behaviors.states = {\n attach: function (context, settings) {\n var $states = $(context).find('[data-drupal-states]');\n var config;\n var state;\n var il = $states.length;\n for (var i = 0; i < il; i++) {\n config = JSON.parse($states[i].getAttribute('data-drupal-states'));\n for (state in config) {\n if (config.hasOwnProperty(state)) {\n new states.Dependent({\n element: $($states[i]),\n state: states.State.sanitize(state),\n constraints: config[state]\n });\n }\n }\n }\n\n // Execute all postponed functions now.\n while (states.postponed.length) {\n (states.postponed.shift())();\n }\n }\n };\n\n /**\n * Object representing an element that depends on other elements.\n *\n * @constructor Drupal.states.Dependent\n *\n * @param {object} args\n * Object with the following keys (all of which are required)\n * @param {jQuery} args.element\n * A jQuery object of the dependent element\n * @param {Drupal.states.State} args.state\n * A State object describing the state that is dependent\n * @param {object} args.constraints\n * An object with dependency specifications. Lists all elements that this\n * element depends on. It can be nested and can contain\n * arbitrary AND and OR clauses.\n */\n states.Dependent = function (args) {\n $.extend(this, {values: {}, oldValue: null}, args);\n\n this.dependees = this.getDependees();\n for (var selector in this.dependees) {\n if (this.dependees.hasOwnProperty(selector)) {\n this.initializeDependee(selector, this.dependees[selector]);\n }\n }\n };\n\n /**\n * Comparison functions for comparing the value of an element with the\n * specification from the dependency settings. If the object type can't be\n * found in this list, the === operator is used by default.\n *\n * @name Drupal.states.Dependent.comparisons\n *\n * @prop {function} RegExp\n * @prop {function} Function\n * @prop {function} Number\n */\n states.Dependent.comparisons = {\n RegExp: function (reference, value) {\n return reference.test(value);\n },\n Function: function (reference, value) {\n // The \"reference\" variable is a comparison function.\n return reference(value);\n },\n Number: function (reference, value) {\n // If \"reference\" is a number and \"value\" is a string, then cast\n // reference as a string before applying the strict comparison in\n // compare().\n // Otherwise numeric keys in the form's #states array fail to match\n // string values returned from jQuery's val().\n return (typeof value === 'string') ? compare(reference.toString(), value) : compare(reference, value);\n }\n };\n\n states.Dependent.prototype = {\n\n /**\n * Initializes one of the elements this dependent depends on.\n *\n * @memberof Drupal.states.Dependent#\n *\n * @param {string} selector\n * The CSS selector describing the dependee.\n * @param {object} dependeeStates\n * The list of states that have to be monitored for tracking the\n * dependee's compliance status.\n */\n initializeDependee: function (selector, dependeeStates) {\n var state;\n var self = this;\n\n function stateEventHandler(e) {\n self.update(e.data.selector, e.data.state, e.value);\n }\n\n // Cache for the states of this dependee.\n this.values[selector] = {};\n\n for (var i in dependeeStates) {\n if (dependeeStates.hasOwnProperty(i)) {\n state = dependeeStates[i];\n // Make sure we're not initializing this selector/state combination\n // twice.\n if ($.inArray(state, dependeeStates) === -1) {\n continue;\n }\n\n state = states.State.sanitize(state);\n\n // Initialize the value of this state.\n this.values[selector][state.name] = null;\n\n // Monitor state changes of the specified state for this dependee.\n $(selector).on('state:' + state, {selector: selector, state: state}, stateEventHandler);\n\n // Make sure the event we just bound ourselves to is actually fired.\n new states.Trigger({selector: selector, state: state});\n }\n }\n },\n\n /**\n * Compares a value with a reference value.\n *\n * @memberof Drupal.states.Dependent#\n *\n * @param {object} reference\n * The value used for reference.\n * @param {string} selector\n * CSS selector describing the dependee.\n * @param {Drupal.states.State} state\n * A State object describing the dependee's updated state.\n *\n * @return {bool}\n * true or false.\n */\n compare: function (reference, selector, state) {\n var value = this.values[selector][state.name];\n if (reference.constructor.name in states.Dependent.comparisons) {\n // Use a custom compare function for certain reference value types.\n return states.Dependent.comparisons[reference.constructor.name](reference, value);\n }\n else {\n // Do a plain comparison otherwise.\n return compare(reference, value);\n }\n },\n\n /**\n * Update the value of a dependee's state.\n *\n * @memberof Drupal.states.Dependent#\n *\n * @param {string} selector\n * CSS selector describing the dependee.\n * @param {Drupal.states.state} state\n * A State object describing the dependee's updated state.\n * @param {string} value\n * The new value for the dependee's updated state.\n */\n update: function (selector, state, value) {\n // Only act when the 'new' value is actually new.\n if (value !== this.values[selector][state.name]) {\n this.values[selector][state.name] = value;\n this.reevaluate();\n }\n },\n\n /**\n * Triggers change events in case a state changed.\n *\n * @memberof Drupal.states.Dependent#\n */\n reevaluate: function () {\n // Check whether any constraint for this dependent state is satisfied.\n var value = this.verifyConstraints(this.constraints);\n\n // Only invoke a state change event when the value actually changed.\n if (value !== this.oldValue) {\n // Store the new value so that we can compare later whether the value\n // actually changed.\n this.oldValue = value;\n\n // Normalize the value to match the normalized state name.\n value = invert(value, this.state.invert);\n\n // By adding \"trigger: true\", we ensure that state changes don't go into\n // infinite loops.\n this.element.trigger({type: 'state:' + this.state, value: value, trigger: true});\n }\n },\n\n /**\n * Evaluates child constraints to determine if a constraint is satisfied.\n *\n * @memberof Drupal.states.Dependent#\n *\n * @param {object|Array} constraints\n * A constraint object or an array of constraints.\n * @param {string} selector\n * The selector for these constraints. If undefined, there isn't yet a\n * selector that these constraints apply to. In that case, the keys of the\n * object are interpreted as the selector if encountered.\n *\n * @return {bool}\n * true or false, depending on whether these constraints are satisfied.\n */\n verifyConstraints: function (constraints, selector) {\n var result;\n if ($.isArray(constraints)) {\n // This constraint is an array (OR or XOR).\n var hasXor = $.inArray('xor', constraints) === -1;\n var len = constraints.length;\n for (var i = 0; i < len; i++) {\n if (constraints[i] !== 'xor') {\n var constraint = this.checkConstraints(constraints[i], selector, i);\n // Return if this is OR and we have a satisfied constraint or if\n // this is XOR and we have a second satisfied constraint.\n if (constraint && (hasXor || result)) {\n return hasXor;\n }\n result = result || constraint;\n }\n }\n }\n // Make sure we don't try to iterate over things other than objects. This\n // shouldn't normally occur, but in case the condition definition is\n // bogus, we don't want to end up with an infinite loop.\n else if ($.isPlainObject(constraints)) {\n // This constraint is an object (AND).\n for (var n in constraints) {\n if (constraints.hasOwnProperty(n)) {\n result = ternary(result, this.checkConstraints(constraints[n], selector, n));\n // False and anything else will evaluate to false, so return when\n // any false condition is found.\n if (result === false) { return false; }\n }\n }\n }\n return result;\n },\n\n /**\n * Checks whether the value matches the requirements for this constraint.\n *\n * @memberof Drupal.states.Dependent#\n *\n * @param {string|Array|object} value\n * Either the value of a state or an array/object of constraints. In the\n * latter case, resolving the constraint continues.\n * @param {string} [selector]\n * The selector for this constraint. If undefined, there isn't yet a\n * selector that this constraint applies to. In that case, the state key\n * is propagates to a selector and resolving continues.\n * @param {Drupal.states.State} [state]\n * The state to check for this constraint. If undefined, resolving\n * continues. If both selector and state aren't undefined and valid\n * non-numeric strings, a lookup for the actual value of that selector's\n * state is performed. This parameter is not a State object but a pristine\n * state string.\n *\n * @return {bool}\n * true or false, depending on whether this constraint is satisfied.\n */\n checkConstraints: function (value, selector, state) {\n // Normalize the last parameter. If it's non-numeric, we treat it either\n // as a selector (in case there isn't one yet) or as a trigger/state.\n if (typeof state !== 'string' || (/[0-9]/).test(state[0])) {\n state = null;\n }\n else if (typeof selector === 'undefined') {\n // Propagate the state to the selector when there isn't one yet.\n selector = state;\n state = null;\n }\n\n if (state !== null) {\n // Constraints is the actual constraints of an element to check for.\n state = states.State.sanitize(state);\n return invert(this.compare(value, selector, state), state.invert);\n }\n else {\n // Resolve this constraint as an AND/OR operator.\n return this.verifyConstraints(value, selector);\n }\n },\n\n /**\n * Gathers information about all required triggers.\n *\n * @memberof Drupal.states.Dependent#\n *\n * @return {object}\n * An object describing the required triggers.\n */\n getDependees: function () {\n var cache = {};\n // Swivel the lookup function so that we can record all available\n // selector- state combinations for initialization.\n var _compare = this.compare;\n this.compare = function (reference, selector, state) {\n (cache[selector] || (cache[selector] = [])).push(state.name);\n // Return nothing (=== undefined) so that the constraint loops are not\n // broken.\n };\n\n // This call doesn't actually verify anything but uses the resolving\n // mechanism to go through the constraints array, trying to look up each\n // value. Since we swivelled the compare function, this comparison returns\n // undefined and lookup continues until the very end. Instead of lookup up\n // the value, we record that combination of selector and state so that we\n // can initialize all triggers.\n this.verifyConstraints(this.constraints);\n // Restore the original function.\n this.compare = _compare;\n\n return cache;\n }\n };\n\n /**\n * @constructor Drupal.states.Trigger\n *\n * @param {object} args\n * Trigger arguments.\n */\n states.Trigger = function (args) {\n $.extend(this, args);\n\n if (this.state in states.Trigger.states) {\n this.element = $(this.selector);\n\n // Only call the trigger initializer when it wasn't yet attached to this\n // element. Otherwise we'd end up with duplicate events.\n if (!this.element.data('trigger:' + this.state)) {\n this.initialize();\n }\n }\n };\n\n states.Trigger.prototype = {\n\n /**\n * @memberof Drupal.states.Trigger#\n */\n initialize: function () {\n var trigger = states.Trigger.states[this.state];\n\n if (typeof trigger === 'function') {\n // We have a custom trigger initialization function.\n trigger.call(window, this.element);\n }\n else {\n for (var event in trigger) {\n if (trigger.hasOwnProperty(event)) {\n this.defaultTrigger(event, trigger[event]);\n }\n }\n }\n\n // Mark this trigger as initialized for this element.\n this.element.data('trigger:' + this.state, true);\n },\n\n /**\n * @memberof Drupal.states.Trigger#\n *\n * @param {jQuery.Event} event\n * The event triggered.\n * @param {function} valueFn\n * The function to call.\n */\n defaultTrigger: function (event, valueFn) {\n var oldValue = valueFn.call(this.element);\n\n // Attach the event callback.\n this.element.on(event, $.proxy(function (e) {\n var value = valueFn.call(this.element, e);\n // Only trigger the event if the value has actually changed.\n if (oldValue !== value) {\n this.element.trigger({type: 'state:' + this.state, value: value, oldValue: oldValue});\n oldValue = value;\n }\n }, this));\n\n states.postponed.push($.proxy(function () {\n // Trigger the event once for initialization purposes.\n this.element.trigger({type: 'state:' + this.state, value: oldValue, oldValue: null});\n }, this));\n }\n };\n\n /**\n * This list of states contains functions that are used to monitor the state\n * of an element. Whenever an element depends on the state of another element,\n * one of these trigger functions is added to the dependee so that the\n * dependent element can be updated.\n *\n * @name Drupal.states.Trigger.states\n *\n * @prop empty\n * @prop checked\n * @prop value\n * @prop collapsed\n */\n states.Trigger.states = {\n // 'empty' describes the state to be monitored.\n empty: {\n // 'keyup' is the (native DOM) event that we watch for.\n keyup: function () {\n // The function associated with that trigger returns the new value for\n // the state.\n return this.val() === '';\n }\n },\n\n checked: {\n change: function () {\n // prop() and attr() only takes the first element into account. To\n // support selectors matching multiple checkboxes, iterate over all and\n // return whether any is checked.\n var checked = false;\n this.each(function () {\n // Use prop() here as we want a boolean of the checkbox state.\n // @see http://api.jquery.com/prop/\n checked = $(this).prop('checked');\n // Break the each() loop if this is checked.\n return !checked;\n });\n return checked;\n }\n },\n\n // For radio buttons, only return the value if the radio button is selected.\n value: {\n keyup: function () {\n // Radio buttons share the same :input[name=\"key\"] selector.\n if (this.length > 1) {\n // Initial checked value of radios is undefined, so we return false.\n return this.filter(':checked').val() || false;\n }\n return this.val();\n },\n change: function () {\n // Radio buttons share the same :input[name=\"key\"] selector.\n if (this.length > 1) {\n // Initial checked value of radios is undefined, so we return false.\n return this.filter(':checked').val() || false;\n }\n return this.val();\n }\n },\n\n collapsed: {\n collapsed: function (e) {\n return (typeof e !== 'undefined' && 'value' in e) ? e.value : !this.is('[open]');\n }\n }\n };\n\n /**\n * A state object is used for describing the state and performing aliasing.\n *\n * @constructor Drupal.states.State\n *\n * @param {string} state\n * The name of the state.\n */\n states.State = function (state) {\n\n /**\n * Original unresolved name.\n */\n this.pristine = this.name = state;\n\n // Normalize the state name.\n var process = true;\n do {\n // Iteratively remove exclamation marks and invert the value.\n while (this.name.charAt(0) === '!') {\n this.name = this.name.substring(1);\n this.invert = !this.invert;\n }\n\n // Replace the state with its normalized name.\n if (this.name in states.State.aliases) {\n this.name = states.State.aliases[this.name];\n }\n else {\n process = false;\n }\n } while (process);\n };\n\n /**\n * Creates a new State object by sanitizing the passed value.\n *\n * @name Drupal.states.State.sanitize\n *\n * @param {string|Drupal.states.State} state\n * A state object or the name of a state.\n *\n * @return {Drupal.states.state}\n * A state object.\n */\n states.State.sanitize = function (state) {\n if (state instanceof states.State) {\n return state;\n }\n else {\n return new states.State(state);\n }\n };\n\n /**\n * This list of aliases is used to normalize states and associates negated\n * names with their respective inverse state.\n *\n * @name Drupal.states.State.aliases\n */\n states.State.aliases = {\n enabled: '!disabled',\n invisible: '!visible',\n invalid: '!valid',\n untouched: '!touched',\n optional: '!required',\n filled: '!empty',\n unchecked: '!checked',\n irrelevant: '!relevant',\n expanded: '!collapsed',\n open: '!collapsed',\n closed: 'collapsed',\n readwrite: '!readonly'\n };\n\n states.State.prototype = {\n\n /**\n * @memberof Drupal.states.State#\n */\n invert: false,\n\n /**\n * Ensures that just using the state object returns the name.\n *\n * @memberof Drupal.states.State#\n *\n * @return {string}\n * The name of the state.\n */\n toString: function () {\n return this.name;\n }\n };\n\n /**\n * Global state change handlers. These are bound to \"document\" to cover all\n * elements whose state changes. Events sent to elements within the page\n * bubble up to these handlers. We use this system so that themes and modules\n * can override these state change handlers for particular parts of a page.\n */\n\n var $document = $(document);\n $document.on('state:disabled', function (e) {\n // Only act when this change was triggered by a dependency and not by the\n // element monitoring itself.\n if (e.trigger) {\n $(e.target)\n .prop('disabled', e.value)\n .closest('.js-form-item, .js-form-submit, .js-form-wrapper').toggleClass('form-disabled', e.value)\n .find('select, input, textarea').prop('disabled', e.value);\n\n // Note: WebKit nightlies don't reflect that change correctly.\n // See https://bugs.webkit.org/show_bug.cgi?id=23789\n }\n });\n\n $document.on('state:required', function (e) {\n if (e.trigger) {\n if (e.value) {\n var label = 'label' + (e.target.id ? '[for=' + e.target.id + ']' : '');\n var $label = $(e.target).attr({'required': 'required', 'aria-required': 'aria-required'}).closest('.js-form-item, .js-form-wrapper').find(label);\n // Avoids duplicate required markers on initialization.\n if (!$label.hasClass('js-form-required').length) {\n $label.addClass('js-form-required form-required');\n }\n }\n else {\n $(e.target).removeAttr('required aria-required').closest('.js-form-item, .js-form-wrapper').find('label.js-form-required').removeClass('js-form-required form-required');\n }\n }\n });\n\n $document.on('state:visible', function (e) {\n if (e.trigger) {\n $(e.target).closest('.js-form-item, .js-form-submit, .js-form-wrapper').toggle(e.value);\n }\n });\n\n $document.on('state:checked', function (e) {\n if (e.trigger) {\n $(e.target).prop('checked', e.value);\n }\n });\n\n $document.on('state:collapsed', function (e) {\n if (e.trigger) {\n if ($(e.target).is('[open]') === e.value) {\n $(e.target).find('> summary').trigger('click');\n }\n }\n });\n\n /**\n * These are helper functions implementing addition \"operators\" and don't\n * implement any logic that is particular to states.\n */\n\n /**\n * Bitwise AND with a third undefined state.\n *\n * @function Drupal.states~ternary\n *\n * @param {*} a\n * Value a.\n * @param {*} b\n * Value b\n *\n * @return {bool}\n * The result.\n */\n function ternary(a, b) {\n if (typeof a === 'undefined') {\n return b;\n }\n else if (typeof b === 'undefined') {\n return a;\n }\n else {\n return a && b;\n }\n }\n\n /**\n * Inverts a (if it's not undefined) when invertState is true.\n *\n * @function Drupal.states~invert\n *\n * @param {*} a\n * The value to maybe invert.\n * @param {bool} invertState\n * Whether to invert state or not.\n *\n * @return {bool}\n * The result.\n */\n function invert(a, invertState) {\n return (invertState && typeof a !== 'undefined') ? !a : a;\n }\n\n /**\n * Compares two values while ignoring undefined values.\n *\n * @function Drupal.states~compare\n *\n * @param {*} a\n * Value a.\n * @param {*} b\n * Value b.\n *\n * @return {bool}\n * The comparison result.\n */\n function compare(a, b) {\n if (a === b) {\n return typeof a === 'undefined' ? a : true;\n }\n else {\n return typeof a === 'undefined' || typeof b === 'undefined';\n }\n }\n\n})(jQuery, Drupal);\n"]} \ No newline at end of file diff --git a/core/misc/tabbingmanager.es6.js b/core/misc/tabbingmanager.es6.js new file mode 100644 index 0000000..134523f --- /dev/null +++ b/core/misc/tabbingmanager.es6.js @@ -0,0 +1,369 @@ +/** + * @file + * Manages page tabbing modifications made by modules. + */ + +/** + * Allow modules to respond to the constrain event. + * + * @event drupalTabbingConstrained + */ + +/** + * Allow modules to respond to the tabbingContext release event. + * + * @event drupalTabbingContextReleased + */ + +/** + * Allow modules to respond to the constrain event. + * + * @event drupalTabbingContextActivated + */ + +/** + * Allow modules to respond to the constrain event. + * + * @event drupalTabbingContextDeactivated + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Provides an API for managing page tabbing order modifications. + * + * @constructor Drupal~TabbingManager + */ + function TabbingManager() { + + /** + * Tabbing sets are stored as a stack. The active set is at the top of the + * stack. We use a JavaScript array as if it were a stack; we consider the + * first element to be the bottom and the last element to be the top. This + * allows us to use JavaScript's built-in Array.push() and Array.pop() + * methods. + * + * @type {Array.} + */ + this.stack = []; + } + + /** + * Add public methods to the TabbingManager class. + */ + $.extend(TabbingManager.prototype, /** @lends Drupal~TabbingManager# */{ + + /** + * Constrain tabbing to the specified set of elements only. + * + * Makes elements outside of the specified set of elements unreachable via + * the tab key. + * + * @param {jQuery} elements + * The set of elements to which tabbing should be constrained. Can also + * be a jQuery-compatible selector string. + * + * @return {Drupal~TabbingContext} + * The TabbingContext instance. + * + * @fires event:drupalTabbingConstrained + */ + constrain: function (elements) { + // Deactivate all tabbingContexts to prepare for the new constraint. A + // tabbingContext instance will only be reactivated if the stack is + // unwound to it in the _unwindStack() method. + var il = this.stack.length; + for (var i = 0; i < il; i++) { + this.stack[i].deactivate(); + } + + // The "active tabbing set" are the elements tabbing should be constrained + // to. + var $elements = $(elements).find(':tabbable').addBack(':tabbable'); + + var tabbingContext = new TabbingContext({ + // The level is the current height of the stack before this new + // tabbingContext is pushed on top of the stack. + level: this.stack.length, + $tabbableElements: $elements + }); + + this.stack.push(tabbingContext); + + // Activates the tabbingContext; this will manipulate the DOM to constrain + // tabbing. + tabbingContext.activate(); + + // Allow modules to respond to the constrain event. + $(document).trigger('drupalTabbingConstrained', tabbingContext); + + return tabbingContext; + }, + + /** + * Restores a former tabbingContext when an active one is released. + * + * The TabbingManager stack of tabbingContext instances will be unwound + * from the top-most released tabbingContext down to the first non-released + * tabbingContext instance. This non-released instance is then activated. + */ + release: function () { + // Unwind as far as possible: find the topmost non-released + // tabbingContext. + var toActivate = this.stack.length - 1; + while (toActivate >= 0 && this.stack[toActivate].released) { + toActivate--; + } + + // Delete all tabbingContexts after the to be activated one. They have + // already been deactivated, so their effect on the DOM has been reversed. + this.stack.splice(toActivate + 1); + + // Get topmost tabbingContext, if one exists, and activate it. + if (toActivate >= 0) { + this.stack[toActivate].activate(); + } + }, + + /** + * Makes all elements outside of the tabbingContext's set untabbable. + * + * Elements made untabbable have their original tabindex and autofocus + * values stored so that they might be restored later when this + * tabbingContext is deactivated. + * + * @param {Drupal~TabbingContext} tabbingContext + * The TabbingContext instance that has been activated. + */ + activate: function (tabbingContext) { + var $set = tabbingContext.$tabbableElements; + var level = tabbingContext.level; + // Determine which elements are reachable via tabbing by default. + var $disabledSet = $(':tabbable') + // Exclude elements of the active tabbing set. + .not($set); + // Set the disabled set on the tabbingContext. + tabbingContext.$disabledElements = $disabledSet; + // Record the tabindex for each element, so we can restore it later. + var il = $disabledSet.length; + for (var i = 0; i < il; i++) { + this.recordTabindex($disabledSet.eq(i), level); + } + // Make all tabbable elements outside of the active tabbing set + // unreachable. + $disabledSet + .prop('tabindex', -1) + .prop('autofocus', false); + + // Set focus on an element in the tabbingContext's set of tabbable + // elements. First, check if there is an element with an autofocus + // attribute. Select the last one from the DOM order. + var $hasFocus = $set.filter('[autofocus]').eq(-1); + // If no element in the tabbable set has an autofocus attribute, select + // the first element in the set. + if ($hasFocus.length === 0) { + $hasFocus = $set.eq(0); + } + $hasFocus.trigger('focus'); + }, + + /** + * Restores that tabbable state of a tabbingContext's disabled elements. + * + * Elements that were made untabbable have their original tabindex and + * autofocus values restored. + * + * @param {Drupal~TabbingContext} tabbingContext + * The TabbingContext instance that has been deactivated. + */ + deactivate: function (tabbingContext) { + var $set = tabbingContext.$disabledElements; + var level = tabbingContext.level; + var il = $set.length; + for (var i = 0; i < il; i++) { + this.restoreTabindex($set.eq(i), level); + } + }, + + /** + * Records the tabindex and autofocus values of an untabbable element. + * + * @param {jQuery} $el + * The set of elements that have been disabled. + * @param {number} level + * The stack level for which the tabindex attribute should be recorded. + */ + recordTabindex: function ($el, level) { + var tabInfo = $el.data('drupalOriginalTabIndices') || {}; + tabInfo[level] = { + tabindex: $el[0].getAttribute('tabindex'), + autofocus: $el[0].hasAttribute('autofocus') + }; + $el.data('drupalOriginalTabIndices', tabInfo); + }, + + /** + * Restores the tabindex and autofocus values of a reactivated element. + * + * @param {jQuery} $el + * The element that is being reactivated. + * @param {number} level + * The stack level for which the tabindex attribute should be restored. + */ + restoreTabindex: function ($el, level) { + var tabInfo = $el.data('drupalOriginalTabIndices'); + if (tabInfo && tabInfo[level]) { + var data = tabInfo[level]; + if (data.tabindex) { + $el[0].setAttribute('tabindex', data.tabindex); + } + // If the element did not have a tabindex at this stack level then + // remove it. + else { + $el[0].removeAttribute('tabindex'); + } + if (data.autofocus) { + $el[0].setAttribute('autofocus', 'autofocus'); + } + + // Clean up $.data. + if (level === 0) { + // Remove all data. + $el.removeData('drupalOriginalTabIndices'); + } + else { + // Remove the data for this stack level and higher. + var levelToDelete = level; + while (tabInfo.hasOwnProperty(levelToDelete)) { + delete tabInfo[levelToDelete]; + levelToDelete++; + } + $el.data('drupalOriginalTabIndices', tabInfo); + } + } + } + }); + + /** + * Stores a set of tabbable elements. + * + * This constraint can be removed with the release() method. + * + * @constructor Drupal~TabbingContext + * + * @param {object} options + * A set of initiating values + * @param {number} options.level + * The level in the TabbingManager's stack of this tabbingContext. + * @param {jQuery} options.$tabbableElements + * The DOM elements that should be reachable via the tab key when this + * tabbingContext is active. + * @param {jQuery} options.$disabledElements + * The DOM elements that should not be reachable via the tab key when this + * tabbingContext is active. + * @param {bool} options.released + * A released tabbingContext can never be activated again. It will be + * cleaned up when the TabbingManager unwinds its stack. + * @param {bool} options.active + * When true, the tabbable elements of this tabbingContext will be reachable + * via the tab key and the disabled elements will not. Only one + * tabbingContext can be active at a time. + */ + function TabbingContext(options) { + + $.extend(this, /** @lends Drupal~TabbingContext# */{ + + /** + * @type {?number} + */ + level: null, + + /** + * @type {jQuery} + */ + $tabbableElements: $(), + + /** + * @type {jQuery} + */ + $disabledElements: $(), + + /** + * @type {bool} + */ + released: false, + + /** + * @type {bool} + */ + active: false + }, options); + } + + /** + * Add public methods to the TabbingContext class. + */ + $.extend(TabbingContext.prototype, /** @lends Drupal~TabbingContext# */{ + + /** + * Releases this TabbingContext. + * + * Once a TabbingContext object is released, it can never be activated + * again. + * + * @fires event:drupalTabbingContextReleased + */ + release: function () { + if (!this.released) { + this.deactivate(); + this.released = true; + Drupal.tabbingManager.release(this); + // Allow modules to respond to the tabbingContext release event. + $(document).trigger('drupalTabbingContextReleased', this); + } + }, + + /** + * Activates this TabbingContext. + * + * @fires event:drupalTabbingContextActivated + */ + activate: function () { + // A released TabbingContext object can never be activated again. + if (!this.active && !this.released) { + this.active = true; + Drupal.tabbingManager.activate(this); + // Allow modules to respond to the constrain event. + $(document).trigger('drupalTabbingContextActivated', this); + } + }, + + /** + * Deactivates this TabbingContext. + * + * @fires event:drupalTabbingContextDeactivated + */ + deactivate: function () { + if (this.active) { + this.active = false; + Drupal.tabbingManager.deactivate(this); + // Allow modules to respond to the constrain event. + $(document).trigger('drupalTabbingContextDeactivated', this); + } + } + }); + + // Mark this behavior as processed on the first pass and return if it is + // already processed. + if (Drupal.tabbingManager) { + return; + } + + /** + * @type {Drupal~TabbingManager} + */ + Drupal.tabbingManager = new TabbingManager(); + +}(jQuery, Drupal)); diff --git a/core/misc/tabbingmanager.js b/core/misc/tabbingmanager.js index 134523f..13aa115 100644 --- a/core/misc/tabbingmanager.js +++ b/core/misc/tabbingmanager.js @@ -1,184 +1,73 @@ -/** - * @file - * Manages page tabbing modifications made by modules. - */ - -/** - * Allow modules to respond to the constrain event. - * - * @event drupalTabbingConstrained - */ - -/** - * Allow modules to respond to the tabbingContext release event. - * - * @event drupalTabbingContextReleased - */ - -/** - * Allow modules to respond to the constrain event. - * - * @event drupalTabbingContextActivated - */ - -/** - * Allow modules to respond to the constrain event. - * - * @event drupalTabbingContextDeactivated - */ +'use strict'; (function ($, Drupal) { 'use strict'; - /** - * Provides an API for managing page tabbing order modifications. - * - * @constructor Drupal~TabbingManager - */ function TabbingManager() { - - /** - * Tabbing sets are stored as a stack. The active set is at the top of the - * stack. We use a JavaScript array as if it were a stack; we consider the - * first element to be the bottom and the last element to be the top. This - * allows us to use JavaScript's built-in Array.push() and Array.pop() - * methods. - * - * @type {Array.} - */ this.stack = []; } - /** - * Add public methods to the TabbingManager class. - */ - $.extend(TabbingManager.prototype, /** @lends Drupal~TabbingManager# */{ - - /** - * Constrain tabbing to the specified set of elements only. - * - * Makes elements outside of the specified set of elements unreachable via - * the tab key. - * - * @param {jQuery} elements - * The set of elements to which tabbing should be constrained. Can also - * be a jQuery-compatible selector string. - * - * @return {Drupal~TabbingContext} - * The TabbingContext instance. - * - * @fires event:drupalTabbingConstrained - */ - constrain: function (elements) { - // Deactivate all tabbingContexts to prepare for the new constraint. A - // tabbingContext instance will only be reactivated if the stack is - // unwound to it in the _unwindStack() method. + $.extend(TabbingManager.prototype, { + constrain: function constrain(elements) { var il = this.stack.length; for (var i = 0; i < il; i++) { this.stack[i].deactivate(); } - // The "active tabbing set" are the elements tabbing should be constrained - // to. var $elements = $(elements).find(':tabbable').addBack(':tabbable'); var tabbingContext = new TabbingContext({ - // The level is the current height of the stack before this new - // tabbingContext is pushed on top of the stack. level: this.stack.length, $tabbableElements: $elements }); this.stack.push(tabbingContext); - // Activates the tabbingContext; this will manipulate the DOM to constrain - // tabbing. tabbingContext.activate(); - // Allow modules to respond to the constrain event. $(document).trigger('drupalTabbingConstrained', tabbingContext); return tabbingContext; }, - /** - * Restores a former tabbingContext when an active one is released. - * - * The TabbingManager stack of tabbingContext instances will be unwound - * from the top-most released tabbingContext down to the first non-released - * tabbingContext instance. This non-released instance is then activated. - */ - release: function () { - // Unwind as far as possible: find the topmost non-released - // tabbingContext. + release: function release() { var toActivate = this.stack.length - 1; while (toActivate >= 0 && this.stack[toActivate].released) { toActivate--; } - // Delete all tabbingContexts after the to be activated one. They have - // already been deactivated, so their effect on the DOM has been reversed. this.stack.splice(toActivate + 1); - // Get topmost tabbingContext, if one exists, and activate it. if (toActivate >= 0) { this.stack[toActivate].activate(); } }, - /** - * Makes all elements outside of the tabbingContext's set untabbable. - * - * Elements made untabbable have their original tabindex and autofocus - * values stored so that they might be restored later when this - * tabbingContext is deactivated. - * - * @param {Drupal~TabbingContext} tabbingContext - * The TabbingContext instance that has been activated. - */ - activate: function (tabbingContext) { + activate: function activate(tabbingContext) { var $set = tabbingContext.$tabbableElements; var level = tabbingContext.level; - // Determine which elements are reachable via tabbing by default. - var $disabledSet = $(':tabbable') - // Exclude elements of the active tabbing set. - .not($set); - // Set the disabled set on the tabbingContext. + + var $disabledSet = $(':tabbable').not($set); + tabbingContext.$disabledElements = $disabledSet; - // Record the tabindex for each element, so we can restore it later. + var il = $disabledSet.length; for (var i = 0; i < il; i++) { this.recordTabindex($disabledSet.eq(i), level); } - // Make all tabbable elements outside of the active tabbing set - // unreachable. - $disabledSet - .prop('tabindex', -1) - .prop('autofocus', false); - - // Set focus on an element in the tabbingContext's set of tabbable - // elements. First, check if there is an element with an autofocus - // attribute. Select the last one from the DOM order. + + $disabledSet.prop('tabindex', -1).prop('autofocus', false); + var $hasFocus = $set.filter('[autofocus]').eq(-1); - // If no element in the tabbable set has an autofocus attribute, select - // the first element in the set. + if ($hasFocus.length === 0) { $hasFocus = $set.eq(0); } $hasFocus.trigger('focus'); }, - /** - * Restores that tabbable state of a tabbingContext's disabled elements. - * - * Elements that were made untabbable have their original tabindex and - * autofocus values restored. - * - * @param {Drupal~TabbingContext} tabbingContext - * The TabbingContext instance that has been deactivated. - */ - deactivate: function (tabbingContext) { + deactivate: function deactivate(tabbingContext) { var $set = tabbingContext.$disabledElements; var level = tabbingContext.level; var il = $set.length; @@ -187,15 +76,7 @@ } }, - /** - * Records the tabindex and autofocus values of an untabbable element. - * - * @param {jQuery} $el - * The set of elements that have been disabled. - * @param {number} level - * The stack level for which the tabindex attribute should be recorded. - */ - recordTabindex: function ($el, level) { + recordTabindex: function recordTabindex($el, level) { var tabInfo = $el.data('drupalOriginalTabIndices') || {}; tabInfo[level] = { tabindex: $el[0].getAttribute('tabindex'), @@ -204,37 +85,22 @@ $el.data('drupalOriginalTabIndices', tabInfo); }, - /** - * Restores the tabindex and autofocus values of a reactivated element. - * - * @param {jQuery} $el - * The element that is being reactivated. - * @param {number} level - * The stack level for which the tabindex attribute should be restored. - */ - restoreTabindex: function ($el, level) { + restoreTabindex: function restoreTabindex($el, level) { var tabInfo = $el.data('drupalOriginalTabIndices'); if (tabInfo && tabInfo[level]) { var data = tabInfo[level]; if (data.tabindex) { $el[0].setAttribute('tabindex', data.tabindex); - } - // If the element did not have a tabindex at this stack level then - // remove it. - else { - $el[0].removeAttribute('tabindex'); - } + } else { + $el[0].removeAttribute('tabindex'); + } if (data.autofocus) { $el[0].setAttribute('autofocus', 'autofocus'); } - // Clean up $.data. if (level === 0) { - // Remove all data. $el.removeData('drupalOriginalTabIndices'); - } - else { - // Remove the data for this stack level and higher. + } else { var levelToDelete = level; while (tabInfo.hasOwnProperty(levelToDelete)) { delete tabInfo[levelToDelete]; @@ -246,124 +112,56 @@ } }); - /** - * Stores a set of tabbable elements. - * - * This constraint can be removed with the release() method. - * - * @constructor Drupal~TabbingContext - * - * @param {object} options - * A set of initiating values - * @param {number} options.level - * The level in the TabbingManager's stack of this tabbingContext. - * @param {jQuery} options.$tabbableElements - * The DOM elements that should be reachable via the tab key when this - * tabbingContext is active. - * @param {jQuery} options.$disabledElements - * The DOM elements that should not be reachable via the tab key when this - * tabbingContext is active. - * @param {bool} options.released - * A released tabbingContext can never be activated again. It will be - * cleaned up when the TabbingManager unwinds its stack. - * @param {bool} options.active - * When true, the tabbable elements of this tabbingContext will be reachable - * via the tab key and the disabled elements will not. Only one - * tabbingContext can be active at a time. - */ function TabbingContext(options) { - $.extend(this, /** @lends Drupal~TabbingContext# */{ - - /** - * @type {?number} - */ + $.extend(this, { level: null, - /** - * @type {jQuery} - */ $tabbableElements: $(), - /** - * @type {jQuery} - */ $disabledElements: $(), - /** - * @type {bool} - */ released: false, - /** - * @type {bool} - */ active: false }, options); } - /** - * Add public methods to the TabbingContext class. - */ - $.extend(TabbingContext.prototype, /** @lends Drupal~TabbingContext# */{ - - /** - * Releases this TabbingContext. - * - * Once a TabbingContext object is released, it can never be activated - * again. - * - * @fires event:drupalTabbingContextReleased - */ - release: function () { + $.extend(TabbingContext.prototype, { + release: function release() { if (!this.released) { this.deactivate(); this.released = true; Drupal.tabbingManager.release(this); - // Allow modules to respond to the tabbingContext release event. + $(document).trigger('drupalTabbingContextReleased', this); } }, - /** - * Activates this TabbingContext. - * - * @fires event:drupalTabbingContextActivated - */ - activate: function () { - // A released TabbingContext object can never be activated again. + activate: function activate() { if (!this.active && !this.released) { this.active = true; Drupal.tabbingManager.activate(this); - // Allow modules to respond to the constrain event. + $(document).trigger('drupalTabbingContextActivated', this); } }, - /** - * Deactivates this TabbingContext. - * - * @fires event:drupalTabbingContextDeactivated - */ - deactivate: function () { + deactivate: function deactivate() { if (this.active) { this.active = false; Drupal.tabbingManager.deactivate(this); - // Allow modules to respond to the constrain event. + $(document).trigger('drupalTabbingContextDeactivated', this); } } }); - // Mark this behavior as processed on the first pass and return if it is - // already processed. if (Drupal.tabbingManager) { return; } - /** - * @type {Drupal~TabbingManager} - */ Drupal.tabbingManager = new TabbingManager(); +})(jQuery, Drupal); -}(jQuery, Drupal)); +//# sourceMappingURL=tabbingmanager.js.map \ No newline at end of file diff --git a/core/misc/tabbingmanager.js.map b/core/misc/tabbingmanager.js.map new file mode 100644 index 0000000..03c527d --- /dev/null +++ b/core/misc/tabbingmanager.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["tabbingmanager.es6.js"],"names":["$","Drupal","TabbingManager","stack","extend","prototype","constrain","elements","il","length","i","deactivate","$elements","find","addBack","tabbingContext","TabbingContext","level","$tabbableElements","push","activate","document","trigger","release","toActivate","released","splice","$set","$disabledSet","not","$disabledElements","recordTabindex","eq","prop","$hasFocus","filter","restoreTabindex","$el","tabInfo","data","tabindex","getAttribute","autofocus","hasAttribute","setAttribute","removeAttribute","removeData","levelToDelete","hasOwnProperty","options","active","tabbingManager","jQuery"],"mappings":"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","file":"tabbingmanager.es6.js","sourcesContent":["/**\n * @file\n * Manages page tabbing modifications made by modules.\n */\n\n/**\n * Allow modules to respond to the constrain event.\n *\n * @event drupalTabbingConstrained\n */\n\n/**\n * Allow modules to respond to the tabbingContext release event.\n *\n * @event drupalTabbingContextReleased\n */\n\n/**\n * Allow modules to respond to the constrain event.\n *\n * @event drupalTabbingContextActivated\n */\n\n/**\n * Allow modules to respond to the constrain event.\n *\n * @event drupalTabbingContextDeactivated\n */\n\n(function ($, Drupal) {\n\n 'use strict';\n\n /**\n * Provides an API for managing page tabbing order modifications.\n *\n * @constructor Drupal~TabbingManager\n */\n function TabbingManager() {\n\n /**\n * Tabbing sets are stored as a stack. The active set is at the top of the\n * stack. We use a JavaScript array as if it were a stack; we consider the\n * first element to be the bottom and the last element to be the top. This\n * allows us to use JavaScript's built-in Array.push() and Array.pop()\n * methods.\n *\n * @type {Array.}\n */\n this.stack = [];\n }\n\n /**\n * Add public methods to the TabbingManager class.\n */\n $.extend(TabbingManager.prototype, /** @lends Drupal~TabbingManager# */{\n\n /**\n * Constrain tabbing to the specified set of elements only.\n *\n * Makes elements outside of the specified set of elements unreachable via\n * the tab key.\n *\n * @param {jQuery} elements\n * The set of elements to which tabbing should be constrained. Can also\n * be a jQuery-compatible selector string.\n *\n * @return {Drupal~TabbingContext}\n * The TabbingContext instance.\n *\n * @fires event:drupalTabbingConstrained\n */\n constrain: function (elements) {\n // Deactivate all tabbingContexts to prepare for the new constraint. A\n // tabbingContext instance will only be reactivated if the stack is\n // unwound to it in the _unwindStack() method.\n var il = this.stack.length;\n for (var i = 0; i < il; i++) {\n this.stack[i].deactivate();\n }\n\n // The \"active tabbing set\" are the elements tabbing should be constrained\n // to.\n var $elements = $(elements).find(':tabbable').addBack(':tabbable');\n\n var tabbingContext = new TabbingContext({\n // The level is the current height of the stack before this new\n // tabbingContext is pushed on top of the stack.\n level: this.stack.length,\n $tabbableElements: $elements\n });\n\n this.stack.push(tabbingContext);\n\n // Activates the tabbingContext; this will manipulate the DOM to constrain\n // tabbing.\n tabbingContext.activate();\n\n // Allow modules to respond to the constrain event.\n $(document).trigger('drupalTabbingConstrained', tabbingContext);\n\n return tabbingContext;\n },\n\n /**\n * Restores a former tabbingContext when an active one is released.\n *\n * The TabbingManager stack of tabbingContext instances will be unwound\n * from the top-most released tabbingContext down to the first non-released\n * tabbingContext instance. This non-released instance is then activated.\n */\n release: function () {\n // Unwind as far as possible: find the topmost non-released\n // tabbingContext.\n var toActivate = this.stack.length - 1;\n while (toActivate >= 0 && this.stack[toActivate].released) {\n toActivate--;\n }\n\n // Delete all tabbingContexts after the to be activated one. They have\n // already been deactivated, so their effect on the DOM has been reversed.\n this.stack.splice(toActivate + 1);\n\n // Get topmost tabbingContext, if one exists, and activate it.\n if (toActivate >= 0) {\n this.stack[toActivate].activate();\n }\n },\n\n /**\n * Makes all elements outside of the tabbingContext's set untabbable.\n *\n * Elements made untabbable have their original tabindex and autofocus\n * values stored so that they might be restored later when this\n * tabbingContext is deactivated.\n *\n * @param {Drupal~TabbingContext} tabbingContext\n * The TabbingContext instance that has been activated.\n */\n activate: function (tabbingContext) {\n var $set = tabbingContext.$tabbableElements;\n var level = tabbingContext.level;\n // Determine which elements are reachable via tabbing by default.\n var $disabledSet = $(':tabbable')\n // Exclude elements of the active tabbing set.\n .not($set);\n // Set the disabled set on the tabbingContext.\n tabbingContext.$disabledElements = $disabledSet;\n // Record the tabindex for each element, so we can restore it later.\n var il = $disabledSet.length;\n for (var i = 0; i < il; i++) {\n this.recordTabindex($disabledSet.eq(i), level);\n }\n // Make all tabbable elements outside of the active tabbing set\n // unreachable.\n $disabledSet\n .prop('tabindex', -1)\n .prop('autofocus', false);\n\n // Set focus on an element in the tabbingContext's set of tabbable\n // elements. First, check if there is an element with an autofocus\n // attribute. Select the last one from the DOM order.\n var $hasFocus = $set.filter('[autofocus]').eq(-1);\n // If no element in the tabbable set has an autofocus attribute, select\n // the first element in the set.\n if ($hasFocus.length === 0) {\n $hasFocus = $set.eq(0);\n }\n $hasFocus.trigger('focus');\n },\n\n /**\n * Restores that tabbable state of a tabbingContext's disabled elements.\n *\n * Elements that were made untabbable have their original tabindex and\n * autofocus values restored.\n *\n * @param {Drupal~TabbingContext} tabbingContext\n * The TabbingContext instance that has been deactivated.\n */\n deactivate: function (tabbingContext) {\n var $set = tabbingContext.$disabledElements;\n var level = tabbingContext.level;\n var il = $set.length;\n for (var i = 0; i < il; i++) {\n this.restoreTabindex($set.eq(i), level);\n }\n },\n\n /**\n * Records the tabindex and autofocus values of an untabbable element.\n *\n * @param {jQuery} $el\n * The set of elements that have been disabled.\n * @param {number} level\n * The stack level for which the tabindex attribute should be recorded.\n */\n recordTabindex: function ($el, level) {\n var tabInfo = $el.data('drupalOriginalTabIndices') || {};\n tabInfo[level] = {\n tabindex: $el[0].getAttribute('tabindex'),\n autofocus: $el[0].hasAttribute('autofocus')\n };\n $el.data('drupalOriginalTabIndices', tabInfo);\n },\n\n /**\n * Restores the tabindex and autofocus values of a reactivated element.\n *\n * @param {jQuery} $el\n * The element that is being reactivated.\n * @param {number} level\n * The stack level for which the tabindex attribute should be restored.\n */\n restoreTabindex: function ($el, level) {\n var tabInfo = $el.data('drupalOriginalTabIndices');\n if (tabInfo && tabInfo[level]) {\n var data = tabInfo[level];\n if (data.tabindex) {\n $el[0].setAttribute('tabindex', data.tabindex);\n }\n // If the element did not have a tabindex at this stack level then\n // remove it.\n else {\n $el[0].removeAttribute('tabindex');\n }\n if (data.autofocus) {\n $el[0].setAttribute('autofocus', 'autofocus');\n }\n\n // Clean up $.data.\n if (level === 0) {\n // Remove all data.\n $el.removeData('drupalOriginalTabIndices');\n }\n else {\n // Remove the data for this stack level and higher.\n var levelToDelete = level;\n while (tabInfo.hasOwnProperty(levelToDelete)) {\n delete tabInfo[levelToDelete];\n levelToDelete++;\n }\n $el.data('drupalOriginalTabIndices', tabInfo);\n }\n }\n }\n });\n\n /**\n * Stores a set of tabbable elements.\n *\n * This constraint can be removed with the release() method.\n *\n * @constructor Drupal~TabbingContext\n *\n * @param {object} options\n * A set of initiating values\n * @param {number} options.level\n * The level in the TabbingManager's stack of this tabbingContext.\n * @param {jQuery} options.$tabbableElements\n * The DOM elements that should be reachable via the tab key when this\n * tabbingContext is active.\n * @param {jQuery} options.$disabledElements\n * The DOM elements that should not be reachable via the tab key when this\n * tabbingContext is active.\n * @param {bool} options.released\n * A released tabbingContext can never be activated again. It will be\n * cleaned up when the TabbingManager unwinds its stack.\n * @param {bool} options.active\n * When true, the tabbable elements of this tabbingContext will be reachable\n * via the tab key and the disabled elements will not. Only one\n * tabbingContext can be active at a time.\n */\n function TabbingContext(options) {\n\n $.extend(this, /** @lends Drupal~TabbingContext# */{\n\n /**\n * @type {?number}\n */\n level: null,\n\n /**\n * @type {jQuery}\n */\n $tabbableElements: $(),\n\n /**\n * @type {jQuery}\n */\n $disabledElements: $(),\n\n /**\n * @type {bool}\n */\n released: false,\n\n /**\n * @type {bool}\n */\n active: false\n }, options);\n }\n\n /**\n * Add public methods to the TabbingContext class.\n */\n $.extend(TabbingContext.prototype, /** @lends Drupal~TabbingContext# */{\n\n /**\n * Releases this TabbingContext.\n *\n * Once a TabbingContext object is released, it can never be activated\n * again.\n *\n * @fires event:drupalTabbingContextReleased\n */\n release: function () {\n if (!this.released) {\n this.deactivate();\n this.released = true;\n Drupal.tabbingManager.release(this);\n // Allow modules to respond to the tabbingContext release event.\n $(document).trigger('drupalTabbingContextReleased', this);\n }\n },\n\n /**\n * Activates this TabbingContext.\n *\n * @fires event:drupalTabbingContextActivated\n */\n activate: function () {\n // A released TabbingContext object can never be activated again.\n if (!this.active && !this.released) {\n this.active = true;\n Drupal.tabbingManager.activate(this);\n // Allow modules to respond to the constrain event.\n $(document).trigger('drupalTabbingContextActivated', this);\n }\n },\n\n /**\n * Deactivates this TabbingContext.\n *\n * @fires event:drupalTabbingContextDeactivated\n */\n deactivate: function () {\n if (this.active) {\n this.active = false;\n Drupal.tabbingManager.deactivate(this);\n // Allow modules to respond to the constrain event.\n $(document).trigger('drupalTabbingContextDeactivated', this);\n }\n }\n });\n\n // Mark this behavior as processed on the first pass and return if it is\n // already processed.\n if (Drupal.tabbingManager) {\n return;\n }\n\n /**\n * @type {Drupal~TabbingManager}\n */\n Drupal.tabbingManager = new TabbingManager();\n\n}(jQuery, Drupal));\n"]} \ No newline at end of file diff --git a/core/misc/tabledrag.es6.js b/core/misc/tabledrag.es6.js new file mode 100644 index 0000000..395d092 --- /dev/null +++ b/core/misc/tabledrag.es6.js @@ -0,0 +1,1557 @@ +/** + * @file + * Provide dragging capabilities to admin uis. + */ + +/** + * Triggers when weights columns are toggled. + * + * @event columnschange + */ + +(function ($, Drupal, drupalSettings) { + + 'use strict'; + + /** + * Store the state of weight columns display for all tables. + * + * Default value is to hide weight columns. + */ + var showWeight = JSON.parse(localStorage.getItem('Drupal.tableDrag.showWeight')); + + /** + * Drag and drop table rows with field manipulation. + * + * Using the drupal_attach_tabledrag() function, any table with weights or + * parent relationships may be made into draggable tables. Columns containing + * a field may optionally be hidden, providing a better user experience. + * + * Created tableDrag instances may be modified with custom behaviors by + * overriding the .onDrag, .onDrop, .row.onSwap, and .row.onIndent methods. + * See blocks.js for an example of adding additional functionality to + * tableDrag. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.tableDrag = { + attach: function (context, settings) { + function initTableDrag(table, base) { + if (table.length) { + // Create the new tableDrag instance. Save in the Drupal variable + // to allow other scripts access to the object. + Drupal.tableDrag[base] = new Drupal.tableDrag(table[0], settings.tableDrag[base]); + } + } + + for (var base in settings.tableDrag) { + if (settings.tableDrag.hasOwnProperty(base)) { + initTableDrag($(context).find('#' + base).once('tabledrag'), base); + } + } + } + }; + + /** + * Provides table and field manipulation. + * + * @constructor + * + * @param {HTMLElement} table + * DOM object for the table to be made draggable. + * @param {object} tableSettings + * Settings for the table added via drupal_add_dragtable(). + */ + Drupal.tableDrag = function (table, tableSettings) { + var self = this; + var $table = $(table); + + /** + * @type {jQuery} + */ + this.$table = $(table); + + /** + * + * @type {HTMLElement} + */ + this.table = table; + + /** + * @type {object} + */ + this.tableSettings = tableSettings; + + /** + * Used to hold information about a current drag operation. + * + * @type {?HTMLElement} + */ + this.dragObject = null; + + /** + * Provides operations for row manipulation. + * + * @type {?HTMLElement} + */ + this.rowObject = null; + + /** + * Remember the previous element. + * + * @type {?HTMLElement} + */ + this.oldRowElement = null; + + /** + * Used to determine up or down direction from last mouse move. + * + * @type {number} + */ + this.oldY = 0; + + /** + * Whether anything in the entire table has changed. + * + * @type {bool} + */ + this.changed = false; + + /** + * Maximum amount of allowed parenting. + * + * @type {number} + */ + this.maxDepth = 0; + + /** + * Direction of the table. + * + * @type {number} + */ + this.rtl = $(this.table).css('direction') === 'rtl' ? -1 : 1; + + /** + * + * @type {bool} + */ + this.striping = $(this.table).data('striping') === 1; + + /** + * Configure the scroll settings. + * + * @type {object} + * + * @prop {number} amount + * @prop {number} interval + * @prop {number} trigger + */ + this.scrollSettings = {amount: 4, interval: 50, trigger: 70}; + + /** + * + * @type {?number} + */ + this.scrollInterval = null; + + /** + * + * @type {number} + */ + this.scrollY = 0; + + /** + * + * @type {number} + */ + this.windowHeight = 0; + + /** + * Check this table's settings for parent relationships. + * + * For efficiency, large sections of code can be skipped if we don't need to + * track horizontal movement and indentations. + * + * @type {bool} + */ + this.indentEnabled = false; + for (var group in tableSettings) { + if (tableSettings.hasOwnProperty(group)) { + for (var n in tableSettings[group]) { + if (tableSettings[group].hasOwnProperty(n)) { + if (tableSettings[group][n].relationship === 'parent') { + this.indentEnabled = true; + } + if (tableSettings[group][n].limit > 0) { + this.maxDepth = tableSettings[group][n].limit; + } + } + } + } + } + if (this.indentEnabled) { + + /** + * Total width of indents, set in makeDraggable. + * + * @type {number} + */ + this.indentCount = 1; + // Find the width of indentations to measure mouse movements against. + // Because the table doesn't need to start with any indentations, we + // manually append 2 indentations in the first draggable row, measure + // the offset, then remove. + var indent = Drupal.theme('tableDragIndentation'); + var testRow = $('').addClass('draggable').appendTo(table); + var testCell = $('').addClass('draggable').appendTo(table); var testCell = $('').addClass('draggable').appendTo(table);\n var testCell = $('
    To retrieve the HTML for text that should be emphasized and + * displayed as a placeholder inside a sentence.To retrieve the HTML for text that should be emphasized and - * displayed as a placeholder inside a sentence.To retrieve the HTML for text that should be emphasized and\n * displayed as a placeholder inside a sentence.
    ').appendTo(testRow).prepend(indent).prepend(indent); + var $indentation = testCell.find('.js-indentation'); + + /** + * + * @type {number} + */ + this.indentAmount = $indentation.get(1).offsetLeft - $indentation.get(0).offsetLeft; + testRow.remove(); + } + + // Make each applicable row draggable. + // Match immediate children of the parent element to allow nesting. + $table.find('> tr.draggable, > tbody > tr.draggable').each(function () { self.makeDraggable(this); }); + + // Add a link before the table for users to show or hide weight columns. + $table.before($('') + .attr('title', Drupal.t('Re-order rows by numerical weight instead of dragging.')) + .on('click', $.proxy(function (e) { + e.preventDefault(); + this.toggleColumns(); + }, this)) + .wrap('
    ') + .parent() + ); + + // Initialize the specified columns (for example, weight or parent columns) + // to show or hide according to user preference. This aids accessibility + // so that, e.g., screen reader users can choose to enter weight values and + // manipulate form elements directly, rather than using drag-and-drop.. + self.initColumns(); + + // Add event bindings to the document. The self variable is passed along + // as event handlers do not have direct access to the tableDrag object. + $(document).on('touchmove', function (event) { return self.dragRow(event.originalEvent.touches[0], self); }); + $(document).on('touchend', function (event) { return self.dropRow(event.originalEvent.touches[0], self); }); + $(document).on('mousemove pointermove', function (event) { return self.dragRow(event, self); }); + $(document).on('mouseup pointerup', function (event) { return self.dropRow(event, self); }); + + // React to localStorage event showing or hiding weight columns. + $(window).on('storage', $.proxy(function (e) { + // Only react to 'Drupal.tableDrag.showWeight' value change. + if (e.originalEvent.key === 'Drupal.tableDrag.showWeight') { + // This was changed in another window, get the new value for this + // window. + showWeight = JSON.parse(e.originalEvent.newValue); + this.displayColumns(showWeight); + } + }, this)); + }; + + /** + * Initialize columns containing form elements to be hidden by default. + * + * Identify and mark each cell with a CSS class so we can easily toggle + * show/hide it. Finally, hide columns if user does not have a + * 'Drupal.tableDrag.showWeight' localStorage value. + */ + Drupal.tableDrag.prototype.initColumns = function () { + var $table = this.$table; + var hidden; + var cell; + var columnIndex; + for (var group in this.tableSettings) { + if (this.tableSettings.hasOwnProperty(group)) { + + // Find the first field in this group. + for (var d in this.tableSettings[group]) { + if (this.tableSettings[group].hasOwnProperty(d)) { + var field = $table.find('.' + this.tableSettings[group][d].target).eq(0); + if (field.length && this.tableSettings[group][d].hidden) { + hidden = this.tableSettings[group][d].hidden; + cell = field.closest('td'); + break; + } + } + } + + // Mark the column containing this field so it can be hidden. + if (hidden && cell[0]) { + // Add 1 to our indexes. The nth-child selector is 1 based, not 0 + // based. Match immediate children of the parent element to allow + // nesting. + columnIndex = cell.parent().find('> td').index(cell.get(0)) + 1; + $table.find('> thead > tr, > tbody > tr, > tr').each(this.addColspanClass(columnIndex)); + } + } + } + this.displayColumns(showWeight); + }; + + /** + * Mark cells that have colspan. + * + * In order to adjust the colspan instead of hiding them altogether. + * + * @param {number} columnIndex + * The column index to add colspan class to. + * + * @return {function} + * Function to add colspan class. + */ + Drupal.tableDrag.prototype.addColspanClass = function (columnIndex) { + return function () { + // Get the columnIndex and adjust for any colspans in this row. + var $row = $(this); + var index = columnIndex; + var cells = $row.children(); + var cell; + cells.each(function (n) { + if (n < index && this.colSpan && this.colSpan > 1) { + index -= this.colSpan - 1; + } + }); + if (index > 0) { + cell = cells.filter(':nth-child(' + index + ')'); + if (cell[0].colSpan && cell[0].colSpan > 1) { + // If this cell has a colspan, mark it so we can reduce the colspan. + cell.addClass('tabledrag-has-colspan'); + } + else { + // Mark this cell so we can hide it. + cell.addClass('tabledrag-hide'); + } + } + }; + }; + + /** + * Hide or display weight columns. Triggers an event on change. + * + * @fires event:columnschange + * + * @param {bool} displayWeight + * 'true' will show weight columns. + */ + Drupal.tableDrag.prototype.displayColumns = function (displayWeight) { + if (displayWeight) { + this.showColumns(); + } + // Default action is to hide columns. + else { + this.hideColumns(); + } + // Trigger an event to allow other scripts to react to this display change. + // Force the extra parameter as a bool. + $('table').findOnce('tabledrag').trigger('columnschange', !!displayWeight); + }; + + /** + * Toggle the weight column depending on 'showWeight' value. + * + * Store only default override. + */ + Drupal.tableDrag.prototype.toggleColumns = function () { + showWeight = !showWeight; + this.displayColumns(showWeight); + if (showWeight) { + // Save default override. + localStorage.setItem('Drupal.tableDrag.showWeight', showWeight); + } + else { + // Reset the value to its default. + localStorage.removeItem('Drupal.tableDrag.showWeight'); + } + }; + + /** + * Hide the columns containing weight/parent form elements. + * + * Undo showColumns(). + */ + Drupal.tableDrag.prototype.hideColumns = function () { + var $tables = $('table').findOnce('tabledrag'); + // Hide weight/parent cells and headers. + $tables.find('.tabledrag-hide').css('display', 'none'); + // Show TableDrag handles. + $tables.find('.tabledrag-handle').css('display', ''); + // Reduce the colspan of any effected multi-span columns. + $tables.find('.tabledrag-has-colspan').each(function () { + this.colSpan = this.colSpan - 1; + }); + // Change link text. + $('.tabledrag-toggle-weight').text(Drupal.t('Show row weights')); + }; + + /** + * Show the columns containing weight/parent form elements. + * + * Undo hideColumns(). + */ + Drupal.tableDrag.prototype.showColumns = function () { + var $tables = $('table').findOnce('tabledrag'); + // Show weight/parent cells and headers. + $tables.find('.tabledrag-hide').css('display', ''); + // Hide TableDrag handles. + $tables.find('.tabledrag-handle').css('display', 'none'); + // Increase the colspan for any columns where it was previously reduced. + $tables.find('.tabledrag-has-colspan').each(function () { + this.colSpan = this.colSpan + 1; + }); + // Change link text. + $('.tabledrag-toggle-weight').text(Drupal.t('Hide row weights')); + }; + + /** + * Find the target used within a particular row and group. + * + * @param {string} group + * Group selector. + * @param {HTMLElement} row + * The row HTML element. + * + * @return {object} + * The table row settings. + */ + Drupal.tableDrag.prototype.rowSettings = function (group, row) { + var field = $(row).find('.' + group); + var tableSettingsGroup = this.tableSettings[group]; + for (var delta in tableSettingsGroup) { + if (tableSettingsGroup.hasOwnProperty(delta)) { + var targetClass = tableSettingsGroup[delta].target; + if (field.is('.' + targetClass)) { + // Return a copy of the row settings. + var rowSettings = {}; + for (var n in tableSettingsGroup[delta]) { + if (tableSettingsGroup[delta].hasOwnProperty(n)) { + rowSettings[n] = tableSettingsGroup[delta][n]; + } + } + return rowSettings; + } + } + } + }; + + /** + * Take an item and add event handlers to make it become draggable. + * + * @param {HTMLElement} item + * The item to add event handlers to. + */ + Drupal.tableDrag.prototype.makeDraggable = function (item) { + var self = this; + var $item = $(item); + // Add a class to the title link. + $item.find('td:first-of-type').find('a').addClass('menu-item__link'); + // Create the handle. + var handle = $('
     
    ').attr('title', Drupal.t('Drag to re-order')); + // Insert the handle after indentations (if any). + var $indentationLast = $item.find('td:first-of-type').find('.js-indentation').eq(-1); + if ($indentationLast.length) { + $indentationLast.after(handle); + // Update the total width of indentation in this entire table. + self.indentCount = Math.max($item.find('.js-indentation').length, self.indentCount); + } + else { + $item.find('td').eq(0).prepend(handle); + } + + handle.on('mousedown touchstart pointerdown', function (event) { + event.preventDefault(); + if (event.originalEvent.type === 'touchstart') { + event = event.originalEvent.touches[0]; + } + self.dragStart(event, self, item); + }); + + // Prevent the anchor tag from jumping us to the top of the page. + handle.on('click', function (e) { + e.preventDefault(); + }); + + // Set blur cleanup when a handle is focused. + handle.on('focus', function () { + self.safeBlur = true; + }); + + // On blur, fire the same function as a touchend/mouseup. This is used to + // update values after a row has been moved through the keyboard support. + handle.on('blur', function (event) { + if (self.rowObject && self.safeBlur) { + self.dropRow(event, self); + } + }); + + // Add arrow-key support to the handle. + handle.on('keydown', function (event) { + // If a rowObject doesn't yet exist and this isn't the tab key. + if (event.keyCode !== 9 && !self.rowObject) { + self.rowObject = new self.row(item, 'keyboard', self.indentEnabled, self.maxDepth, true); + } + + var keyChange = false; + var groupHeight; + + /* eslint-disable no-fallthrough */ + + switch (event.keyCode) { + // Left arrow. + case 37: + // Safari left arrow. + case 63234: + keyChange = true; + self.rowObject.indent(-1 * self.rtl); + break; + + // Up arrow. + case 38: + // Safari up arrow. + case 63232: + var $previousRow = $(self.rowObject.element).prev('tr:first-of-type'); + var previousRow = $previousRow.get(0); + while (previousRow && $previousRow.is(':hidden')) { + $previousRow = $(previousRow).prev('tr:first-of-type'); + previousRow = $previousRow.get(0); + } + if (previousRow) { + // Do not allow the onBlur cleanup. + self.safeBlur = false; + self.rowObject.direction = 'up'; + keyChange = true; + + if ($(item).is('.tabledrag-root')) { + // Swap with the previous top-level row. + groupHeight = 0; + while (previousRow && $previousRow.find('.js-indentation').length) { + $previousRow = $(previousRow).prev('tr:first-of-type'); + previousRow = $previousRow.get(0); + groupHeight += $previousRow.is(':hidden') ? 0 : previousRow.offsetHeight; + } + if (previousRow) { + self.rowObject.swap('before', previousRow); + // No need to check for indentation, 0 is the only valid one. + window.scrollBy(0, -groupHeight); + } + } + else if (self.table.tBodies[0].rows[0] !== previousRow || $previousRow.is('.draggable')) { + // Swap with the previous row (unless previous row is the first + // one and undraggable). + self.rowObject.swap('before', previousRow); + self.rowObject.interval = null; + self.rowObject.indent(0); + window.scrollBy(0, -parseInt(item.offsetHeight, 10)); + } + // Regain focus after the DOM manipulation. + handle.trigger('focus'); + } + break; + + // Right arrow. + case 39: + // Safari right arrow. + case 63235: + keyChange = true; + self.rowObject.indent(self.rtl); + break; + + // Down arrow. + case 40: + // Safari down arrow. + case 63233: + var $nextRow = $(self.rowObject.group).eq(-1).next('tr:first-of-type'); + var nextRow = $nextRow.get(0); + while (nextRow && $nextRow.is(':hidden')) { + $nextRow = $(nextRow).next('tr:first-of-type'); + nextRow = $nextRow.get(0); + } + if (nextRow) { + // Do not allow the onBlur cleanup. + self.safeBlur = false; + self.rowObject.direction = 'down'; + keyChange = true; + + if ($(item).is('.tabledrag-root')) { + // Swap with the next group (necessarily a top-level one). + groupHeight = 0; + var nextGroup = new self.row(nextRow, 'keyboard', self.indentEnabled, self.maxDepth, false); + if (nextGroup) { + $(nextGroup.group).each(function () { + groupHeight += $(this).is(':hidden') ? 0 : this.offsetHeight; + }); + var nextGroupRow = $(nextGroup.group).eq(-1).get(0); + self.rowObject.swap('after', nextGroupRow); + // No need to check for indentation, 0 is the only valid one. + window.scrollBy(0, parseInt(groupHeight, 10)); + } + } + else { + // Swap with the next row. + self.rowObject.swap('after', nextRow); + self.rowObject.interval = null; + self.rowObject.indent(0); + window.scrollBy(0, parseInt(item.offsetHeight, 10)); + } + // Regain focus after the DOM manipulation. + handle.trigger('focus'); + } + break; + } + + /* eslint-enable no-fallthrough */ + + if (self.rowObject && self.rowObject.changed === true) { + $(item).addClass('drag'); + if (self.oldRowElement) { + $(self.oldRowElement).removeClass('drag-previous'); + } + self.oldRowElement = item; + if (self.striping === true) { + self.restripeTable(); + } + self.onDrag(); + } + + // Returning false if we have an arrow key to prevent scrolling. + if (keyChange) { + return false; + } + }); + + // Compatibility addition, return false on keypress to prevent unwanted + // scrolling. IE and Safari will suppress scrolling on keydown, but all + // other browsers need to return false on keypress. + // http://www.quirksmode.org/js/keys.html + handle.on('keypress', function (event) { + + /* eslint-disable no-fallthrough */ + + switch (event.keyCode) { + // Left arrow. + case 37: + // Up arrow. + case 38: + // Right arrow. + case 39: + // Down arrow. + case 40: + return false; + } + + /* eslint-enable no-fallthrough */ + + }); + }; + + /** + * Pointer event initiator, creates drag object and information. + * + * @param {jQuery.Event} event + * The event object that trigger the drag. + * @param {Drupal.tableDrag} self + * The drag handle. + * @param {HTMLElement} item + * The item that that is being dragged. + */ + Drupal.tableDrag.prototype.dragStart = function (event, self, item) { + // Create a new dragObject recording the pointer information. + self.dragObject = {}; + self.dragObject.initOffset = self.getPointerOffset(item, event); + self.dragObject.initPointerCoords = self.pointerCoords(event); + if (self.indentEnabled) { + self.dragObject.indentPointerPos = self.dragObject.initPointerCoords; + } + + // If there's a lingering row object from the keyboard, remove its focus. + if (self.rowObject) { + $(self.rowObject.element).find('a.tabledrag-handle').trigger('blur'); + } + + // Create a new rowObject for manipulation of this row. + self.rowObject = new self.row(item, 'pointer', self.indentEnabled, self.maxDepth, true); + + // Save the position of the table. + self.table.topY = $(self.table).offset().top; + self.table.bottomY = self.table.topY + self.table.offsetHeight; + + // Add classes to the handle and row. + $(item).addClass('drag'); + + // Set the document to use the move cursor during drag. + $('body').addClass('drag'); + if (self.oldRowElement) { + $(self.oldRowElement).removeClass('drag-previous'); + } + }; + + /** + * Pointer movement handler, bound to document. + * + * @param {jQuery.Event} event + * The pointer event. + * @param {Drupal.tableDrag} self + * The tableDrag instance. + * + * @return {bool|undefined} + * Undefined if no dragObject is defined, false otherwise. + */ + Drupal.tableDrag.prototype.dragRow = function (event, self) { + if (self.dragObject) { + self.currentPointerCoords = self.pointerCoords(event); + var y = self.currentPointerCoords.y - self.dragObject.initOffset.y; + var x = self.currentPointerCoords.x - self.dragObject.initOffset.x; + + // Check for row swapping and vertical scrolling. + if (y !== self.oldY) { + self.rowObject.direction = y > self.oldY ? 'down' : 'up'; + // Update the old value. + self.oldY = y; + // Check if the window should be scrolled (and how fast). + var scrollAmount = self.checkScroll(self.currentPointerCoords.y); + // Stop any current scrolling. + clearInterval(self.scrollInterval); + // Continue scrolling if the mouse has moved in the scroll direction. + if (scrollAmount > 0 && self.rowObject.direction === 'down' || scrollAmount < 0 && self.rowObject.direction === 'up') { + self.setScroll(scrollAmount); + } + + // If we have a valid target, perform the swap and restripe the table. + var currentRow = self.findDropTargetRow(x, y); + if (currentRow) { + if (self.rowObject.direction === 'down') { + self.rowObject.swap('after', currentRow, self); + } + else { + self.rowObject.swap('before', currentRow, self); + } + if (self.striping === true) { + self.restripeTable(); + } + } + } + + // Similar to row swapping, handle indentations. + if (self.indentEnabled) { + var xDiff = self.currentPointerCoords.x - self.dragObject.indentPointerPos.x; + // Set the number of indentations the pointer has been moved left or + // right. + var indentDiff = Math.round(xDiff / self.indentAmount); + // Indent the row with our estimated diff, which may be further + // restricted according to the rows around this row. + var indentChange = self.rowObject.indent(indentDiff); + // Update table and pointer indentations. + self.dragObject.indentPointerPos.x += self.indentAmount * indentChange * self.rtl; + self.indentCount = Math.max(self.indentCount, self.rowObject.indents); + } + + return false; + } + }; + + /** + * Pointerup behavior. + * + * @param {jQuery.Event} event + * The pointer event. + * @param {Drupal.tableDrag} self + * The tableDrag instance. + */ + Drupal.tableDrag.prototype.dropRow = function (event, self) { + var droppedRow; + var $droppedRow; + + // Drop row functionality. + if (self.rowObject !== null) { + droppedRow = self.rowObject.element; + $droppedRow = $(droppedRow); + // The row is already in the right place so we just release it. + if (self.rowObject.changed === true) { + // Update the fields in the dropped row. + self.updateFields(droppedRow); + + // If a setting exists for affecting the entire group, update all the + // fields in the entire dragged group. + for (var group in self.tableSettings) { + if (self.tableSettings.hasOwnProperty(group)) { + var rowSettings = self.rowSettings(group, droppedRow); + if (rowSettings.relationship === 'group') { + for (var n in self.rowObject.children) { + if (self.rowObject.children.hasOwnProperty(n)) { + self.updateField(self.rowObject.children[n], group); + } + } + } + } + } + + self.rowObject.markChanged(); + if (self.changed === false) { + $(Drupal.theme('tableDragChangedWarning')).insertBefore(self.table).hide().fadeIn('slow'); + self.changed = true; + } + } + + if (self.indentEnabled) { + self.rowObject.removeIndentClasses(); + } + if (self.oldRowElement) { + $(self.oldRowElement).removeClass('drag-previous'); + } + $droppedRow.removeClass('drag').addClass('drag-previous'); + self.oldRowElement = droppedRow; + self.onDrop(); + self.rowObject = null; + } + + // Functionality specific only to pointerup events. + if (self.dragObject !== null) { + self.dragObject = null; + $('body').removeClass('drag'); + clearInterval(self.scrollInterval); + } + }; + + /** + * Get the coordinates from the event (allowing for browser differences). + * + * @param {jQuery.Event} event + * The pointer event. + * + * @return {object} + * An object with `x` and `y` keys indicating the position. + */ + Drupal.tableDrag.prototype.pointerCoords = function (event) { + if (event.pageX || event.pageY) { + return {x: event.pageX, y: event.pageY}; + } + return { + x: event.clientX + document.body.scrollLeft - document.body.clientLeft, + y: event.clientY + document.body.scrollTop - document.body.clientTop + }; + }; + + /** + * Get the event offset from the target element. + * + * Given a target element and a pointer event, get the event offset from that + * element. To do this we need the element's position and the target position. + * + * @param {HTMLElement} target + * The target HTML element. + * @param {jQuery.Event} event + * The pointer event. + * + * @return {object} + * An object with `x` and `y` keys indicating the position. + */ + Drupal.tableDrag.prototype.getPointerOffset = function (target, event) { + var docPos = $(target).offset(); + var pointerPos = this.pointerCoords(event); + return {x: pointerPos.x - docPos.left, y: pointerPos.y - docPos.top}; + }; + + /** + * Find the row the mouse is currently over. + * + * This row is then taken and swapped with the one being dragged. + * + * @param {number} x + * The x coordinate of the mouse on the page (not the screen). + * @param {number} y + * The y coordinate of the mouse on the page (not the screen). + * + * @return {*} + * The drop target row, if found. + */ + Drupal.tableDrag.prototype.findDropTargetRow = function (x, y) { + var rows = $(this.table.tBodies[0].rows).not(':hidden'); + for (var n = 0; n < rows.length; n++) { + var row = rows[n]; + var $row = $(row); + var rowY = $row.offset().top; + var rowHeight; + // Because Safari does not report offsetHeight on table rows, but does on + // table cells, grab the firstChild of the row and use that instead. + // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari. + if (row.offsetHeight === 0) { + rowHeight = parseInt(row.firstChild.offsetHeight, 10) / 2; + } + // Other browsers. + else { + rowHeight = parseInt(row.offsetHeight, 10) / 2; + } + + // Because we always insert before, we need to offset the height a bit. + if ((y > (rowY - rowHeight)) && (y < (rowY + rowHeight))) { + if (this.indentEnabled) { + // Check that this row is not a child of the row being dragged. + for (n in this.rowObject.group) { + if (this.rowObject.group[n] === row) { + return null; + } + } + } + else { + // Do not allow a row to be swapped with itself. + if (row === this.rowObject.element) { + return null; + } + } + + // Check that swapping with this row is allowed. + if (!this.rowObject.isValidSwap(row)) { + return null; + } + + // We may have found the row the mouse just passed over, but it doesn't + // take into account hidden rows. Skip backwards until we find a + // draggable row. + while ($row.is(':hidden') && $row.prev('tr').is(':hidden')) { + $row = $row.prev('tr:first-of-type'); + row = $row.get(0); + } + return row; + } + } + return null; + }; + + /** + * After the row is dropped, update the table fields. + * + * @param {HTMLElement} changedRow + * DOM object for the row that was just dropped. + */ + Drupal.tableDrag.prototype.updateFields = function (changedRow) { + for (var group in this.tableSettings) { + if (this.tableSettings.hasOwnProperty(group)) { + // Each group may have a different setting for relationship, so we find + // the source rows for each separately. + this.updateField(changedRow, group); + } + } + }; + + /** + * After the row is dropped, update a single table field. + * + * @param {HTMLElement} changedRow + * DOM object for the row that was just dropped. + * @param {string} group + * The settings group on which field updates will occur. + */ + Drupal.tableDrag.prototype.updateField = function (changedRow, group) { + var rowSettings = this.rowSettings(group, changedRow); + var $changedRow = $(changedRow); + var sourceRow; + var $previousRow; + var previousRow; + var useSibling; + // Set the row as its own target. + if (rowSettings.relationship === 'self' || rowSettings.relationship === 'group') { + sourceRow = changedRow; + } + // Siblings are easy, check previous and next rows. + else if (rowSettings.relationship === 'sibling') { + $previousRow = $changedRow.prev('tr:first-of-type'); + previousRow = $previousRow.get(0); + var $nextRow = $changedRow.next('tr:first-of-type'); + var nextRow = $nextRow.get(0); + sourceRow = changedRow; + if ($previousRow.is('.draggable') && $previousRow.find('.' + group).length) { + if (this.indentEnabled) { + if ($previousRow.find('.js-indentations').length === $changedRow.find('.js-indentations').length) { + sourceRow = previousRow; + } + } + else { + sourceRow = previousRow; + } + } + else if ($nextRow.is('.draggable') && $nextRow.find('.' + group).length) { + if (this.indentEnabled) { + if ($nextRow.find('.js-indentations').length === $changedRow.find('.js-indentations').length) { + sourceRow = nextRow; + } + } + else { + sourceRow = nextRow; + } + } + } + // Parents, look up the tree until we find a field not in this group. + // Go up as many parents as indentations in the changed row. + else if (rowSettings.relationship === 'parent') { + $previousRow = $changedRow.prev('tr'); + previousRow = $previousRow; + while ($previousRow.length && $previousRow.find('.js-indentation').length >= this.rowObject.indents) { + $previousRow = $previousRow.prev('tr'); + previousRow = $previousRow; + } + // If we found a row. + if ($previousRow.length) { + sourceRow = $previousRow.get(0); + } + // Otherwise we went all the way to the left of the table without finding + // a parent, meaning this item has been placed at the root level. + else { + // Use the first row in the table as source, because it's guaranteed to + // be at the root level. Find the first item, then compare this row + // against it as a sibling. + sourceRow = $(this.table).find('tr.draggable:first-of-type').get(0); + if (sourceRow === this.rowObject.element) { + sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0); + } + useSibling = true; + } + } + + // Because we may have moved the row from one category to another, + // take a look at our sibling and borrow its sources and targets. + this.copyDragClasses(sourceRow, changedRow, group); + rowSettings = this.rowSettings(group, changedRow); + + // In the case that we're looking for a parent, but the row is at the top + // of the tree, copy our sibling's values. + if (useSibling) { + rowSettings.relationship = 'sibling'; + rowSettings.source = rowSettings.target; + } + + var targetClass = '.' + rowSettings.target; + var targetElement = $changedRow.find(targetClass).get(0); + + // Check if a target element exists in this row. + if (targetElement) { + var sourceClass = '.' + rowSettings.source; + var sourceElement = $(sourceClass, sourceRow).get(0); + switch (rowSettings.action) { + case 'depth': + // Get the depth of the target row. + targetElement.value = $(sourceElement).closest('tr').find('.js-indentation').length; + break; + + case 'match': + // Update the value. + targetElement.value = sourceElement.value; + break; + + case 'order': + var siblings = this.rowObject.findSiblings(rowSettings); + if ($(targetElement).is('select')) { + // Get a list of acceptable values. + var values = []; + $(targetElement).find('option').each(function () { + values.push(this.value); + }); + var maxVal = values[values.length - 1]; + // Populate the values in the siblings. + $(siblings).find(targetClass).each(function () { + // If there are more items than possible values, assign the + // maximum value to the row. + if (values.length > 0) { + this.value = values.shift(); + } + else { + this.value = maxVal; + } + }); + } + else { + // Assume a numeric input field. + var weight = parseInt($(siblings[0]).find(targetClass).val(), 10) || 0; + $(siblings).find(targetClass).each(function () { + this.value = weight; + weight++; + }); + } + break; + } + } + }; + + /** + * Copy all tableDrag related classes from one row to another. + * + * Copy all special tableDrag classes from one row's form elements to a + * different one, removing any special classes that the destination row + * may have had. + * + * @param {HTMLElement} sourceRow + * The element for the source row. + * @param {HTMLElement} targetRow + * The element for the target row. + * @param {string} group + * The group selector. + */ + Drupal.tableDrag.prototype.copyDragClasses = function (sourceRow, targetRow, group) { + var sourceElement = $(sourceRow).find('.' + group); + var targetElement = $(targetRow).find('.' + group); + if (sourceElement.length && targetElement.length) { + targetElement[0].className = sourceElement[0].className; + } + }; + + /** + * Check the suggested scroll of the table. + * + * @param {number} cursorY + * The Y position of the cursor. + * + * @return {number} + * The suggested scroll. + */ + Drupal.tableDrag.prototype.checkScroll = function (cursorY) { + var de = document.documentElement; + var b = document.body; + + var windowHeight = this.windowHeight = window.innerHeight || (de.clientHeight && de.clientWidth !== 0 ? de.clientHeight : b.offsetHeight); + var scrollY; + if (document.all) { + scrollY = this.scrollY = !de.scrollTop ? b.scrollTop : de.scrollTop; + } + else { + scrollY = this.scrollY = window.pageYOffset ? window.pageYOffset : window.scrollY; + } + var trigger = this.scrollSettings.trigger; + var delta = 0; + + // Return a scroll speed relative to the edge of the screen. + if (cursorY - scrollY > windowHeight - trigger) { + delta = trigger / (windowHeight + scrollY - cursorY); + delta = (delta > 0 && delta < trigger) ? delta : trigger; + return delta * this.scrollSettings.amount; + } + else if (cursorY - scrollY < trigger) { + delta = trigger / (cursorY - scrollY); + delta = (delta > 0 && delta < trigger) ? delta : trigger; + return -delta * this.scrollSettings.amount; + } + }; + + /** + * Set the scroll for the table. + * + * @param {number} scrollAmount + * The amount of scroll to apply to the window. + */ + Drupal.tableDrag.prototype.setScroll = function (scrollAmount) { + var self = this; + + this.scrollInterval = setInterval(function () { + // Update the scroll values stored in the object. + self.checkScroll(self.currentPointerCoords.y); + var aboveTable = self.scrollY > self.table.topY; + var belowTable = self.scrollY + self.windowHeight < self.table.bottomY; + if (scrollAmount > 0 && belowTable || scrollAmount < 0 && aboveTable) { + window.scrollBy(0, scrollAmount); + } + }, this.scrollSettings.interval); + }; + + /** + * Command to restripe table properly. + */ + Drupal.tableDrag.prototype.restripeTable = function () { + // :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. + $(this.table).find('> tbody > tr.draggable:visible, > tr.draggable:visible') + .removeClass('odd even') + .filter(':odd').addClass('even').end() + .filter(':even').addClass('odd'); + }; + + /** + * Stub function. Allows a custom handler when a row begins dragging. + * + * @return {null} + * Returns null when the stub function is used. + */ + Drupal.tableDrag.prototype.onDrag = function () { + return null; + }; + + /** + * Stub function. Allows a custom handler when a row is dropped. + * + * @return {null} + * Returns null when the stub function is used. + */ + Drupal.tableDrag.prototype.onDrop = function () { + return null; + }; + + /** + * Constructor to make a new object to manipulate a table row. + * + * @param {HTMLElement} tableRow + * The DOM element for the table row we will be manipulating. + * @param {string} method + * The method in which this row is being moved. Either 'keyboard' or + * 'mouse'. + * @param {bool} indentEnabled + * Whether the containing table uses indentations. Used for optimizations. + * @param {number} maxDepth + * The maximum amount of indentations this row may contain. + * @param {bool} addClasses + * Whether we want to add classes to this row to indicate child + * relationships. + */ + Drupal.tableDrag.prototype.row = function (tableRow, method, indentEnabled, maxDepth, addClasses) { + var $tableRow = $(tableRow); + + this.element = tableRow; + this.method = method; + this.group = [tableRow]; + this.groupDepth = $tableRow.find('.js-indentation').length; + this.changed = false; + this.table = $tableRow.closest('table')[0]; + this.indentEnabled = indentEnabled; + this.maxDepth = maxDepth; + // Direction the row is being moved. + this.direction = ''; + if (this.indentEnabled) { + this.indents = $tableRow.find('.js-indentation').length; + this.children = this.findChildren(addClasses); + this.group = $.merge(this.group, this.children); + // Find the depth of this entire group. + for (var n = 0; n < this.group.length; n++) { + this.groupDepth = Math.max($(this.group[n]).find('.js-indentation').length, this.groupDepth); + } + } + }; + + /** + * Find all children of rowObject by indentation. + * + * @param {bool} addClasses + * Whether we want to add classes to this row to indicate child + * relationships. + * + * @return {Array} + * An array of children of the row. + */ + Drupal.tableDrag.prototype.row.prototype.findChildren = function (addClasses) { + var parentIndentation = this.indents; + var currentRow = $(this.element, this.table).next('tr.draggable'); + var rows = []; + var child = 0; + + function rowIndentation(indentNum, el) { + var self = $(el); + if (child === 1 && (indentNum === parentIndentation)) { + self.addClass('tree-child-first'); + } + if (indentNum === parentIndentation) { + self.addClass('tree-child'); + } + else if (indentNum > parentIndentation) { + self.addClass('tree-child-horizontal'); + } + } + + while (currentRow.length) { + // A greater indentation indicates this is a child. + if (currentRow.find('.js-indentation').length > parentIndentation) { + child++; + rows.push(currentRow[0]); + if (addClasses) { + currentRow.find('.js-indentation').each(rowIndentation); + } + } + else { + break; + } + currentRow = currentRow.next('tr.draggable'); + } + if (addClasses && rows.length) { + $(rows[rows.length - 1]).find('.js-indentation:nth-child(' + (parentIndentation + 1) + ')').addClass('tree-child-last'); + } + return rows; + }; + + /** + * Ensure that two rows are allowed to be swapped. + * + * @param {HTMLElement} row + * DOM object for the row being considered for swapping. + * + * @return {bool} + * Whether the swap is a valid swap or not. + */ + Drupal.tableDrag.prototype.row.prototype.isValidSwap = function (row) { + var $row = $(row); + if (this.indentEnabled) { + var prevRow; + var nextRow; + if (this.direction === 'down') { + prevRow = row; + nextRow = $row.next('tr').get(0); + } + else { + prevRow = $row.prev('tr').get(0); + nextRow = row; + } + this.interval = this.validIndentInterval(prevRow, nextRow); + + // We have an invalid swap if the valid indentations interval is empty. + if (this.interval.min > this.interval.max) { + return false; + } + } + + // Do not let an un-draggable first row have anything put before it. + if (this.table.tBodies[0].rows[0] === row && $row.is(':not(.draggable)')) { + return false; + } + + return true; + }; + + /** + * Perform the swap between two rows. + * + * @param {string} position + * Whether the swap will occur 'before' or 'after' the given row. + * @param {HTMLElement} row + * DOM element what will be swapped with the row group. + */ + Drupal.tableDrag.prototype.row.prototype.swap = function (position, row) { + // Makes sure only DOM object are passed to Drupal.detachBehaviors(). + this.group.forEach(function (row) { + Drupal.detachBehaviors(row, drupalSettings, 'move'); + }); + $(row)[position](this.group); + // Makes sure only DOM object are passed to Drupal.attachBehaviors()s. + this.group.forEach(function (row) { + Drupal.attachBehaviors(row, drupalSettings); + }); + this.changed = true; + this.onSwap(row); + }; + + /** + * Determine the valid indentations interval for the row at a given position. + * + * @param {?HTMLElement} prevRow + * DOM object for the row before the tested position + * (or null for first position in the table). + * @param {?HTMLElement} nextRow + * DOM object for the row after the tested position + * (or null for last position in the table). + * + * @return {object} + * An object with the keys `min` and `max` to indicate the valid indent + * interval. + */ + Drupal.tableDrag.prototype.row.prototype.validIndentInterval = function (prevRow, nextRow) { + var $prevRow = $(prevRow); + var minIndent; + var maxIndent; + + // Minimum indentation: + // Do not orphan the next row. + minIndent = nextRow ? $(nextRow).find('.js-indentation').length : 0; + + // Maximum indentation: + if (!prevRow || $prevRow.is(':not(.draggable)') || $(this.element).is('.tabledrag-root')) { + // Do not indent: + // - the first row in the table, + // - rows dragged below a non-draggable row, + // - 'root' rows. + maxIndent = 0; + } + else { + // Do not go deeper than as a child of the previous row. + maxIndent = $prevRow.find('.js-indentation').length + ($prevRow.is('.tabledrag-leaf') ? 0 : 1); + // Limit by the maximum allowed depth for the table. + if (this.maxDepth) { + maxIndent = Math.min(maxIndent, this.maxDepth - (this.groupDepth - this.indents)); + } + } + + return {min: minIndent, max: maxIndent}; + }; + + /** + * Indent a row within the legal bounds of the table. + * + * @param {number} indentDiff + * The number of additional indentations proposed for the row (can be + * positive or negative). This number will be adjusted to nearest valid + * indentation level for the row. + * + * @return {number} + * The number of indentations applied. + */ + Drupal.tableDrag.prototype.row.prototype.indent = function (indentDiff) { + var $group = $(this.group); + // Determine the valid indentations interval if not available yet. + if (!this.interval) { + var prevRow = $(this.element).prev('tr').get(0); + var nextRow = $group.eq(-1).next('tr').get(0); + this.interval = this.validIndentInterval(prevRow, nextRow); + } + + // Adjust to the nearest valid indentation. + var indent = this.indents + indentDiff; + indent = Math.max(indent, this.interval.min); + indent = Math.min(indent, this.interval.max); + indentDiff = indent - this.indents; + + for (var n = 1; n <= Math.abs(indentDiff); n++) { + // Add or remove indentations. + if (indentDiff < 0) { + $group.find('.js-indentation:first-of-type').remove(); + this.indents--; + } + else { + $group.find('td:first-of-type').prepend(Drupal.theme('tableDragIndentation')); + this.indents++; + } + } + if (indentDiff) { + // Update indentation for this row. + this.changed = true; + this.groupDepth += indentDiff; + this.onIndent(); + } + + return indentDiff; + }; + + /** + * Find all siblings for a row. + * + * According to its subgroup or indentation. Note that the passed-in row is + * included in the list of siblings. + * + * @param {object} rowSettings + * The field settings we're using to identify what constitutes a sibling. + * + * @return {Array} + * An array of siblings. + */ + Drupal.tableDrag.prototype.row.prototype.findSiblings = function (rowSettings) { + var siblings = []; + var directions = ['prev', 'next']; + var rowIndentation = this.indents; + var checkRowIndentation; + for (var d = 0; d < directions.length; d++) { + var checkRow = $(this.element)[directions[d]](); + while (checkRow.length) { + // Check that the sibling contains a similar target field. + if (checkRow.find('.' + rowSettings.target)) { + // Either add immediately if this is a flat table, or check to ensure + // that this row has the same level of indentation. + if (this.indentEnabled) { + checkRowIndentation = checkRow.find('.js-indentation').length; + } + + if (!(this.indentEnabled) || (checkRowIndentation === rowIndentation)) { + siblings.push(checkRow[0]); + } + else if (checkRowIndentation < rowIndentation) { + // No need to keep looking for siblings when we get to a parent. + break; + } + } + else { + break; + } + checkRow = checkRow[directions[d]](); + } + // Since siblings are added in reverse order for previous, reverse the + // completed list of previous siblings. Add the current row and continue. + if (directions[d] === 'prev') { + siblings.reverse(); + siblings.push(this.element); + } + } + return siblings; + }; + + /** + * Remove indentation helper classes from the current row group. + */ + Drupal.tableDrag.prototype.row.prototype.removeIndentClasses = function () { + for (var n in this.children) { + if (this.children.hasOwnProperty(n)) { + $(this.children[n]).find('.js-indentation') + .removeClass('tree-child') + .removeClass('tree-child-first') + .removeClass('tree-child-last') + .removeClass('tree-child-horizontal'); + } + } + }; + + /** + * Add an asterisk or other marker to the changed row. + */ + Drupal.tableDrag.prototype.row.prototype.markChanged = function () { + var marker = Drupal.theme('tableDragChangedMarker'); + var cell = $(this.element).find('td:first-of-type'); + if (cell.find('abbr.tabledrag-changed').length === 0) { + cell.append(marker); + } + }; + + /** + * Stub function. Allows a custom handler when a row is indented. + * + * @return {null} + * Returns null when the stub function is used. + */ + Drupal.tableDrag.prototype.row.prototype.onIndent = function () { + return null; + }; + + /** + * Stub function. Allows a custom handler when a row is swapped. + * + * @param {HTMLElement} swappedRow + * The element for the swapped row. + * + * @return {null} + * Returns null when the stub function is used. + */ + Drupal.tableDrag.prototype.row.prototype.onSwap = function (swappedRow) { + return null; + }; + + $.extend(Drupal.theme, /** @lends Drupal.theme */{ + + /** + * @return {string} + * Markup for the marker. + */ + tableDragChangedMarker: function () { + return '*'; + }, + + /** + * @return {string} + * Markup for the indentation. + */ + tableDragIndentation: function () { + return '
     
    '; + }, + + /** + * @return {string} + * Markup for the warning. + */ + tableDragChangedWarning: function () { + return ''; + } + }); + +})(jQuery, Drupal, drupalSettings); diff --git a/core/misc/tabledrag.js b/core/misc/tabledrag.js index 395d092..3326a48 100644 --- a/core/misc/tabledrag.js +++ b/core/misc/tabledrag.js @@ -1,45 +1,15 @@ -/** - * @file - * Provide dragging capabilities to admin uis. - */ - -/** - * Triggers when weights columns are toggled. - * - * @event columnschange - */ +'use strict'; (function ($, Drupal, drupalSettings) { 'use strict'; - /** - * Store the state of weight columns display for all tables. - * - * Default value is to hide weight columns. - */ var showWeight = JSON.parse(localStorage.getItem('Drupal.tableDrag.showWeight')); - /** - * Drag and drop table rows with field manipulation. - * - * Using the drupal_attach_tabledrag() function, any table with weights or - * parent relationships may be made into draggable tables. Columns containing - * a field may optionally be hidden, providing a better user experience. - * - * Created tableDrag instances may be modified with custom behaviors by - * overriding the .onDrag, .onDrop, .row.onSwap, and .row.onIndent methods. - * See blocks.js for an example of adding additional functionality to - * tableDrag. - * - * @type {Drupal~behavior} - */ Drupal.behaviors.tableDrag = { - attach: function (context, settings) { + attach: function attach(context, settings) { function initTableDrag(table, base) { if (table.length) { - // Create the new tableDrag instance. Save in the Drupal variable - // to allow other scripts access to the object. Drupal.tableDrag[base] = new Drupal.tableDrag(table[0], settings.tableDrag[base]); } } @@ -52,128 +22,40 @@ } }; - /** - * Provides table and field manipulation. - * - * @constructor - * - * @param {HTMLElement} table - * DOM object for the table to be made draggable. - * @param {object} tableSettings - * Settings for the table added via drupal_add_dragtable(). - */ Drupal.tableDrag = function (table, tableSettings) { var self = this; var $table = $(table); - /** - * @type {jQuery} - */ this.$table = $(table); - /** - * - * @type {HTMLElement} - */ this.table = table; - /** - * @type {object} - */ this.tableSettings = tableSettings; - /** - * Used to hold information about a current drag operation. - * - * @type {?HTMLElement} - */ this.dragObject = null; - /** - * Provides operations for row manipulation. - * - * @type {?HTMLElement} - */ this.rowObject = null; - /** - * Remember the previous element. - * - * @type {?HTMLElement} - */ this.oldRowElement = null; - /** - * Used to determine up or down direction from last mouse move. - * - * @type {number} - */ this.oldY = 0; - /** - * Whether anything in the entire table has changed. - * - * @type {bool} - */ this.changed = false; - /** - * Maximum amount of allowed parenting. - * - * @type {number} - */ this.maxDepth = 0; - /** - * Direction of the table. - * - * @type {number} - */ this.rtl = $(this.table).css('direction') === 'rtl' ? -1 : 1; - /** - * - * @type {bool} - */ this.striping = $(this.table).data('striping') === 1; - /** - * Configure the scroll settings. - * - * @type {object} - * - * @prop {number} amount - * @prop {number} interval - * @prop {number} trigger - */ - this.scrollSettings = {amount: 4, interval: 50, trigger: 70}; - - /** - * - * @type {?number} - */ + this.scrollSettings = { amount: 4, interval: 50, trigger: 70 }; + this.scrollInterval = null; - /** - * - * @type {number} - */ this.scrollY = 0; - /** - * - * @type {number} - */ this.windowHeight = 0; - /** - * Check this table's settings for parent relationships. - * - * For efficiency, large sections of code can be skipped if we don't need to - * track horizontal movement and indentations. - * - * @type {bool} - */ this.indentEnabled = false; for (var group in tableSettings) { if (tableSettings.hasOwnProperty(group)) { @@ -190,77 +72,49 @@ } } if (this.indentEnabled) { - - /** - * Total width of indents, set in makeDraggable. - * - * @type {number} - */ this.indentCount = 1; - // Find the width of indentations to measure mouse movements against. - // Because the table doesn't need to start with any indentations, we - // manually append 2 indentations in the first draggable row, measure - // the offset, then remove. + var indent = Drupal.theme('tableDragIndentation'); var testRow = $('
    ').appendTo(testRow).prepend(indent).prepend(indent); var $indentation = testCell.find('.js-indentation'); - /** - * - * @type {number} - */ this.indentAmount = $indentation.get(1).offsetLeft - $indentation.get(0).offsetLeft; testRow.remove(); } - // Make each applicable row draggable. - // Match immediate children of the parent element to allow nesting. - $table.find('> tr.draggable, > tbody > tr.draggable').each(function () { self.makeDraggable(this); }); - - // Add a link before the table for users to show or hide weight columns. - $table.before($('') - .attr('title', Drupal.t('Re-order rows by numerical weight instead of dragging.')) - .on('click', $.proxy(function (e) { - e.preventDefault(); - this.toggleColumns(); - }, this)) - .wrap('
    ') - .parent() - ); - - // Initialize the specified columns (for example, weight or parent columns) - // to show or hide according to user preference. This aids accessibility - // so that, e.g., screen reader users can choose to enter weight values and - // manipulate form elements directly, rather than using drag-and-drop.. + $table.find('> tr.draggable, > tbody > tr.draggable').each(function () { + self.makeDraggable(this); + }); + + $table.before($('').attr('title', Drupal.t('Re-order rows by numerical weight instead of dragging.')).on('click', $.proxy(function (e) { + e.preventDefault(); + this.toggleColumns(); + }, this)).wrap('
    ').parent()); + self.initColumns(); - // Add event bindings to the document. The self variable is passed along - // as event handlers do not have direct access to the tableDrag object. - $(document).on('touchmove', function (event) { return self.dragRow(event.originalEvent.touches[0], self); }); - $(document).on('touchend', function (event) { return self.dropRow(event.originalEvent.touches[0], self); }); - $(document).on('mousemove pointermove', function (event) { return self.dragRow(event, self); }); - $(document).on('mouseup pointerup', function (event) { return self.dropRow(event, self); }); + $(document).on('touchmove', function (event) { + return self.dragRow(event.originalEvent.touches[0], self); + }); + $(document).on('touchend', function (event) { + return self.dropRow(event.originalEvent.touches[0], self); + }); + $(document).on('mousemove pointermove', function (event) { + return self.dragRow(event, self); + }); + $(document).on('mouseup pointerup', function (event) { + return self.dropRow(event, self); + }); - // React to localStorage event showing or hiding weight columns. $(window).on('storage', $.proxy(function (e) { - // Only react to 'Drupal.tableDrag.showWeight' value change. if (e.originalEvent.key === 'Drupal.tableDrag.showWeight') { - // This was changed in another window, get the new value for this - // window. showWeight = JSON.parse(e.originalEvent.newValue); this.displayColumns(showWeight); } }, this)); }; - /** - * Initialize columns containing form elements to be hidden by default. - * - * Identify and mark each cell with a CSS class so we can easily toggle - * show/hide it. Finally, hide columns if user does not have a - * 'Drupal.tableDrag.showWeight' localStorage value. - */ Drupal.tableDrag.prototype.initColumns = function () { var $table = this.$table; var hidden; @@ -268,8 +122,6 @@ var columnIndex; for (var group in this.tableSettings) { if (this.tableSettings.hasOwnProperty(group)) { - - // Find the first field in this group. for (var d in this.tableSettings[group]) { if (this.tableSettings[group].hasOwnProperty(d)) { var field = $table.find('.' + this.tableSettings[group][d].target).eq(0); @@ -281,11 +133,7 @@ } } - // Mark the column containing this field so it can be hidden. if (hidden && cell[0]) { - // Add 1 to our indexes. The nth-child selector is 1 based, not 0 - // based. Match immediate children of the parent element to allow - // nesting. columnIndex = cell.parent().find('> td').index(cell.get(0)) + 1; $table.find('> thead > tr, > tbody > tr, > tr').each(this.addColspanClass(columnIndex)); } @@ -294,20 +142,8 @@ this.displayColumns(showWeight); }; - /** - * Mark cells that have colspan. - * - * In order to adjust the colspan instead of hiding them altogether. - * - * @param {number} columnIndex - * The column index to add colspan class to. - * - * @return {function} - * Function to add colspan class. - */ Drupal.tableDrag.prototype.addColspanClass = function (columnIndex) { return function () { - // Get the columnIndex and adjust for any colspans in this row. var $row = $(this); var index = columnIndex; var cells = $row.children(); @@ -320,105 +156,62 @@ if (index > 0) { cell = cells.filter(':nth-child(' + index + ')'); if (cell[0].colSpan && cell[0].colSpan > 1) { - // If this cell has a colspan, mark it so we can reduce the colspan. cell.addClass('tabledrag-has-colspan'); - } - else { - // Mark this cell so we can hide it. + } else { cell.addClass('tabledrag-hide'); } } }; }; - /** - * Hide or display weight columns. Triggers an event on change. - * - * @fires event:columnschange - * - * @param {bool} displayWeight - * 'true' will show weight columns. - */ Drupal.tableDrag.prototype.displayColumns = function (displayWeight) { if (displayWeight) { this.showColumns(); - } - // Default action is to hide columns. - else { - this.hideColumns(); - } - // Trigger an event to allow other scripts to react to this display change. - // Force the extra parameter as a bool. + } else { + this.hideColumns(); + } + $('table').findOnce('tabledrag').trigger('columnschange', !!displayWeight); }; - /** - * Toggle the weight column depending on 'showWeight' value. - * - * Store only default override. - */ Drupal.tableDrag.prototype.toggleColumns = function () { showWeight = !showWeight; this.displayColumns(showWeight); if (showWeight) { - // Save default override. localStorage.setItem('Drupal.tableDrag.showWeight', showWeight); - } - else { - // Reset the value to its default. + } else { localStorage.removeItem('Drupal.tableDrag.showWeight'); } }; - /** - * Hide the columns containing weight/parent form elements. - * - * Undo showColumns(). - */ Drupal.tableDrag.prototype.hideColumns = function () { var $tables = $('table').findOnce('tabledrag'); - // Hide weight/parent cells and headers. + $tables.find('.tabledrag-hide').css('display', 'none'); - // Show TableDrag handles. + $tables.find('.tabledrag-handle').css('display', ''); - // Reduce the colspan of any effected multi-span columns. + $tables.find('.tabledrag-has-colspan').each(function () { this.colSpan = this.colSpan - 1; }); - // Change link text. + $('.tabledrag-toggle-weight').text(Drupal.t('Show row weights')); }; - /** - * Show the columns containing weight/parent form elements. - * - * Undo hideColumns(). - */ Drupal.tableDrag.prototype.showColumns = function () { var $tables = $('table').findOnce('tabledrag'); - // Show weight/parent cells and headers. + $tables.find('.tabledrag-hide').css('display', ''); - // Hide TableDrag handles. + $tables.find('.tabledrag-handle').css('display', 'none'); - // Increase the colspan for any columns where it was previously reduced. + $tables.find('.tabledrag-has-colspan').each(function () { this.colSpan = this.colSpan + 1; }); - // Change link text. + $('.tabledrag-toggle-weight').text(Drupal.t('Hide row weights')); }; - /** - * Find the target used within a particular row and group. - * - * @param {string} group - * Group selector. - * @param {HTMLElement} row - * The row HTML element. - * - * @return {object} - * The table row settings. - */ Drupal.tableDrag.prototype.rowSettings = function (group, row) { var field = $(row).find('.' + group); var tableSettingsGroup = this.tableSettings[group]; @@ -426,7 +219,6 @@ if (tableSettingsGroup.hasOwnProperty(delta)) { var targetClass = tableSettingsGroup[delta].target; if (field.is('.' + targetClass)) { - // Return a copy of the row settings. var rowSettings = {}; for (var n in tableSettingsGroup[delta]) { if (tableSettingsGroup[delta].hasOwnProperty(n)) { @@ -439,27 +231,20 @@ } }; - /** - * Take an item and add event handlers to make it become draggable. - * - * @param {HTMLElement} item - * The item to add event handlers to. - */ Drupal.tableDrag.prototype.makeDraggable = function (item) { var self = this; var $item = $(item); - // Add a class to the title link. + $item.find('td:first-of-type').find('a').addClass('menu-item__link'); - // Create the handle. + var handle = $('
     
    ').attr('title', Drupal.t('Drag to re-order')); - // Insert the handle after indentations (if any). + var $indentationLast = $item.find('td:first-of-type').find('.js-indentation').eq(-1); if ($indentationLast.length) { $indentationLast.after(handle); - // Update the total width of indentation in this entire table. + self.indentCount = Math.max($item.find('.js-indentation').length, self.indentCount); - } - else { + } else { $item.find('td').eq(0).prepend(handle); } @@ -471,27 +256,21 @@ self.dragStart(event, self, item); }); - // Prevent the anchor tag from jumping us to the top of the page. handle.on('click', function (e) { e.preventDefault(); }); - // Set blur cleanup when a handle is focused. handle.on('focus', function () { self.safeBlur = true; }); - // On blur, fire the same function as a touchend/mouseup. This is used to - // update values after a row has been moved through the keyboard support. handle.on('blur', function (event) { if (self.rowObject && self.safeBlur) { self.dropRow(event, self); } }); - // Add arrow-key support to the handle. handle.on('keydown', function (event) { - // If a rowObject doesn't yet exist and this isn't the tab key. if (event.keyCode !== 9 && !self.rowObject) { self.rowObject = new self.row(item, 'keyboard', self.indentEnabled, self.maxDepth, true); } @@ -499,20 +278,14 @@ var keyChange = false; var groupHeight; - /* eslint-disable no-fallthrough */ - switch (event.keyCode) { - // Left arrow. case 37: - // Safari left arrow. case 63234: keyChange = true; self.rowObject.indent(-1 * self.rtl); break; - // Up arrow. case 38: - // Safari up arrow. case 63232: var $previousRow = $(self.rowObject.element).prev('tr:first-of-type'); var previousRow = $previousRow.get(0); @@ -521,13 +294,11 @@ previousRow = $previousRow.get(0); } if (previousRow) { - // Do not allow the onBlur cleanup. self.safeBlur = false; self.rowObject.direction = 'up'; keyChange = true; if ($(item).is('.tabledrag-root')) { - // Swap with the previous top-level row. groupHeight = 0; while (previousRow && $previousRow.find('.js-indentation').length) { $previousRow = $(previousRow).prev('tr:first-of-type'); @@ -536,34 +307,27 @@ } if (previousRow) { self.rowObject.swap('before', previousRow); - // No need to check for indentation, 0 is the only valid one. + window.scrollBy(0, -groupHeight); } - } - else if (self.table.tBodies[0].rows[0] !== previousRow || $previousRow.is('.draggable')) { - // Swap with the previous row (unless previous row is the first - // one and undraggable). + } else if (self.table.tBodies[0].rows[0] !== previousRow || $previousRow.is('.draggable')) { self.rowObject.swap('before', previousRow); self.rowObject.interval = null; self.rowObject.indent(0); window.scrollBy(0, -parseInt(item.offsetHeight, 10)); } - // Regain focus after the DOM manipulation. + handle.trigger('focus'); } break; - // Right arrow. case 39: - // Safari right arrow. case 63235: keyChange = true; self.rowObject.indent(self.rtl); break; - // Down arrow. case 40: - // Safari down arrow. case 63233: var $nextRow = $(self.rowObject.group).eq(-1).next('tr:first-of-type'); var nextRow = $nextRow.get(0); @@ -572,13 +336,11 @@ nextRow = $nextRow.get(0); } if (nextRow) { - // Do not allow the onBlur cleanup. self.safeBlur = false; self.rowObject.direction = 'down'; keyChange = true; if ($(item).is('.tabledrag-root')) { - // Swap with the next group (necessarily a top-level one). groupHeight = 0; var nextGroup = new self.row(nextRow, 'keyboard', self.indentEnabled, self.maxDepth, false); if (nextGroup) { @@ -587,25 +349,21 @@ }); var nextGroupRow = $(nextGroup.group).eq(-1).get(0); self.rowObject.swap('after', nextGroupRow); - // No need to check for indentation, 0 is the only valid one. + window.scrollBy(0, parseInt(groupHeight, 10)); } - } - else { - // Swap with the next row. + } else { self.rowObject.swap('after', nextRow); self.rowObject.interval = null; self.rowObject.indent(0); window.scrollBy(0, parseInt(item.offsetHeight, 10)); } - // Regain focus after the DOM manipulation. + handle.trigger('focus'); } break; } - /* eslint-enable no-fallthrough */ - if (self.rowObject && self.rowObject.changed === true) { $(item).addClass('drag'); if (self.oldRowElement) { @@ -618,49 +376,24 @@ self.onDrag(); } - // Returning false if we have an arrow key to prevent scrolling. if (keyChange) { return false; } }); - // Compatibility addition, return false on keypress to prevent unwanted - // scrolling. IE and Safari will suppress scrolling on keydown, but all - // other browsers need to return false on keypress. - // http://www.quirksmode.org/js/keys.html handle.on('keypress', function (event) { - /* eslint-disable no-fallthrough */ - switch (event.keyCode) { - // Left arrow. case 37: - // Up arrow. case 38: - // Right arrow. case 39: - // Down arrow. case 40: return false; } - - /* eslint-enable no-fallthrough */ - }); }; - /** - * Pointer event initiator, creates drag object and information. - * - * @param {jQuery.Event} event - * The event object that trigger the drag. - * @param {Drupal.tableDrag} self - * The drag handle. - * @param {HTMLElement} item - * The item that that is being dragged. - */ Drupal.tableDrag.prototype.dragStart = function (event, self, item) { - // Create a new dragObject recording the pointer information. self.dragObject = {}; self.dragObject.initOffset = self.getPointerOffset(item, event); self.dragObject.initPointerCoords = self.pointerCoords(event); @@ -668,66 +401,47 @@ self.dragObject.indentPointerPos = self.dragObject.initPointerCoords; } - // If there's a lingering row object from the keyboard, remove its focus. if (self.rowObject) { $(self.rowObject.element).find('a.tabledrag-handle').trigger('blur'); } - // Create a new rowObject for manipulation of this row. self.rowObject = new self.row(item, 'pointer', self.indentEnabled, self.maxDepth, true); - // Save the position of the table. self.table.topY = $(self.table).offset().top; self.table.bottomY = self.table.topY + self.table.offsetHeight; - // Add classes to the handle and row. $(item).addClass('drag'); - // Set the document to use the move cursor during drag. $('body').addClass('drag'); if (self.oldRowElement) { $(self.oldRowElement).removeClass('drag-previous'); } }; - /** - * Pointer movement handler, bound to document. - * - * @param {jQuery.Event} event - * The pointer event. - * @param {Drupal.tableDrag} self - * The tableDrag instance. - * - * @return {bool|undefined} - * Undefined if no dragObject is defined, false otherwise. - */ Drupal.tableDrag.prototype.dragRow = function (event, self) { if (self.dragObject) { self.currentPointerCoords = self.pointerCoords(event); var y = self.currentPointerCoords.y - self.dragObject.initOffset.y; var x = self.currentPointerCoords.x - self.dragObject.initOffset.x; - // Check for row swapping and vertical scrolling. if (y !== self.oldY) { self.rowObject.direction = y > self.oldY ? 'down' : 'up'; - // Update the old value. + self.oldY = y; - // Check if the window should be scrolled (and how fast). + var scrollAmount = self.checkScroll(self.currentPointerCoords.y); - // Stop any current scrolling. + clearInterval(self.scrollInterval); - // Continue scrolling if the mouse has moved in the scroll direction. + if (scrollAmount > 0 && self.rowObject.direction === 'down' || scrollAmount < 0 && self.rowObject.direction === 'up') { self.setScroll(scrollAmount); } - // If we have a valid target, perform the swap and restripe the table. var currentRow = self.findDropTargetRow(x, y); if (currentRow) { if (self.rowObject.direction === 'down') { self.rowObject.swap('after', currentRow, self); - } - else { + } else { self.rowObject.swap('before', currentRow, self); } if (self.striping === true) { @@ -736,16 +450,13 @@ } } - // Similar to row swapping, handle indentations. if (self.indentEnabled) { var xDiff = self.currentPointerCoords.x - self.dragObject.indentPointerPos.x; - // Set the number of indentations the pointer has been moved left or - // right. + var indentDiff = Math.round(xDiff / self.indentAmount); - // Indent the row with our estimated diff, which may be further - // restricted according to the rows around this row. + var indentChange = self.rowObject.indent(indentDiff); - // Update table and pointer indentations. + self.dragObject.indentPointerPos.x += self.indentAmount * indentChange * self.rtl; self.indentCount = Math.max(self.indentCount, self.rowObject.indents); } @@ -754,29 +465,17 @@ } }; - /** - * Pointerup behavior. - * - * @param {jQuery.Event} event - * The pointer event. - * @param {Drupal.tableDrag} self - * The tableDrag instance. - */ Drupal.tableDrag.prototype.dropRow = function (event, self) { var droppedRow; var $droppedRow; - // Drop row functionality. if (self.rowObject !== null) { droppedRow = self.rowObject.element; $droppedRow = $(droppedRow); - // The row is already in the right place so we just release it. + if (self.rowObject.changed === true) { - // Update the fields in the dropped row. self.updateFields(droppedRow); - // If a setting exists for affecting the entire group, update all the - // fields in the entire dragged group. for (var group in self.tableSettings) { if (self.tableSettings.hasOwnProperty(group)) { var rowSettings = self.rowSettings(group, droppedRow); @@ -809,7 +508,6 @@ self.rowObject = null; } - // Functionality specific only to pointerup events. if (self.dragObject !== null) { self.dragObject = null; $('body').removeClass('drag'); @@ -817,18 +515,9 @@ } }; - /** - * Get the coordinates from the event (allowing for browser differences). - * - * @param {jQuery.Event} event - * The pointer event. - * - * @return {object} - * An object with `x` and `y` keys indicating the position. - */ Drupal.tableDrag.prototype.pointerCoords = function (event) { if (event.pageX || event.pageY) { - return {x: event.pageX, y: event.pageY}; + return { x: event.pageX, y: event.pageY }; } return { x: event.clientX + document.body.scrollLeft - document.body.clientLeft, @@ -836,39 +525,12 @@ }; }; - /** - * Get the event offset from the target element. - * - * Given a target element and a pointer event, get the event offset from that - * element. To do this we need the element's position and the target position. - * - * @param {HTMLElement} target - * The target HTML element. - * @param {jQuery.Event} event - * The pointer event. - * - * @return {object} - * An object with `x` and `y` keys indicating the position. - */ Drupal.tableDrag.prototype.getPointerOffset = function (target, event) { var docPos = $(target).offset(); var pointerPos = this.pointerCoords(event); - return {x: pointerPos.x - docPos.left, y: pointerPos.y - docPos.top}; + return { x: pointerPos.x - docPos.left, y: pointerPos.y - docPos.top }; }; - /** - * Find the row the mouse is currently over. - * - * This row is then taken and swapped with the one being dragged. - * - * @param {number} x - * The x coordinate of the mouse on the page (not the screen). - * @param {number} y - * The y coordinate of the mouse on the page (not the screen). - * - * @return {*} - * The drop target row, if found. - */ Drupal.tableDrag.prototype.findDropTargetRow = function (x, y) { var rows = $(this.table.tBodies[0].rows).not(':hidden'); for (var n = 0; n < rows.length; n++) { @@ -876,42 +538,30 @@ var $row = $(row); var rowY = $row.offset().top; var rowHeight; - // Because Safari does not report offsetHeight on table rows, but does on - // table cells, grab the firstChild of the row and use that instead. - // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari. + if (row.offsetHeight === 0) { rowHeight = parseInt(row.firstChild.offsetHeight, 10) / 2; - } - // Other browsers. - else { - rowHeight = parseInt(row.offsetHeight, 10) / 2; - } + } else { + rowHeight = parseInt(row.offsetHeight, 10) / 2; + } - // Because we always insert before, we need to offset the height a bit. - if ((y > (rowY - rowHeight)) && (y < (rowY + rowHeight))) { + if (y > rowY - rowHeight && y < rowY + rowHeight) { if (this.indentEnabled) { - // Check that this row is not a child of the row being dragged. for (n in this.rowObject.group) { if (this.rowObject.group[n] === row) { return null; } } - } - else { - // Do not allow a row to be swapped with itself. + } else { if (row === this.rowObject.element) { return null; } } - // Check that swapping with this row is allowed. if (!this.rowObject.isValidSwap(row)) { return null; } - // We may have found the row the mouse just passed over, but it doesn't - // take into account hidden rows. Skip backwards until we find a - // draggable row. while ($row.is(':hidden') && $row.prev('tr').is(':hidden')) { $row = $row.prev('tr:first-of-type'); row = $row.get(0); @@ -922,30 +572,14 @@ return null; }; - /** - * After the row is dropped, update the table fields. - * - * @param {HTMLElement} changedRow - * DOM object for the row that was just dropped. - */ Drupal.tableDrag.prototype.updateFields = function (changedRow) { for (var group in this.tableSettings) { if (this.tableSettings.hasOwnProperty(group)) { - // Each group may have a different setting for relationship, so we find - // the source rows for each separately. this.updateField(changedRow, group); } } }; - /** - * After the row is dropped, update a single table field. - * - * @param {HTMLElement} changedRow - * DOM object for the row that was just dropped. - * @param {string} group - * The settings group on which field updates will occur. - */ Drupal.tableDrag.prototype.updateField = function (changedRow, group) { var rowSettings = this.rowSettings(group, changedRow); var $changedRow = $(changedRow); @@ -953,72 +587,54 @@ var $previousRow; var previousRow; var useSibling; - // Set the row as its own target. + if (rowSettings.relationship === 'self' || rowSettings.relationship === 'group') { sourceRow = changedRow; - } - // Siblings are easy, check previous and next rows. - else if (rowSettings.relationship === 'sibling') { - $previousRow = $changedRow.prev('tr:first-of-type'); - previousRow = $previousRow.get(0); - var $nextRow = $changedRow.next('tr:first-of-type'); - var nextRow = $nextRow.get(0); - sourceRow = changedRow; - if ($previousRow.is('.draggable') && $previousRow.find('.' + group).length) { - if (this.indentEnabled) { - if ($previousRow.find('.js-indentations').length === $changedRow.find('.js-indentations').length) { + } else if (rowSettings.relationship === 'sibling') { + $previousRow = $changedRow.prev('tr:first-of-type'); + previousRow = $previousRow.get(0); + var $nextRow = $changedRow.next('tr:first-of-type'); + var nextRow = $nextRow.get(0); + sourceRow = changedRow; + if ($previousRow.is('.draggable') && $previousRow.find('.' + group).length) { + if (this.indentEnabled) { + if ($previousRow.find('.js-indentations').length === $changedRow.find('.js-indentations').length) { + sourceRow = previousRow; + } + } else { sourceRow = previousRow; } - } - else { - sourceRow = previousRow; - } - } - else if ($nextRow.is('.draggable') && $nextRow.find('.' + group).length) { - if (this.indentEnabled) { - if ($nextRow.find('.js-indentations').length === $changedRow.find('.js-indentations').length) { + } else if ($nextRow.is('.draggable') && $nextRow.find('.' + group).length) { + if (this.indentEnabled) { + if ($nextRow.find('.js-indentations').length === $changedRow.find('.js-indentations').length) { + sourceRow = nextRow; + } + } else { sourceRow = nextRow; } } - else { - sourceRow = nextRow; - } - } - } - // Parents, look up the tree until we find a field not in this group. - // Go up as many parents as indentations in the changed row. - else if (rowSettings.relationship === 'parent') { - $previousRow = $changedRow.prev('tr'); - previousRow = $previousRow; - while ($previousRow.length && $previousRow.find('.js-indentation').length >= this.rowObject.indents) { - $previousRow = $previousRow.prev('tr'); - previousRow = $previousRow; - } - // If we found a row. - if ($previousRow.length) { - sourceRow = $previousRow.get(0); - } - // Otherwise we went all the way to the left of the table without finding - // a parent, meaning this item has been placed at the root level. - else { - // Use the first row in the table as source, because it's guaranteed to - // be at the root level. Find the first item, then compare this row - // against it as a sibling. - sourceRow = $(this.table).find('tr.draggable:first-of-type').get(0); - if (sourceRow === this.rowObject.element) { - sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0); + } else if (rowSettings.relationship === 'parent') { + $previousRow = $changedRow.prev('tr'); + previousRow = $previousRow; + while ($previousRow.length && $previousRow.find('.js-indentation').length >= this.rowObject.indents) { + $previousRow = $previousRow.prev('tr'); + previousRow = $previousRow; + } + + if ($previousRow.length) { + sourceRow = $previousRow.get(0); + } else { + sourceRow = $(this.table).find('tr.draggable:first-of-type').get(0); + if (sourceRow === this.rowObject.element) { + sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0); + } + useSibling = true; + } } - useSibling = true; - } - } - // Because we may have moved the row from one category to another, - // take a look at our sibling and borrow its sources and targets. this.copyDragClasses(sourceRow, changedRow, group); rowSettings = this.rowSettings(group, changedRow); - // In the case that we're looking for a parent, but the row is at the top - // of the tree, copy our sibling's values. if (useSibling) { rowSettings.relationship = 'sibling'; rowSettings.source = rowSettings.target; @@ -1027,44 +643,35 @@ var targetClass = '.' + rowSettings.target; var targetElement = $changedRow.find(targetClass).get(0); - // Check if a target element exists in this row. if (targetElement) { var sourceClass = '.' + rowSettings.source; var sourceElement = $(sourceClass, sourceRow).get(0); switch (rowSettings.action) { case 'depth': - // Get the depth of the target row. targetElement.value = $(sourceElement).closest('tr').find('.js-indentation').length; break; case 'match': - // Update the value. targetElement.value = sourceElement.value; break; case 'order': var siblings = this.rowObject.findSiblings(rowSettings); if ($(targetElement).is('select')) { - // Get a list of acceptable values. var values = []; $(targetElement).find('option').each(function () { values.push(this.value); }); var maxVal = values[values.length - 1]; - // Populate the values in the siblings. + $(siblings).find(targetClass).each(function () { - // If there are more items than possible values, assign the - // maximum value to the row. if (values.length > 0) { this.value = values.shift(); - } - else { + } else { this.value = maxVal; } }); - } - else { - // Assume a numeric input field. + } else { var weight = parseInt($(siblings[0]).find(targetClass).val(), 10) || 0; $(siblings).find(targetClass).each(function () { this.value = weight; @@ -1076,20 +683,6 @@ } }; - /** - * Copy all tableDrag related classes from one row to another. - * - * Copy all special tableDrag classes from one row's form elements to a - * different one, removing any special classes that the destination row - * may have had. - * - * @param {HTMLElement} sourceRow - * The element for the source row. - * @param {HTMLElement} targetRow - * The element for the target row. - * @param {string} group - * The group selector. - */ Drupal.tableDrag.prototype.copyDragClasses = function (sourceRow, targetRow, group) { var sourceElement = $(sourceRow).find('.' + group); var targetElement = $(targetRow).find('.' + group); @@ -1098,15 +691,6 @@ } }; - /** - * Check the suggested scroll of the table. - * - * @param {number} cursorY - * The Y position of the cursor. - * - * @return {number} - * The suggested scroll. - */ Drupal.tableDrag.prototype.checkScroll = function (cursorY) { var de = document.documentElement; var b = document.body; @@ -1115,37 +699,27 @@ var scrollY; if (document.all) { scrollY = this.scrollY = !de.scrollTop ? b.scrollTop : de.scrollTop; - } - else { + } else { scrollY = this.scrollY = window.pageYOffset ? window.pageYOffset : window.scrollY; } var trigger = this.scrollSettings.trigger; var delta = 0; - // Return a scroll speed relative to the edge of the screen. if (cursorY - scrollY > windowHeight - trigger) { delta = trigger / (windowHeight + scrollY - cursorY); - delta = (delta > 0 && delta < trigger) ? delta : trigger; + delta = delta > 0 && delta < trigger ? delta : trigger; return delta * this.scrollSettings.amount; - } - else if (cursorY - scrollY < trigger) { + } else if (cursorY - scrollY < trigger) { delta = trigger / (cursorY - scrollY); - delta = (delta > 0 && delta < trigger) ? delta : trigger; + delta = delta > 0 && delta < trigger ? delta : trigger; return -delta * this.scrollSettings.amount; } }; - /** - * Set the scroll for the table. - * - * @param {number} scrollAmount - * The amount of scroll to apply to the window. - */ Drupal.tableDrag.prototype.setScroll = function (scrollAmount) { var self = this; this.scrollInterval = setInterval(function () { - // Update the scroll values stored in the object. self.checkScroll(self.currentPointerCoords.y); var aboveTable = self.scrollY > self.table.topY; var belowTable = self.scrollY + self.windowHeight < self.table.bottomY; @@ -1155,55 +729,18 @@ }, this.scrollSettings.interval); }; - /** - * Command to restripe table properly. - */ Drupal.tableDrag.prototype.restripeTable = function () { - // :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. - $(this.table).find('> tbody > tr.draggable:visible, > tr.draggable:visible') - .removeClass('odd even') - .filter(':odd').addClass('even').end() - .filter(':even').addClass('odd'); + $(this.table).find('> tbody > tr.draggable:visible, > tr.draggable:visible').removeClass('odd even').filter(':odd').addClass('even').end().filter(':even').addClass('odd'); }; - /** - * Stub function. Allows a custom handler when a row begins dragging. - * - * @return {null} - * Returns null when the stub function is used. - */ Drupal.tableDrag.prototype.onDrag = function () { return null; }; - /** - * Stub function. Allows a custom handler when a row is dropped. - * - * @return {null} - * Returns null when the stub function is used. - */ Drupal.tableDrag.prototype.onDrop = function () { return null; }; - /** - * Constructor to make a new object to manipulate a table row. - * - * @param {HTMLElement} tableRow - * The DOM element for the table row we will be manipulating. - * @param {string} method - * The method in which this row is being moved. Either 'keyboard' or - * 'mouse'. - * @param {bool} indentEnabled - * Whether the containing table uses indentations. Used for optimizations. - * @param {number} maxDepth - * The maximum amount of indentations this row may contain. - * @param {bool} addClasses - * Whether we want to add classes to this row to indicate child - * relationships. - */ Drupal.tableDrag.prototype.row = function (tableRow, method, indentEnabled, maxDepth, addClasses) { var $tableRow = $(tableRow); @@ -1215,29 +752,19 @@ this.table = $tableRow.closest('table')[0]; this.indentEnabled = indentEnabled; this.maxDepth = maxDepth; - // Direction the row is being moved. + this.direction = ''; if (this.indentEnabled) { this.indents = $tableRow.find('.js-indentation').length; this.children = this.findChildren(addClasses); this.group = $.merge(this.group, this.children); - // Find the depth of this entire group. + for (var n = 0; n < this.group.length; n++) { this.groupDepth = Math.max($(this.group[n]).find('.js-indentation').length, this.groupDepth); } } }; - /** - * Find all children of rowObject by indentation. - * - * @param {bool} addClasses - * Whether we want to add classes to this row to indicate child - * relationships. - * - * @return {Array} - * An array of children of the row. - */ Drupal.tableDrag.prototype.row.prototype.findChildren = function (addClasses) { var parentIndentation = this.indents; var currentRow = $(this.element, this.table).next('tr.draggable'); @@ -1246,27 +773,24 @@ function rowIndentation(indentNum, el) { var self = $(el); - if (child === 1 && (indentNum === parentIndentation)) { + if (child === 1 && indentNum === parentIndentation) { self.addClass('tree-child-first'); } if (indentNum === parentIndentation) { self.addClass('tree-child'); - } - else if (indentNum > parentIndentation) { + } else if (indentNum > parentIndentation) { self.addClass('tree-child-horizontal'); } } while (currentRow.length) { - // A greater indentation indicates this is a child. if (currentRow.find('.js-indentation').length > parentIndentation) { child++; rows.push(currentRow[0]); if (addClasses) { currentRow.find('.js-indentation').each(rowIndentation); } - } - else { + } else { break; } currentRow = currentRow.next('tr.draggable'); @@ -1277,15 +801,6 @@ return rows; }; - /** - * Ensure that two rows are allowed to be swapped. - * - * @param {HTMLElement} row - * DOM object for the row being considered for swapping. - * - * @return {bool} - * Whether the swap is a valid swap or not. - */ Drupal.tableDrag.prototype.row.prototype.isValidSwap = function (row) { var $row = $(row); if (this.indentEnabled) { @@ -1294,20 +809,17 @@ if (this.direction === 'down') { prevRow = row; nextRow = $row.next('tr').get(0); - } - else { + } else { prevRow = $row.prev('tr').get(0); nextRow = row; } this.interval = this.validIndentInterval(prevRow, nextRow); - // We have an invalid swap if the valid indentations interval is empty. if (this.interval.min > this.interval.max) { return false; } } - // Do not let an un-draggable first row have anything put before it. if (this.table.tBodies[0].rows[0] === row && $row.is(':not(.draggable)')) { return false; } @@ -1315,21 +827,12 @@ return true; }; - /** - * Perform the swap between two rows. - * - * @param {string} position - * Whether the swap will occur 'before' or 'after' the given row. - * @param {HTMLElement} row - * DOM element what will be swapped with the row group. - */ Drupal.tableDrag.prototype.row.prototype.swap = function (position, row) { - // Makes sure only DOM object are passed to Drupal.detachBehaviors(). this.group.forEach(function (row) { Drupal.detachBehaviors(row, drupalSettings, 'move'); }); $(row)[position](this.group); - // Makes sure only DOM object are passed to Drupal.attachBehaviors()s. + this.group.forEach(function (row) { Drupal.attachBehaviors(row, drupalSettings); }); @@ -1337,88 +840,50 @@ this.onSwap(row); }; - /** - * Determine the valid indentations interval for the row at a given position. - * - * @param {?HTMLElement} prevRow - * DOM object for the row before the tested position - * (or null for first position in the table). - * @param {?HTMLElement} nextRow - * DOM object for the row after the tested position - * (or null for last position in the table). - * - * @return {object} - * An object with the keys `min` and `max` to indicate the valid indent - * interval. - */ Drupal.tableDrag.prototype.row.prototype.validIndentInterval = function (prevRow, nextRow) { var $prevRow = $(prevRow); var minIndent; var maxIndent; - // Minimum indentation: - // Do not orphan the next row. minIndent = nextRow ? $(nextRow).find('.js-indentation').length : 0; - // Maximum indentation: if (!prevRow || $prevRow.is(':not(.draggable)') || $(this.element).is('.tabledrag-root')) { - // Do not indent: - // - the first row in the table, - // - rows dragged below a non-draggable row, - // - 'root' rows. maxIndent = 0; - } - else { - // Do not go deeper than as a child of the previous row. + } else { maxIndent = $prevRow.find('.js-indentation').length + ($prevRow.is('.tabledrag-leaf') ? 0 : 1); - // Limit by the maximum allowed depth for the table. + if (this.maxDepth) { maxIndent = Math.min(maxIndent, this.maxDepth - (this.groupDepth - this.indents)); } } - return {min: minIndent, max: maxIndent}; + return { min: minIndent, max: maxIndent }; }; - /** - * Indent a row within the legal bounds of the table. - * - * @param {number} indentDiff - * The number of additional indentations proposed for the row (can be - * positive or negative). This number will be adjusted to nearest valid - * indentation level for the row. - * - * @return {number} - * The number of indentations applied. - */ Drupal.tableDrag.prototype.row.prototype.indent = function (indentDiff) { var $group = $(this.group); - // Determine the valid indentations interval if not available yet. + if (!this.interval) { var prevRow = $(this.element).prev('tr').get(0); var nextRow = $group.eq(-1).next('tr').get(0); this.interval = this.validIndentInterval(prevRow, nextRow); } - // Adjust to the nearest valid indentation. var indent = this.indents + indentDiff; indent = Math.max(indent, this.interval.min); indent = Math.min(indent, this.interval.max); indentDiff = indent - this.indents; for (var n = 1; n <= Math.abs(indentDiff); n++) { - // Add or remove indentations. if (indentDiff < 0) { $group.find('.js-indentation:first-of-type').remove(); this.indents--; - } - else { + } else { $group.find('td:first-of-type').prepend(Drupal.theme('tableDragIndentation')); this.indents++; } } if (indentDiff) { - // Update indentation for this row. this.changed = true; this.groupDepth += indentDiff; this.onIndent(); @@ -1427,18 +892,6 @@ return indentDiff; }; - /** - * Find all siblings for a row. - * - * According to its subgroup or indentation. Note that the passed-in row is - * included in the list of siblings. - * - * @param {object} rowSettings - * The field settings we're using to identify what constitutes a sibling. - * - * @return {Array} - * An array of siblings. - */ Drupal.tableDrag.prototype.row.prototype.findSiblings = function (rowSettings) { var siblings = []; var directions = ['prev', 'next']; @@ -1447,29 +900,22 @@ for (var d = 0; d < directions.length; d++) { var checkRow = $(this.element)[directions[d]](); while (checkRow.length) { - // Check that the sibling contains a similar target field. if (checkRow.find('.' + rowSettings.target)) { - // Either add immediately if this is a flat table, or check to ensure - // that this row has the same level of indentation. if (this.indentEnabled) { checkRowIndentation = checkRow.find('.js-indentation').length; } - if (!(this.indentEnabled) || (checkRowIndentation === rowIndentation)) { + if (!this.indentEnabled || checkRowIndentation === rowIndentation) { siblings.push(checkRow[0]); - } - else if (checkRowIndentation < rowIndentation) { - // No need to keep looking for siblings when we get to a parent. + } else if (checkRowIndentation < rowIndentation) { break; } - } - else { + } else { break; } checkRow = checkRow[directions[d]](); } - // Since siblings are added in reverse order for previous, reverse the - // completed list of previous siblings. Add the current row and continue. + if (directions[d] === 'prev') { siblings.reverse(); siblings.push(this.element); @@ -1478,24 +924,14 @@ return siblings; }; - /** - * Remove indentation helper classes from the current row group. - */ Drupal.tableDrag.prototype.row.prototype.removeIndentClasses = function () { for (var n in this.children) { if (this.children.hasOwnProperty(n)) { - $(this.children[n]).find('.js-indentation') - .removeClass('tree-child') - .removeClass('tree-child-first') - .removeClass('tree-child-last') - .removeClass('tree-child-horizontal'); + $(this.children[n]).find('.js-indentation').removeClass('tree-child').removeClass('tree-child-first').removeClass('tree-child-last').removeClass('tree-child-horizontal'); } } }; - /** - * Add an asterisk or other marker to the changed row. - */ Drupal.tableDrag.prototype.row.prototype.markChanged = function () { var marker = Drupal.theme('tableDragChangedMarker'); var cell = $(this.element).find('td:first-of-type'); @@ -1504,54 +940,27 @@ } }; - /** - * Stub function. Allows a custom handler when a row is indented. - * - * @return {null} - * Returns null when the stub function is used. - */ Drupal.tableDrag.prototype.row.prototype.onIndent = function () { return null; }; - /** - * Stub function. Allows a custom handler when a row is swapped. - * - * @param {HTMLElement} swappedRow - * The element for the swapped row. - * - * @return {null} - * Returns null when the stub function is used. - */ Drupal.tableDrag.prototype.row.prototype.onSwap = function (swappedRow) { return null; }; - $.extend(Drupal.theme, /** @lends Drupal.theme */{ - - /** - * @return {string} - * Markup for the marker. - */ - tableDragChangedMarker: function () { + $.extend(Drupal.theme, { + tableDragChangedMarker: function tableDragChangedMarker() { return '*'; }, - /** - * @return {string} - * Markup for the indentation. - */ - tableDragIndentation: function () { + tableDragIndentation: function tableDragIndentation() { return '
     
    '; }, - /** - * @return {string} - * Markup for the warning. - */ - tableDragChangedWarning: function () { + tableDragChangedWarning: function tableDragChangedWarning() { return ''; } }); - })(jQuery, Drupal, drupalSettings); + +//# sourceMappingURL=tabledrag.js.map \ No newline at end of file diff --git a/core/misc/tabledrag.js.map b/core/misc/tabledrag.js.map new file mode 100644 index 0000000..d1555d4 --- /dev/null +++ b/core/misc/tabledrag.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["tabledrag.es6.js"],"names":["$","Drupal","drupalSettings","showWeight","JSON","parse","localStorage","getItem","behaviors","tableDrag","attach","context","settings","initTableDrag","table","base","length","hasOwnProperty","find","once","tableSettings","self","$table","dragObject","rowObject","oldRowElement","oldY","changed","maxDepth","rtl","css","striping","data","scrollSettings","amount","interval","trigger","scrollInterval","scrollY","windowHeight","indentEnabled","group","n","relationship","limit","indentCount","indent","theme","testRow","addClass","appendTo","testCell","prepend","$indentation","indentAmount","get","offsetLeft","remove","each","makeDraggable","before","attr","t","on","proxy","e","preventDefault","toggleColumns","wrap","parent","initColumns","document","event","dragRow","originalEvent","touches","dropRow","window","key","newValue","displayColumns","prototype","hidden","cell","columnIndex","d","field","target","eq","closest","index","addColspanClass","$row","cells","children","colSpan","filter","displayWeight","showColumns","hideColumns","findOnce","setItem","removeItem","$tables","text","rowSettings","row","tableSettingsGroup","delta","targetClass","is","item","$item","handle","$indentationLast","after","Math","max","type","dragStart","safeBlur","keyCode","keyChange","groupHeight","$previousRow","element","prev","previousRow","direction","offsetHeight","swap","scrollBy","tBodies","rows","parseInt","$nextRow","next","nextRow","nextGroup","nextGroupRow","removeClass","restripeTable","onDrag","initOffset","getPointerOffset","initPointerCoords","pointerCoords","indentPointerPos","topY","offset","top","bottomY","currentPointerCoords","y","x","scrollAmount","checkScroll","clearInterval","setScroll","currentRow","findDropTargetRow","xDiff","indentDiff","round","indentChange","indents","droppedRow","$droppedRow","updateFields","updateField","markChanged","insertBefore","hide","fadeIn","removeIndentClasses","onDrop","pageX","pageY","clientX","body","scrollLeft","clientLeft","clientY","scrollTop","clientTop","docPos","pointerPos","left","not","rowY","rowHeight","firstChild","isValidSwap","changedRow","$changedRow","sourceRow","useSibling","copyDragClasses","source","targetElement","sourceClass","sourceElement","action","value","siblings","findSiblings","values","push","maxVal","shift","weight","val","targetRow","className","cursorY","de","documentElement","b","innerHeight","clientHeight","clientWidth","all","pageYOffset","setInterval","aboveTable","belowTable","end","tableRow","method","addClasses","$tableRow","groupDepth","findChildren","merge","parentIndentation","child","rowIndentation","indentNum","el","prevRow","validIndentInterval","min","position","forEach","detachBehaviors","attachBehaviors","onSwap","$prevRow","minIndent","maxIndent","$group","abs","onIndent","directions","checkRowIndentation","checkRow","reverse","marker","append","swappedRow","extend","tableDragChangedMarker","tableDragIndentation","tableDragChangedWarning","jQuery"],"mappings":"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","file":"tabledrag.es6.js","sourcesContent":["/**\n * @file\n * Provide dragging capabilities to admin uis.\n */\n\n/**\n * Triggers when weights columns are toggled.\n *\n * @event columnschange\n */\n\n(function ($, Drupal, drupalSettings) {\n\n 'use strict';\n\n /**\n * Store the state of weight columns display for all tables.\n *\n * Default value is to hide weight columns.\n */\n var showWeight = JSON.parse(localStorage.getItem('Drupal.tableDrag.showWeight'));\n\n /**\n * Drag and drop table rows with field manipulation.\n *\n * Using the drupal_attach_tabledrag() function, any table with weights or\n * parent relationships may be made into draggable tables. Columns containing\n * a field may optionally be hidden, providing a better user experience.\n *\n * Created tableDrag instances may be modified with custom behaviors by\n * overriding the .onDrag, .onDrop, .row.onSwap, and .row.onIndent methods.\n * See blocks.js for an example of adding additional functionality to\n * tableDrag.\n *\n * @type {Drupal~behavior}\n */\n Drupal.behaviors.tableDrag = {\n attach: function (context, settings) {\n function initTableDrag(table, base) {\n if (table.length) {\n // Create the new tableDrag instance. Save in the Drupal variable\n // to allow other scripts access to the object.\n Drupal.tableDrag[base] = new Drupal.tableDrag(table[0], settings.tableDrag[base]);\n }\n }\n\n for (var base in settings.tableDrag) {\n if (settings.tableDrag.hasOwnProperty(base)) {\n initTableDrag($(context).find('#' + base).once('tabledrag'), base);\n }\n }\n }\n };\n\n /**\n * Provides table and field manipulation.\n *\n * @constructor\n *\n * @param {HTMLElement} table\n * DOM object for the table to be made draggable.\n * @param {object} tableSettings\n * Settings for the table added via drupal_add_dragtable().\n */\n Drupal.tableDrag = function (table, tableSettings) {\n var self = this;\n var $table = $(table);\n\n /**\n * @type {jQuery}\n */\n this.$table = $(table);\n\n /**\n *\n * @type {HTMLElement}\n */\n this.table = table;\n\n /**\n * @type {object}\n */\n this.tableSettings = tableSettings;\n\n /**\n * Used to hold information about a current drag operation.\n *\n * @type {?HTMLElement}\n */\n this.dragObject = null;\n\n /**\n * Provides operations for row manipulation.\n *\n * @type {?HTMLElement}\n */\n this.rowObject = null;\n\n /**\n * Remember the previous element.\n *\n * @type {?HTMLElement}\n */\n this.oldRowElement = null;\n\n /**\n * Used to determine up or down direction from last mouse move.\n *\n * @type {number}\n */\n this.oldY = 0;\n\n /**\n * Whether anything in the entire table has changed.\n *\n * @type {bool}\n */\n this.changed = false;\n\n /**\n * Maximum amount of allowed parenting.\n *\n * @type {number}\n */\n this.maxDepth = 0;\n\n /**\n * Direction of the table.\n *\n * @type {number}\n */\n this.rtl = $(this.table).css('direction') === 'rtl' ? -1 : 1;\n\n /**\n *\n * @type {bool}\n */\n this.striping = $(this.table).data('striping') === 1;\n\n /**\n * Configure the scroll settings.\n *\n * @type {object}\n *\n * @prop {number} amount\n * @prop {number} interval\n * @prop {number} trigger\n */\n this.scrollSettings = {amount: 4, interval: 50, trigger: 70};\n\n /**\n *\n * @type {?number}\n */\n this.scrollInterval = null;\n\n /**\n *\n * @type {number}\n */\n this.scrollY = 0;\n\n /**\n *\n * @type {number}\n */\n this.windowHeight = 0;\n\n /**\n * Check this table's settings for parent relationships.\n *\n * For efficiency, large sections of code can be skipped if we don't need to\n * track horizontal movement and indentations.\n *\n * @type {bool}\n */\n this.indentEnabled = false;\n for (var group in tableSettings) {\n if (tableSettings.hasOwnProperty(group)) {\n for (var n in tableSettings[group]) {\n if (tableSettings[group].hasOwnProperty(n)) {\n if (tableSettings[group][n].relationship === 'parent') {\n this.indentEnabled = true;\n }\n if (tableSettings[group][n].limit > 0) {\n this.maxDepth = tableSettings[group][n].limit;\n }\n }\n }\n }\n }\n if (this.indentEnabled) {\n\n /**\n * Total width of indents, set in makeDraggable.\n *\n * @type {number}\n */\n this.indentCount = 1;\n // Find the width of indentations to measure mouse movements against.\n // Because the table doesn't need to start with any indentations, we\n // manually append 2 indentations in the first draggable row, measure\n // the offset, then remove.\n var indent = Drupal.theme('tableDragIndentation');\n var testRow = $('
    ').appendTo(testRow).prepend(indent).prepend(indent);\n var $indentation = testCell.find('.js-indentation');\n\n /**\n *\n * @type {number}\n */\n this.indentAmount = $indentation.get(1).offsetLeft - $indentation.get(0).offsetLeft;\n testRow.remove();\n }\n\n // Make each applicable row draggable.\n // Match immediate children of the parent element to allow nesting.\n $table.find('> tr.draggable, > tbody > tr.draggable').each(function () { self.makeDraggable(this); });\n\n // Add a link before the table for users to show or hide weight columns.\n $table.before($('')\n .attr('title', Drupal.t('Re-order rows by numerical weight instead of dragging.'))\n .on('click', $.proxy(function (e) {\n e.preventDefault();\n this.toggleColumns();\n }, this))\n .wrap('
    ')\n .parent()\n );\n\n // Initialize the specified columns (for example, weight or parent columns)\n // to show or hide according to user preference. This aids accessibility\n // so that, e.g., screen reader users can choose to enter weight values and\n // manipulate form elements directly, rather than using drag-and-drop..\n self.initColumns();\n\n // Add event bindings to the document. The self variable is passed along\n // as event handlers do not have direct access to the tableDrag object.\n $(document).on('touchmove', function (event) { return self.dragRow(event.originalEvent.touches[0], self); });\n $(document).on('touchend', function (event) { return self.dropRow(event.originalEvent.touches[0], self); });\n $(document).on('mousemove pointermove', function (event) { return self.dragRow(event, self); });\n $(document).on('mouseup pointerup', function (event) { return self.dropRow(event, self); });\n\n // React to localStorage event showing or hiding weight columns.\n $(window).on('storage', $.proxy(function (e) {\n // Only react to 'Drupal.tableDrag.showWeight' value change.\n if (e.originalEvent.key === 'Drupal.tableDrag.showWeight') {\n // This was changed in another window, get the new value for this\n // window.\n showWeight = JSON.parse(e.originalEvent.newValue);\n this.displayColumns(showWeight);\n }\n }, this));\n };\n\n /**\n * Initialize columns containing form elements to be hidden by default.\n *\n * Identify and mark each cell with a CSS class so we can easily toggle\n * show/hide it. Finally, hide columns if user does not have a\n * 'Drupal.tableDrag.showWeight' localStorage value.\n */\n Drupal.tableDrag.prototype.initColumns = function () {\n var $table = this.$table;\n var hidden;\n var cell;\n var columnIndex;\n for (var group in this.tableSettings) {\n if (this.tableSettings.hasOwnProperty(group)) {\n\n // Find the first field in this group.\n for (var d in this.tableSettings[group]) {\n if (this.tableSettings[group].hasOwnProperty(d)) {\n var field = $table.find('.' + this.tableSettings[group][d].target).eq(0);\n if (field.length && this.tableSettings[group][d].hidden) {\n hidden = this.tableSettings[group][d].hidden;\n cell = field.closest('td');\n break;\n }\n }\n }\n\n // Mark the column containing this field so it can be hidden.\n if (hidden && cell[0]) {\n // Add 1 to our indexes. The nth-child selector is 1 based, not 0\n // based. Match immediate children of the parent element to allow\n // nesting.\n columnIndex = cell.parent().find('> td').index(cell.get(0)) + 1;\n $table.find('> thead > tr, > tbody > tr, > tr').each(this.addColspanClass(columnIndex));\n }\n }\n }\n this.displayColumns(showWeight);\n };\n\n /**\n * Mark cells that have colspan.\n *\n * In order to adjust the colspan instead of hiding them altogether.\n *\n * @param {number} columnIndex\n * The column index to add colspan class to.\n *\n * @return {function}\n * Function to add colspan class.\n */\n Drupal.tableDrag.prototype.addColspanClass = function (columnIndex) {\n return function () {\n // Get the columnIndex and adjust for any colspans in this row.\n var $row = $(this);\n var index = columnIndex;\n var cells = $row.children();\n var cell;\n cells.each(function (n) {\n if (n < index && this.colSpan && this.colSpan > 1) {\n index -= this.colSpan - 1;\n }\n });\n if (index > 0) {\n cell = cells.filter(':nth-child(' + index + ')');\n if (cell[0].colSpan && cell[0].colSpan > 1) {\n // If this cell has a colspan, mark it so we can reduce the colspan.\n cell.addClass('tabledrag-has-colspan');\n }\n else {\n // Mark this cell so we can hide it.\n cell.addClass('tabledrag-hide');\n }\n }\n };\n };\n\n /**\n * Hide or display weight columns. Triggers an event on change.\n *\n * @fires event:columnschange\n *\n * @param {bool} displayWeight\n * 'true' will show weight columns.\n */\n Drupal.tableDrag.prototype.displayColumns = function (displayWeight) {\n if (displayWeight) {\n this.showColumns();\n }\n // Default action is to hide columns.\n else {\n this.hideColumns();\n }\n // Trigger an event to allow other scripts to react to this display change.\n // Force the extra parameter as a bool.\n $('table').findOnce('tabledrag').trigger('columnschange', !!displayWeight);\n };\n\n /**\n * Toggle the weight column depending on 'showWeight' value.\n *\n * Store only default override.\n */\n Drupal.tableDrag.prototype.toggleColumns = function () {\n showWeight = !showWeight;\n this.displayColumns(showWeight);\n if (showWeight) {\n // Save default override.\n localStorage.setItem('Drupal.tableDrag.showWeight', showWeight);\n }\n else {\n // Reset the value to its default.\n localStorage.removeItem('Drupal.tableDrag.showWeight');\n }\n };\n\n /**\n * Hide the columns containing weight/parent form elements.\n *\n * Undo showColumns().\n */\n Drupal.tableDrag.prototype.hideColumns = function () {\n var $tables = $('table').findOnce('tabledrag');\n // Hide weight/parent cells and headers.\n $tables.find('.tabledrag-hide').css('display', 'none');\n // Show TableDrag handles.\n $tables.find('.tabledrag-handle').css('display', '');\n // Reduce the colspan of any effected multi-span columns.\n $tables.find('.tabledrag-has-colspan').each(function () {\n this.colSpan = this.colSpan - 1;\n });\n // Change link text.\n $('.tabledrag-toggle-weight').text(Drupal.t('Show row weights'));\n };\n\n /**\n * Show the columns containing weight/parent form elements.\n *\n * Undo hideColumns().\n */\n Drupal.tableDrag.prototype.showColumns = function () {\n var $tables = $('table').findOnce('tabledrag');\n // Show weight/parent cells and headers.\n $tables.find('.tabledrag-hide').css('display', '');\n // Hide TableDrag handles.\n $tables.find('.tabledrag-handle').css('display', 'none');\n // Increase the colspan for any columns where it was previously reduced.\n $tables.find('.tabledrag-has-colspan').each(function () {\n this.colSpan = this.colSpan + 1;\n });\n // Change link text.\n $('.tabledrag-toggle-weight').text(Drupal.t('Hide row weights'));\n };\n\n /**\n * Find the target used within a particular row and group.\n *\n * @param {string} group\n * Group selector.\n * @param {HTMLElement} row\n * The row HTML element.\n *\n * @return {object}\n * The table row settings.\n */\n Drupal.tableDrag.prototype.rowSettings = function (group, row) {\n var field = $(row).find('.' + group);\n var tableSettingsGroup = this.tableSettings[group];\n for (var delta in tableSettingsGroup) {\n if (tableSettingsGroup.hasOwnProperty(delta)) {\n var targetClass = tableSettingsGroup[delta].target;\n if (field.is('.' + targetClass)) {\n // Return a copy of the row settings.\n var rowSettings = {};\n for (var n in tableSettingsGroup[delta]) {\n if (tableSettingsGroup[delta].hasOwnProperty(n)) {\n rowSettings[n] = tableSettingsGroup[delta][n];\n }\n }\n return rowSettings;\n }\n }\n }\n };\n\n /**\n * Take an item and add event handlers to make it become draggable.\n *\n * @param {HTMLElement} item\n * The item to add event handlers to.\n */\n Drupal.tableDrag.prototype.makeDraggable = function (item) {\n var self = this;\n var $item = $(item);\n // Add a class to the title link.\n $item.find('td:first-of-type').find('a').addClass('menu-item__link');\n // Create the handle.\n var handle = $('
     
    ').attr('title', Drupal.t('Drag to re-order'));\n // Insert the handle after indentations (if any).\n var $indentationLast = $item.find('td:first-of-type').find('.js-indentation').eq(-1);\n if ($indentationLast.length) {\n $indentationLast.after(handle);\n // Update the total width of indentation in this entire table.\n self.indentCount = Math.max($item.find('.js-indentation').length, self.indentCount);\n }\n else {\n $item.find('td').eq(0).prepend(handle);\n }\n\n handle.on('mousedown touchstart pointerdown', function (event) {\n event.preventDefault();\n if (event.originalEvent.type === 'touchstart') {\n event = event.originalEvent.touches[0];\n }\n self.dragStart(event, self, item);\n });\n\n // Prevent the anchor tag from jumping us to the top of the page.\n handle.on('click', function (e) {\n e.preventDefault();\n });\n\n // Set blur cleanup when a handle is focused.\n handle.on('focus', function () {\n self.safeBlur = true;\n });\n\n // On blur, fire the same function as a touchend/mouseup. This is used to\n // update values after a row has been moved through the keyboard support.\n handle.on('blur', function (event) {\n if (self.rowObject && self.safeBlur) {\n self.dropRow(event, self);\n }\n });\n\n // Add arrow-key support to the handle.\n handle.on('keydown', function (event) {\n // If a rowObject doesn't yet exist and this isn't the tab key.\n if (event.keyCode !== 9 && !self.rowObject) {\n self.rowObject = new self.row(item, 'keyboard', self.indentEnabled, self.maxDepth, true);\n }\n\n var keyChange = false;\n var groupHeight;\n\n /* eslint-disable no-fallthrough */\n\n switch (event.keyCode) {\n // Left arrow.\n case 37:\n // Safari left arrow.\n case 63234:\n keyChange = true;\n self.rowObject.indent(-1 * self.rtl);\n break;\n\n // Up arrow.\n case 38:\n // Safari up arrow.\n case 63232:\n var $previousRow = $(self.rowObject.element).prev('tr:first-of-type');\n var previousRow = $previousRow.get(0);\n while (previousRow && $previousRow.is(':hidden')) {\n $previousRow = $(previousRow).prev('tr:first-of-type');\n previousRow = $previousRow.get(0);\n }\n if (previousRow) {\n // Do not allow the onBlur cleanup.\n self.safeBlur = false;\n self.rowObject.direction = 'up';\n keyChange = true;\n\n if ($(item).is('.tabledrag-root')) {\n // Swap with the previous top-level row.\n groupHeight = 0;\n while (previousRow && $previousRow.find('.js-indentation').length) {\n $previousRow = $(previousRow).prev('tr:first-of-type');\n previousRow = $previousRow.get(0);\n groupHeight += $previousRow.is(':hidden') ? 0 : previousRow.offsetHeight;\n }\n if (previousRow) {\n self.rowObject.swap('before', previousRow);\n // No need to check for indentation, 0 is the only valid one.\n window.scrollBy(0, -groupHeight);\n }\n }\n else if (self.table.tBodies[0].rows[0] !== previousRow || $previousRow.is('.draggable')) {\n // Swap with the previous row (unless previous row is the first\n // one and undraggable).\n self.rowObject.swap('before', previousRow);\n self.rowObject.interval = null;\n self.rowObject.indent(0);\n window.scrollBy(0, -parseInt(item.offsetHeight, 10));\n }\n // Regain focus after the DOM manipulation.\n handle.trigger('focus');\n }\n break;\n\n // Right arrow.\n case 39:\n // Safari right arrow.\n case 63235:\n keyChange = true;\n self.rowObject.indent(self.rtl);\n break;\n\n // Down arrow.\n case 40:\n // Safari down arrow.\n case 63233:\n var $nextRow = $(self.rowObject.group).eq(-1).next('tr:first-of-type');\n var nextRow = $nextRow.get(0);\n while (nextRow && $nextRow.is(':hidden')) {\n $nextRow = $(nextRow).next('tr:first-of-type');\n nextRow = $nextRow.get(0);\n }\n if (nextRow) {\n // Do not allow the onBlur cleanup.\n self.safeBlur = false;\n self.rowObject.direction = 'down';\n keyChange = true;\n\n if ($(item).is('.tabledrag-root')) {\n // Swap with the next group (necessarily a top-level one).\n groupHeight = 0;\n var nextGroup = new self.row(nextRow, 'keyboard', self.indentEnabled, self.maxDepth, false);\n if (nextGroup) {\n $(nextGroup.group).each(function () {\n groupHeight += $(this).is(':hidden') ? 0 : this.offsetHeight;\n });\n var nextGroupRow = $(nextGroup.group).eq(-1).get(0);\n self.rowObject.swap('after', nextGroupRow);\n // No need to check for indentation, 0 is the only valid one.\n window.scrollBy(0, parseInt(groupHeight, 10));\n }\n }\n else {\n // Swap with the next row.\n self.rowObject.swap('after', nextRow);\n self.rowObject.interval = null;\n self.rowObject.indent(0);\n window.scrollBy(0, parseInt(item.offsetHeight, 10));\n }\n // Regain focus after the DOM manipulation.\n handle.trigger('focus');\n }\n break;\n }\n\n /* eslint-enable no-fallthrough */\n\n if (self.rowObject && self.rowObject.changed === true) {\n $(item).addClass('drag');\n if (self.oldRowElement) {\n $(self.oldRowElement).removeClass('drag-previous');\n }\n self.oldRowElement = item;\n if (self.striping === true) {\n self.restripeTable();\n }\n self.onDrag();\n }\n\n // Returning false if we have an arrow key to prevent scrolling.\n if (keyChange) {\n return false;\n }\n });\n\n // Compatibility addition, return false on keypress to prevent unwanted\n // scrolling. IE and Safari will suppress scrolling on keydown, but all\n // other browsers need to return false on keypress.\n // http://www.quirksmode.org/js/keys.html\n handle.on('keypress', function (event) {\n\n /* eslint-disable no-fallthrough */\n\n switch (event.keyCode) {\n // Left arrow.\n case 37:\n // Up arrow.\n case 38:\n // Right arrow.\n case 39:\n // Down arrow.\n case 40:\n return false;\n }\n\n /* eslint-enable no-fallthrough */\n\n });\n };\n\n /**\n * Pointer event initiator, creates drag object and information.\n *\n * @param {jQuery.Event} event\n * The event object that trigger the drag.\n * @param {Drupal.tableDrag} self\n * The drag handle.\n * @param {HTMLElement} item\n * The item that that is being dragged.\n */\n Drupal.tableDrag.prototype.dragStart = function (event, self, item) {\n // Create a new dragObject recording the pointer information.\n self.dragObject = {};\n self.dragObject.initOffset = self.getPointerOffset(item, event);\n self.dragObject.initPointerCoords = self.pointerCoords(event);\n if (self.indentEnabled) {\n self.dragObject.indentPointerPos = self.dragObject.initPointerCoords;\n }\n\n // If there's a lingering row object from the keyboard, remove its focus.\n if (self.rowObject) {\n $(self.rowObject.element).find('a.tabledrag-handle').trigger('blur');\n }\n\n // Create a new rowObject for manipulation of this row.\n self.rowObject = new self.row(item, 'pointer', self.indentEnabled, self.maxDepth, true);\n\n // Save the position of the table.\n self.table.topY = $(self.table).offset().top;\n self.table.bottomY = self.table.topY + self.table.offsetHeight;\n\n // Add classes to the handle and row.\n $(item).addClass('drag');\n\n // Set the document to use the move cursor during drag.\n $('body').addClass('drag');\n if (self.oldRowElement) {\n $(self.oldRowElement).removeClass('drag-previous');\n }\n };\n\n /**\n * Pointer movement handler, bound to document.\n *\n * @param {jQuery.Event} event\n * The pointer event.\n * @param {Drupal.tableDrag} self\n * The tableDrag instance.\n *\n * @return {bool|undefined}\n * Undefined if no dragObject is defined, false otherwise.\n */\n Drupal.tableDrag.prototype.dragRow = function (event, self) {\n if (self.dragObject) {\n self.currentPointerCoords = self.pointerCoords(event);\n var y = self.currentPointerCoords.y - self.dragObject.initOffset.y;\n var x = self.currentPointerCoords.x - self.dragObject.initOffset.x;\n\n // Check for row swapping and vertical scrolling.\n if (y !== self.oldY) {\n self.rowObject.direction = y > self.oldY ? 'down' : 'up';\n // Update the old value.\n self.oldY = y;\n // Check if the window should be scrolled (and how fast).\n var scrollAmount = self.checkScroll(self.currentPointerCoords.y);\n // Stop any current scrolling.\n clearInterval(self.scrollInterval);\n // Continue scrolling if the mouse has moved in the scroll direction.\n if (scrollAmount > 0 && self.rowObject.direction === 'down' || scrollAmount < 0 && self.rowObject.direction === 'up') {\n self.setScroll(scrollAmount);\n }\n\n // If we have a valid target, perform the swap and restripe the table.\n var currentRow = self.findDropTargetRow(x, y);\n if (currentRow) {\n if (self.rowObject.direction === 'down') {\n self.rowObject.swap('after', currentRow, self);\n }\n else {\n self.rowObject.swap('before', currentRow, self);\n }\n if (self.striping === true) {\n self.restripeTable();\n }\n }\n }\n\n // Similar to row swapping, handle indentations.\n if (self.indentEnabled) {\n var xDiff = self.currentPointerCoords.x - self.dragObject.indentPointerPos.x;\n // Set the number of indentations the pointer has been moved left or\n // right.\n var indentDiff = Math.round(xDiff / self.indentAmount);\n // Indent the row with our estimated diff, which may be further\n // restricted according to the rows around this row.\n var indentChange = self.rowObject.indent(indentDiff);\n // Update table and pointer indentations.\n self.dragObject.indentPointerPos.x += self.indentAmount * indentChange * self.rtl;\n self.indentCount = Math.max(self.indentCount, self.rowObject.indents);\n }\n\n return false;\n }\n };\n\n /**\n * Pointerup behavior.\n *\n * @param {jQuery.Event} event\n * The pointer event.\n * @param {Drupal.tableDrag} self\n * The tableDrag instance.\n */\n Drupal.tableDrag.prototype.dropRow = function (event, self) {\n var droppedRow;\n var $droppedRow;\n\n // Drop row functionality.\n if (self.rowObject !== null) {\n droppedRow = self.rowObject.element;\n $droppedRow = $(droppedRow);\n // The row is already in the right place so we just release it.\n if (self.rowObject.changed === true) {\n // Update the fields in the dropped row.\n self.updateFields(droppedRow);\n\n // If a setting exists for affecting the entire group, update all the\n // fields in the entire dragged group.\n for (var group in self.tableSettings) {\n if (self.tableSettings.hasOwnProperty(group)) {\n var rowSettings = self.rowSettings(group, droppedRow);\n if (rowSettings.relationship === 'group') {\n for (var n in self.rowObject.children) {\n if (self.rowObject.children.hasOwnProperty(n)) {\n self.updateField(self.rowObject.children[n], group);\n }\n }\n }\n }\n }\n\n self.rowObject.markChanged();\n if (self.changed === false) {\n $(Drupal.theme('tableDragChangedWarning')).insertBefore(self.table).hide().fadeIn('slow');\n self.changed = true;\n }\n }\n\n if (self.indentEnabled) {\n self.rowObject.removeIndentClasses();\n }\n if (self.oldRowElement) {\n $(self.oldRowElement).removeClass('drag-previous');\n }\n $droppedRow.removeClass('drag').addClass('drag-previous');\n self.oldRowElement = droppedRow;\n self.onDrop();\n self.rowObject = null;\n }\n\n // Functionality specific only to pointerup events.\n if (self.dragObject !== null) {\n self.dragObject = null;\n $('body').removeClass('drag');\n clearInterval(self.scrollInterval);\n }\n };\n\n /**\n * Get the coordinates from the event (allowing for browser differences).\n *\n * @param {jQuery.Event} event\n * The pointer event.\n *\n * @return {object}\n * An object with `x` and `y` keys indicating the position.\n */\n Drupal.tableDrag.prototype.pointerCoords = function (event) {\n if (event.pageX || event.pageY) {\n return {x: event.pageX, y: event.pageY};\n }\n return {\n x: event.clientX + document.body.scrollLeft - document.body.clientLeft,\n y: event.clientY + document.body.scrollTop - document.body.clientTop\n };\n };\n\n /**\n * Get the event offset from the target element.\n *\n * Given a target element and a pointer event, get the event offset from that\n * element. To do this we need the element's position and the target position.\n *\n * @param {HTMLElement} target\n * The target HTML element.\n * @param {jQuery.Event} event\n * The pointer event.\n *\n * @return {object}\n * An object with `x` and `y` keys indicating the position.\n */\n Drupal.tableDrag.prototype.getPointerOffset = function (target, event) {\n var docPos = $(target).offset();\n var pointerPos = this.pointerCoords(event);\n return {x: pointerPos.x - docPos.left, y: pointerPos.y - docPos.top};\n };\n\n /**\n * Find the row the mouse is currently over.\n *\n * This row is then taken and swapped with the one being dragged.\n *\n * @param {number} x\n * The x coordinate of the mouse on the page (not the screen).\n * @param {number} y\n * The y coordinate of the mouse on the page (not the screen).\n *\n * @return {*}\n * The drop target row, if found.\n */\n Drupal.tableDrag.prototype.findDropTargetRow = function (x, y) {\n var rows = $(this.table.tBodies[0].rows).not(':hidden');\n for (var n = 0; n < rows.length; n++) {\n var row = rows[n];\n var $row = $(row);\n var rowY = $row.offset().top;\n var rowHeight;\n // Because Safari does not report offsetHeight on table rows, but does on\n // table cells, grab the firstChild of the row and use that instead.\n // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari.\n if (row.offsetHeight === 0) {\n rowHeight = parseInt(row.firstChild.offsetHeight, 10) / 2;\n }\n // Other browsers.\n else {\n rowHeight = parseInt(row.offsetHeight, 10) / 2;\n }\n\n // Because we always insert before, we need to offset the height a bit.\n if ((y > (rowY - rowHeight)) && (y < (rowY + rowHeight))) {\n if (this.indentEnabled) {\n // Check that this row is not a child of the row being dragged.\n for (n in this.rowObject.group) {\n if (this.rowObject.group[n] === row) {\n return null;\n }\n }\n }\n else {\n // Do not allow a row to be swapped with itself.\n if (row === this.rowObject.element) {\n return null;\n }\n }\n\n // Check that swapping with this row is allowed.\n if (!this.rowObject.isValidSwap(row)) {\n return null;\n }\n\n // We may have found the row the mouse just passed over, but it doesn't\n // take into account hidden rows. Skip backwards until we find a\n // draggable row.\n while ($row.is(':hidden') && $row.prev('tr').is(':hidden')) {\n $row = $row.prev('tr:first-of-type');\n row = $row.get(0);\n }\n return row;\n }\n }\n return null;\n };\n\n /**\n * After the row is dropped, update the table fields.\n *\n * @param {HTMLElement} changedRow\n * DOM object for the row that was just dropped.\n */\n Drupal.tableDrag.prototype.updateFields = function (changedRow) {\n for (var group in this.tableSettings) {\n if (this.tableSettings.hasOwnProperty(group)) {\n // Each group may have a different setting for relationship, so we find\n // the source rows for each separately.\n this.updateField(changedRow, group);\n }\n }\n };\n\n /**\n * After the row is dropped, update a single table field.\n *\n * @param {HTMLElement} changedRow\n * DOM object for the row that was just dropped.\n * @param {string} group\n * The settings group on which field updates will occur.\n */\n Drupal.tableDrag.prototype.updateField = function (changedRow, group) {\n var rowSettings = this.rowSettings(group, changedRow);\n var $changedRow = $(changedRow);\n var sourceRow;\n var $previousRow;\n var previousRow;\n var useSibling;\n // Set the row as its own target.\n if (rowSettings.relationship === 'self' || rowSettings.relationship === 'group') {\n sourceRow = changedRow;\n }\n // Siblings are easy, check previous and next rows.\n else if (rowSettings.relationship === 'sibling') {\n $previousRow = $changedRow.prev('tr:first-of-type');\n previousRow = $previousRow.get(0);\n var $nextRow = $changedRow.next('tr:first-of-type');\n var nextRow = $nextRow.get(0);\n sourceRow = changedRow;\n if ($previousRow.is('.draggable') && $previousRow.find('.' + group).length) {\n if (this.indentEnabled) {\n if ($previousRow.find('.js-indentations').length === $changedRow.find('.js-indentations').length) {\n sourceRow = previousRow;\n }\n }\n else {\n sourceRow = previousRow;\n }\n }\n else if ($nextRow.is('.draggable') && $nextRow.find('.' + group).length) {\n if (this.indentEnabled) {\n if ($nextRow.find('.js-indentations').length === $changedRow.find('.js-indentations').length) {\n sourceRow = nextRow;\n }\n }\n else {\n sourceRow = nextRow;\n }\n }\n }\n // Parents, look up the tree until we find a field not in this group.\n // Go up as many parents as indentations in the changed row.\n else if (rowSettings.relationship === 'parent') {\n $previousRow = $changedRow.prev('tr');\n previousRow = $previousRow;\n while ($previousRow.length && $previousRow.find('.js-indentation').length >= this.rowObject.indents) {\n $previousRow = $previousRow.prev('tr');\n previousRow = $previousRow;\n }\n // If we found a row.\n if ($previousRow.length) {\n sourceRow = $previousRow.get(0);\n }\n // Otherwise we went all the way to the left of the table without finding\n // a parent, meaning this item has been placed at the root level.\n else {\n // Use the first row in the table as source, because it's guaranteed to\n // be at the root level. Find the first item, then compare this row\n // against it as a sibling.\n sourceRow = $(this.table).find('tr.draggable:first-of-type').get(0);\n if (sourceRow === this.rowObject.element) {\n sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0);\n }\n useSibling = true;\n }\n }\n\n // Because we may have moved the row from one category to another,\n // take a look at our sibling and borrow its sources and targets.\n this.copyDragClasses(sourceRow, changedRow, group);\n rowSettings = this.rowSettings(group, changedRow);\n\n // In the case that we're looking for a parent, but the row is at the top\n // of the tree, copy our sibling's values.\n if (useSibling) {\n rowSettings.relationship = 'sibling';\n rowSettings.source = rowSettings.target;\n }\n\n var targetClass = '.' + rowSettings.target;\n var targetElement = $changedRow.find(targetClass).get(0);\n\n // Check if a target element exists in this row.\n if (targetElement) {\n var sourceClass = '.' + rowSettings.source;\n var sourceElement = $(sourceClass, sourceRow).get(0);\n switch (rowSettings.action) {\n case 'depth':\n // Get the depth of the target row.\n targetElement.value = $(sourceElement).closest('tr').find('.js-indentation').length;\n break;\n\n case 'match':\n // Update the value.\n targetElement.value = sourceElement.value;\n break;\n\n case 'order':\n var siblings = this.rowObject.findSiblings(rowSettings);\n if ($(targetElement).is('select')) {\n // Get a list of acceptable values.\n var values = [];\n $(targetElement).find('option').each(function () {\n values.push(this.value);\n });\n var maxVal = values[values.length - 1];\n // Populate the values in the siblings.\n $(siblings).find(targetClass).each(function () {\n // If there are more items than possible values, assign the\n // maximum value to the row.\n if (values.length > 0) {\n this.value = values.shift();\n }\n else {\n this.value = maxVal;\n }\n });\n }\n else {\n // Assume a numeric input field.\n var weight = parseInt($(siblings[0]).find(targetClass).val(), 10) || 0;\n $(siblings).find(targetClass).each(function () {\n this.value = weight;\n weight++;\n });\n }\n break;\n }\n }\n };\n\n /**\n * Copy all tableDrag related classes from one row to another.\n *\n * Copy all special tableDrag classes from one row's form elements to a\n * different one, removing any special classes that the destination row\n * may have had.\n *\n * @param {HTMLElement} sourceRow\n * The element for the source row.\n * @param {HTMLElement} targetRow\n * The element for the target row.\n * @param {string} group\n * The group selector.\n */\n Drupal.tableDrag.prototype.copyDragClasses = function (sourceRow, targetRow, group) {\n var sourceElement = $(sourceRow).find('.' + group);\n var targetElement = $(targetRow).find('.' + group);\n if (sourceElement.length && targetElement.length) {\n targetElement[0].className = sourceElement[0].className;\n }\n };\n\n /**\n * Check the suggested scroll of the table.\n *\n * @param {number} cursorY\n * The Y position of the cursor.\n *\n * @return {number}\n * The suggested scroll.\n */\n Drupal.tableDrag.prototype.checkScroll = function (cursorY) {\n var de = document.documentElement;\n var b = document.body;\n\n var windowHeight = this.windowHeight = window.innerHeight || (de.clientHeight && de.clientWidth !== 0 ? de.clientHeight : b.offsetHeight);\n var scrollY;\n if (document.all) {\n scrollY = this.scrollY = !de.scrollTop ? b.scrollTop : de.scrollTop;\n }\n else {\n scrollY = this.scrollY = window.pageYOffset ? window.pageYOffset : window.scrollY;\n }\n var trigger = this.scrollSettings.trigger;\n var delta = 0;\n\n // Return a scroll speed relative to the edge of the screen.\n if (cursorY - scrollY > windowHeight - trigger) {\n delta = trigger / (windowHeight + scrollY - cursorY);\n delta = (delta > 0 && delta < trigger) ? delta : trigger;\n return delta * this.scrollSettings.amount;\n }\n else if (cursorY - scrollY < trigger) {\n delta = trigger / (cursorY - scrollY);\n delta = (delta > 0 && delta < trigger) ? delta : trigger;\n return -delta * this.scrollSettings.amount;\n }\n };\n\n /**\n * Set the scroll for the table.\n *\n * @param {number} scrollAmount\n * The amount of scroll to apply to the window.\n */\n Drupal.tableDrag.prototype.setScroll = function (scrollAmount) {\n var self = this;\n\n this.scrollInterval = setInterval(function () {\n // Update the scroll values stored in the object.\n self.checkScroll(self.currentPointerCoords.y);\n var aboveTable = self.scrollY > self.table.topY;\n var belowTable = self.scrollY + self.windowHeight < self.table.bottomY;\n if (scrollAmount > 0 && belowTable || scrollAmount < 0 && aboveTable) {\n window.scrollBy(0, scrollAmount);\n }\n }, this.scrollSettings.interval);\n };\n\n /**\n * Command to restripe table properly.\n */\n Drupal.tableDrag.prototype.restripeTable = function () {\n // :even and :odd are reversed because jQuery counts from 0 and\n // we count from 1, so we're out of sync.\n // Match immediate children of the parent element to allow nesting.\n $(this.table).find('> tbody > tr.draggable:visible, > tr.draggable:visible')\n .removeClass('odd even')\n .filter(':odd').addClass('even').end()\n .filter(':even').addClass('odd');\n };\n\n /**\n * Stub function. Allows a custom handler when a row begins dragging.\n *\n * @return {null}\n * Returns null when the stub function is used.\n */\n Drupal.tableDrag.prototype.onDrag = function () {\n return null;\n };\n\n /**\n * Stub function. Allows a custom handler when a row is dropped.\n *\n * @return {null}\n * Returns null when the stub function is used.\n */\n Drupal.tableDrag.prototype.onDrop = function () {\n return null;\n };\n\n /**\n * Constructor to make a new object to manipulate a table row.\n *\n * @param {HTMLElement} tableRow\n * The DOM element for the table row we will be manipulating.\n * @param {string} method\n * The method in which this row is being moved. Either 'keyboard' or\n * 'mouse'.\n * @param {bool} indentEnabled\n * Whether the containing table uses indentations. Used for optimizations.\n * @param {number} maxDepth\n * The maximum amount of indentations this row may contain.\n * @param {bool} addClasses\n * Whether we want to add classes to this row to indicate child\n * relationships.\n */\n Drupal.tableDrag.prototype.row = function (tableRow, method, indentEnabled, maxDepth, addClasses) {\n var $tableRow = $(tableRow);\n\n this.element = tableRow;\n this.method = method;\n this.group = [tableRow];\n this.groupDepth = $tableRow.find('.js-indentation').length;\n this.changed = false;\n this.table = $tableRow.closest('table')[0];\n this.indentEnabled = indentEnabled;\n this.maxDepth = maxDepth;\n // Direction the row is being moved.\n this.direction = '';\n if (this.indentEnabled) {\n this.indents = $tableRow.find('.js-indentation').length;\n this.children = this.findChildren(addClasses);\n this.group = $.merge(this.group, this.children);\n // Find the depth of this entire group.\n for (var n = 0; n < this.group.length; n++) {\n this.groupDepth = Math.max($(this.group[n]).find('.js-indentation').length, this.groupDepth);\n }\n }\n };\n\n /**\n * Find all children of rowObject by indentation.\n *\n * @param {bool} addClasses\n * Whether we want to add classes to this row to indicate child\n * relationships.\n *\n * @return {Array}\n * An array of children of the row.\n */\n Drupal.tableDrag.prototype.row.prototype.findChildren = function (addClasses) {\n var parentIndentation = this.indents;\n var currentRow = $(this.element, this.table).next('tr.draggable');\n var rows = [];\n var child = 0;\n\n function rowIndentation(indentNum, el) {\n var self = $(el);\n if (child === 1 && (indentNum === parentIndentation)) {\n self.addClass('tree-child-first');\n }\n if (indentNum === parentIndentation) {\n self.addClass('tree-child');\n }\n else if (indentNum > parentIndentation) {\n self.addClass('tree-child-horizontal');\n }\n }\n\n while (currentRow.length) {\n // A greater indentation indicates this is a child.\n if (currentRow.find('.js-indentation').length > parentIndentation) {\n child++;\n rows.push(currentRow[0]);\n if (addClasses) {\n currentRow.find('.js-indentation').each(rowIndentation);\n }\n }\n else {\n break;\n }\n currentRow = currentRow.next('tr.draggable');\n }\n if (addClasses && rows.length) {\n $(rows[rows.length - 1]).find('.js-indentation:nth-child(' + (parentIndentation + 1) + ')').addClass('tree-child-last');\n }\n return rows;\n };\n\n /**\n * Ensure that two rows are allowed to be swapped.\n *\n * @param {HTMLElement} row\n * DOM object for the row being considered for swapping.\n *\n * @return {bool}\n * Whether the swap is a valid swap or not.\n */\n Drupal.tableDrag.prototype.row.prototype.isValidSwap = function (row) {\n var $row = $(row);\n if (this.indentEnabled) {\n var prevRow;\n var nextRow;\n if (this.direction === 'down') {\n prevRow = row;\n nextRow = $row.next('tr').get(0);\n }\n else {\n prevRow = $row.prev('tr').get(0);\n nextRow = row;\n }\n this.interval = this.validIndentInterval(prevRow, nextRow);\n\n // We have an invalid swap if the valid indentations interval is empty.\n if (this.interval.min > this.interval.max) {\n return false;\n }\n }\n\n // Do not let an un-draggable first row have anything put before it.\n if (this.table.tBodies[0].rows[0] === row && $row.is(':not(.draggable)')) {\n return false;\n }\n\n return true;\n };\n\n /**\n * Perform the swap between two rows.\n *\n * @param {string} position\n * Whether the swap will occur 'before' or 'after' the given row.\n * @param {HTMLElement} row\n * DOM element what will be swapped with the row group.\n */\n Drupal.tableDrag.prototype.row.prototype.swap = function (position, row) {\n // Makes sure only DOM object are passed to Drupal.detachBehaviors().\n this.group.forEach(function (row) {\n Drupal.detachBehaviors(row, drupalSettings, 'move');\n });\n $(row)[position](this.group);\n // Makes sure only DOM object are passed to Drupal.attachBehaviors()s.\n this.group.forEach(function (row) {\n Drupal.attachBehaviors(row, drupalSettings);\n });\n this.changed = true;\n this.onSwap(row);\n };\n\n /**\n * Determine the valid indentations interval for the row at a given position.\n *\n * @param {?HTMLElement} prevRow\n * DOM object for the row before the tested position\n * (or null for first position in the table).\n * @param {?HTMLElement} nextRow\n * DOM object for the row after the tested position\n * (or null for last position in the table).\n *\n * @return {object}\n * An object with the keys `min` and `max` to indicate the valid indent\n * interval.\n */\n Drupal.tableDrag.prototype.row.prototype.validIndentInterval = function (prevRow, nextRow) {\n var $prevRow = $(prevRow);\n var minIndent;\n var maxIndent;\n\n // Minimum indentation:\n // Do not orphan the next row.\n minIndent = nextRow ? $(nextRow).find('.js-indentation').length : 0;\n\n // Maximum indentation:\n if (!prevRow || $prevRow.is(':not(.draggable)') || $(this.element).is('.tabledrag-root')) {\n // Do not indent:\n // - the first row in the table,\n // - rows dragged below a non-draggable row,\n // - 'root' rows.\n maxIndent = 0;\n }\n else {\n // Do not go deeper than as a child of the previous row.\n maxIndent = $prevRow.find('.js-indentation').length + ($prevRow.is('.tabledrag-leaf') ? 0 : 1);\n // Limit by the maximum allowed depth for the table.\n if (this.maxDepth) {\n maxIndent = Math.min(maxIndent, this.maxDepth - (this.groupDepth - this.indents));\n }\n }\n\n return {min: minIndent, max: maxIndent};\n };\n\n /**\n * Indent a row within the legal bounds of the table.\n *\n * @param {number} indentDiff\n * The number of additional indentations proposed for the row (can be\n * positive or negative). This number will be adjusted to nearest valid\n * indentation level for the row.\n *\n * @return {number}\n * The number of indentations applied.\n */\n Drupal.tableDrag.prototype.row.prototype.indent = function (indentDiff) {\n var $group = $(this.group);\n // Determine the valid indentations interval if not available yet.\n if (!this.interval) {\n var prevRow = $(this.element).prev('tr').get(0);\n var nextRow = $group.eq(-1).next('tr').get(0);\n this.interval = this.validIndentInterval(prevRow, nextRow);\n }\n\n // Adjust to the nearest valid indentation.\n var indent = this.indents + indentDiff;\n indent = Math.max(indent, this.interval.min);\n indent = Math.min(indent, this.interval.max);\n indentDiff = indent - this.indents;\n\n for (var n = 1; n <= Math.abs(indentDiff); n++) {\n // Add or remove indentations.\n if (indentDiff < 0) {\n $group.find('.js-indentation:first-of-type').remove();\n this.indents--;\n }\n else {\n $group.find('td:first-of-type').prepend(Drupal.theme('tableDragIndentation'));\n this.indents++;\n }\n }\n if (indentDiff) {\n // Update indentation for this row.\n this.changed = true;\n this.groupDepth += indentDiff;\n this.onIndent();\n }\n\n return indentDiff;\n };\n\n /**\n * Find all siblings for a row.\n *\n * According to its subgroup or indentation. Note that the passed-in row is\n * included in the list of siblings.\n *\n * @param {object} rowSettings\n * The field settings we're using to identify what constitutes a sibling.\n *\n * @return {Array}\n * An array of siblings.\n */\n Drupal.tableDrag.prototype.row.prototype.findSiblings = function (rowSettings) {\n var siblings = [];\n var directions = ['prev', 'next'];\n var rowIndentation = this.indents;\n var checkRowIndentation;\n for (var d = 0; d < directions.length; d++) {\n var checkRow = $(this.element)[directions[d]]();\n while (checkRow.length) {\n // Check that the sibling contains a similar target field.\n if (checkRow.find('.' + rowSettings.target)) {\n // Either add immediately if this is a flat table, or check to ensure\n // that this row has the same level of indentation.\n if (this.indentEnabled) {\n checkRowIndentation = checkRow.find('.js-indentation').length;\n }\n\n if (!(this.indentEnabled) || (checkRowIndentation === rowIndentation)) {\n siblings.push(checkRow[0]);\n }\n else if (checkRowIndentation < rowIndentation) {\n // No need to keep looking for siblings when we get to a parent.\n break;\n }\n }\n else {\n break;\n }\n checkRow = checkRow[directions[d]]();\n }\n // Since siblings are added in reverse order for previous, reverse the\n // completed list of previous siblings. Add the current row and continue.\n if (directions[d] === 'prev') {\n siblings.reverse();\n siblings.push(this.element);\n }\n }\n return siblings;\n };\n\n /**\n * Remove indentation helper classes from the current row group.\n */\n Drupal.tableDrag.prototype.row.prototype.removeIndentClasses = function () {\n for (var n in this.children) {\n if (this.children.hasOwnProperty(n)) {\n $(this.children[n]).find('.js-indentation')\n .removeClass('tree-child')\n .removeClass('tree-child-first')\n .removeClass('tree-child-last')\n .removeClass('tree-child-horizontal');\n }\n }\n };\n\n /**\n * Add an asterisk or other marker to the changed row.\n */\n Drupal.tableDrag.prototype.row.prototype.markChanged = function () {\n var marker = Drupal.theme('tableDragChangedMarker');\n var cell = $(this.element).find('td:first-of-type');\n if (cell.find('abbr.tabledrag-changed').length === 0) {\n cell.append(marker);\n }\n };\n\n /**\n * Stub function. Allows a custom handler when a row is indented.\n *\n * @return {null}\n * Returns null when the stub function is used.\n */\n Drupal.tableDrag.prototype.row.prototype.onIndent = function () {\n return null;\n };\n\n /**\n * Stub function. Allows a custom handler when a row is swapped.\n *\n * @param {HTMLElement} swappedRow\n * The element for the swapped row.\n *\n * @return {null}\n * Returns null when the stub function is used.\n */\n Drupal.tableDrag.prototype.row.prototype.onSwap = function (swappedRow) {\n return null;\n };\n\n $.extend(Drupal.theme, /** @lends Drupal.theme */{\n\n /**\n * @return {string}\n * Markup for the marker.\n */\n tableDragChangedMarker: function () {\n return '*';\n },\n\n /**\n * @return {string}\n * Markup for the indentation.\n */\n tableDragIndentation: function () {\n return '
     
    ';\n },\n\n /**\n * @return {string}\n * Markup for the warning.\n */\n tableDragChangedWarning: function () {\n return '
    ' + Drupal.theme('tableDragChangedMarker') + ' ' + Drupal.t('You have unsaved changes.') + '
    ';\n }\n });\n\n})(jQuery, Drupal, drupalSettings);\n"]} \ No newline at end of file diff --git a/core/misc/tableheader.es6.js b/core/misc/tableheader.es6.js new file mode 100644 index 0000000..8fc1b04 --- /dev/null +++ b/core/misc/tableheader.es6.js @@ -0,0 +1,316 @@ +/** + * @file + * Sticky table headers. + */ + +(function ($, Drupal, displace) { + + 'use strict'; + + /** + * Attaches sticky table headers. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the sticky table header behavior. + */ + Drupal.behaviors.tableHeader = { + attach: function (context) { + $(window).one('scroll.TableHeaderInit', {context: context}, tableHeaderInitHandler); + } + }; + + function scrollValue(position) { + return document.documentElement[position] || document.body[position]; + } + + // Select and initialize sticky table headers. + function tableHeaderInitHandler(e) { + var $tables = $(e.data.context).find('table.sticky-enabled').once('tableheader'); + var il = $tables.length; + for (var i = 0; i < il; i++) { + TableHeader.tables.push(new TableHeader($tables[i])); + } + forTables('onScroll'); + } + + // Helper method to loop through tables and execute a method. + function forTables(method, arg) { + var tables = TableHeader.tables; + var il = tables.length; + for (var i = 0; i < il; i++) { + tables[i][method](arg); + } + } + + function tableHeaderResizeHandler(e) { + forTables('recalculateSticky'); + } + + function tableHeaderOnScrollHandler(e) { + forTables('onScroll'); + } + + function tableHeaderOffsetChangeHandler(e, offsets) { + forTables('stickyPosition', offsets.top); + } + + // Bind event that need to change all tables. + $(window).on({ + + /** + * When resizing table width can change, recalculate everything. + * + * @ignore + */ + 'resize.TableHeader': tableHeaderResizeHandler, + + /** + * Bind only one event to take care of calling all scroll callbacks. + * + * @ignore + */ + 'scroll.TableHeader': tableHeaderOnScrollHandler + }); + // Bind to custom Drupal events. + $(document).on({ + + /** + * Recalculate columns width when window is resized and when show/hide + * weight is triggered. + * + * @ignore + */ + 'columnschange.TableHeader': tableHeaderResizeHandler, + + /** + * Recalculate TableHeader.topOffset when viewport is resized. + * + * @ignore + */ + 'drupalViewportOffsetChange.TableHeader': tableHeaderOffsetChangeHandler + }); + + /** + * Constructor for the tableHeader object. Provides sticky table headers. + * + * TableHeader will make the current table header stick to the top of the page + * if the table is very long. + * + * @constructor Drupal.TableHeader + * + * @param {HTMLElement} table + * DOM object for the table to add a sticky header to. + * + * @listens event:columnschange + */ + function TableHeader(table) { + var $table = $(table); + + /** + * @name Drupal.TableHeader#$originalTable + * + * @type {HTMLElement} + */ + this.$originalTable = $table; + + /** + * @type {jQuery} + */ + this.$originalHeader = $table.children('thead'); + + /** + * @type {jQuery} + */ + this.$originalHeaderCells = this.$originalHeader.find('> tr > th'); + + /** + * @type {null|bool} + */ + this.displayWeight = null; + this.$originalTable.addClass('sticky-table'); + this.tableHeight = $table[0].clientHeight; + this.tableOffset = this.$originalTable.offset(); + + // React to columns change to avoid making checks in the scroll callback. + this.$originalTable.on('columnschange', {tableHeader: this}, function (e, display) { + var tableHeader = e.data.tableHeader; + if (tableHeader.displayWeight === null || tableHeader.displayWeight !== display) { + tableHeader.recalculateSticky(); + } + tableHeader.displayWeight = display; + }); + + // Create and display sticky header. + this.createSticky(); + } + + /** + * Store the state of TableHeader. + */ + $.extend(TableHeader, /** @lends Drupal.TableHeader */{ + + /** + * This will store the state of all processed tables. + * + * @type {Array.} + */ + tables: [] + }); + + /** + * Extend TableHeader prototype. + */ + $.extend(TableHeader.prototype, /** @lends Drupal.TableHeader# */{ + + /** + * Minimum height in pixels for the table to have a sticky header. + * + * @type {number} + */ + minHeight: 100, + + /** + * Absolute position of the table on the page. + * + * @type {?Drupal~displaceOffset} + */ + tableOffset: null, + + /** + * Absolute position of the table on the page. + * + * @type {?number} + */ + tableHeight: null, + + /** + * Boolean storing the sticky header visibility state. + * + * @type {bool} + */ + stickyVisible: false, + + /** + * Create the duplicate header. + */ + createSticky: function () { + // Clone the table header so it inherits original jQuery properties. + var $stickyHeader = this.$originalHeader.clone(true); + // Hide the table to avoid a flash of the header clone upon page load. + this.$stickyTable = $('') + .css({ + visibility: 'hidden', + position: 'fixed', + top: '0px' + }) + .append($stickyHeader) + .insertBefore(this.$originalTable); + + this.$stickyHeaderCells = $stickyHeader.find('> tr > th'); + + // Initialize all computations. + this.recalculateSticky(); + }, + + /** + * Set absolute position of sticky. + * + * @param {number} offsetTop + * The top offset for the sticky header. + * @param {number} offsetLeft + * The left offset for the sticky header. + * + * @return {jQuery} + * The sticky table as a jQuery collection. + */ + stickyPosition: function (offsetTop, offsetLeft) { + var css = {}; + if (typeof offsetTop === 'number') { + css.top = offsetTop + 'px'; + } + if (typeof offsetLeft === 'number') { + css.left = (this.tableOffset.left - offsetLeft) + 'px'; + } + return this.$stickyTable.css(css); + }, + + /** + * Returns true if sticky is currently visible. + * + * @return {bool} + * The visibility status. + */ + checkStickyVisible: function () { + var scrollTop = scrollValue('scrollTop'); + var tableTop = this.tableOffset.top - displace.offsets.top; + var tableBottom = tableTop + this.tableHeight; + var visible = false; + + if (tableTop < scrollTop && scrollTop < (tableBottom - this.minHeight)) { + visible = true; + } + + this.stickyVisible = visible; + return visible; + }, + + /** + * Check if sticky header should be displayed. + * + * This function is throttled to once every 250ms to avoid unnecessary + * calls. + * + * @param {jQuery.Event} e + * The scroll event. + */ + onScroll: function (e) { + this.checkStickyVisible(); + // Track horizontal positioning relative to the viewport. + this.stickyPosition(null, scrollValue('scrollLeft')); + this.$stickyTable.css('visibility', this.stickyVisible ? 'visible' : 'hidden'); + }, + + /** + * Event handler: recalculates position of the sticky table header. + * + * @param {jQuery.Event} event + * Event being triggered. + */ + recalculateSticky: function (event) { + // Update table size. + this.tableHeight = this.$originalTable[0].clientHeight; + + // Update offset top. + displace.offsets.top = displace.calculateOffset('top'); + this.tableOffset = this.$originalTable.offset(); + this.stickyPosition(displace.offsets.top, scrollValue('scrollLeft')); + + // Update columns width. + var $that = null; + var $stickyCell = null; + var display = null; + // Resize header and its cell widths. + // Only apply width to visible table cells. This prevents the header from + // displaying incorrectly when the sticky header is no longer visible. + var il = this.$originalHeaderCells.length; + for (var i = 0; i < il; i++) { + $that = $(this.$originalHeaderCells[i]); + $stickyCell = this.$stickyHeaderCells.eq($that.index()); + display = $that.css('display'); + if (display !== 'none') { + $stickyCell.css({width: $that.css('width'), display: display}); + } + else { + $stickyCell.css('display', 'none'); + } + } + this.$stickyTable.css('width', this.$originalTable.outerWidth()); + } + }); + + // Expose constructor in the public space. + Drupal.TableHeader = TableHeader; + +}(jQuery, Drupal, window.parent.Drupal.displace)); diff --git a/core/misc/tableheader.js b/core/misc/tableheader.js index 8fc1b04..2ebf79f 100644 --- a/core/misc/tableheader.js +++ b/core/misc/tableheader.js @@ -1,23 +1,12 @@ -/** - * @file - * Sticky table headers. - */ +'use strict'; (function ($, Drupal, displace) { 'use strict'; - /** - * Attaches sticky table headers. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches the sticky table header behavior. - */ Drupal.behaviors.tableHeader = { - attach: function (context) { - $(window).one('scroll.TableHeaderInit', {context: context}, tableHeaderInitHandler); + attach: function attach(context) { + $(window).one('scroll.TableHeaderInit', { context: context }, tableHeaderInitHandler); } }; @@ -25,7 +14,6 @@ return document.documentElement[position] || document.body[position]; } - // Select and initialize sticky table headers. function tableHeaderInitHandler(e) { var $tables = $(e.data.context).find('table.sticky-enabled').once('tableheader'); var il = $tables.length; @@ -35,7 +23,6 @@ forTables('onScroll'); } - // Helper method to loop through tables and execute a method. function forTables(method, arg) { var tables = TableHeader.tables; var il = tables.length; @@ -56,85 +43,33 @@ forTables('stickyPosition', offsets.top); } - // Bind event that need to change all tables. $(window).on({ - - /** - * When resizing table width can change, recalculate everything. - * - * @ignore - */ 'resize.TableHeader': tableHeaderResizeHandler, - /** - * Bind only one event to take care of calling all scroll callbacks. - * - * @ignore - */ 'scroll.TableHeader': tableHeaderOnScrollHandler }); - // Bind to custom Drupal events. - $(document).on({ - /** - * Recalculate columns width when window is resized and when show/hide - * weight is triggered. - * - * @ignore - */ + $(document).on({ 'columnschange.TableHeader': tableHeaderResizeHandler, - /** - * Recalculate TableHeader.topOffset when viewport is resized. - * - * @ignore - */ 'drupalViewportOffsetChange.TableHeader': tableHeaderOffsetChangeHandler }); - /** - * Constructor for the tableHeader object. Provides sticky table headers. - * - * TableHeader will make the current table header stick to the top of the page - * if the table is very long. - * - * @constructor Drupal.TableHeader - * - * @param {HTMLElement} table - * DOM object for the table to add a sticky header to. - * - * @listens event:columnschange - */ function TableHeader(table) { var $table = $(table); - /** - * @name Drupal.TableHeader#$originalTable - * - * @type {HTMLElement} - */ this.$originalTable = $table; - /** - * @type {jQuery} - */ this.$originalHeader = $table.children('thead'); - /** - * @type {jQuery} - */ this.$originalHeaderCells = this.$originalHeader.find('> tr > th'); - /** - * @type {null|bool} - */ this.displayWeight = null; this.$originalTable.addClass('sticky-table'); this.tableHeight = $table[0].clientHeight; this.tableOffset = this.$originalTable.offset(); - // React to columns change to avoid making checks in the scroll callback. - this.$originalTable.on('columnschange', {tableHeader: this}, function (e, display) { + this.$originalTable.on('columnschange', { tableHeader: this }, function (e, display) { var tableHeader = e.data.tableHeader; if (tableHeader.displayWeight === null || tableHeader.displayWeight !== display) { tableHeader.recalculateSticky(); @@ -142,113 +77,54 @@ tableHeader.displayWeight = display; }); - // Create and display sticky header. this.createSticky(); } - /** - * Store the state of TableHeader. - */ - $.extend(TableHeader, /** @lends Drupal.TableHeader */{ - - /** - * This will store the state of all processed tables. - * - * @type {Array.} - */ + $.extend(TableHeader, { tables: [] }); - /** - * Extend TableHeader prototype. - */ - $.extend(TableHeader.prototype, /** @lends Drupal.TableHeader# */{ - - /** - * Minimum height in pixels for the table to have a sticky header. - * - * @type {number} - */ + $.extend(TableHeader.prototype, { minHeight: 100, - /** - * Absolute position of the table on the page. - * - * @type {?Drupal~displaceOffset} - */ tableOffset: null, - /** - * Absolute position of the table on the page. - * - * @type {?number} - */ tableHeight: null, - /** - * Boolean storing the sticky header visibility state. - * - * @type {bool} - */ stickyVisible: false, - /** - * Create the duplicate header. - */ - createSticky: function () { - // Clone the table header so it inherits original jQuery properties. + createSticky: function createSticky() { var $stickyHeader = this.$originalHeader.clone(true); - // Hide the table to avoid a flash of the header clone upon page load. - this.$stickyTable = $('') - .css({ - visibility: 'hidden', - position: 'fixed', - top: '0px' - }) - .append($stickyHeader) - .insertBefore(this.$originalTable); + + this.$stickyTable = $('').css({ + visibility: 'hidden', + position: 'fixed', + top: '0px' + }).append($stickyHeader).insertBefore(this.$originalTable); this.$stickyHeaderCells = $stickyHeader.find('> tr > th'); - // Initialize all computations. this.recalculateSticky(); }, - /** - * Set absolute position of sticky. - * - * @param {number} offsetTop - * The top offset for the sticky header. - * @param {number} offsetLeft - * The left offset for the sticky header. - * - * @return {jQuery} - * The sticky table as a jQuery collection. - */ - stickyPosition: function (offsetTop, offsetLeft) { + stickyPosition: function stickyPosition(offsetTop, offsetLeft) { var css = {}; if (typeof offsetTop === 'number') { css.top = offsetTop + 'px'; } if (typeof offsetLeft === 'number') { - css.left = (this.tableOffset.left - offsetLeft) + 'px'; + css.left = this.tableOffset.left - offsetLeft + 'px'; } return this.$stickyTable.css(css); }, - /** - * Returns true if sticky is currently visible. - * - * @return {bool} - * The visibility status. - */ - checkStickyVisible: function () { + checkStickyVisible: function checkStickyVisible() { var scrollTop = scrollValue('scrollTop'); var tableTop = this.tableOffset.top - displace.offsets.top; var tableBottom = tableTop + this.tableHeight; var visible = false; - if (tableTop < scrollTop && scrollTop < (tableBottom - this.minHeight)) { + if (tableTop < scrollTop && scrollTop < tableBottom - this.minHeight) { visible = true; } @@ -256,53 +132,32 @@ return visible; }, - /** - * Check if sticky header should be displayed. - * - * This function is throttled to once every 250ms to avoid unnecessary - * calls. - * - * @param {jQuery.Event} e - * The scroll event. - */ - onScroll: function (e) { + onScroll: function onScroll(e) { this.checkStickyVisible(); - // Track horizontal positioning relative to the viewport. + this.stickyPosition(null, scrollValue('scrollLeft')); this.$stickyTable.css('visibility', this.stickyVisible ? 'visible' : 'hidden'); }, - /** - * Event handler: recalculates position of the sticky table header. - * - * @param {jQuery.Event} event - * Event being triggered. - */ - recalculateSticky: function (event) { - // Update table size. + recalculateSticky: function recalculateSticky(event) { this.tableHeight = this.$originalTable[0].clientHeight; - // Update offset top. displace.offsets.top = displace.calculateOffset('top'); this.tableOffset = this.$originalTable.offset(); this.stickyPosition(displace.offsets.top, scrollValue('scrollLeft')); - // Update columns width. var $that = null; var $stickyCell = null; var display = null; - // Resize header and its cell widths. - // Only apply width to visible table cells. This prevents the header from - // displaying incorrectly when the sticky header is no longer visible. + var il = this.$originalHeaderCells.length; for (var i = 0; i < il; i++) { $that = $(this.$originalHeaderCells[i]); $stickyCell = this.$stickyHeaderCells.eq($that.index()); display = $that.css('display'); if (display !== 'none') { - $stickyCell.css({width: $that.css('width'), display: display}); - } - else { + $stickyCell.css({ width: $that.css('width'), display: display }); + } else { $stickyCell.css('display', 'none'); } } @@ -310,7 +165,7 @@ } }); - // Expose constructor in the public space. Drupal.TableHeader = TableHeader; +})(jQuery, Drupal, window.parent.Drupal.displace); -}(jQuery, Drupal, window.parent.Drupal.displace)); +//# sourceMappingURL=tableheader.js.map \ No newline at end of file diff --git a/core/misc/tableheader.js.map b/core/misc/tableheader.js.map new file mode 100644 index 0000000..ab2f315 --- /dev/null +++ b/core/misc/tableheader.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["tableheader.es6.js"],"names":["$","Drupal","displace","behaviors","tableHeader","attach","context","window","one","tableHeaderInitHandler","scrollValue","position","document","documentElement","body","e","$tables","data","find","once","il","length","i","TableHeader","tables","push","forTables","method","arg","tableHeaderResizeHandler","tableHeaderOnScrollHandler","tableHeaderOffsetChangeHandler","offsets","top","on","table","$table","$originalTable","$originalHeader","children","$originalHeaderCells","displayWeight","addClass","tableHeight","clientHeight","tableOffset","offset","display","recalculateSticky","createSticky","extend","prototype","minHeight","stickyVisible","$stickyHeader","clone","$stickyTable","css","visibility","append","insertBefore","$stickyHeaderCells","stickyPosition","offsetTop","offsetLeft","left","checkStickyVisible","scrollTop","tableTop","tableBottom","visible","onScroll","event","calculateOffset","$that","$stickyCell","eq","index","width","outerWidth","jQuery","parent"],"mappings":"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","file":"tableheader.es6.js","sourcesContent":["/**\n * @file\n * Sticky table headers.\n */\n\n(function ($, Drupal, displace) {\n\n 'use strict';\n\n /**\n * Attaches sticky table headers.\n *\n * @type {Drupal~behavior}\n *\n * @prop {Drupal~behaviorAttach} attach\n * Attaches the sticky table header behavior.\n */\n Drupal.behaviors.tableHeader = {\n attach: function (context) {\n $(window).one('scroll.TableHeaderInit', {context: context}, tableHeaderInitHandler);\n }\n };\n\n function scrollValue(position) {\n return document.documentElement[position] || document.body[position];\n }\n\n // Select and initialize sticky table headers.\n function tableHeaderInitHandler(e) {\n var $tables = $(e.data.context).find('table.sticky-enabled').once('tableheader');\n var il = $tables.length;\n for (var i = 0; i < il; i++) {\n TableHeader.tables.push(new TableHeader($tables[i]));\n }\n forTables('onScroll');\n }\n\n // Helper method to loop through tables and execute a method.\n function forTables(method, arg) {\n var tables = TableHeader.tables;\n var il = tables.length;\n for (var i = 0; i < il; i++) {\n tables[i][method](arg);\n }\n }\n\n function tableHeaderResizeHandler(e) {\n forTables('recalculateSticky');\n }\n\n function tableHeaderOnScrollHandler(e) {\n forTables('onScroll');\n }\n\n function tableHeaderOffsetChangeHandler(e, offsets) {\n forTables('stickyPosition', offsets.top);\n }\n\n // Bind event that need to change all tables.\n $(window).on({\n\n /**\n * When resizing table width can change, recalculate everything.\n *\n * @ignore\n */\n 'resize.TableHeader': tableHeaderResizeHandler,\n\n /**\n * Bind only one event to take care of calling all scroll callbacks.\n *\n * @ignore\n */\n 'scroll.TableHeader': tableHeaderOnScrollHandler\n });\n // Bind to custom Drupal events.\n $(document).on({\n\n /**\n * Recalculate columns width when window is resized and when show/hide\n * weight is triggered.\n *\n * @ignore\n */\n 'columnschange.TableHeader': tableHeaderResizeHandler,\n\n /**\n * Recalculate TableHeader.topOffset when viewport is resized.\n *\n * @ignore\n */\n 'drupalViewportOffsetChange.TableHeader': tableHeaderOffsetChangeHandler\n });\n\n /**\n * Constructor for the tableHeader object. Provides sticky table headers.\n *\n * TableHeader will make the current table header stick to the top of the page\n * if the table is very long.\n *\n * @constructor Drupal.TableHeader\n *\n * @param {HTMLElement} table\n * DOM object for the table to add a sticky header to.\n *\n * @listens event:columnschange\n */\n function TableHeader(table) {\n var $table = $(table);\n\n /**\n * @name Drupal.TableHeader#$originalTable\n *\n * @type {HTMLElement}\n */\n this.$originalTable = $table;\n\n /**\n * @type {jQuery}\n */\n this.$originalHeader = $table.children('thead');\n\n /**\n * @type {jQuery}\n */\n this.$originalHeaderCells = this.$originalHeader.find('> tr > th');\n\n /**\n * @type {null|bool}\n */\n this.displayWeight = null;\n this.$originalTable.addClass('sticky-table');\n this.tableHeight = $table[0].clientHeight;\n this.tableOffset = this.$originalTable.offset();\n\n // React to columns change to avoid making checks in the scroll callback.\n this.$originalTable.on('columnschange', {tableHeader: this}, function (e, display) {\n var tableHeader = e.data.tableHeader;\n if (tableHeader.displayWeight === null || tableHeader.displayWeight !== display) {\n tableHeader.recalculateSticky();\n }\n tableHeader.displayWeight = display;\n });\n\n // Create and display sticky header.\n this.createSticky();\n }\n\n /**\n * Store the state of TableHeader.\n */\n $.extend(TableHeader, /** @lends Drupal.TableHeader */{\n\n /**\n * This will store the state of all processed tables.\n *\n * @type {Array.}\n */\n tables: []\n });\n\n /**\n * Extend TableHeader prototype.\n */\n $.extend(TableHeader.prototype, /** @lends Drupal.TableHeader# */{\n\n /**\n * Minimum height in pixels for the table to have a sticky header.\n *\n * @type {number}\n */\n minHeight: 100,\n\n /**\n * Absolute position of the table on the page.\n *\n * @type {?Drupal~displaceOffset}\n */\n tableOffset: null,\n\n /**\n * Absolute position of the table on the page.\n *\n * @type {?number}\n */\n tableHeight: null,\n\n /**\n * Boolean storing the sticky header visibility state.\n *\n * @type {bool}\n */\n stickyVisible: false,\n\n /**\n * Create the duplicate header.\n */\n createSticky: function () {\n // Clone the table header so it inherits original jQuery properties.\n var $stickyHeader = this.$originalHeader.clone(true);\n // Hide the table to avoid a flash of the header clone upon page load.\n this.$stickyTable = $('')\n .css({\n visibility: 'hidden',\n position: 'fixed',\n top: '0px'\n })\n .append($stickyHeader)\n .insertBefore(this.$originalTable);\n\n this.$stickyHeaderCells = $stickyHeader.find('> tr > th');\n\n // Initialize all computations.\n this.recalculateSticky();\n },\n\n /**\n * Set absolute position of sticky.\n *\n * @param {number} offsetTop\n * The top offset for the sticky header.\n * @param {number} offsetLeft\n * The left offset for the sticky header.\n *\n * @return {jQuery}\n * The sticky table as a jQuery collection.\n */\n stickyPosition: function (offsetTop, offsetLeft) {\n var css = {};\n if (typeof offsetTop === 'number') {\n css.top = offsetTop + 'px';\n }\n if (typeof offsetLeft === 'number') {\n css.left = (this.tableOffset.left - offsetLeft) + 'px';\n }\n return this.$stickyTable.css(css);\n },\n\n /**\n * Returns true if sticky is currently visible.\n *\n * @return {bool}\n * The visibility status.\n */\n checkStickyVisible: function () {\n var scrollTop = scrollValue('scrollTop');\n var tableTop = this.tableOffset.top - displace.offsets.top;\n var tableBottom = tableTop + this.tableHeight;\n var visible = false;\n\n if (tableTop < scrollTop && scrollTop < (tableBottom - this.minHeight)) {\n visible = true;\n }\n\n this.stickyVisible = visible;\n return visible;\n },\n\n /**\n * Check if sticky header should be displayed.\n *\n * This function is throttled to once every 250ms to avoid unnecessary\n * calls.\n *\n * @param {jQuery.Event} e\n * The scroll event.\n */\n onScroll: function (e) {\n this.checkStickyVisible();\n // Track horizontal positioning relative to the viewport.\n this.stickyPosition(null, scrollValue('scrollLeft'));\n this.$stickyTable.css('visibility', this.stickyVisible ? 'visible' : 'hidden');\n },\n\n /**\n * Event handler: recalculates position of the sticky table header.\n *\n * @param {jQuery.Event} event\n * Event being triggered.\n */\n recalculateSticky: function (event) {\n // Update table size.\n this.tableHeight = this.$originalTable[0].clientHeight;\n\n // Update offset top.\n displace.offsets.top = displace.calculateOffset('top');\n this.tableOffset = this.$originalTable.offset();\n this.stickyPosition(displace.offsets.top, scrollValue('scrollLeft'));\n\n // Update columns width.\n var $that = null;\n var $stickyCell = null;\n var display = null;\n // Resize header and its cell widths.\n // Only apply width to visible table cells. This prevents the header from\n // displaying incorrectly when the sticky header is no longer visible.\n var il = this.$originalHeaderCells.length;\n for (var i = 0; i < il; i++) {\n $that = $(this.$originalHeaderCells[i]);\n $stickyCell = this.$stickyHeaderCells.eq($that.index());\n display = $that.css('display');\n if (display !== 'none') {\n $stickyCell.css({width: $that.css('width'), display: display});\n }\n else {\n $stickyCell.css('display', 'none');\n }\n }\n this.$stickyTable.css('width', this.$originalTable.outerWidth());\n }\n });\n\n // Expose constructor in the public space.\n Drupal.TableHeader = TableHeader;\n\n}(jQuery, Drupal, window.parent.Drupal.displace));\n"]} \ No newline at end of file diff --git a/core/misc/tableresponsive.es6.js b/core/misc/tableresponsive.es6.js new file mode 100644 index 0000000..0127ec8 --- /dev/null +++ b/core/misc/tableresponsive.es6.js @@ -0,0 +1,174 @@ +/** + * @file + * Responsive table functionality. + */ + +(function ($, Drupal, window) { + + 'use strict'; + + /** + * Attach the tableResponsive function to {@link Drupal.behaviors}. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches tableResponsive functionality. + */ + Drupal.behaviors.tableResponsive = { + attach: function (context, settings) { + var $tables = $(context).find('table.responsive-enabled').once('tableresponsive'); + if ($tables.length) { + var il = $tables.length; + for (var i = 0; i < il; i++) { + TableResponsive.tables.push(new TableResponsive($tables[i])); + } + } + } + }; + + /** + * The TableResponsive object optimizes table presentation for screen size. + * + * A responsive table hides columns at small screen sizes, leaving the most + * important columns visible to the end user. Users should not be prevented + * from accessing all columns, however. This class adds a toggle to a table + * with hidden columns that exposes the columns. Exposing the columns will + * likely break layouts, but it provides the user with a means to access + * data, which is a guiding principle of responsive design. + * + * @constructor Drupal.TableResponsive + * + * @param {HTMLElement} table + * The table element to initialize the responsive table on. + */ + function TableResponsive(table) { + this.table = table; + this.$table = $(table); + this.showText = Drupal.t('Show all columns'); + this.hideText = Drupal.t('Hide lower priority columns'); + // Store a reference to the header elements of the table so that the DOM is + // traversed only once to find them. + this.$headers = this.$table.find('th'); + // Add a link before the table for users to show or hide weight columns. + this.$link = $('') + .attr('title', Drupal.t('Show table cells that were hidden to make the table fit within a small screen.')) + .on('click', $.proxy(this, 'eventhandlerToggleColumns')); + + this.$table.before($('
    ').append(this.$link)); + + // Attach a resize handler to the window. + $(window) + .on('resize.tableresponsive', $.proxy(this, 'eventhandlerEvaluateColumnVisibility')) + .trigger('resize.tableresponsive'); + } + + /** + * Extend the TableResponsive function with a list of managed tables. + */ + $.extend(TableResponsive, /** @lends Drupal.TableResponsive */{ + + /** + * Store all created instances. + * + * @type {Array.} + */ + tables: [] + }); + + /** + * Associates an action link with the table that will show hidden columns. + * + * Columns are assumed to be hidden if their header has the class priority-low + * or priority-medium. + */ + $.extend(TableResponsive.prototype, /** @lends Drupal.TableResponsive# */{ + + /** + * @param {jQuery.Event} e + * The event triggered. + */ + eventhandlerEvaluateColumnVisibility: function (e) { + var pegged = parseInt(this.$link.data('pegged'), 10); + var hiddenLength = this.$headers.filter('.priority-medium:hidden, .priority-low:hidden').length; + // If the table has hidden columns, associate an action link with the + // table to show the columns. + if (hiddenLength > 0) { + this.$link.show().text(this.showText); + } + // When the toggle is pegged, its presence is maintained because the user + // has interacted with it. This is necessary to keep the link visible if + // the user adjusts screen size and changes the visibility of columns. + if (!pegged && hiddenLength === 0) { + this.$link.hide().text(this.hideText); + } + }, + + /** + * Toggle the visibility of columns based on their priority. + * + * Columns are classed with either 'priority-low' or 'priority-medium'. + * + * @param {jQuery.Event} e + * The event triggered. + */ + eventhandlerToggleColumns: function (e) { + e.preventDefault(); + var self = this; + var $hiddenHeaders = this.$headers.filter('.priority-medium:hidden, .priority-low:hidden'); + this.$revealedCells = this.$revealedCells || $(); + // Reveal hidden columns. + if ($hiddenHeaders.length > 0) { + $hiddenHeaders.each(function (index, element) { + var $header = $(this); + var position = $header.prevAll('th').length; + self.$table.find('tbody tr').each(function () { + var $cells = $(this).find('td').eq(position); + $cells.show(); + // Keep track of the revealed cells, so they can be hidden later. + self.$revealedCells = $().add(self.$revealedCells).add($cells); + }); + $header.show(); + // Keep track of the revealed headers, so they can be hidden later. + self.$revealedCells = $().add(self.$revealedCells).add($header); + }); + this.$link.text(this.hideText).data('pegged', 1); + } + // Hide revealed columns. + else { + this.$revealedCells.hide(); + // Strip the 'display:none' declaration from the style attributes of + // the table cells that .hide() added. + this.$revealedCells.each(function (index, element) { + var $cell = $(this); + var properties = $cell.attr('style').split(';'); + var newProps = []; + // The hide method adds display none to the element. The element + // should be returned to the same state it was in before the columns + // were revealed, so it is necessary to remove the display none value + // from the style attribute. + var match = /^display\s*\:\s*none$/; + for (var i = 0; i < properties.length; i++) { + var prop = properties[i]; + prop.trim(); + // Find the display:none property and remove it. + var isDisplayNone = match.exec(prop); + if (isDisplayNone) { + continue; + } + newProps.push(prop); + } + // Return the rest of the style attribute values to the element. + $cell.attr('style', newProps.join(';')); + }); + this.$link.text(this.showText).data('pegged', 0); + // Refresh the toggle link. + $(window).trigger('resize.tableresponsive'); + } + } + }); + + // Make the TableResponsive object available in the Drupal namespace. + Drupal.TableResponsive = TableResponsive; + +})(jQuery, Drupal, window); diff --git a/core/misc/tableresponsive.js b/core/misc/tableresponsive.js index 0127ec8..8268743 100644 --- a/core/misc/tableresponsive.js +++ b/core/misc/tableresponsive.js @@ -1,22 +1,11 @@ -/** - * @file - * Responsive table functionality. - */ +'use strict'; (function ($, Drupal, window) { 'use strict'; - /** - * Attach the tableResponsive function to {@link Drupal.behaviors}. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches tableResponsive functionality. - */ Drupal.behaviors.tableResponsive = { - attach: function (context, settings) { + attach: function attach(context, settings) { var $tables = $(context).find('table.responsive-enabled').once('tableresponsive'); if ($tables.length) { var il = $tables.length; @@ -27,97 +16,45 @@ } }; - /** - * The TableResponsive object optimizes table presentation for screen size. - * - * A responsive table hides columns at small screen sizes, leaving the most - * important columns visible to the end user. Users should not be prevented - * from accessing all columns, however. This class adds a toggle to a table - * with hidden columns that exposes the columns. Exposing the columns will - * likely break layouts, but it provides the user with a means to access - * data, which is a guiding principle of responsive design. - * - * @constructor Drupal.TableResponsive - * - * @param {HTMLElement} table - * The table element to initialize the responsive table on. - */ function TableResponsive(table) { this.table = table; this.$table = $(table); this.showText = Drupal.t('Show all columns'); this.hideText = Drupal.t('Hide lower priority columns'); - // Store a reference to the header elements of the table so that the DOM is - // traversed only once to find them. + this.$headers = this.$table.find('th'); - // Add a link before the table for users to show or hide weight columns. - this.$link = $('') - .attr('title', Drupal.t('Show table cells that were hidden to make the table fit within a small screen.')) - .on('click', $.proxy(this, 'eventhandlerToggleColumns')); + + this.$link = $('').attr('title', Drupal.t('Show table cells that were hidden to make the table fit within a small screen.')).on('click', $.proxy(this, 'eventhandlerToggleColumns')); this.$table.before($('
    ').append(this.$link)); - // Attach a resize handler to the window. - $(window) - .on('resize.tableresponsive', $.proxy(this, 'eventhandlerEvaluateColumnVisibility')) - .trigger('resize.tableresponsive'); + $(window).on('resize.tableresponsive', $.proxy(this, 'eventhandlerEvaluateColumnVisibility')).trigger('resize.tableresponsive'); } - /** - * Extend the TableResponsive function with a list of managed tables. - */ - $.extend(TableResponsive, /** @lends Drupal.TableResponsive */{ - - /** - * Store all created instances. - * - * @type {Array.} - */ + $.extend(TableResponsive, { tables: [] }); - /** - * Associates an action link with the table that will show hidden columns. - * - * Columns are assumed to be hidden if their header has the class priority-low - * or priority-medium. - */ - $.extend(TableResponsive.prototype, /** @lends Drupal.TableResponsive# */{ - - /** - * @param {jQuery.Event} e - * The event triggered. - */ - eventhandlerEvaluateColumnVisibility: function (e) { + $.extend(TableResponsive.prototype, { + eventhandlerEvaluateColumnVisibility: function eventhandlerEvaluateColumnVisibility(e) { var pegged = parseInt(this.$link.data('pegged'), 10); var hiddenLength = this.$headers.filter('.priority-medium:hidden, .priority-low:hidden').length; - // If the table has hidden columns, associate an action link with the - // table to show the columns. + if (hiddenLength > 0) { this.$link.show().text(this.showText); } - // When the toggle is pegged, its presence is maintained because the user - // has interacted with it. This is necessary to keep the link visible if - // the user adjusts screen size and changes the visibility of columns. + if (!pegged && hiddenLength === 0) { this.$link.hide().text(this.hideText); } }, - /** - * Toggle the visibility of columns based on their priority. - * - * Columns are classed with either 'priority-low' or 'priority-medium'. - * - * @param {jQuery.Event} e - * The event triggered. - */ - eventhandlerToggleColumns: function (e) { + eventhandlerToggleColumns: function eventhandlerToggleColumns(e) { e.preventDefault(); var self = this; var $hiddenHeaders = this.$headers.filter('.priority-medium:hidden, .priority-low:hidden'); this.$revealedCells = this.$revealedCells || $(); - // Reveal hidden columns. + if ($hiddenHeaders.length > 0) { $hiddenHeaders.each(function (index, element) { var $header = $(this); @@ -125,50 +62,44 @@ self.$table.find('tbody tr').each(function () { var $cells = $(this).find('td').eq(position); $cells.show(); - // Keep track of the revealed cells, so they can be hidden later. + self.$revealedCells = $().add(self.$revealedCells).add($cells); }); $header.show(); - // Keep track of the revealed headers, so they can be hidden later. + self.$revealedCells = $().add(self.$revealedCells).add($header); }); this.$link.text(this.hideText).data('pegged', 1); - } - // Hide revealed columns. - else { - this.$revealedCells.hide(); - // Strip the 'display:none' declaration from the style attributes of - // the table cells that .hide() added. - this.$revealedCells.each(function (index, element) { - var $cell = $(this); - var properties = $cell.attr('style').split(';'); - var newProps = []; - // The hide method adds display none to the element. The element - // should be returned to the same state it was in before the columns - // were revealed, so it is necessary to remove the display none value - // from the style attribute. - var match = /^display\s*\:\s*none$/; - for (var i = 0; i < properties.length; i++) { - var prop = properties[i]; - prop.trim(); - // Find the display:none property and remove it. - var isDisplayNone = match.exec(prop); - if (isDisplayNone) { - continue; + } else { + this.$revealedCells.hide(); + + this.$revealedCells.each(function (index, element) { + var $cell = $(this); + var properties = $cell.attr('style').split(';'); + var newProps = []; + + var match = /^display\s*\:\s*none$/; + for (var i = 0; i < properties.length; i++) { + var prop = properties[i]; + prop.trim(); + + var isDisplayNone = match.exec(prop); + if (isDisplayNone) { + continue; + } + newProps.push(prop); } - newProps.push(prop); - } - // Return the rest of the style attribute values to the element. - $cell.attr('style', newProps.join(';')); - }); - this.$link.text(this.showText).data('pegged', 0); - // Refresh the toggle link. - $(window).trigger('resize.tableresponsive'); - } + + $cell.attr('style', newProps.join(';')); + }); + this.$link.text(this.showText).data('pegged', 0); + + $(window).trigger('resize.tableresponsive'); + } } }); - // Make the TableResponsive object available in the Drupal namespace. Drupal.TableResponsive = TableResponsive; - })(jQuery, Drupal, window); + +//# sourceMappingURL=tableresponsive.js.map \ No newline at end of file diff --git a/core/misc/tableresponsive.js.map b/core/misc/tableresponsive.js.map new file mode 100644 index 0000000..496f444 --- /dev/null +++ b/core/misc/tableresponsive.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["tableresponsive.es6.js"],"names":["$","Drupal","window","behaviors","tableResponsive","attach","context","settings","$tables","find","once","length","il","i","TableResponsive","tables","push","table","$table","showText","t","hideText","$headers","$link","attr","on","proxy","before","append","trigger","extend","prototype","eventhandlerEvaluateColumnVisibility","e","pegged","parseInt","data","hiddenLength","filter","show","text","hide","eventhandlerToggleColumns","preventDefault","self","$hiddenHeaders","$revealedCells","each","index","element","$header","position","prevAll","$cells","eq","add","$cell","properties","split","newProps","match","prop","trim","isDisplayNone","exec","join","jQuery"],"mappings":"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","file":"tableresponsive.es6.js","sourcesContent":["/**\n * @file\n * Responsive table functionality.\n */\n\n(function ($, Drupal, window) {\n\n 'use strict';\n\n /**\n * Attach the tableResponsive function to {@link Drupal.behaviors}.\n *\n * @type {Drupal~behavior}\n *\n * @prop {Drupal~behaviorAttach} attach\n * Attaches tableResponsive functionality.\n */\n Drupal.behaviors.tableResponsive = {\n attach: function (context, settings) {\n var $tables = $(context).find('table.responsive-enabled').once('tableresponsive');\n if ($tables.length) {\n var il = $tables.length;\n for (var i = 0; i < il; i++) {\n TableResponsive.tables.push(new TableResponsive($tables[i]));\n }\n }\n }\n };\n\n /**\n * The TableResponsive object optimizes table presentation for screen size.\n *\n * A responsive table hides columns at small screen sizes, leaving the most\n * important columns visible to the end user. Users should not be prevented\n * from accessing all columns, however. This class adds a toggle to a table\n * with hidden columns that exposes the columns. Exposing the columns will\n * likely break layouts, but it provides the user with a means to access\n * data, which is a guiding principle of responsive design.\n *\n * @constructor Drupal.TableResponsive\n *\n * @param {HTMLElement} table\n * The table element to initialize the responsive table on.\n */\n function TableResponsive(table) {\n this.table = table;\n this.$table = $(table);\n this.showText = Drupal.t('Show all columns');\n this.hideText = Drupal.t('Hide lower priority columns');\n // Store a reference to the header elements of the table so that the DOM is\n // traversed only once to find them.\n this.$headers = this.$table.find('th');\n // Add a link before the table for users to show or hide weight columns.\n this.$link = $('')\n .attr('title', Drupal.t('Show table cells that were hidden to make the table fit within a small screen.'))\n .on('click', $.proxy(this, 'eventhandlerToggleColumns'));\n\n this.$table.before($('
    ').append(this.$link));\n\n // Attach a resize handler to the window.\n $(window)\n .on('resize.tableresponsive', $.proxy(this, 'eventhandlerEvaluateColumnVisibility'))\n .trigger('resize.tableresponsive');\n }\n\n /**\n * Extend the TableResponsive function with a list of managed tables.\n */\n $.extend(TableResponsive, /** @lends Drupal.TableResponsive */{\n\n /**\n * Store all created instances.\n *\n * @type {Array.}\n */\n tables: []\n });\n\n /**\n * Associates an action link with the table that will show hidden columns.\n *\n * Columns are assumed to be hidden if their header has the class priority-low\n * or priority-medium.\n */\n $.extend(TableResponsive.prototype, /** @lends Drupal.TableResponsive# */{\n\n /**\n * @param {jQuery.Event} e\n * The event triggered.\n */\n eventhandlerEvaluateColumnVisibility: function (e) {\n var pegged = parseInt(this.$link.data('pegged'), 10);\n var hiddenLength = this.$headers.filter('.priority-medium:hidden, .priority-low:hidden').length;\n // If the table has hidden columns, associate an action link with the\n // table to show the columns.\n if (hiddenLength > 0) {\n this.$link.show().text(this.showText);\n }\n // When the toggle is pegged, its presence is maintained because the user\n // has interacted with it. This is necessary to keep the link visible if\n // the user adjusts screen size and changes the visibility of columns.\n if (!pegged && hiddenLength === 0) {\n this.$link.hide().text(this.hideText);\n }\n },\n\n /**\n * Toggle the visibility of columns based on their priority.\n *\n * Columns are classed with either 'priority-low' or 'priority-medium'.\n *\n * @param {jQuery.Event} e\n * The event triggered.\n */\n eventhandlerToggleColumns: function (e) {\n e.preventDefault();\n var self = this;\n var $hiddenHeaders = this.$headers.filter('.priority-medium:hidden, .priority-low:hidden');\n this.$revealedCells = this.$revealedCells || $();\n // Reveal hidden columns.\n if ($hiddenHeaders.length > 0) {\n $hiddenHeaders.each(function (index, element) {\n var $header = $(this);\n var position = $header.prevAll('th').length;\n self.$table.find('tbody tr').each(function () {\n var $cells = $(this).find('td').eq(position);\n $cells.show();\n // Keep track of the revealed cells, so they can be hidden later.\n self.$revealedCells = $().add(self.$revealedCells).add($cells);\n });\n $header.show();\n // Keep track of the revealed headers, so they can be hidden later.\n self.$revealedCells = $().add(self.$revealedCells).add($header);\n });\n this.$link.text(this.hideText).data('pegged', 1);\n }\n // Hide revealed columns.\n else {\n this.$revealedCells.hide();\n // Strip the 'display:none' declaration from the style attributes of\n // the table cells that .hide() added.\n this.$revealedCells.each(function (index, element) {\n var $cell = $(this);\n var properties = $cell.attr('style').split(';');\n var newProps = [];\n // The hide method adds display none to the element. The element\n // should be returned to the same state it was in before the columns\n // were revealed, so it is necessary to remove the display none value\n // from the style attribute.\n var match = /^display\\s*\\:\\s*none$/;\n for (var i = 0; i < properties.length; i++) {\n var prop = properties[i];\n prop.trim();\n // Find the display:none property and remove it.\n var isDisplayNone = match.exec(prop);\n if (isDisplayNone) {\n continue;\n }\n newProps.push(prop);\n }\n // Return the rest of the style attribute values to the element.\n $cell.attr('style', newProps.join(';'));\n });\n this.$link.text(this.showText).data('pegged', 0);\n // Refresh the toggle link.\n $(window).trigger('resize.tableresponsive');\n }\n }\n });\n\n // Make the TableResponsive object available in the Drupal namespace.\n Drupal.TableResponsive = TableResponsive;\n\n})(jQuery, Drupal, window);\n"]} \ No newline at end of file diff --git a/core/misc/tableselect.es6.js b/core/misc/tableselect.es6.js new file mode 100644 index 0000000..243e000 --- /dev/null +++ b/core/misc/tableselect.es6.js @@ -0,0 +1,159 @@ +/** + * @file + * Table select functionality. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Initialize tableSelects. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches tableSelect functionality. + */ + Drupal.behaviors.tableSelect = { + attach: function (context, settings) { + // Select the inner-most table in case of nested tables. + $(context).find('th.select-all').closest('table').once('table-select').each(Drupal.tableSelect); + } + }; + + /** + * Callback used in {@link Drupal.behaviors.tableSelect}. + */ + Drupal.tableSelect = function () { + // Do not add a "Select all" checkbox if there are no rows with checkboxes + // in the table. + if ($(this).find('td input[type="checkbox"]').length === 0) { + return; + } + + // Keep track of the table, which checkbox is checked and alias the + // settings. + var table = this; + var checkboxes; + var lastChecked; + var $table = $(table); + var strings = { + selectAll: Drupal.t('Select all rows in this table'), + selectNone: Drupal.t('Deselect all rows in this table') + }; + var updateSelectAll = function (state) { + // Update table's select-all checkbox (and sticky header's if available). + $table.prev('table.sticky-header').addBack().find('th.select-all input[type="checkbox"]').each(function () { + var $checkbox = $(this); + var stateChanged = $checkbox.prop('checked') !== state; + + $checkbox.attr('title', state ? strings.selectNone : strings.selectAll); + + /** + * @checkbox {HTMLElement} + */ + if (stateChanged) { + $checkbox.prop('checked', state).trigger('change'); + } + }); + }; + + // Find all
    to do our range searching. + Drupal.tableSelectRange($(e.target).closest('tr')[0], $(lastChecked).closest('tr')[0], e.target.checked); + } + + // If all checkboxes are checked, make sure the select-all one is checked + // too, otherwise keep unchecked. + updateSelectAll((checkboxes.length === checkboxes.filter(':checked').length)); + + // Keep track of the last checked checkbox. + lastChecked = e.target; + }); + + // If all checkboxes are checked on page load, make sure the select-all one + // is checked too, otherwise keep unchecked. + updateSelectAll((checkboxes.length === checkboxes.filter(':checked').length)); + }; + + /** + * @param {HTMLElement} from + * The HTML element representing the "from" part of the range. + * @param {HTMLElement} to + * The HTML element representing the "to" part of the range. + * @param {bool} state + * The state to set on the range. + */ + Drupal.tableSelectRange = function (from, to, state) { + // We determine the looping mode based on the order of from and to. + var mode = from.rowIndex > to.rowIndex ? 'previousSibling' : 'nextSibling'; + + // Traverse through the sibling nodes. + for (var i = from[mode]; i; i = i[mode]) { + var $i; + // Make sure that we're only dealing with elements. + if (i.nodeType !== 1) { + continue; + } + $i = $(i); + // Either add or remove the selected class based on the state of the + // target checkbox. + $i.toggleClass('selected', state); + $i.find('input[type="checkbox"]').prop('checked', state); + + if (to.nodeType) { + // If we are at the end of the range, stop. + if (i === to) { + break; + } + } + // A faster alternative to doing $(i).filter(to).length. + else if ($.filter(to, [i]).r.length) { + break; + } + } + }; + +})(jQuery, Drupal); diff --git a/core/misc/tableselect.js b/core/misc/tableselect.js index 243e000..2dd5a01 100644 --- a/core/misc/tableselect.js +++ b/core/misc/tableselect.js @@ -1,39 +1,20 @@ -/** - * @file - * Table select functionality. - */ +'use strict'; (function ($, Drupal) { 'use strict'; - /** - * Initialize tableSelects. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches tableSelect functionality. - */ Drupal.behaviors.tableSelect = { - attach: function (context, settings) { - // Select the inner-most table in case of nested tables. + attach: function attach(context, settings) { $(context).find('th.select-all').closest('table').once('table-select').each(Drupal.tableSelect); } }; - /** - * Callback used in {@link Drupal.behaviors.tableSelect}. - */ Drupal.tableSelect = function () { - // Do not add a "Select all" checkbox if there are no rows with checkboxes - // in the table. if ($(this).find('td input[type="checkbox"]').length === 0) { return; } - // Keep track of the table, which checkbox is checked and alias the - // settings. var table = this; var checkboxes; var lastChecked; @@ -42,118 +23,74 @@ selectAll: Drupal.t('Select all rows in this table'), selectNone: Drupal.t('Deselect all rows in this table') }; - var updateSelectAll = function (state) { - // Update table's select-all checkbox (and sticky header's if available). + var updateSelectAll = function updateSelectAll(state) { $table.prev('table.sticky-header').addBack().find('th.select-all input[type="checkbox"]').each(function () { var $checkbox = $(this); var stateChanged = $checkbox.prop('checked') !== state; $checkbox.attr('title', state ? strings.selectNone : strings.selectAll); - /** - * @checkbox {HTMLElement} - */ if (stateChanged) { $checkbox.prop('checked', state).trigger('change'); } }); }; - // Find all to do our range searching. Drupal.tableSelectRange($(e.target).closest('tr')[0], $(lastChecked).closest('tr')[0], e.target.checked); } - // If all checkboxes are checked, make sure the select-all one is checked - // too, otherwise keep unchecked. - updateSelectAll((checkboxes.length === checkboxes.filter(':checked').length)); + updateSelectAll(checkboxes.length === checkboxes.filter(':checked').length); - // Keep track of the last checked checkbox. lastChecked = e.target; }); - // If all checkboxes are checked on page load, make sure the select-all one - // is checked too, otherwise keep unchecked. - updateSelectAll((checkboxes.length === checkboxes.filter(':checked').length)); + updateSelectAll(checkboxes.length === checkboxes.filter(':checked').length); }; - /** - * @param {HTMLElement} from - * The HTML element representing the "from" part of the range. - * @param {HTMLElement} to - * The HTML element representing the "to" part of the range. - * @param {bool} state - * The state to set on the range. - */ Drupal.tableSelectRange = function (from, to, state) { - // We determine the looping mode based on the order of from and to. var mode = from.rowIndex > to.rowIndex ? 'previousSibling' : 'nextSibling'; - // Traverse through the sibling nodes. for (var i = from[mode]; i; i = i[mode]) { var $i; - // Make sure that we're only dealing with elements. + if (i.nodeType !== 1) { continue; } $i = $(i); - // Either add or remove the selected class based on the state of the - // target checkbox. + $i.toggleClass('selected', state); $i.find('input[type="checkbox"]').prop('checked', state); if (to.nodeType) { - // If we are at the end of the range, stop. if (i === to) { break; } - } - // A faster alternative to doing $(i).filter(to).length. - else if ($.filter(to, [i]).r.length) { - break; - } + } else if ($.filter(to, [i]).r.length) { + break; + } } }; - })(jQuery, Drupal); + +//# sourceMappingURL=tableselect.js.map \ No newline at end of file diff --git a/core/misc/tableselect.js.map b/core/misc/tableselect.js.map new file mode 100644 index 0000000..59fe416 --- /dev/null +++ b/core/misc/tableselect.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["tableselect.es6.js"],"names":["$","Drupal","behaviors","tableSelect","attach","context","settings","find","closest","once","each","length","table","checkboxes","lastChecked","$table","strings","selectAll","t","selectNone","updateSelectAll","state","prev","addBack","$checkbox","stateChanged","prop","attr","trigger","prepend","on","event","target","is","checked","toggleClass","e","shiftKey","tableSelectRange","filter","from","to","mode","rowIndex","i","$i","nodeType","r","jQuery"],"mappings":"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","file":"tableselect.es6.js","sourcesContent":["/**\n * @file\n * Table select functionality.\n */\n\n(function ($, Drupal) {\n\n 'use strict';\n\n /**\n * Initialize tableSelects.\n *\n * @type {Drupal~behavior}\n *\n * @prop {Drupal~behaviorAttach} attach\n * Attaches tableSelect functionality.\n */\n Drupal.behaviors.tableSelect = {\n attach: function (context, settings) {\n // Select the inner-most table in case of nested tables.\n $(context).find('th.select-all').closest('table').once('table-select').each(Drupal.tableSelect);\n }\n };\n\n /**\n * Callback used in {@link Drupal.behaviors.tableSelect}.\n */\n Drupal.tableSelect = function () {\n // Do not add a \"Select all\" checkbox if there are no rows with checkboxes\n // in the table.\n if ($(this).find('td input[type=\"checkbox\"]').length === 0) {\n return;\n }\n\n // Keep track of the table, which checkbox is checked and alias the\n // settings.\n var table = this;\n var checkboxes;\n var lastChecked;\n var $table = $(table);\n var strings = {\n selectAll: Drupal.t('Select all rows in this table'),\n selectNone: Drupal.t('Deselect all rows in this table')\n };\n var updateSelectAll = function (state) {\n // Update table's select-all checkbox (and sticky header's if available).\n $table.prev('table.sticky-header').addBack().find('th.select-all input[type=\"checkbox\"]').each(function () {\n var $checkbox = $(this);\n var stateChanged = $checkbox.prop('checked') !== state;\n\n $checkbox.attr('title', state ? strings.selectNone : strings.selectAll);\n\n /**\n * @checkbox {HTMLElement}\n */\n if (stateChanged) {\n $checkbox.prop('checked', state).trigger('change');\n }\n });\n };\n\n // Find all to do our range searching.\n Drupal.tableSelectRange($(e.target).closest('tr')[0], $(lastChecked).closest('tr')[0], e.target.checked);\n }\n\n // If all checkboxes are checked, make sure the select-all one is checked\n // too, otherwise keep unchecked.\n updateSelectAll((checkboxes.length === checkboxes.filter(':checked').length));\n\n // Keep track of the last checked checkbox.\n lastChecked = e.target;\n });\n\n // If all checkboxes are checked on page load, make sure the select-all one\n // is checked too, otherwise keep unchecked.\n updateSelectAll((checkboxes.length === checkboxes.filter(':checked').length));\n };\n\n /**\n * @param {HTMLElement} from\n * The HTML element representing the \"from\" part of the range.\n * @param {HTMLElement} to\n * The HTML element representing the \"to\" part of the range.\n * @param {bool} state\n * The state to set on the range.\n */\n Drupal.tableSelectRange = function (from, to, state) {\n // We determine the looping mode based on the order of from and to.\n var mode = from.rowIndex > to.rowIndex ? 'previousSibling' : 'nextSibling';\n\n // Traverse through the sibling nodes.\n for (var i = from[mode]; i; i = i[mode]) {\n var $i;\n // Make sure that we're only dealing with elements.\n if (i.nodeType !== 1) {\n continue;\n }\n $i = $(i);\n // Either add or remove the selected class based on the state of the\n // target checkbox.\n $i.toggleClass('selected', state);\n $i.find('input[type=\"checkbox\"]').prop('checked', state);\n\n if (to.nodeType) {\n // If we are at the end of the range, stop.\n if (i === to) {\n break;\n }\n }\n // A faster alternative to doing $(i).filter(to).length.\n else if ($.filter(to, [i]).r.length) {\n break;\n }\n }\n };\n\n})(jQuery, Drupal);\n"]} \ No newline at end of file diff --git a/core/misc/tests/drupal.test.js b/core/misc/tests/drupal.test.js new file mode 100644 index 0000000..f2e537a --- /dev/null +++ b/core/misc/tests/drupal.test.js @@ -0,0 +1,14 @@ +const assert = require('assert'); +domready = function () {}; +Drupal = {}; +const _ = require('../drupal.es6'); + +describe('Drupal', function () { + describe('#checkPlain()', function () { + it('should escape html strings safe for printing', function () { + const html = 'Rock & roll'; + const escaped = '<span class="music">Rock & roll</span>'; + assert.strictEqual(Drupal.checkPlain(html), escaped, 'Drupal.checkPlain escape HTML strings'); + }); + }); +}); diff --git a/core/misc/timezone.es6.js b/core/misc/timezone.es6.js new file mode 100644 index 0000000..3c88d46 --- /dev/null +++ b/core/misc/timezone.es6.js @@ -0,0 +1,76 @@ +/** + * @file + * Timezone detection. + */ + +(function ($, Drupal) { + + 'use strict'; + + /** + * Set the client's system time zone as default values of form fields. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.setTimezone = { + attach: function (context, settings) { + var $timezone = $(context).find('.timezone-detect').once('timezone'); + if ($timezone.length) { + var dateString = Date(); + // In some client environments, date strings include a time zone + // abbreviation, between 3 and 5 letters enclosed in parentheses, + // which can be interpreted by PHP. + var matches = dateString.match(/\(([A-Z]{3,5})\)/); + var abbreviation = matches ? matches[1] : 0; + + // For all other client environments, the abbreviation is set to "0" + // and the current offset from UTC and daylight saving time status are + // used to guess the time zone. + var dateNow = new Date(); + var offsetNow = dateNow.getTimezoneOffset() * -60; + + // Use January 1 and July 1 as test dates for determining daylight + // saving time status by comparing their offsets. + var dateJan = new Date(dateNow.getFullYear(), 0, 1, 12, 0, 0, 0); + var dateJul = new Date(dateNow.getFullYear(), 6, 1, 12, 0, 0, 0); + var offsetJan = dateJan.getTimezoneOffset() * -60; + var offsetJul = dateJul.getTimezoneOffset() * -60; + + var isDaylightSavingTime; + // If the offset from UTC is identical on January 1 and July 1, + // assume daylight saving time is not used in this time zone. + if (offsetJan === offsetJul) { + isDaylightSavingTime = ''; + } + // If the maximum annual offset is equivalent to the current offset, + // assume daylight saving time is in effect. + else if (Math.max(offsetJan, offsetJul) === offsetNow) { + isDaylightSavingTime = 1; + } + // Otherwise, assume daylight saving time is not in effect. + else { + isDaylightSavingTime = 0; + } + + // Submit request to the system/timezone callback and set the form + // field to the response time zone. The client date is passed to the + // callback for debugging purposes. Submit a synchronous request to + // avoid database errors associated with concurrent requests + // during install. + var path = 'system/timezone/' + abbreviation + '/' + offsetNow + '/' + isDaylightSavingTime; + $.ajax({ + async: false, + url: Drupal.url(path), + data: {date: dateString}, + dataType: 'json', + success: function (data) { + if (data) { + $timezone.val(data); + } + } + }); + } + } + }; + +})(jQuery, Drupal); diff --git a/core/misc/timezone.js b/core/misc/timezone.js index 3c88d46..6eb4440 100644 --- a/core/misc/timezone.js +++ b/core/misc/timezone.js @@ -1,69 +1,43 @@ -/** - * @file - * Timezone detection. - */ +'use strict'; (function ($, Drupal) { 'use strict'; - /** - * Set the client's system time zone as default values of form fields. - * - * @type {Drupal~behavior} - */ Drupal.behaviors.setTimezone = { - attach: function (context, settings) { + attach: function attach(context, settings) { var $timezone = $(context).find('.timezone-detect').once('timezone'); if ($timezone.length) { var dateString = Date(); - // In some client environments, date strings include a time zone - // abbreviation, between 3 and 5 letters enclosed in parentheses, - // which can be interpreted by PHP. + var matches = dateString.match(/\(([A-Z]{3,5})\)/); var abbreviation = matches ? matches[1] : 0; - // For all other client environments, the abbreviation is set to "0" - // and the current offset from UTC and daylight saving time status are - // used to guess the time zone. var dateNow = new Date(); var offsetNow = dateNow.getTimezoneOffset() * -60; - // Use January 1 and July 1 as test dates for determining daylight - // saving time status by comparing their offsets. var dateJan = new Date(dateNow.getFullYear(), 0, 1, 12, 0, 0, 0); var dateJul = new Date(dateNow.getFullYear(), 6, 1, 12, 0, 0, 0); var offsetJan = dateJan.getTimezoneOffset() * -60; var offsetJul = dateJul.getTimezoneOffset() * -60; var isDaylightSavingTime; - // If the offset from UTC is identical on January 1 and July 1, - // assume daylight saving time is not used in this time zone. + if (offsetJan === offsetJul) { isDaylightSavingTime = ''; - } - // If the maximum annual offset is equivalent to the current offset, - // assume daylight saving time is in effect. - else if (Math.max(offsetJan, offsetJul) === offsetNow) { - isDaylightSavingTime = 1; - } - // Otherwise, assume daylight saving time is not in effect. - else { - isDaylightSavingTime = 0; - } + } else if (Math.max(offsetJan, offsetJul) === offsetNow) { + isDaylightSavingTime = 1; + } else { + isDaylightSavingTime = 0; + } - // Submit request to the system/timezone callback and set the form - // field to the response time zone. The client date is passed to the - // callback for debugging purposes. Submit a synchronous request to - // avoid database errors associated with concurrent requests - // during install. var path = 'system/timezone/' + abbreviation + '/' + offsetNow + '/' + isDaylightSavingTime; $.ajax({ async: false, url: Drupal.url(path), - data: {date: dateString}, + data: { date: dateString }, dataType: 'json', - success: function (data) { + success: function success(data) { if (data) { $timezone.val(data); } @@ -72,5 +46,6 @@ } } }; - })(jQuery, Drupal); + +//# sourceMappingURL=timezone.js.map \ No newline at end of file diff --git a/core/misc/timezone.js.map b/core/misc/timezone.js.map new file mode 100644 index 0000000..165d480 --- /dev/null +++ b/core/misc/timezone.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["timezone.es6.js"],"names":["$","Drupal","behaviors","setTimezone","attach","context","settings","$timezone","find","once","length","dateString","Date","matches","match","abbreviation","dateNow","offsetNow","getTimezoneOffset","dateJan","getFullYear","dateJul","offsetJan","offsetJul","isDaylightSavingTime","Math","max","path","ajax","async","url","data","date","dataType","success","val","jQuery"],"mappings":";;AAKA,CAAC,UAAUA,CAAV,EAAaC,MAAb,EAAqB;;AAEpB;;AAOAA,SAAOC,SAAP,CAAiBC,WAAjB,GAA+B;AAC7BC,YAAQ,gBAAUC,OAAV,EAAmBC,QAAnB,EAA6B;AACnC,UAAIC,YAAYP,EAAEK,OAAF,EAAWG,IAAX,CAAgB,kBAAhB,EAAoCC,IAApC,CAAyC,UAAzC,CAAhB;AACA,UAAIF,UAAUG,MAAd,EAAsB;AACpB,YAAIC,aAAaC,MAAjB;;AAIA,YAAIC,UAAUF,WAAWG,KAAX,CAAiB,kBAAjB,CAAd;AACA,YAAIC,eAAeF,UAAUA,QAAQ,CAAR,CAAV,GAAuB,CAA1C;;AAKA,YAAIG,UAAU,IAAIJ,IAAJ,EAAd;AACA,YAAIK,YAAYD,QAAQE,iBAAR,KAA8B,CAAC,EAA/C;;AAIA,YAAIC,UAAU,IAAIP,IAAJ,CAASI,QAAQI,WAAR,EAAT,EAAgC,CAAhC,EAAmC,CAAnC,EAAsC,EAAtC,EAA0C,CAA1C,EAA6C,CAA7C,EAAgD,CAAhD,CAAd;AACA,YAAIC,UAAU,IAAIT,IAAJ,CAASI,QAAQI,WAAR,EAAT,EAAgC,CAAhC,EAAmC,CAAnC,EAAsC,EAAtC,EAA0C,CAA1C,EAA6C,CAA7C,EAAgD,CAAhD,CAAd;AACA,YAAIE,YAAYH,QAAQD,iBAAR,KAA8B,CAAC,EAA/C;AACA,YAAIK,YAAYF,QAAQH,iBAAR,KAA8B,CAAC,EAA/C;;AAEA,YAAIM,oBAAJ;;AAGA,YAAIF,cAAcC,SAAlB,EAA6B;AAC3BC,iCAAuB,EAAvB;AACD,SAFD,MAKK,IAAIC,KAAKC,GAAL,CAASJ,SAAT,EAAoBC,SAApB,MAAmCN,SAAvC,EAAkD;AACrDO,mCAAuB,CAAvB;AACD,WAFI,MAIA;AACHA,qCAAuB,CAAvB;AACD;;AAOD,YAAIG,OAAO,qBAAqBZ,YAArB,GAAoC,GAApC,GAA0CE,SAA1C,GAAsD,GAAtD,GAA4DO,oBAAvE;AACAxB,UAAE4B,IAAF,CAAO;AACLC,iBAAO,KADF;AAELC,eAAK7B,OAAO6B,GAAP,CAAWH,IAAX,CAFA;AAGLI,gBAAM,EAACC,MAAMrB,UAAP,EAHD;AAILsB,oBAAU,MAJL;AAKLC,mBAAS,iBAAUH,IAAV,EAAgB;AACvB,gBAAIA,IAAJ,EAAU;AACRxB,wBAAU4B,GAAV,CAAcJ,IAAd;AACD;AACF;AATI,SAAP;AAWD;AACF;AA1D4B,GAA/B;AA6DD,CAtED,EAsEGK,MAtEH,EAsEWnC,MAtEX","file":"timezone.es6.js","sourcesContent":["/**\n * @file\n * Timezone detection.\n */\n\n(function ($, Drupal) {\n\n 'use strict';\n\n /**\n * Set the client's system time zone as default values of form fields.\n *\n * @type {Drupal~behavior}\n */\n Drupal.behaviors.setTimezone = {\n attach: function (context, settings) {\n var $timezone = $(context).find('.timezone-detect').once('timezone');\n if ($timezone.length) {\n var dateString = Date();\n // In some client environments, date strings include a time zone\n // abbreviation, between 3 and 5 letters enclosed in parentheses,\n // which can be interpreted by PHP.\n var matches = dateString.match(/\\(([A-Z]{3,5})\\)/);\n var abbreviation = matches ? matches[1] : 0;\n\n // For all other client environments, the abbreviation is set to \"0\"\n // and the current offset from UTC and daylight saving time status are\n // used to guess the time zone.\n var dateNow = new Date();\n var offsetNow = dateNow.getTimezoneOffset() * -60;\n\n // Use January 1 and July 1 as test dates for determining daylight\n // saving time status by comparing their offsets.\n var dateJan = new Date(dateNow.getFullYear(), 0, 1, 12, 0, 0, 0);\n var dateJul = new Date(dateNow.getFullYear(), 6, 1, 12, 0, 0, 0);\n var offsetJan = dateJan.getTimezoneOffset() * -60;\n var offsetJul = dateJul.getTimezoneOffset() * -60;\n\n var isDaylightSavingTime;\n // If the offset from UTC is identical on January 1 and July 1,\n // assume daylight saving time is not used in this time zone.\n if (offsetJan === offsetJul) {\n isDaylightSavingTime = '';\n }\n // If the maximum annual offset is equivalent to the current offset,\n // assume daylight saving time is in effect.\n else if (Math.max(offsetJan, offsetJul) === offsetNow) {\n isDaylightSavingTime = 1;\n }\n // Otherwise, assume daylight saving time is not in effect.\n else {\n isDaylightSavingTime = 0;\n }\n\n // Submit request to the system/timezone callback and set the form\n // field to the response time zone. The client date is passed to the\n // callback for debugging purposes. Submit a synchronous request to\n // avoid database errors associated with concurrent requests\n // during install.\n var path = 'system/timezone/' + abbreviation + '/' + offsetNow + '/' + isDaylightSavingTime;\n $.ajax({\n async: false,\n url: Drupal.url(path),\n data: {date: dateString},\n dataType: 'json',\n success: function (data) {\n if (data) {\n $timezone.val(data);\n }\n }\n });\n }\n }\n };\n\n})(jQuery, Drupal);\n"]} \ No newline at end of file diff --git a/core/misc/vertical-tabs.es6.js b/core/misc/vertical-tabs.es6.js new file mode 100644 index 0000000..c7ad2fd --- /dev/null +++ b/core/misc/vertical-tabs.es6.js @@ -0,0 +1,252 @@ +/** + * @file + * Define vertical tabs functionality. + */ + +/** + * Triggers when form values inside a vertical tab changes. + * + * This is used to update the summary in vertical tabs in order to know what + * are the important fields' values. + * + * @event summaryUpdated + */ + +(function ($, Drupal, drupalSettings) { + + 'use strict'; + + /** + * This script transforms a set of details into a stack of vertical tabs. + * + * Each tab may have a summary which can be updated by another + * script. For that to work, each details element has an associated + * 'verticalTabCallback' (with jQuery.data() attached to the details), + * which is called every time the user performs an update to a form + * element inside the tab pane. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches behaviors for vertical tabs. + */ + Drupal.behaviors.verticalTabs = { + attach: function (context) { + var width = drupalSettings.widthBreakpoint || 640; + var mq = '(max-width: ' + width + 'px)'; + + if (window.matchMedia(mq).matches) { + return; + } + + $(context).find('[data-vertical-tabs-panes]').once('vertical-tabs').each(function () { + var $this = $(this).addClass('vertical-tabs__panes'); + var focusID = $this.find(':hidden.vertical-tabs__active-tab').val(); + var tab_focus; + + // Check if there are some details that can be converted to + // vertical-tabs. + var $details = $this.find('> details'); + if ($details.length === 0) { + return; + } + + // Create the tab column. + var tab_list = $('
      '); + $this.wrap('
      ').before(tab_list); + + // Transform each details into a tab. + $details.each(function () { + var $that = $(this); + var vertical_tab = new Drupal.verticalTab({ + title: $that.find('> summary').text(), + details: $that + }); + tab_list.append(vertical_tab.item); + $that + .removeClass('collapsed') + // prop() can't be used on browsers not supporting details element, + // the style won't apply to them if prop() is used. + .attr('open', true) + .addClass('vertical-tabs__pane') + .data('verticalTab', vertical_tab); + if (this.id === focusID) { + tab_focus = $that; + } + }); + + $(tab_list).find('> li').eq(0).addClass('first'); + $(tab_list).find('> li').eq(-1).addClass('last'); + + if (!tab_focus) { + // If the current URL has a fragment and one of the tabs contains an + // element that matches the URL fragment, activate that tab. + var $locationHash = $this.find(window.location.hash); + if (window.location.hash && $locationHash.length) { + tab_focus = $locationHash.closest('.vertical-tabs__pane'); + } + else { + tab_focus = $this.find('> .vertical-tabs__pane').eq(0); + } + } + if (tab_focus.length) { + tab_focus.data('verticalTab').focus(); + } + }); + } + }; + + /** + * The vertical tab object represents a single tab within a tab group. + * + * @constructor + * + * @param {object} settings + * Settings object. + * @param {string} settings.title + * The name of the tab. + * @param {jQuery} settings.details + * The jQuery object of the details element that is the tab pane. + * + * @fires event:summaryUpdated + * + * @listens event:summaryUpdated + */ + Drupal.verticalTab = function (settings) { + var self = this; + $.extend(this, settings, Drupal.theme('verticalTab', settings)); + + this.link.attr('href', '#' + settings.details.attr('id')); + + this.link.on('click', function (e) { + e.preventDefault(); + self.focus(); + }); + + // Keyboard events added: + // Pressing the Enter key will open the tab pane. + this.link.on('keydown', function (event) { + if (event.keyCode === 13) { + event.preventDefault(); + self.focus(); + // Set focus on the first input field of the visible details/tab pane. + $('.vertical-tabs__pane :input:visible:enabled').eq(0).trigger('focus'); + } + }); + + this.details + .on('summaryUpdated', function () { + self.updateSummary(); + }) + .trigger('summaryUpdated'); + }; + + Drupal.verticalTab.prototype = { + + /** + * Displays the tab's content pane. + */ + focus: function () { + this.details + .siblings('.vertical-tabs__pane') + .each(function () { + var tab = $(this).data('verticalTab'); + tab.details.hide(); + tab.item.removeClass('is-selected'); + }) + .end() + .show() + .siblings(':hidden.vertical-tabs__active-tab') + .val(this.details.attr('id')); + this.item.addClass('is-selected'); + // Mark the active tab for screen readers. + $('#active-vertical-tab').remove(); + this.link.append('' + Drupal.t('(active tab)') + ''); + }, + + /** + * Updates the tab's summary. + */ + updateSummary: function () { + this.summary.html(this.details.drupalGetSummary()); + }, + + /** + * Shows a vertical tab pane. + * + * @return {Drupal.verticalTab} + * The verticalTab instance. + */ + tabShow: function () { + // Display the tab. + this.item.show(); + // Show the vertical tabs. + this.item.closest('.js-form-type-vertical-tabs').show(); + // Update .first marker for items. We need recurse from parent to retain + // the actual DOM element order as jQuery implements sortOrder, but not + // as public method. + this.item.parent().children('.vertical-tabs__menu-item').removeClass('first') + .filter(':visible').eq(0).addClass('first'); + // Display the details element. + this.details.removeClass('vertical-tab--hidden').show(); + // Focus this tab. + this.focus(); + return this; + }, + + /** + * Hides a vertical tab pane. + * + * @return {Drupal.verticalTab} + * The verticalTab instance. + */ + tabHide: function () { + // Hide this tab. + this.item.hide(); + // Update .first marker for items. We need recurse from parent to retain + // the actual DOM element order as jQuery implements sortOrder, but not + // as public method. + this.item.parent().children('.vertical-tabs__menu-item').removeClass('first') + .filter(':visible').eq(0).addClass('first'); + // Hide the details element. + this.details.addClass('vertical-tab--hidden').hide(); + // Focus the first visible tab (if there is one). + var $firstTab = this.details.siblings('.vertical-tabs__pane:not(.vertical-tab--hidden)').eq(0); + if ($firstTab.length) { + $firstTab.data('verticalTab').focus(); + } + // Hide the vertical tabs (if no tabs remain). + else { + this.item.closest('.js-form-type-vertical-tabs').hide(); + } + return this; + } + }; + + /** + * Theme function for a vertical tab. + * + * @param {object} settings + * An object with the following keys: + * @param {string} settings.title + * The name of the tab. + * + * @return {object} + * This function has to return an object with at least these keys: + * - item: The root tab jQuery element + * - link: The anchor tag that acts as the clickable area of the tab + * (jQuery version) + * - summary: The jQuery element that contains the tab summary + */ + Drupal.theme.verticalTab = function (settings) { + var tab = {}; + tab.item = $('
    • ') + .append(tab.link = $('') + .append(tab.title = $('').text(settings.title)) + .append(tab.summary = $('') + ) + ); + return tab; + }; + +})(jQuery, Drupal, drupalSettings); diff --git a/core/misc/vertical-tabs.js b/core/misc/vertical-tabs.js index c7ad2fd..614f65d 100644 --- a/core/misc/vertical-tabs.js +++ b/core/misc/vertical-tabs.js @@ -1,37 +1,11 @@ -/** - * @file - * Define vertical tabs functionality. - */ - -/** - * Triggers when form values inside a vertical tab changes. - * - * This is used to update the summary in vertical tabs in order to know what - * are the important fields' values. - * - * @event summaryUpdated - */ +'use strict'; (function ($, Drupal, drupalSettings) { 'use strict'; - /** - * This script transforms a set of details into a stack of vertical tabs. - * - * Each tab may have a summary which can be updated by another - * script. For that to work, each details element has an associated - * 'verticalTabCallback' (with jQuery.data() attached to the details), - * which is called every time the user performs an update to a form - * element inside the tab pane. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches behaviors for vertical tabs. - */ Drupal.behaviors.verticalTabs = { - attach: function (context) { + attach: function attach(context) { var width = drupalSettings.widthBreakpoint || 640; var mq = '(max-width: ' + width + 'px)'; @@ -44,18 +18,14 @@ var focusID = $this.find(':hidden.vertical-tabs__active-tab').val(); var tab_focus; - // Check if there are some details that can be converted to - // vertical-tabs. var $details = $this.find('> details'); if ($details.length === 0) { return; } - // Create the tab column. var tab_list = $('
        '); $this.wrap('
        ').before(tab_list); - // Transform each details into a tab. $details.each(function () { var $that = $(this); var vertical_tab = new Drupal.verticalTab({ @@ -63,13 +33,7 @@ details: $that }); tab_list.append(vertical_tab.item); - $that - .removeClass('collapsed') - // prop() can't be used on browsers not supporting details element, - // the style won't apply to them if prop() is used. - .attr('open', true) - .addClass('vertical-tabs__pane') - .data('verticalTab', vertical_tab); + $that.removeClass('collapsed').attr('open', true).addClass('vertical-tabs__pane').data('verticalTab', vertical_tab); if (this.id === focusID) { tab_focus = $that; } @@ -79,13 +43,10 @@ $(tab_list).find('> li').eq(-1).addClass('last'); if (!tab_focus) { - // If the current URL has a fragment and one of the tabs contains an - // element that matches the URL fragment, activate that tab. var $locationHash = $this.find(window.location.hash); if (window.location.hash && $locationHash.length) { tab_focus = $locationHash.closest('.vertical-tabs__pane'); - } - else { + } else { tab_focus = $this.find('> .vertical-tabs__pane').eq(0); } } @@ -96,22 +57,6 @@ } }; - /** - * The vertical tab object represents a single tab within a tab group. - * - * @constructor - * - * @param {object} settings - * Settings object. - * @param {string} settings.title - * The name of the tab. - * @param {jQuery} settings.details - * The jQuery object of the details element that is the tab pane. - * - * @fires event:summaryUpdated - * - * @listens event:summaryUpdated - */ Drupal.verticalTab = function (settings) { var self = this; $.extend(this, settings, Drupal.theme('verticalTab', settings)); @@ -123,130 +68,72 @@ self.focus(); }); - // Keyboard events added: - // Pressing the Enter key will open the tab pane. this.link.on('keydown', function (event) { if (event.keyCode === 13) { event.preventDefault(); self.focus(); - // Set focus on the first input field of the visible details/tab pane. + $('.vertical-tabs__pane :input:visible:enabled').eq(0).trigger('focus'); } }); - this.details - .on('summaryUpdated', function () { - self.updateSummary(); - }) - .trigger('summaryUpdated'); + this.details.on('summaryUpdated', function () { + self.updateSummary(); + }).trigger('summaryUpdated'); }; Drupal.verticalTab.prototype = { - - /** - * Displays the tab's content pane. - */ - focus: function () { - this.details - .siblings('.vertical-tabs__pane') - .each(function () { - var tab = $(this).data('verticalTab'); - tab.details.hide(); - tab.item.removeClass('is-selected'); - }) - .end() - .show() - .siblings(':hidden.vertical-tabs__active-tab') - .val(this.details.attr('id')); + focus: function focus() { + this.details.siblings('.vertical-tabs__pane').each(function () { + var tab = $(this).data('verticalTab'); + tab.details.hide(); + tab.item.removeClass('is-selected'); + }).end().show().siblings(':hidden.vertical-tabs__active-tab').val(this.details.attr('id')); this.item.addClass('is-selected'); - // Mark the active tab for screen readers. + $('#active-vertical-tab').remove(); this.link.append('' + Drupal.t('(active tab)') + ''); }, - /** - * Updates the tab's summary. - */ - updateSummary: function () { + updateSummary: function updateSummary() { this.summary.html(this.details.drupalGetSummary()); }, - /** - * Shows a vertical tab pane. - * - * @return {Drupal.verticalTab} - * The verticalTab instance. - */ - tabShow: function () { - // Display the tab. + tabShow: function tabShow() { this.item.show(); - // Show the vertical tabs. + this.item.closest('.js-form-type-vertical-tabs').show(); - // Update .first marker for items. We need recurse from parent to retain - // the actual DOM element order as jQuery implements sortOrder, but not - // as public method. - this.item.parent().children('.vertical-tabs__menu-item').removeClass('first') - .filter(':visible').eq(0).addClass('first'); - // Display the details element. + + this.item.parent().children('.vertical-tabs__menu-item').removeClass('first').filter(':visible').eq(0).addClass('first'); + this.details.removeClass('vertical-tab--hidden').show(); - // Focus this tab. + this.focus(); return this; }, - /** - * Hides a vertical tab pane. - * - * @return {Drupal.verticalTab} - * The verticalTab instance. - */ - tabHide: function () { - // Hide this tab. + tabHide: function tabHide() { this.item.hide(); - // Update .first marker for items. We need recurse from parent to retain - // the actual DOM element order as jQuery implements sortOrder, but not - // as public method. - this.item.parent().children('.vertical-tabs__menu-item').removeClass('first') - .filter(':visible').eq(0).addClass('first'); - // Hide the details element. + + this.item.parent().children('.vertical-tabs__menu-item').removeClass('first').filter(':visible').eq(0).addClass('first'); + this.details.addClass('vertical-tab--hidden').hide(); - // Focus the first visible tab (if there is one). + var $firstTab = this.details.siblings('.vertical-tabs__pane:not(.vertical-tab--hidden)').eq(0); if ($firstTab.length) { $firstTab.data('verticalTab').focus(); - } - // Hide the vertical tabs (if no tabs remain). - else { - this.item.closest('.js-form-type-vertical-tabs').hide(); - } + } else { + this.item.closest('.js-form-type-vertical-tabs').hide(); + } return this; } }; - /** - * Theme function for a vertical tab. - * - * @param {object} settings - * An object with the following keys: - * @param {string} settings.title - * The name of the tab. - * - * @return {object} - * This function has to return an object with at least these keys: - * - item: The root tab jQuery element - * - link: The anchor tag that acts as the clickable area of the tab - * (jQuery version) - * - summary: The jQuery element that contains the tab summary - */ Drupal.theme.verticalTab = function (settings) { var tab = {}; - tab.item = $('
      • ') - .append(tab.link = $('') - .append(tab.title = $('').text(settings.title)) - .append(tab.summary = $('') - ) - ); + tab.item = $('
      • ').append(tab.link = $('').append(tab.title = $('').text(settings.title)).append(tab.summary = $(''))); return tab; }; - })(jQuery, Drupal, drupalSettings); + +//# sourceMappingURL=vertical-tabs.js.map \ No newline at end of file diff --git a/core/misc/vertical-tabs.js.map b/core/misc/vertical-tabs.js.map new file mode 100644 index 0000000..87178f4 --- /dev/null +++ b/core/misc/vertical-tabs.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["vertical-tabs.es6.js"],"names":["$","Drupal","drupalSettings","behaviors","verticalTabs","attach","context","width","widthBreakpoint","mq","window","matchMedia","matches","find","once","each","$this","addClass","focusID","val","tab_focus","$details","length","tab_list","wrap","before","$that","vertical_tab","verticalTab","title","text","details","append","item","removeClass","attr","data","id","eq","$locationHash","location","hash","closest","focus","settings","self","extend","theme","link","on","e","preventDefault","event","keyCode","trigger","updateSummary","prototype","siblings","tab","hide","end","show","remove","t","summary","html","drupalGetSummary","tabShow","parent","children","filter","tabHide","$firstTab","jQuery"],"mappings":"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","file":"vertical-tabs.es6.js","sourcesContent":["/**\n * @file\n * Define vertical tabs functionality.\n */\n\n/**\n * Triggers when form values inside a vertical tab changes.\n *\n * This is used to update the summary in vertical tabs in order to know what\n * are the important fields' values.\n *\n * @event summaryUpdated\n */\n\n(function ($, Drupal, drupalSettings) {\n\n 'use strict';\n\n /**\n * This script transforms a set of details into a stack of vertical tabs.\n *\n * Each tab may have a summary which can be updated by another\n * script. For that to work, each details element has an associated\n * 'verticalTabCallback' (with jQuery.data() attached to the details),\n * which is called every time the user performs an update to a form\n * element inside the tab pane.\n *\n * @type {Drupal~behavior}\n *\n * @prop {Drupal~behaviorAttach} attach\n * Attaches behaviors for vertical tabs.\n */\n Drupal.behaviors.verticalTabs = {\n attach: function (context) {\n var width = drupalSettings.widthBreakpoint || 640;\n var mq = '(max-width: ' + width + 'px)';\n\n if (window.matchMedia(mq).matches) {\n return;\n }\n\n $(context).find('[data-vertical-tabs-panes]').once('vertical-tabs').each(function () {\n var $this = $(this).addClass('vertical-tabs__panes');\n var focusID = $this.find(':hidden.vertical-tabs__active-tab').val();\n var tab_focus;\n\n // Check if there are some details that can be converted to\n // vertical-tabs.\n var $details = $this.find('> details');\n if ($details.length === 0) {\n return;\n }\n\n // Create the tab column.\n var tab_list = $('
          ');\n $this.wrap('
          ').before(tab_list);\n\n // Transform each details into a tab.\n $details.each(function () {\n var $that = $(this);\n var vertical_tab = new Drupal.verticalTab({\n title: $that.find('> summary').text(),\n details: $that\n });\n tab_list.append(vertical_tab.item);\n $that\n .removeClass('collapsed')\n // prop() can't be used on browsers not supporting details element,\n // the style won't apply to them if prop() is used.\n .attr('open', true)\n .addClass('vertical-tabs__pane')\n .data('verticalTab', vertical_tab);\n if (this.id === focusID) {\n tab_focus = $that;\n }\n });\n\n $(tab_list).find('> li').eq(0).addClass('first');\n $(tab_list).find('> li').eq(-1).addClass('last');\n\n if (!tab_focus) {\n // If the current URL has a fragment and one of the tabs contains an\n // element that matches the URL fragment, activate that tab.\n var $locationHash = $this.find(window.location.hash);\n if (window.location.hash && $locationHash.length) {\n tab_focus = $locationHash.closest('.vertical-tabs__pane');\n }\n else {\n tab_focus = $this.find('> .vertical-tabs__pane').eq(0);\n }\n }\n if (tab_focus.length) {\n tab_focus.data('verticalTab').focus();\n }\n });\n }\n };\n\n /**\n * The vertical tab object represents a single tab within a tab group.\n *\n * @constructor\n *\n * @param {object} settings\n * Settings object.\n * @param {string} settings.title\n * The name of the tab.\n * @param {jQuery} settings.details\n * The jQuery object of the details element that is the tab pane.\n *\n * @fires event:summaryUpdated\n *\n * @listens event:summaryUpdated\n */\n Drupal.verticalTab = function (settings) {\n var self = this;\n $.extend(this, settings, Drupal.theme('verticalTab', settings));\n\n this.link.attr('href', '#' + settings.details.attr('id'));\n\n this.link.on('click', function (e) {\n e.preventDefault();\n self.focus();\n });\n\n // Keyboard events added:\n // Pressing the Enter key will open the tab pane.\n this.link.on('keydown', function (event) {\n if (event.keyCode === 13) {\n event.preventDefault();\n self.focus();\n // Set focus on the first input field of the visible details/tab pane.\n $('.vertical-tabs__pane :input:visible:enabled').eq(0).trigger('focus');\n }\n });\n\n this.details\n .on('summaryUpdated', function () {\n self.updateSummary();\n })\n .trigger('summaryUpdated');\n };\n\n Drupal.verticalTab.prototype = {\n\n /**\n * Displays the tab's content pane.\n */\n focus: function () {\n this.details\n .siblings('.vertical-tabs__pane')\n .each(function () {\n var tab = $(this).data('verticalTab');\n tab.details.hide();\n tab.item.removeClass('is-selected');\n })\n .end()\n .show()\n .siblings(':hidden.vertical-tabs__active-tab')\n .val(this.details.attr('id'));\n this.item.addClass('is-selected');\n // Mark the active tab for screen readers.\n $('#active-vertical-tab').remove();\n this.link.append('' + Drupal.t('(active tab)') + '');\n },\n\n /**\n * Updates the tab's summary.\n */\n updateSummary: function () {\n this.summary.html(this.details.drupalGetSummary());\n },\n\n /**\n * Shows a vertical tab pane.\n *\n * @return {Drupal.verticalTab}\n * The verticalTab instance.\n */\n tabShow: function () {\n // Display the tab.\n this.item.show();\n // Show the vertical tabs.\n this.item.closest('.js-form-type-vertical-tabs').show();\n // Update .first marker for items. We need recurse from parent to retain\n // the actual DOM element order as jQuery implements sortOrder, but not\n // as public method.\n this.item.parent().children('.vertical-tabs__menu-item').removeClass('first')\n .filter(':visible').eq(0).addClass('first');\n // Display the details element.\n this.details.removeClass('vertical-tab--hidden').show();\n // Focus this tab.\n this.focus();\n return this;\n },\n\n /**\n * Hides a vertical tab pane.\n *\n * @return {Drupal.verticalTab}\n * The verticalTab instance.\n */\n tabHide: function () {\n // Hide this tab.\n this.item.hide();\n // Update .first marker for items. We need recurse from parent to retain\n // the actual DOM element order as jQuery implements sortOrder, but not\n // as public method.\n this.item.parent().children('.vertical-tabs__menu-item').removeClass('first')\n .filter(':visible').eq(0).addClass('first');\n // Hide the details element.\n this.details.addClass('vertical-tab--hidden').hide();\n // Focus the first visible tab (if there is one).\n var $firstTab = this.details.siblings('.vertical-tabs__pane:not(.vertical-tab--hidden)').eq(0);\n if ($firstTab.length) {\n $firstTab.data('verticalTab').focus();\n }\n // Hide the vertical tabs (if no tabs remain).\n else {\n this.item.closest('.js-form-type-vertical-tabs').hide();\n }\n return this;\n }\n };\n\n /**\n * Theme function for a vertical tab.\n *\n * @param {object} settings\n * An object with the following keys:\n * @param {string} settings.title\n * The name of the tab.\n *\n * @return {object}\n * This function has to return an object with at least these keys:\n * - item: The root tab jQuery element\n * - link: The anchor tag that acts as the clickable area of the tab\n * (jQuery version)\n * - summary: The jQuery element that contains the tab summary\n */\n Drupal.theme.verticalTab = function (settings) {\n var tab = {};\n tab.item = $('
        • ')\n .append(tab.link = $('')\n .append(tab.title = $('').text(settings.title))\n .append(tab.summary = $('')\n )\n );\n return tab;\n };\n\n})(jQuery, Drupal, drupalSettings);\n"]} \ No newline at end of file diff --git a/core/modules/big_pipe/js/big_pipe.es6.js b/core/modules/big_pipe/js/big_pipe.es6.js new file mode 100644 index 0000000..cdfd766 --- /dev/null +++ b/core/modules/big_pipe/js/big_pipe.es6.js @@ -0,0 +1,110 @@ +/** + * @file + * Renders BigPipe placeholders using Drupal's Ajax system. + */ + +(function ($, Drupal, drupalSettings) { + + 'use strict'; + + /** + * Executes Ajax commands in
          with class select-all, and insert the check all checkbox. + $table.find('th.select-all').prepend($('').attr('title', strings.selectAll)).on('click', function (event) { + if ($(event.target).is('input[type="checkbox"]')) { + // Loop through all checkboxes and set their state to the select all + // checkbox' state. + checkboxes.each(function () { + var $checkbox = $(this); + var stateChanged = $checkbox.prop('checked') !== event.target.checked; + + /** + * @checkbox {HTMLElement} + */ + if (stateChanged) { + $checkbox.prop('checked', event.target.checked).trigger('change'); + } + // Either add or remove the selected class based on the state of the + // check all checkbox. + + /** + * @checkbox {HTMLElement} + */ + $checkbox.closest('tr').toggleClass('selected', this.checked); + }); + // Update the title and the state of the check all box. + updateSelectAll(event.target.checked); + } + }); + + // For each of the checkboxes within the table that are not disabled. + checkboxes = $table.find('td input[type="checkbox"]:enabled').on('click', function (e) { + // Either add or remove the selected class based on the state of the + // check all checkbox. + + /** + * @this {HTMLElement} + */ + $(this).closest('tr').toggleClass('selected', this.checked); + + // If this is a shift click, we need to highlight everything in the + // range. Also make sure that we are actually checking checkboxes + // over a range and that a checkbox has been checked or unchecked before. + if (e.shiftKey && lastChecked && lastChecked !== e.target) { + // We use the checkbox's parent
          with class select-all, and insert the check all checkbox. $table.find('th.select-all').prepend($('').attr('title', strings.selectAll)).on('click', function (event) { if ($(event.target).is('input[type="checkbox"]')) { - // Loop through all checkboxes and set their state to the select all - // checkbox' state. checkboxes.each(function () { var $checkbox = $(this); var stateChanged = $checkbox.prop('checked') !== event.target.checked; - /** - * @checkbox {HTMLElement} - */ if (stateChanged) { $checkbox.prop('checked', event.target.checked).trigger('change'); } - // Either add or remove the selected class based on the state of the - // check all checkbox. - /** - * @checkbox {HTMLElement} - */ $checkbox.closest('tr').toggleClass('selected', this.checked); }); - // Update the title and the state of the check all box. + updateSelectAll(event.target.checked); } }); - // For each of the checkboxes within the table that are not disabled. checkboxes = $table.find('td input[type="checkbox"]:enabled').on('click', function (e) { - // Either add or remove the selected class based on the state of the - // check all checkbox. - - /** - * @this {HTMLElement} - */ $(this).closest('tr').toggleClass('selected', this.checked); - // If this is a shift click, we need to highlight everything in the - // range. Also make sure that we are actually checking checkboxes - // over a range and that a checkbox has been checked or unchecked before. if (e.shiftKey && lastChecked && lastChecked !== e.target) { - // We use the checkbox's parent
          with class select-all, and insert the check all checkbox.\n $table.find('th.select-all').prepend($('').attr('title', strings.selectAll)).on('click', function (event) {\n if ($(event.target).is('input[type=\"checkbox\"]')) {\n // Loop through all checkboxes and set their state to the select all\n // checkbox' state.\n checkboxes.each(function () {\n var $checkbox = $(this);\n var stateChanged = $checkbox.prop('checked') !== event.target.checked;\n\n /**\n * @checkbox {HTMLElement}\n */\n if (stateChanged) {\n $checkbox.prop('checked', event.target.checked).trigger('change');\n }\n // Either add or remove the selected class based on the state of the\n // check all checkbox.\n\n /**\n * @checkbox {HTMLElement}\n */\n $checkbox.closest('tr').toggleClass('selected', this.checked);\n });\n // Update the title and the state of the check all box.\n updateSelectAll(event.target.checked);\n }\n });\n\n // For each of the checkboxes within the table that are not disabled.\n checkboxes = $table.find('td input[type=\"checkbox\"]:enabled').on('click', function (e) {\n // Either add or remove the selected class based on the state of the\n // check all checkbox.\n\n /**\n * @this {HTMLElement}\n */\n $(this).closest('tr').toggleClass('selected', this.checked);\n\n // If this is a shift click, we need to highlight everything in the\n // range. Also make sure that we are actually checking checkboxes\n // over a range and that a checkbox has been checked or unchecked before.\n if (e.shiftKey && lastChecked && lastChecked !== e.target) {\n // We use the checkbox's parent