diff --git a/core/misc/active-link.js b/core/misc/active-link.js index 3a2f818b009e4b4c80b2994f90e3812d5871c74f..97c9966bc26fed9f20e3a3619450c4739197e7cd 100644 --- a/core/misc/active-link.js +++ b/core/misc/active-link.js @@ -18,6 +18,8 @@ * * 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) { @@ -28,7 +30,8 @@ var originalSelectors = ['[data-drupal-link-system-path="' + path.currentPath + '"]']; var selectors; - // If this is the front page, we have to check for the <front> path as well. + // If this is the front page, we have to check for the <front> path as + // well. if (path.isFront) { originalSelectors.push('[data-drupal-link-system-path="<front>"]'); } diff --git a/core/misc/ajax.js b/core/misc/ajax.js index db8dd26fa632c1c05ae1cc720f812a67b342bbfc..3cb1c8d2aed4c98c04bd2425b8e9a01c741a943d 100644 --- a/core/misc/ajax.js +++ b/core/misc/ajax.js @@ -1,9 +1,24 @@ +/** + * @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} */ Drupal.behaviors.AJAX = { attach: function (context, settings) { @@ -58,7 +73,8 @@ 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. + // 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; @@ -70,6 +86,15 @@ /** * 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. */ Drupal.AjaxError = function (xmlhttp, uri) { @@ -87,14 +112,15 @@ 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. + // 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 + // Empty. } responseText = ''; @@ -114,7 +140,18 @@ // We don't need readyState except for status == 0. readyStateText = xmlhttp.status === 0 ? ("\n" + Drupal.t("ReadyState: !readyState", {'!readyState': xmlhttp.readyState})) : ""; + /** + * Formatted and translated error message. + * + * @type {string} + */ this.message = statusCode + pathText + statusText + responseText + readyStateText; + + /** + * Used by some browsers to display a more accurate stack trace. + * + * @type {string} + */ this.name = 'AjaxError'; }; @@ -125,8 +162,8 @@ * Provides Ajax page updating via jQuery $.ajax. * * This function is designed to improve developer experience by wrapping the - * initialization of Drupal.Ajax objects and storing all created object in the - * Drupal.ajax.instances array. + * initialization of {@link Drupal.Ajax} objects and storing all created + * objects in the {@link Drupal.ajax.instances} array. * * @example * Drupal.behaviors.myCustomAJAXStuff = { @@ -158,17 +195,18 @@ * } * }; * - * @see Drupal.AjaxCommands - * * @param {object} settings - * The settings object passed to Drupal.Ajax constructor. + * The settings object passed to {@link Drupal.Ajax} constructor. * @param {string} [settings.base] - * Base is passed to Drupal.Ajax constructor as the 'base' parameter. + * Base is passed to {@link Drupal.Ajax} constructor as the 'base' + * parameter. * @param {HTMLElement} [settings.element] - * Element parameter of Drupal.Ajax constructor, element on which + * Element parameter of {@link Drupal.Ajax} constructor, element on which * event listeners will be bound. * * @return {Drupal.Ajax} + * + * @see Drupal.AjaxCommands */ Drupal.ajax = function (settings) { if (arguments.length !== 1) { @@ -195,7 +233,7 @@ /** * Contains all created Ajax objects. * - * @type {Array} + * @type {Array.<Drupal.Ajax>} */ Drupal.ajax.instances = []; @@ -205,16 +243,16 @@ * 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 + * 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 Drupal.Ajax constructor + * Base parameter of {@link Drupal.Ajax} constructor * @param {HTMLElement} [element] - * Element parameter of Drupal.Ajax constructor, element on which + * Element parameter of {@link Drupal.Ajax} constructor, element on which * event listeners will be bound. * @param {object} element_settings * @param {string} element_settings.url @@ -244,6 +282,9 @@ $.extend(this, defaults, element_settings); + /** + * @type {Drupal.AjaxCommands} + */ this.commands = new Drupal.AjaxCommands(); this.instanceIndex = false; @@ -252,15 +293,30 @@ // - Include the '#' for ID-based selectors. // - Support non-ID-based selectors. if (this.wrapper) { + + /** + * @type {string} + */ this.wrapper = '#' + this.wrapper; } + /** + * @type {HTMLElement} + */ this.element = element; + + /** + * @type {object} + */ this.element_settings = element_settings; // If there isn't a form, jQuery.ajax() will be used instead, allowing us to // bind Ajax to links as well. if (this.element && this.element.form) { + + /** + * @type {jQuery} + */ this.$form = $(this.element.form); } @@ -273,15 +329,16 @@ else if (this.element && element.form) { this.url = this.$form.attr('action'); - // @todo If there's a file input on this form, then jQuery will submit the - // Ajax response with a hidden Iframe rather than the XHR object. If the - // response to the submission is an HTTP redirect, then the Iframe will - // follow it, but the server won't content negotiate it correctly, - // because there won't be an ajax_iframe_upload POST variable. Until we - // figure out a work around to this problem, we prevent Ajax-enabling - // elements that submit to the same URL as the form when there's a file - // input. For example, this means the Delete button on the edit form of - // an Article node doesn't open its confirmation form in a dialog. + // @todo If there's a file input on this form, then jQuery will submit + // the AJAX response with a hidden Iframe rather than the XHR object. + // If the response to the submission is an HTTP redirect, then the + // Iframe will follow it, but the server won't content negotiate it + // correctly, because there won't be an ajax_iframe_upload POST + // variable. Until we figure out a work around to this problem, we + // prevent AJAX-enabling elements that submit to the same URL as the + // form when there's a file input. For example, this means the Delete + // button on the edit form of an Article node doesn't open its + // confirmation form in a dialog. if (this.$form.find(':file').length) { return; } @@ -300,6 +357,26 @@ // Set the options for the ajaxSubmit function. // The 'this' variable will not persist inside of the options object. var ajax = this; + + /** + * Options for the ajaxSubmit function. + * + * @name Drupal.Ajax#options + * + * @type {object} + * + * @prop {string} url + * @prop {object} data + * @prop {function} beforeSerialize + * @prop {function} beforeSubmit + * @prop {function} beforeSend + * @prop {function} success + * @prop {function} complete + * @prop {string} dataType + * @prop {object} accepts + * @prop {string} accepts.json + * @prop {string} type + */ ajax.options = { url: ajax.url, data: ajax.submit, @@ -373,6 +450,8 @@ * * The wrapper format determines how the HTML is wrapped, for example in a * modal dialog. + * + * @const {string} */ Drupal.ajax.WRAPPER_FORMAT = '_wrapper_format'; @@ -409,6 +488,9 @@ * In this case we're handling RETURN and SPACEBAR keypresses (event codes 13 * and 32. RETURN is often used to submit a form when in a textfield, and * SPACE is often used to activate an element without submitting. + * + * @param {HTMLElement} element + * @param {jQuery.Event} event */ Drupal.Ajax.prototype.keypressResponse = function (element, event) { // Create a synonym for this to reduce code confusion. @@ -417,8 +499,8 @@ // 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. + // #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(); @@ -434,6 +516,9 @@ * perform the actual Ajax call. It is bound to the event using * bind() in the constructor, and it uses the options specified on the * Ajax object. + * + * @param {HTMLElement} element + * @param {jQuery.Event} event */ Drupal.Ajax.prototype.eventResponse = function (element, event) { event.preventDefault(); @@ -479,6 +564,10 @@ * * Runs before the beforeSend() handler (see below), and unlike that one, runs * before field data is collected. + * + * @param {HTMLElement} element + * @param {object} options + * @param {object} options.data */ Drupal.Ajax.prototype.beforeSerialize = function (element, options) { // Allow detaching behaviors to update field values before collecting them. @@ -515,6 +604,10 @@ /** * Modify form values prior to form submission. + * + * @param {object} form_values + * @param {HTMLElement} element + * @param {object} options */ Drupal.Ajax.prototype.beforeSubmit = function (form_values, element, options) { // This function is left empty to make it simple to override for modules @@ -523,30 +616,35 @@ /** * Prepare the Ajax request before it is sent. + * + * @param {XMLHttpRequest} xmlhttprequest + * @param {object} options + * @param {object} options.extraData */ Drupal.Ajax.prototype.beforeSend = function (xmlhttprequest, options) { - // For forms without file inputs, the jQuery Form plugin serializes the form - // values, and then calls jQuery's $.ajax() function, which invokes this - // handler. In this circumstance, options.extraData is never used. For forms - // with file inputs, the jQuery Form plugin uses the browser's normal form - // submission mechanism, but captures the response in a hidden IFRAME. In this - // circumstance, it calls this handler first, and then appends hidden fields - // to the form to submit the values in options.extraData. There is no simple - // way to know which submission mechanism will be used, so we add to extraData - // regardless, and allow it to be ignored in the former case. + // 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. + // 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. + // 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; @@ -563,7 +661,7 @@ return; } - // Insert progress indicator + // 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); @@ -607,6 +705,9 @@ /** * Handler for the form redirection completion. + * + * @param {Array.<Drupal.AjaxCommands~commandDefinition>} response + * @param {number} status */ Drupal.Ajax.prototype.success = function (response, status) { // Remove the progress element. @@ -639,7 +740,13 @@ }; /** - * Build an effect object which tells us how to apply the effect when adding new HTML. + * Build an effect object to apply an effect when adding new HTML. + * + * @param {object} response + * @param {string} [response.effect] + * @param {string|number} [response.speed] + * + * @return {object} */ Drupal.Ajax.prototype.getEffect = function (response) { var type = response.effect || this.effect; @@ -667,6 +774,9 @@ /** * Handler for the form redirection error. + * + * @param {object} response + * @param {string} uri */ Drupal.Ajax.prototype.error = function (response, uri) { // Remove the progress element. @@ -689,12 +799,46 @@ }; /** - * Provide a series of commands that the server can request the client perform. + * @typedef {object} Drupal.AjaxCommands~commandDefinition + * + * @prop {string} command + * @prop {string} [method] + * @prop {string} [selector] + * @prop {string} [data] + * @prop {object} [settings] + * @prop {bool} [asterisk] + * @prop {string} [text] + * @prop {string} [title] + * @prop {string} [url] + * @prop {object} [argument] + * @prop {string} [name] + * @prop {string} [value] + * @prop {string} [old] + * @prop {string} [new] + * @prop {bool} [merge] + * @prop {Array} [args] + * + * @see Drupal.AjaxCommands + */ + + /** + * Provide a series of commands that the client will perform. + * + * @constructor */ Drupal.AjaxCommands = function () {}; Drupal.AjaxCommands.prototype = { + /** * Command to insert new content into the DOM. + * + * @param {Drupal.Ajax} ajax + * @param {object} response + * @param {string} response.data + * @param {string} [response.method] + * @param {string} [response.selector] + * @param {object} [response.settings] + * @param {number} [status] */ insert: function (ajax, response, status) { // Get information from the response. If it is not there, default to @@ -712,16 +856,17 @@ var new_content_wrapped = $('<div></div>').html(response.data); var new_content = new_content_wrapped.contents(); - // For legacy reasons, the effects processing code assumes that new_content - // consists of a single top-level element. Also, it has not been - // sufficiently tested whether attachBehaviors() can be successfully called - // with a context object that includes top-level text nodes. However, to - // give developers full control of the HTML appearing in the page, and to - // enable Ajax content to be inserted in places where DIV elements are not - // allowed (e.g., within TABLE, TR, and SPAN parents), we check if the new - // content satisfies the requirement of a single top-level element, and - // only use the container DIV created above when it doesn't. For more - // information, please see https://www.drupal.org/node/736066. + // For legacy reasons, the effects processing code assumes that + // new_content consists of a single top-level element. Also, it has not + // been sufficiently tested whether attachBehaviors() can be successfully + // called with a context object that includes top-level text nodes. + // However, to give developers full control of the HTML appearing in the + // page, and to enable Ajax content to be inserted in places where DIV + // elements are not allowed (e.g., within TABLE, TR, and SPAN parents), + // we check if the new content satisfies the requirement of a single + // top-level element, and only use the container DIV created above when + // it doesn't. For more information, please see + // https://www.drupal.org/node/736066. if (new_content.length !== 1 || new_content.get(0).nodeType !== 1) { new_content = new_content_wrapped; } @@ -756,9 +901,9 @@ 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. + // 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; @@ -768,6 +913,12 @@ /** * Command to remove a chunk from the page. + * + * @param {Drupal.Ajax} [ajax] + * @param {object} response + * @param {string} response.selector + * @param {object} [response.settings] + * @param {number} [status] */ remove: function (ajax, response, status) { var settings = response.settings || ajax.settings || drupalSettings; @@ -779,6 +930,12 @@ /** * Command to mark a chunk changed. + * + * @param {Drupal.Ajax} [ajax] + * @param {object} response + * @param {string} response.selector + * @param {bool} [response.asterisk] + * @param {number} [status] */ changed: function (ajax, response, status) { if (!$(response.selector).hasClass('ajax-changed')) { @@ -791,6 +948,12 @@ /** * Command to provide an alert. + * + * @param {Drupal.Ajax} [ajax] + * @param {object} response + * @param {string} response.text + * @param {string} response.title + * @param {number} [status] */ alert: function (ajax, response, status) { window.alert(response.text, response.title); @@ -798,6 +961,11 @@ /** * Command to set the window.location, redirecting the browser. + * + * @param {Drupal.Ajax} [ajax] + * @param {object} response + * @param {string} response.url + * @param {number} [status] */ redirect: function (ajax, response, status) { window.location = response.url; @@ -805,13 +973,24 @@ /** * Command to provide the jQuery css() function. + * + * @param {Drupal.Ajax} [ajax] + * @param {object} response + * @param {object} response.argument + * @param {number} [status] */ css: function (ajax, response, status) { $(response.selector).css(response.argument); }, /** - * Command to set the settings that will be used for other commands in this response. + * Command to set the settings used for other commands in this response. + * + * @param {Drupal.Ajax} [ajax] + * @param {object} response + * @param {bool} response.merge + * @param {object} response.settings + * @param {number} [status] */ settings: function (ajax, response, status) { if (response.merge) { @@ -824,6 +1003,13 @@ /** * Command to attach data using jQuery's data API. + * + * @param {Drupal.Ajax} [ajax] + * @param {object} response + * @param {string} response.name + * @param {string} response.selector + * @param {string|object} response.value + * @param {number} [status] */ data: function (ajax, response, status) { $(response.selector).data(response.name, response.value); @@ -831,6 +1017,13 @@ /** * Command to apply a jQuery method. + * + * @param {Drupal.Ajax} [ajax] + * @param {object} response + * @param {Array} response.args + * @param {string} response.method + * @param {string} response.selector + * @param {number} [status] */ invoke: function (ajax, response, status) { var $element = $(response.selector); @@ -839,6 +1032,11 @@ /** * Command to restripe a table. + * + * @param {Drupal.Ajax} [ajax] + * @param {object} response + * @param {string} response.selector + * @param {number} [status] */ restripe: function (ajax, response, status) { // :even and :odd are reversed because jQuery counts from 0 and @@ -852,6 +1050,12 @@ /** * Command to update a form's build ID. + * + * @param {Drupal.Ajax} [ajax] + * @param {object} response + * @param {string} response.old + * @param {string} response.new + * @param {number} [status] */ update_build_id: function (ajax, response, status) { $('input[name="form_build_id"][value="' + response.old + '"]').val(response.new); @@ -863,6 +1067,11 @@ * Uses the proprietary addImport method if available as browsers which * support that method ignore @import statements in dynamically added * stylesheets. + * + * @param {Drupal.Ajax} [ajax] + * @param {object} response + * @param {string} response.data + * @param {number} [status] */ add_css: function (ajax, response, status) { // Add the styles in the normal way. diff --git a/core/misc/announce.js b/core/misc/announce.js index 9aafbbcbce4a7ed55e2cbc68d78e103bb7cf60a0..0a0abebbdfde58fdebcb459ec4de12ceef3dcc29 100644 --- a/core/misc/announce.js +++ b/core/misc/announce.js @@ -1,9 +1,12 @@ /** + * @file * Adds an HTML element and method to trigger audio UAs to read system messages. * - * Use 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. + * 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. @@ -14,6 +17,7 @@ * )); * }); */ + (function (Drupal, debounce) { "use strict"; @@ -22,8 +26,9 @@ var announcements = []; /** - * Builds a div element with the aria-live attribute and attaches it - * to the DOM. + * Builds a div element with the aria-live attribute and add it to the DOM. + * + * @type {Drupal~behavior} */ Drupal.behaviors.drupalAnnounce = { attach: function (context) { @@ -80,17 +85,19 @@ * * 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 Drupal.announce() being read. - * By wrapping the call to announce in a debounce function, we allow for - * time for multiple calls to Drupal.announce() to queue up their messages. - * These messages are then joined and append to the aria-live region as one - * text node. + * 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 + * @param {string} text * A string to be read by the UA. - * @param String priority + * @param {string} [priority='polite'] * A string to indicate the priority of the message. Can be either - * 'polite' or 'assertive'. Polite is the default. + * 'polite' or 'assertive'. + * + * @return {function} * * @see http://www.w3.org/WAI/PF/aria-practices/#liveprops */ diff --git a/core/misc/autocomplete.js b/core/misc/autocomplete.js index 92bb86785af0fb591f83834cb63f330c8e560414..f826281b4818e8c6399418197b39810d620e8f21 100644 --- a/core/misc/autocomplete.js +++ b/core/misc/autocomplete.js @@ -1,3 +1,8 @@ +/** + * @file + * Autocomplete based on jQuery UI. + */ + (function ($, Drupal) { "use strict"; @@ -7,7 +12,9 @@ /** * Helper splitting terms from the autocomplete value. * - * @param {String} value + * @function Drupal.autocomplete.splitValues + * + * @param {string} value * * @return {Array} */ @@ -43,9 +50,11 @@ /** * Returns the last value of an multi-value textfield. * - * @param {String} terms + * @function Drupal.autocomplete.extractLastTerm + * + * @param {string} terms * - * @return {String} + * @return {string} */ function extractLastTerm(terms) { return autocomplete.splitValues(terms).pop(); @@ -54,9 +63,11 @@ /** * The search handler is called before a search is performed. * - * @param {Object} event + * @function Drupal.autocomplete.options.search * - * @return {Boolean} + * @param {object} event + * + * @return {bool} */ function searchHandler(event) { var options = autocomplete.options; @@ -70,10 +81,10 @@ } /** - * jQuery UI autocomplete source callback. + * JQuery UI autocomplete source callback. * - * @param {Object} request - * @param {Function} response + * @param {object} request + * @param {function} response */ function sourceData(request, response) { var elementId = this.element.attr('id'); @@ -86,7 +97,7 @@ * Filter through the suggestions removing all terms already tagged and * display the available terms to the user. * - * @param {Object} suggestions + * @param {object} suggestions */ function showSuggestions(suggestions) { var tagged = autocomplete.splitValues(request.term); @@ -103,7 +114,7 @@ /** * Transforms the data object into an array and update autocomplete results. * - * @param {Object} data + * @param {object} data */ function sourceCallbackHandler(data) { autocomplete.cache[elementId][term] = data; @@ -128,7 +139,7 @@ /** * Handles an autocompletefocus event. * - * @return {Boolean} + * @return {bool} */ function focusHandler() { return false; @@ -137,10 +148,10 @@ /** * Handles an autocompleteselect event. * - * @param {Object} event - * @param {Object} ui + * @param {jQuery.Event} event + * @param {object} ui * - * @return {Boolean} + * @return {bool} */ function selectHandler(event, ui) { var terms = autocomplete.splitValues(event.target.value); @@ -161,10 +172,10 @@ /** * Override jQuery UI _renderItem function to output HTML by default. * - * @param {Object} ul - * @param {Object} item + * @param {object} ul + * @param {object} item * - * @return {Object} + * @return {object} */ function renderItem(ul, item) { return $("<li>") @@ -174,6 +185,8 @@ /** * Attaches the autocomplete behavior to all required fields. + * + * @type {Drupal~behavior} */ Drupal.behaviors.autocomplete = { attach: function (context) { @@ -202,6 +215,8 @@ /** * Autocomplete object implementation. + * + * @namespace Drupal.autocomplete */ autocomplete = { cache: {}, @@ -209,6 +224,12 @@ splitValues: autocompleteSplitValues, extractLastTerm: extractLastTerm, // jQuery UI autocomplete options. + + /** + * JQuery UI option object. + * + * @name Drupal.autocomplete.options + */ options: { source: sourceData, focus: focusHandler, diff --git a/core/misc/batch.js b/core/misc/batch.js index ee6cf68705df9f68ce542ddd1f067b2d9c2ba015..43242eab9427edc2fba47d9423f12723ebbf0c0d 100644 --- a/core/misc/batch.js +++ b/core/misc/batch.js @@ -1,12 +1,16 @@ /** + * @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) { diff --git a/core/misc/collapse.js b/core/misc/collapse.js index 2d44e3f403b3c6b0d8ee1a75aeb9c5e2b947501f..1ad89f7e80fe6f8ccabaf5b0c498ef77a4dcc21e 100644 --- a/core/misc/collapse.js +++ b/core/misc/collapse.js @@ -1,9 +1,18 @@ +/** + * @file + * Polyfill for HTML5 details elements. + */ + (function ($, Modernizr, Drupal) { "use strict"; /** - * The collapsible details object represents a single collapsible details element. + * The collapsible details object represents a single details element. + * + * @constructor Drupal.CollapsibleDetails + * + * @param {HTMLElement} node */ function CollapsibleDetails(node) { this.$node = $(node); @@ -20,22 +29,24 @@ this.setupLegend(); } - /** - * Extend CollapsibleDetails function. - */ - $.extend(CollapsibleDetails, { + $.extend(CollapsibleDetails, /** @lends Drupal.CollapsibleDetails */{ + /** * Holds references to instantiated CollapsibleDetails objects. + * + * @type {Array.<Drupal.CollapsibleDetails>} */ instances: [] }); - /** - * Extend CollapsibleDetails prototype. - */ - $.extend(CollapsibleDetails.prototype, { + $.extend(CollapsibleDetails.prototype, /** @lends Drupal.CollapsibleDetails# */{ + /** * Initialize and setup summary events and markup. + * + * @fires event:summaryUpdated + * + * @listens event:summaryUpdated */ setupSummary: function () { this.$summary = $('<span class="summary"></span>'); @@ -43,6 +54,7 @@ .on('summaryUpdated', $.proxy(this.onSummaryUpdated, this)) .trigger('summaryUpdated'); }, + /** * Initialize and setup legend markup. */ @@ -65,20 +77,25 @@ .append(this.$summary) .on('click', $.proxy(this.onLegendClick, this)); }, + /** - * Handle legend clicks + * Handle legend clicks. + * + * @param {jQuery.Event} e */ onLegendClick: function (e) { this.toggle(); e.preventDefault(); }, + /** - * Update summary + * 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. */ @@ -99,6 +116,11 @@ } }); + /** + * Polyfill HTML5 details element. + * + * @type {Drupal~behavior} + */ Drupal.behaviors.collapse = { attach: function (context) { if (Modernizr.details) { diff --git a/core/misc/debounce.js b/core/misc/debounce.js index 45a5b87358a42cf167af0ec52e5ec0fd99a5c8f6..0239ce6af8e4e9a7244f7e1c49a0f9062318afda 100644 --- a/core/misc/debounce.js +++ b/core/misc/debounce.js @@ -1,7 +1,10 @@ /** - * Limits the invocations of a function in a given time frame. - * + * @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. @@ -11,13 +14,17 @@ * function can be written in such a way that it is only invoked under specific * conditions. * - * @param {Function} callback + * @param {function} func * The function to be invoked. - * - * @param {Number} wait + * @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) { diff --git a/core/misc/details-aria.js b/core/misc/details-aria.js index b400b4fc7a1c188e0cf85bdbaf86d306e8f8fc96..4de6bdf4f55e2f3a9c01ea6dc08b2a941d583bfd 100644 --- a/core/misc/details-aria.js +++ b/core/misc/details-aria.js @@ -7,6 +7,11 @@ "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) { diff --git a/core/misc/dialog/dialog.ajax.js b/core/misc/dialog/dialog.ajax.js index e786d5a16bccf9f7a7b60583d0293241a4bf8172..d70ca70f2633f526a9187e4a264a250a61ec6591 100644 --- a/core/misc/dialog/dialog.ajax.js +++ b/core/misc/dialog/dialog.ajax.js @@ -7,6 +7,11 @@ "use strict"; + /** + * Initialize dialogs for Ajax purposes. + * + * @type {Drupal~behavior} + */ Drupal.behaviors.dialog = { attach: function (context, settings) { var $context = $(context); @@ -46,9 +51,10 @@ /** * Scan a dialog for any primary buttons and move them to the button area. * - * @param $dialog + * @param {jQuery} $dialog * An jQuery object containing the element that is the dialog target. - * @return + * + * @return {Array} * An array of buttons that need to be added to the button area. */ prepareDialogButtons: function ($dialog) { @@ -81,6 +87,12 @@ /** * Command to open a dialog. + * + * @param {Drupal.Ajax} ajax + * @param {object} response + * @param {number} [status] + * + * @return {bool|undefined} */ Drupal.AjaxCommands.prototype.openDialog = function (ajax, response, status) { if (!response.selector) { @@ -107,7 +119,7 @@ response.dialogOptions.buttons = Drupal.behaviors.dialog.prepareDialogButtons($dialog); } - // Bind dialogButtonsChange + // Bind dialogButtonsChange. $dialog.on('dialogButtonsChange', function () { var buttons = Drupal.behaviors.dialog.prepareDialogButtons($dialog); $dialog.dialog('option', 'buttons', buttons); @@ -131,6 +143,12 @@ * Command to close a dialog. * * If no selector is given, it defaults to trying to close the modal. + * + * @param {Drupal.Ajax} [ajax] + * @param {object} response + * @param {string} response.selector + * @param {bool} response.persist + * @param {number} [status] */ Drupal.AjaxCommands.prototype.closeDialog = function (ajax, response, status) { var $dialog = $(response.selector); @@ -141,14 +159,21 @@ } } - // Unbind dialogButtonsChange + // Unbind dialogButtonsChange. $dialog.off('dialogButtonsChange'); }; /** * Command to set a dialog property. * - * jQuery UI specific way of setting dialog options. + * JQuery UI specific way of setting dialog options. + * + * @param {Drupal.Ajax} [ajax] + * @param {object} response + * @param {string} response.selector + * @param {string} response.optionsName + * @param {string} response.optionValue + * @param {number} [status] */ Drupal.AjaxCommands.prototype.setDialogOption = function (ajax, response, status) { var $dialog = $(response.selector); @@ -159,6 +184,11 @@ /** * Binds a listener on dialog creation to handle the cancel link. + * + * @param {jQuery.Event} e + * @param {Drupal.dialog~dialogDefinition} dialog + * @param {jQuery} $element + * @param {object} settings */ $(window).on('dialog:aftercreate', function (e, dialog, $element, settings) { $element.on('click.dialog', '.dialog-cancel', function (e) { @@ -170,6 +200,10 @@ /** * Removes all 'dialog' listeners. + * + * @param {jQuery.Event} e + * @param {Drupal.dialog~dialogDefinition} dialog + * @param {jQuery} $element */ $(window).on('dialog:beforeclose', function (e, dialog, $element) { $element.off('.dialog'); diff --git a/core/misc/dialog/dialog.jquery-ui.js b/core/misc/dialog/dialog.jquery-ui.js index cd6e863fec588023557f93856b00f3b9298774a4..27fff752ad46f5179f58de8fdf15bed511c6986e 100644 --- a/core/misc/dialog/dialog.jquery-ui.js +++ b/core/misc/dialog/dialog.jquery-ui.js @@ -2,6 +2,7 @@ * @file * Adds default classes to buttons for styling purposes. */ + (function ($) { "use strict"; diff --git a/core/misc/dialog/dialog.js b/core/misc/dialog/dialog.js index f63b405bdcabae21b509357db9fe157fd49f1d47..cb7fb9275060821817704fc18149d9f077a8ce8d 100644 --- a/core/misc/dialog/dialog.js +++ b/core/misc/dialog/dialog.js @@ -1,30 +1,64 @@ /** * @file + * Dialog API inspired by HTML5 dialog element. * - * Dialog API inspired by HTML5 dialog element: - * http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#the-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 - // @code - // jQuery(event.target).remove() - // @endcode - // as well, to remove the dialog on closing. + // 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.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 + * @param {object} options + * jQuery UI options to be passed to the dialog. + * + * @return {Drupal.dialog~dialogDefinition} + */ Drupal.dialog = function (element, options) { function openDialog(settings) { diff --git a/core/misc/dialog/dialog.position.js b/core/misc/dialog/dialog.position.js index 3e08574a6947c0e9e588906861fa449c4dd64f45..a3564778b1c9b619797bfc204586ff6f0bde9439 100644 --- a/core/misc/dialog/dialog.position.js +++ b/core/misc/dialog/dialog.position.js @@ -1,3 +1,14 @@ +/** + * @file + * Positioning extensions for dialogs. + */ + +/** + * Triggers when content inside a dialog changes. + * + * @event dialogContentResize + */ + (function ($, Drupal, drupalSettings, debounce, displace) { "use strict"; @@ -11,7 +22,13 @@ * 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 Drupal.dialog(). + * a new {@link Drupal.dialog}. + * + * @function Drupal.dialog~resetSize + * + * @param {jQuery.Event} event + * + * @fires event:dialogContentResize */ function resetSize(event) { var positionOptions = ['width', 'height', 'minWidth', 'minHeight', 'maxHeight', 'maxWidth', 'position']; @@ -46,6 +63,12 @@ /** * Position the dialog's center at the center of displace.offsets boundaries. + * + * @function Drupal.dialog~resetPosition + * + * @param {object} options + * + * @return {object} */ function resetPosition(options) { var offsets = displace.offsets; diff --git a/core/misc/displace.js b/core/misc/displace.js index a52a82650ed389abdfbf146dd033badad64673a0..5e21ca12afbd983750b0caf540ed85c68103d86b 100644 --- a/core/misc/displace.js +++ b/core/misc/displace.js @@ -1,13 +1,38 @@ /** + * @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, @@ -17,6 +42,8 @@ /** * Registers a resize handler on the window. + * + * @type {Drupal~behavior} */ Drupal.behaviors.drupalDisplace = { attach: function () { @@ -33,14 +60,20 @@ /** * Informs listeners of the current offset dimensions. * - * @param {boolean} broadcast - * (optional) When true or undefined, causes the recalculated offsets values to be + * @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 {object} + * @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(); @@ -53,7 +86,7 @@ /** * Determines the viewport offsets. * - * @return {object} + * @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. @@ -75,6 +108,8 @@ * 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'. @@ -111,7 +146,7 @@ /** * Calculates displacement for element based on its dimensions and placement. * - * @param {jQuery} $el + * @param {HTMLElement} el * The jQuery element whose dimensions and placement will be measured. * * @param {string} edge @@ -163,15 +198,23 @@ /** * 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 + * 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 }); diff --git a/core/misc/dropbutton/dropbutton.js b/core/misc/dropbutton/dropbutton.js index eebf354ef9df7944055f472937786247af02b0dc..787cc560e336935612f9416304c85d97b937f3af 100644 --- a/core/misc/dropbutton/dropbutton.js +++ b/core/misc/dropbutton/dropbutton.js @@ -1,9 +1,16 @@ +/** + * @file + * Dropbutton feature. + */ + (function ($, Drupal) { "use strict"; /** * Process elements with the .dropbutton class on page load. + * + * @type {Drupal~behavior} */ Drupal.behaviors.dropButton = { attach: function (context, settings) { @@ -25,6 +32,10 @@ /** * Delegated callback for opening and closing dropbutton secondary actions. + * + * @function Drupal.DropButton~dropbuttonClickHandler + * + * @param {jQuery.Event} e */ function dropbuttonClickHandler(e) { e.preventDefault(); @@ -37,21 +48,36 @@ * All secondary actions beyond the first in the list are presented in a * dropdown list accessible through a toggle arrow associated with the button. * - * @param {jQuery} $dropbutton - * A jQuery element. + * @constructor Drupal.DropButton * - * @param {Object} settings + * @param {HTMLElement} dropbutton + * A DOM element. + * @param {object} settings * A list of options including: - * - {String} title: The text inside the toggle link element. This text is - * hidden from visual UAs. + * @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. + + /** + * 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. @@ -67,18 +93,31 @@ 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) }); } @@ -90,11 +129,11 @@ /** * Extend the DropButton constructor. */ - $.extend(DropButton, { + $.extend(DropButton, /** @lends Drupal.DropButton */{ /** * Store all processed DropButtons. * - * @type {Array} + * @type {Array.<Drupal.DropButton>} */ dropbuttons: [] }); @@ -102,12 +141,13 @@ /** * Extend the DropButton prototype. */ - $.extend(DropButton.prototype, { + $.extend(DropButton.prototype, /** @lends Drupal.DropButton# */{ + /** * Toggle the dropbutton open and closed. * - * @param {Boolean} show - * (optional) Force the dropbutton to open by passing true or to close by + * @param {bool} [show] + * Force the dropbutton to open by passing true or to close by * passing false. */ toggle: function (show) { @@ -116,6 +156,9 @@ this.$dropbutton.toggleClass('open', show); }, + /** + * @method + */ hoverIn: function () { // Clear any previous timer we were using. if (this.timerID) { @@ -123,37 +166,53 @@ } }, + /** + * @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 + */ focusOut: function (e) { this.hoverOut.call(this, e); }, + /** + * @param {jQuery.Event} e + */ focusIn: function (e) { this.hoverIn.call(this, e); } }); - $.extend(Drupal.theme, { + $.extend(Drupal.theme, /** @lends Drupal.theme */{ + /** * A toggle is an interactive element often bound to a click handler. * - * @param {Object} options - * - {String} title: (optional) The HTML anchor title attribute and - * text for the inner span element. + * @param {object} options + * @param {string} [options.title] + * The HTML anchor title attribute and text for the inner span element. * - * @return {String} + * @return {string} * A string representing a DOM fragment. */ dropbuttonToggle: function (options) { diff --git a/core/misc/drupal.js b/core/misc/drupal.js index 9e9d24e0438cfe1f326094c64b8b5c0e973c3163..75f85bbacdef78ec093c713a3aa96c32c13caf30 100644 --- a/core/misc/drupal.js +++ b/core/misc/drupal.js @@ -1,7 +1,40 @@ /** - * Base framework for Drupal-specific JavaScript, behaviors, and settings. + * @file + * Defines the Drupal JS API. */ -window.Drupal = {behaviors: {}}; + +/** + * A jQuery object. + * + * @typedef {object} jQuery + * + * @prop {number} length=0 + */ + +/** + * Variable generated by Drupal with all the configuration created from PHP. + * + * @global + * + * @var {object} drupalSettings + */ + +/** + * Variable generated by Drupal that holds all translated strings from PHP. + * + * @global + * + * @var {object} drupalTranslations + */ + +/** + * Global Drupal object. + * + * @global + * + * @namespace + */ +window.Drupal = {behaviors: {}, locale: {}}; // Class indicating that JS is enabled; used for styling purpose. document.documentElement.className += ' js'; @@ -18,16 +51,43 @@ if (window.jQuery) { "use strict"; /** - * Custom error type thrown after attach/detach if one or more behaviors failed. + * Custom error type thrown after attach/detach if one or more behaviors + * failed. + * + * @memberof Drupal * - * @param list + * @constructor + * + * @augments Error + * + * @param {Array} list * An array of errors thrown during attach/detach. - * @param event + * @param {string} event * A string containing either 'attach' or 'detach'. + * + * @inner */ function DrupalBehaviorError(list, event) { + + /** + * Setting name helps debuggers. + * + * @type {string} + */ this.name = 'DrupalBehaviorError'; + + /** + * Execution phase errors were triggered. + * + * @type {string} + */ this.event = event || 'attach'; + + /** + * All thrown errors. + * + * @type {Array.<Error>} + */ this.list = list; // Makes the list of errors readable. var messageList = []; @@ -36,48 +96,100 @@ if (window.jQuery) { for (var i = 0; i < il; i++) { messageList.push(this.list[i].behavior + ': ' + this.list[i].error.message); } + + /** + * Final message to send to debuggers. + * + * @type {string} + */ this.message = messageList.join(' ; '); } DrupalBehaviorError.prototype = new Error(); + /** + * Callback function initializing code run on page load and Ajax requests. + * + * @callback Drupal~behaviorAttach + * + * @param {HTMLElement} context + * @param {object} settings + * + * @see Drupal.attachBehaviors + */ + + /** + * Callback function for reverting and cleaning up behavior initialization. + * + * @callback Drupal~behaviorDetach + * + * @param {HTMLElement} context + * @param {object} settings + * @param {string} trigger + * One of 'unload', 'serialize' or 'move'. + * + * @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.<string, Drupal~behavior>} + */ + /** * Attach 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 Drupal.behaviors - * object using the method 'attach' and optionally also 'detach' as follows: - * @code - * Drupal.behaviors.behaviorName = { - * attach: function (context, settings) { - * ... - * }, - * detach: function (context, settings, trigger) { - * ... - * } - * }; - * @endcode - * - * 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 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' as follows: + * + * {@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 - * @code - * var elements = $(context).find(selector).once('behavior-name'); - * @endcode + * `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.) + * 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 context - * An element to attach behaviors to. If none is given, the document element - * is used. - * @param settings + * @param {Element} context + * An element to attach behaviors to. If none is given, the document + * element is used. + * @param {object} settings * An object containing settings for the current context. If none is given, * the global drupalSettings object is used. + * + * @see Drupal~behaviorAttach + * @see Drupal.detachBehaviors + * + * @throws {Drupal~DrupalBehaviorError} */ Drupal.attachBehaviors = function (context, settings) { context = context || document; @@ -115,22 +227,22 @@ if (window.jQuery) { * * 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 context - * An element to detach behaviors from. If none is given, the document element - * is used. - * @param settings - * An object containing settings for the current context. If none given, the - * global drupalSettings object is used. - * @param trigger + * implementation, i.e. .removeOnce('behaviorName'), to ensure the behavior + * is detached only from previously processed elements. + * + * @param {Element} context + * An element to detach behaviors from. If none is given, the document + * element is used. + * @param {object} settings + * An object containing settings for the current context. If none given, + * the global drupalSettings object is used. + * @param {string} trigger * A string containing what's causing the behaviors to be detached. The * possible triggers are: * - unload: (default) 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, - * Drupal.attachBehaviors() is called, so that the behavior can undo + * {@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, @@ -143,6 +255,9 @@ if (window.jQuery) { * 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) { @@ -171,7 +286,12 @@ if (window.jQuery) { /** * Helper to test document width for mobile configurations. - * @todo Temporary solution for the mobile initiative. + * + * @param {number} [width=640] + * + * @return {bool} + * + * @deprecated Temporary solution for the mobile initiative. */ Drupal.checkWidthBreakpoint = function (width) { width = width || drupalSettings.widthBreakpoint || 640; @@ -181,10 +301,12 @@ if (window.jQuery) { /** * Encode special characters in a plain-text string for display as HTML. * - * @param str + * @param {string} str * The string to be encoded. - * @return + * + * @return {string} * The encoded string. + * * @ingroup sanitization */ Drupal.checkPlain = function (str) { @@ -199,22 +321,21 @@ if (window.jQuery) { /** * Replace placeholders with sanitized values in a string. * - * @param {String} str + * @param {string} str * A string with placeholders. - * @param {Object} args + * @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 (Drupal.checkPlain) + * - @variable: escape plain text to HTML ({@link Drupal.checkPlain}) * - %variable: escape text and theme as a placeholder for user-submitted - * content (checkPlain + Drupal.theme('placeholder')) + * content ({@link Drupal.checkPlain} + + * {@link Drupal.theme}('placeholder')) * - * @return {String} - * Returns the replaced string. + * @return {string} * - * @see Drupal.t() - * @ingroup sanitization + * @see Drupal.t */ Drupal.formatString = function (str, args) { // Keep args intact. @@ -227,10 +348,12 @@ if (window.jQuery) { 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]); @@ -248,14 +371,14 @@ if (window.jQuery) { * The longest keys will be tried first. Once a substring has been replaced, * its new value will not be searched again. * - * @param {String} str + * @param {string} str * A string with placeholders. - * @param {Object} args + * @param {object} args * Key-value pairs. * @param {Array|null} keys * Array of keys from the "args". Internal use only. * - * @return {String} + * @return {string} * Returns the replaced string. */ Drupal.stringReplace = function (str, args, keys) { @@ -299,18 +422,17 @@ if (window.jQuery) { * * See the documentation of the server-side t() function for further details. * - * @param str + * @param {string} str * A string containing the English string to translate. - * @param args + * @param {Object.<string, string>} [args] * An object of replacements pairs to make after translation. Incidences * of any key in this array are replaced with the corresponding value. - * See Drupal.formatString(). + * See {@link Drupal.formatString}. + * @param {object} [options] + * @param {string} [options.context=''] + * The context the source string belongs to. * - * @param options - * - 'context' (defaults to the empty context): The context the source string - * belongs to. - * - * @return + * @return {string} * The translated string. */ Drupal.t = function (str, args, options) { @@ -330,6 +452,11 @@ if (window.jQuery) { /** * Returns the URL to a Drupal page. + * + * @param {string} path + * Drupal path to transform to URL. + * + * @return {string} */ Drupal.url = function (path) { return drupalSettings.path.baseUrl + drupalSettings.path.pathPrefix + path; @@ -339,33 +466,33 @@ if (window.jQuery) { * Format a string containing a count of items. * * This function ensures that the string is pluralized correctly. Since - * Drupal.t() is called by this function, make sure not to pass + * {@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 + * @param {number} count * The item count to display. - * @param {String} singular + * @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 + * @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 + * @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 Drupal.formatString(). + * 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 Drupal.t() function. + * @param {object} [options] + * The options to pass to the {@link Drupal.t} function. * - * @return {String} + * @return {string} * A translated string. */ Drupal.formatPlural = function (count, singular, plural, args, options) { @@ -391,6 +518,11 @@ if (window.jQuery) { * Encodes a Drupal path for use in a URL. * * For aesthetic reasons slashes are not escaped. + * + * @param {string} item + * Unencoded path. + * + * @return {string} */ Drupal.encodePath = function (item) { return window.encodeURIComponent(item).replace(/%2F/g, '/'); @@ -406,13 +538,16 @@ if (window.jQuery) { * * For example, to retrieve the HTML for text that should be emphasized and * displayed as a placeholder inside a sentence, call - * Drupal.theme('placeholder', text). + * `Drupal.theme('placeholder', text)`. + * + * @namespace * - * @param func + * @param {function} func * The name of the theme function to call. - * @param ... + * @param {...args} * Additional arguments to pass along to the theme function. - * @return + * + * @return {string|object|HTMLElement|jQuery} * Any data the theme function returns. This could be a plain HTML string, * but also a complex object. */ @@ -426,9 +561,10 @@ if (window.jQuery) { /** * Formats text for emphasized display in a placeholder inside a sentence. * - * @param str + * @param {string} str * The text to format (plain-text). - * @return + * + * @return {string} * The formatted text (html). */ Drupal.theme.placeholder = function (str) { diff --git a/core/misc/form.js b/core/misc/form.js index 68b4e9e16723cc89655a7561080c31d092f506a9..7210be5d34ab951c9726aeac5f45eceb6255e54e 100644 --- a/core/misc/form.js +++ b/core/misc/form.js @@ -1,9 +1,25 @@ +/** + * @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} */ $.fn.drupalGetSummary = function () { var callback = this.data('summaryCallback'); @@ -13,9 +29,15 @@ /** * Sets the summary for all matched elements. * - * @param callback + * @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} + * + * @fires event:summaryUpdated + * + * @listens event:formUpdated */ $.fn.drupalSetSummary = function (callback) { var self = this; @@ -43,13 +65,13 @@ /** * 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. + * 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. + * 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 @@ -57,26 +79,28 @@ * 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: + * 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. + * 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 () { @@ -99,6 +123,10 @@ /** * Sends a 'formUpdated' event each time a form element is modified. + * + * @param {HTMLElement} element + * + * @fires event:formUpdated */ function triggerFormUpdated(element) { $(element).trigger('formUpdated'); @@ -108,6 +136,7 @@ * Collects the IDs of all form fields in the given form. * * @param {HTMLFormElement} form + * * @return {Array} */ function fieldsList(form) { @@ -122,6 +151,10 @@ /** * Triggers the 'formUpdated' event on form elements when they are modified. + * + * @type {Drupal~behavior} + * + * @fires event:formUpdated */ Drupal.behaviors.formUpdated = { attach: function (context) { @@ -132,7 +165,8 @@ 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. + // 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); @@ -147,7 +181,7 @@ 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 + // If there has been a change in the fields or their order, trigger // formUpdated. if (formFields !== currentFields) { triggerFormUpdated(context); @@ -172,6 +206,8 @@ /** * Prepopulate form fields with information from the visitor browser. + * + * @type {Drupal~behavior} */ Drupal.behaviors.fillUserInfoFromBrowser = { attach: function (context, settings) { diff --git a/core/misc/machine-name.js b/core/misc/machine-name.js index 13e0034f2e9d71ec0437b936bcd190559155a0cc..2b304bf87a2290140e9c8b1d31b213bcd858da86 100644 --- a/core/misc/machine-name.js +++ b/core/misc/machine-name.js @@ -1,28 +1,39 @@ +/** + * @file + * Machine name functionality. + */ + (function ($, Drupal, drupalSettings) { "use strict"; /** * Attach the machine-readable name form element behavior. + * + * @type {Drupal~behavior} */ Drupal.behaviors.machineName = { + /** * Attaches the behavior. * - * @param 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: + * @param {Element} context + * @param {object} settings + * @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). + * - 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. + * - 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. */ @@ -94,9 +105,9 @@ 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. + // 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(); } @@ -163,17 +174,19 @@ /** * Transliterate a human-readable name to a machine name. * - * @param source + * @param {string} source * A string to transliterate. - * @param settings - * The machine name settings for the corresponding field, containing: - * - 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 '-'. - * - maxlength: The maximum length of the machine name. + * @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 + * @return {jQuery} * The transliterated source string. */ transliterate: function (source, settings) { diff --git a/core/misc/progress.js b/core/misc/progress.js index e3d1223d0b187cb0c71f6ddf8d4f25b422eb2976..fdfd3b69b966ae57cccbf970f7e12123e201dab0 100644 --- a/core/misc/progress.js +++ b/core/misc/progress.js @@ -1,3 +1,8 @@ +/** + * @file + * Progress bar. + */ + (function ($, Drupal) { "use strict"; @@ -5,7 +10,9 @@ /** * Theme function for the progress bar. * - * @return + * @param {string} id + * + * @return {string} * The HTML for the progress bar. */ Drupal.theme.progressBar = function (id) { @@ -21,11 +28,19 @@ * 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 + * Method is the function which will perform the HTTP request to get the * progress bar state. Either "GET" or "POST". * - * e.g. pb = new Drupal.ProgressBar('myProgressBar'); - * some_element.appendChild(pb.element); + * @example + * pb = new Drupal.ProgressBar('myProgressBar'); + * some_element.appendChild(pb.element); + * + * @constructor + * + * @param {string} id + * @param {function} updateCallback + * @param {string} method + * @param {function} errorCallback */ Drupal.ProgressBar = function (id, updateCallback, method, errorCallback) { this.id = id; @@ -33,14 +48,21 @@ 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. + // 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, { + $.extend(Drupal.ProgressBar.prototype, /** @lends Drupal.ProgressBar# */{ + /** * Set the percentage and status message for the progressbar. + * + * @param {number} percentage + * @param {string} message + * @param {string} label */ setProgress: function (percentage, message, label) { if (percentage >= 0 && percentage <= 100) { @@ -56,6 +78,9 @@ /** * Start monitoring progress via Ajax. + * + * @param {string} uri + * @param {number} delay */ startMonitoring: function (uri, delay) { this.delay = delay; @@ -116,6 +141,8 @@ /** * Display errors on the page. + * + * @param {string} string */ displayError: function (string) { var error = $('<div class="messages messages--error"></div>').html(string); diff --git a/core/misc/states.js b/core/misc/states.js index b969da76dfb4695b43cdd2088cf089e79804f0f8..3a67aeaae71cd02a373056fc2d460a01e999e052 100644 --- a/core/misc/states.js +++ b/core/misc/states.js @@ -1,6 +1,8 @@ /** + * @file * Drupal's states library. */ + (function ($) { "use strict"; @@ -10,14 +12,21 @@ * * 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. + + /** + * An array of functions that should be postponed. + */ postponed: [] }; /** * Attaches the states. + * + * @type {Drupal~behavior} */ Drupal.behaviors.states = { attach: function (context, settings) { @@ -48,13 +57,18 @@ /** * Object representing an element that depends on other elements. * - * @param args - * Object with the following keys (all of which are required): - * - element: A jQuery object of the dependent element - * - state: A State object describing the state that is dependent - * - 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. + * @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); @@ -71,6 +85,12 @@ * 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) { @@ -81,21 +101,25 @@ 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(). + // 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. * - * @param selector + * @memberof Drupal.states.Dependent# + * + * @param {string} selector * The CSS selector describing the dependee. - * @param dependeeStates + * @param {object} dependeeStates * The list of states that have to be monitored for tracking the * dependee's compliance status. */ @@ -113,7 +137,8 @@ for (var i in dependeeStates) { if (dependeeStates.hasOwnProperty(i)) { state = dependeeStates[i]; - // Make sure we're not initializing this selector/state combination twice. + // Make sure we're not initializing this selector/state combination + // twice. if ($.inArray(state, dependeeStates) === -1) { continue; } @@ -135,14 +160,16 @@ /** * Compares a value with a reference value. * - * @param reference + * @memberof Drupal.states.Dependent# + * + * @param {object} reference * The value used for reference. - * @param selector + * @param {string} selector * CSS selector describing the dependee. - * @param state + * @param {Drupal.states.State} state * A State object describing the dependee's updated state. * - * @return + * @return {bool} * true or false. */ compare: function (reference, selector, state) { @@ -160,11 +187,13 @@ /** * Update the value of a dependee's state. * - * @param selector + * @memberof Drupal.states.Dependent# + * + * @param {string} selector * CSS selector describing the dependee. - * @param state + * @param {Drupal.states.state} state * A State object describing the dependee's updated state. - * @param value + * @param {string} value * The new value for the dependee's updated state. */ update: function (selector, state, value) { @@ -177,6 +206,8 @@ /** * Triggers change events in case a state changed. + * + * @memberof Drupal.states.Dependent# */ reevaluate: function () { // Check whether any constraint for this dependent state is satisfied. @@ -200,14 +231,16 @@ /** * Evaluates child constraints to determine if a constraint is satisfied. * - * @param constraints + * @memberof Drupal.states.Dependent# + * + * @param {object|Array} constraints * A constraint object or an array of constraints. - * @param selector + * @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 + * @return {bool} * true or false, depending on whether these constraints are satisfied. */ verifyConstraints: function (constraints, selector) { @@ -219,8 +252,8 @@ 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. + // 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; } @@ -229,15 +262,15 @@ } } // 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. + // 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. + // False and anything else will evaluate to false, so return when + // any false condition is found. if (result === false) { return false; } } } @@ -248,27 +281,28 @@ /** * Checks whether the value matches the requirements for this constraint. * - * @param value + * @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 selector + * @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 state + * 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. + * 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 + * @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. + // 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; } @@ -279,7 +313,7 @@ } if (state !== null) { - // constraints is the actual constraints of an element to check for. + // 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); } @@ -291,11 +325,15 @@ /** * Gathers information about all required triggers. + * + * @memberof Drupal.states.Dependent# + * + * @return {object} */ getDependees: function () { var cache = {}; - // Swivel the lookup function so that we can record all available selector- - // state combinations for initialization. + // 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); @@ -317,6 +355,11 @@ } }; + /** + * @constructor Drupal.states.Trigger + * + * @param {object} args + */ states.Trigger = function (args) { $.extend(this, args); @@ -332,6 +375,10 @@ }; states.Trigger.prototype = { + + /** + * @memberof Drupal.states.Trigger# + */ initialize: function () { var trigger = states.Trigger.states[this.state]; @@ -351,6 +398,12 @@ this.element.data('trigger:' + this.state, true); }, + /** + * @memberof Drupal.states.Trigger# + * + * @param {jQuery.Event} event + * @param {function} valueFn + */ defaultTrigger: function (event, valueFn) { var oldValue = valueFn.call(this.element); @@ -376,9 +429,16 @@ * 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' describes the state to be monitored. empty: { // 'keyup' is the (native DOM) event that we watch for. 'keyup': function () { @@ -390,9 +450,9 @@ 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. + // 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. @@ -434,9 +494,16 @@ /** * A state object is used for describing the state and performing aliasing. + * + * @constructor Drupal.states.State + * + * @param {string} state */ states.State = function (state) { - // We may need the original unresolved name later. + + /** + * Original unresolved name. + */ this.pristine = this.name = state; // Normalize the state name. @@ -460,6 +527,12 @@ /** * Creates a new State object by sanitizing the passed value. + * + * @name Drupal.states.State.sanitize + * + * @param {string|Drupal.states.State} state + * + * @return {Drupal.states.state} */ states.State.sanitize = function (state) { if (state instanceof states.State) { @@ -471,8 +544,10 @@ }; /** - * This list of aliases is used to normalize states and associates negated names - * with their respective inverse 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', @@ -490,10 +565,18 @@ }; 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} */ toString: function () { return this.name; @@ -563,6 +646,13 @@ /** * Bitwise AND with a third undefined state. + * + * @function Drupal.states~ternary + * + * @param {*} a + * @param {*} b + * + * @return {bool} */ function ternary(a, b) { if (typeof a === 'undefined') { @@ -578,6 +668,13 @@ /** * Inverts a (if it's not undefined) when invertState is true. + * + * @function Drupal.states~invert + * + * @param {*} a + * @param {bool} invertState + * + * @return {bool} */ function invert(a, invertState) { return (invertState && typeof a !== 'undefined') ? !a : a; @@ -585,6 +682,13 @@ /** * Compares two values while ignoring undefined values. + * + * @function Drupal.states~compare + * + * @param {*} a + * @param {*} b + * + * @return {bool} */ function compare(a, b) { if (a === b) { diff --git a/core/misc/tabbingmanager.js b/core/misc/tabbingmanager.js index dc21c0263bf956f6064205af8f447e3acf79c96e..4593ec48f70194e87fc859832481e14fc8925374 100644 --- a/core/misc/tabbingmanager.js +++ b/core/misc/tabbingmanager.js @@ -3,42 +3,76 @@ * 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. + + /** + * 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.<Drupal~TabbingContext>} + */ this.stack = []; } /** * Add public methods to the TabbingManager class. */ - $.extend(TabbingManager.prototype, { + $.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. + * 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. * - * @param jQuery elements - * The set of elements to which tabbing should be constrained. Can also be - * a jQuery-compatible selector string. + * @return {Drupal~TabbingContext} * - * @return TabbingContext + * @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. + // 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(); @@ -68,14 +102,15 @@ }, /** - * Restores a former tabbingContext when an active tabbingContext is released. + * 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 + * 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. + // 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--; @@ -94,11 +129,11 @@ /** * Makes all elements outside the 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. + * Elements made untabbable have their original tabindex and autofocus + * values stored so that they might be restored later when this + * tabbingContext is deactivated. * - * @param TabbingContext tabbingContext + * @param {Drupal~TabbingContext} tabbingContext * The TabbingContext instance that has been activated. */ activate: function (tabbingContext) { @@ -115,17 +150,18 @@ for (var i = 0; i < il; i++) { this.recordTabindex($disabledSet.eq(i), level); } - // Make all tabbable elements outside of the active tabbing set unreachable. + // 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. + // 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 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); } @@ -135,10 +171,10 @@ /** * Restores that tabbable state of a tabbingContext's disabled elements. * - * Elements that were made untabbable have their original tabindex and autofocus - * values restored. + * Elements that were made untabbable have their original tabindex and + * autofocus values restored. * - * @param TabbingContext tabbingContext + * @param {Drupal~TabbingContext} tabbingContext * The TabbingContext instance that has been deactivated. */ deactivate: function (tabbingContext) { @@ -153,9 +189,9 @@ /** * Records the tabindex and autofocus values of an untabbable element. * - * @param jQuery $set + * @param {jQuery} $el * The set of elements that have been disabled. - * @param Number level + * @param {number} level * The stack level for which the tabindex attribute should be recorded. */ recordTabindex: function ($el, level) { @@ -170,9 +206,9 @@ /** * Restores the tabindex and autofocus values of a reactivated element. * - * @param jQuery $el + * @param {jQuery} $el * The element that is being reactivated. - * @param Number level + * @param {number} level * The stack level for which the tabindex attribute should be restored. */ restoreTabindex: function ($el, level) { @@ -214,26 +250,53 @@ * * This constraint can be removed with the release() method. * - * @param Object options - * A set of initiating values that include: - * - Number level: The level in the TabbingManager's stack of this - * tabbingContext. - * - jQuery $tabbableElements: The DOM elements that should be reachable via - * the tab key when this tabbingContext is active. - * - jQuery $disabledElements: The DOM elements that should not be reachable - * via the tab key when this tabbingContext is active. - * - Boolean released: A released tabbingContext can never be activated again. - * It will be cleaned up when the TabbingManager unwinds its stack. - * - Boolean 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. + * @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, { + + $.extend(this, /** @lends Drupal~TabbingContext# */{ + + /** + * @type {?number} + */ level: null, + + /** + * @type {jQuery} + */ $tabbableElements: $(), + + /** + * @type {jQuery} + */ $disabledElements: $(), + + /** + * @type {bool} + */ released: false, + + /** + * @type {bool} + */ active: false }, options); } @@ -241,11 +304,15 @@ /** * Add public methods to the TabbingContext class. */ - $.extend(TabbingContext.prototype, { + $.extend(TabbingContext.prototype, /** @lends Drupal~TabbingContext# */{ + /** * Releases this TabbingContext. * - * Once a TabbingContext object is released, it can never be activated again. + * Once a TabbingContext object is released, it can never be activated + * again. + * + * @fires event:drupalTabbingContextReleased */ release: function () { if (!this.released) { @@ -259,6 +326,8 @@ /** * Activates this TabbingContext. + * + * @fires event:drupalTabbingContextActivated */ activate: function () { // A released TabbingContext object can never be activated again. @@ -272,6 +341,8 @@ /** * Deactivates this TabbingContext. + * + * @fires event:drupalTabbingContextDeactivated */ deactivate: function () { if (this.active) { @@ -288,6 +359,10 @@ if (Drupal.tabbingManager) { return; } + + /** + * @type {Drupal~TabbingManager} + */ Drupal.tabbingManager = new TabbingManager(); }(jQuery, Drupal)); diff --git a/core/misc/tabledrag.js b/core/misc/tabledrag.js index faea38bb6296e4e4e86e8b6605886ea22c6eaf50..7b68df376821582687897a5ecdb2625036496299 100644 --- a/core/misc/tabledrag.js +++ b/core/misc/tabledrag.js @@ -1,9 +1,21 @@ +/** + * @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')); @@ -12,12 +24,15 @@ * 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. + * 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. + * See blocks.js for an example of adding additional functionality to + * tableDrag. + * + * @type {Drupal~behavior} */ Drupal.behaviors.tableDrag = { attach: function (context, settings) { @@ -38,39 +53,126 @@ }; /** - * Constructor for the tableDrag object. Provides table and field manipulation. + * Provides table and field manipulation. + * + * @constructor * - * @param table + * @param {HTMLElement} table * DOM object for the table to be made draggable. - * @param tableSettings + * @param {object} tableSettings * Settings for the table added via drupal_add_dragtable(). */ Drupal.tableDrag = function (table, tableSettings) { var self = this; var $table = $(table); - // Required object variables. + /** + * @type {jQuery} + */ this.$table = $(table); + + /** + * + * @type {HTMLElement} + */ this.table = table; + + /** + * @type {object} + */ this.tableSettings = tableSettings; - this.dragObject = null; // Used to hold information about a current drag operation. - this.rowObject = null; // Provides operations for row manipulation. - this.oldRowElement = null; // Remember the previous element. - this.oldY = 0; // Used to determine up or down direction from last mouse move. - this.changed = false; // Whether anything in the entire table has changed. - this.maxDepth = 0; // Maximum amount of allowed parenting. - this.rtl = $(this.table).css('direction') === 'rtl' ? -1 : 1; // Direction of the table. + + /** + * 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. + /** + * 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 to see if there are parent relationships in - // this table. For efficiency, large sections of code can be skipped if we - // don't need to track horizontal movement and indentations. + /** + * Check this table's settings to see if there are parent relationships in + * this table. 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)) { @@ -87,7 +189,13 @@ } } if (this.indentEnabled) { - this.indentCount = 1; // Total width of indents, set in makeDraggable. + + /** + * 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 @@ -96,6 +204,11 @@ var testRow = $('<tr/>').addClass('draggable').appendTo(table); var testCell = $('<td/>').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(); } @@ -136,7 +249,8 @@ $(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. + // This was changed in another window, get the new value for this + // window. showWeight = JSON.parse(e.originalEvent.newValue); this.displayColumns(showWeight); } @@ -144,8 +258,7 @@ }; /** - * Initialize columns containing form elements to be hidden by default, - * according to the settings for this tableDrag instance. + * 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 @@ -157,7 +270,9 @@ var cell; var columnIndex; for (var group in this.tableSettings) { - if (this.tableSettings.hasOwnProperty(group)) { // Find the first field in this group. + 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); @@ -171,8 +286,9 @@ // 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. + // 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)); } @@ -182,8 +298,13 @@ }; /** - * Mark cells that have colspan so we can adjust the colspan - * instead of hiding them altogether. + * Mark cells that have colspan. + * + * In order to adjust the colspan instead of hiding them altogether. + * + * @param {number} columnIndex + * + * @return {function} */ Drupal.tableDrag.prototype.addColspanClass = function (columnIndex) { return function () { @@ -214,7 +335,9 @@ /** * Hide or display weight columns. Triggers an event on change. * - * @param bool displayWeight + * @fires event:columnschange + * + * @param {bool} displayWeight * 'true' will show weight columns. */ Drupal.tableDrag.prototype.displayColumns = function (displayWeight) { @@ -232,6 +355,7 @@ /** * Toggle the weight column depending on 'showWeight' value. + * * Store only default override. */ Drupal.tableDrag.prototype.toggleColumns = function () { @@ -249,6 +373,7 @@ /** * Hide the columns containing weight/parent form elements. + * * Undo showColumns(). */ Drupal.tableDrag.prototype.hideColumns = function () { @@ -266,7 +391,8 @@ }; /** - * Show the columns containing weight/parent form elements + * Show the columns containing weight/parent form elements. + * * Undo hideColumns(). */ Drupal.tableDrag.prototype.showColumns = function () { @@ -285,6 +411,11 @@ /** * Find the target used within a particular row and group. + * + * @param {string} group + * @param {HTMLElement} row + * + * @return {object} */ Drupal.tableDrag.prototype.rowSettings = function (group, row) { var field = $(row).find('.' + group); @@ -308,6 +439,8 @@ /** * Take an item and add event handlers to make it become draggable. + * + * @param {HTMLElement} item */ Drupal.tableDrag.prototype.makeDraggable = function (item) { var self = this; @@ -369,13 +502,18 @@ var keyChange = false; var groupHeight; switch (event.keyCode) { - case 37: // Left arrow. - case 63234: // Safari left arrow. + // Left arrow. + case 37: + // Safari left arrow. + case 63234: keyChange = true; self.rowObject.indent(-1 * self.rtl); break; - case 38: // Up arrow. - case 63232: // Safari up arrow. + + // 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')) { @@ -383,7 +521,8 @@ previousRow = $previousRow.get(0); } if (previousRow) { - self.safeBlur = false; // Do not allow the onBlur cleanup. + // Do not allow the onBlur cleanup. + self.safeBlur = false; self.rowObject.direction = 'up'; keyChange = true; @@ -402,23 +541,30 @@ } } 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). + // 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)); } - handle.trigger('focus'); // Regain focus after the DOM manipulation. + // Regain focus after the DOM manipulation. + handle.trigger('focus'); } break; - case 39: // Right arrow. - case 63235: // Safari right arrow. + + // Right arrow. + case 39: + // Safari right arrow. + case 63235: keyChange = true; self.rowObject.indent(self.rtl); break; - case 40: // Down arrow. - case 63233: // Safari down arrow. + + // 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')) { @@ -426,7 +572,8 @@ nextRow = $nextRow.get(0); } if (nextRow) { - self.safeBlur = false; // Do not allow the onBlur cleanup. + // Do not allow the onBlur cleanup. + self.safeBlur = false; self.rowObject.direction = 'down'; keyChange = true; @@ -451,7 +598,8 @@ self.rowObject.indent(0); window.scrollBy(0, parseInt(item.offsetHeight, 10)); } - handle.trigger('focus'); // Regain focus after the DOM manipulation. + // Regain focus after the DOM manipulation. + handle.trigger('focus'); } break; } @@ -474,15 +622,20 @@ } }); - // 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 + // 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) { switch (event.keyCode) { - case 37: // Left arrow. - case 38: // Up arrow. - case 39: // Right arrow. - case 40: // Down arrow. + // Left arrow. + case 37: + // Up arrow. + case 38: + // Right arrow. + case 39: + // Down arrow. + case 40: return false; } }); @@ -491,11 +644,11 @@ /** * Pointer event initiator, creates drag object and information. * - * @param jQuery.Event event + * @param {jQuery.Event} event * The event object that trigger the drag. - * @param Drupal.tableDrag self + * @param {Drupal.tableDrag} self * The drag handle. - * @param DOM item + * @param {HTMLElement} item * The item that that is being dragged. */ Drupal.tableDrag.prototype.dragStart = function (event, self, item) { @@ -531,6 +684,11 @@ /** * Pointer movement handler, bound to document. + * + * @param {jQuery.Event} event + * @param {Drupal.tableDrag} self + * + * @return {bool|undefined} */ Drupal.tableDrag.prototype.dragRow = function (event, self) { if (self.dragObject) { @@ -541,8 +699,8 @@ // Check for row swapping and vertical scrolling. if (y !== self.oldY) { self.rowObject.direction = y > self.oldY ? 'down' : 'up'; - self.oldY = y; // Update the old value. - + // 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. @@ -570,7 +728,8 @@ // 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. + // 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. @@ -586,6 +745,9 @@ /** * Pointerup behavior. + * + * @param {jQuery.Event} event + * @param {Drupal.tableDrag} self */ Drupal.tableDrag.prototype.dropRow = function (event, self) { var droppedRow; @@ -644,6 +806,10 @@ /** * Get the coordinates from the event (allowing for browser differences). + * + * @param {jQuery.Event} event + * + * @return {{x: number, y: number}} */ Drupal.tableDrag.prototype.pointerCoords = function (event) { if (event.pageX || event.pageY) { @@ -656,8 +822,15 @@ }; /** + * 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 + * @param {jQuery.Event} event + * + * @return {{x: number, y: number}} */ Drupal.tableDrag.prototype.getPointerOffset = function (target, event) { var docPos = $(target).offset(); @@ -666,13 +839,16 @@ }; /** - * Find the row the mouse is currently over. This row is then taken and swapped - * with the one being dragged. + * Find the row the mouse is currently over. + * + * This row is then taken and swapped with the one being dragged. * - * @param x + * @param {number} x * The x coordinate of the mouse on the page (not the screen). - * @param y + * @param {number} y * The y coordinate of the mouse on the page (not the screen). + * + * @return {*} */ Drupal.tableDrag.prototype.findDropTargetRow = function (x, y) { var rows = $(this.table.tBodies[0].rows).not(':hidden'); @@ -715,8 +891,8 @@ } // 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. + // 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); @@ -728,10 +904,9 @@ }; /** - * After the row is dropped, update the table fields according to the settings - * set for this table. + * After the row is dropped, update the table fields. * - * @param changedRow + * @param {HTMLElement} changedRow * DOM object for the row that was just dropped. */ Drupal.tableDrag.prototype.updateFields = function (changedRow) { @@ -745,12 +920,11 @@ }; /** - * After the row is dropped, update a single table field according to specific - * settings. + * After the row is dropped, update a single table field. * - * @param changedRow + * @param {HTMLElement} changedRow * DOM object for the row that was just dropped. - * @param group + * @param {string} group * The settings group on which field updates will occur. */ Drupal.tableDrag.prototype.updateField = function (changedRow, group) { @@ -843,10 +1017,12 @@ // 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')) { @@ -858,7 +1034,8 @@ 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 there are more items than possible values, assign the + // maximum value to the row. if (values.length > 0) { this.value = values.shift(); } @@ -881,9 +1058,15 @@ }; /** + * 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 + * @param {HTMLElement} targetRow + * @param {string} group */ Drupal.tableDrag.prototype.copyDragClasses = function (sourceRow, targetRow, group) { var sourceElement = $(sourceRow).find('.' + group); @@ -893,6 +1076,10 @@ } }; + /** + * @param {number} cursorY + * @return {number} + */ Drupal.tableDrag.prototype.checkScroll = function (cursorY) { var de = document.documentElement; var b = document.body; @@ -921,6 +1108,9 @@ } }; + /** + * @param {number} scrollAmount + */ Drupal.tableDrag.prototype.setScroll = function (scrollAmount) { var self = this; @@ -935,6 +1125,9 @@ }, 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. @@ -947,6 +1140,8 @@ /** * Stub function. Allows a custom handler when a row begins dragging. + * + * @return {?bool} */ Drupal.tableDrag.prototype.onDrag = function () { return null; @@ -954,6 +1149,8 @@ /** * Stub function. Allows a custom handler when a row is dropped. + * + * @return {?bool} */ Drupal.tableDrag.prototype.onDrop = function () { return null; @@ -962,16 +1159,18 @@ /** * Constructor to make a new object to manipulate a table row. * - * @param tableRow + * @param {HTMLElement} tableRow * The DOM element for the table row we will be manipulating. - * @param method - * The method in which this row is being moved. Either 'keyboard' or 'mouse'. - * @param indentEnabled + * @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 maxDepth + * @param {number} maxDepth * The maximum amount of indentations this row may contain. - * @param addClasses - * Whether we want to add classes to this row to indicate child relationships. + * @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); @@ -984,8 +1183,8 @@ this.table = $tableRow.closest('table')[0]; this.indentEnabled = indentEnabled; this.maxDepth = maxDepth; - this.direction = ''; // Direction the row is being moved. - + // Direction the row is being moved. + this.direction = ''; if (this.indentEnabled) { this.indents = $tableRow.find('.js-indentation').length; this.children = this.findChildren(addClasses); @@ -1000,8 +1199,11 @@ /** * Find all children of rowObject by indentation. * - * @param addClasses - * Whether we want to add classes to this row to indicate child relationships. + * @param {bool} addClasses + * Whether we want to add classes to this row to indicate child + * relationships. + * + * @return {Array} */ Drupal.tableDrag.prototype.row.prototype.findChildren = function (addClasses) { var parentIndentation = this.indents; @@ -1045,8 +1247,10 @@ /** * Ensure that two rows are allowed to be swapped. * - * @param row + * @param {HTMLElement} row * DOM object for the row being considered for swapping. + * + * @return {bool} */ Drupal.tableDrag.prototype.row.prototype.isValidSwap = function (row) { var $row = $(row); @@ -1080,9 +1284,9 @@ /** * Perform the swap between two rows. * - * @param position + * @param {string} position * Whether the swap will occur 'before' or 'after' the given row. - * @param row + * @param {HTMLElement} row * DOM element what will be swapped with the row group. */ Drupal.tableDrag.prototype.row.prototype.swap = function (position, row) { @@ -1100,15 +1304,16 @@ }; /** - * Determine the valid indentations interval for the row at a given position - * in the table. + * Determine the valid indentations interval for the row at a given position. * - * @param prevRow + * @param {?HTMLElement} prevRow * DOM object for the row before the tested position * (or null for first position in the table). - * @param nextRow + * @param {?HTMLElement} nextRow * DOM object for the row after the tested position * (or null for last position in the table). + * + * @return {{min: number, max: number}} */ Drupal.tableDrag.prototype.row.prototype.validIndentInterval = function (prevRow, nextRow) { var $prevRow = $(prevRow); @@ -1142,10 +1347,12 @@ /** * Indent a row within the legal bounds of the table. * - * @param indentDiff + * @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} */ Drupal.tableDrag.prototype.row.prototype.indent = function (indentDiff) { var $group = $(this.group); @@ -1184,11 +1391,15 @@ }; /** - * Find all siblings for a row, either according to its subgroup or indentation. - * Note that the passed-in row is included in the list of siblings. + * Find all siblings for a row. * - * @param settings + * 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} */ Drupal.tableDrag.prototype.row.prototype.findSiblings = function (rowSettings) { var siblings = []; @@ -1257,6 +1468,8 @@ /** * Stub function. Allows a custom handler when a row is indented. + * + * @return {?bool} */ Drupal.tableDrag.prototype.row.prototype.onIndent = function () { return null; @@ -1264,18 +1477,34 @@ /** * Stub function. Allows a custom handler when a row is swapped. + * + * @param {HTMLElement} swappedRow + * + * @return {?bool} */ Drupal.tableDrag.prototype.row.prototype.onSwap = function (swappedRow) { return null; }; - $.extend(Drupal.theme, { + $.extend(Drupal.theme, /** @lends Drupal.theme */{ + + /** + * @return {string} + */ tableDragChangedMarker: function () { return '<abbr class="warning tabledrag-changed" title="' + Drupal.t('Changed') + '">*</abbr>'; }, + + /** + * @return {string} + */ tableDragIndentation: function () { return '<div class="js-indentation indentation"> </div>'; }, + + /** + * @return {string} + */ tableDragChangedWarning: function () { return '<div class="tabledrag-changed-warning messages messages--warning" role="alert">' + Drupal.theme('tableDragChangedMarker') + ' ' + Drupal.t('You have unsaved changes.') + '</div>'; } diff --git a/core/misc/tableheader.js b/core/misc/tableheader.js index 2d6ce16e100353072c0e54fd9cf4d03f6dbf2a41..fbdb33c35f284241a104c7cda2f8c38973f18dad 100644 --- a/core/misc/tableheader.js +++ b/core/misc/tableheader.js @@ -1,9 +1,16 @@ +/** + * @file + * Sticky table headers. + */ + (function ($, Drupal, displace) { "use strict"; /** * Attaches sticky table headers. + * + * @type {Drupal~behavior} */ Drupal.behaviors.tableHeader = { attach: function (context) { @@ -48,26 +55,36 @@ // 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 + * Recalculate TableHeader.topOffset when viewport is resized. + * + * @ignore */ 'drupalViewportOffsetChange.TableHeader': tableHeaderOffsetChangeHandler }); @@ -78,19 +95,37 @@ * TableHeader will make the current table header stick to the top of the page * if the table is very long. * - * @param table + * @constructor Drupal.TableHeader + * + * @param {HTMLElement} table * DOM object for the table to add a sticky header to. * - * @constructor + * @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'); - this.displayWeight = null; + /** + * @type {null|bool} + */ + this.displayWeight = null; this.$originalTable.addClass('sticky-table'); this.tableHeight = $table[0].clientHeight; this.tableOffset = this.$originalTable.offset(); @@ -111,11 +146,12 @@ /** * Store the state of TableHeader. */ - $.extend(TableHeader, { + $.extend(TableHeader, /** @lends Drupal.TableHeader */{ + /** * This will store the state of all processed tables. * - * @type {Array} + * @type {Array.<Drupal.TableHeader>} */ tables: [] }); @@ -123,24 +159,33 @@ /** * Extend TableHeader prototype. */ - $.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, @@ -169,8 +214,10 @@ /** * Set absolute position of sticky. * - * @param offsetTop - * @param offsetLeft + * @param {number} offsetTop + * @param {number} offsetLeft + * + * @return {jQuery} */ stickyPosition: function (offsetTop, offsetLeft) { var css = {}; @@ -185,6 +232,8 @@ /** * Returns true if sticky is currently visible. + * + * @return {bool} */ checkStickyVisible: function () { var scrollTop = scrollValue('scrollTop'); @@ -203,9 +252,10 @@ /** * Check if sticky header should be displayed. * - * This function is throttled to once every 250ms to avoid unnecessary calls. + * This function is throttled to once every 250ms to avoid unnecessary + * calls. * - * @param event + * @param {jQuery.Event} e */ onScroll: function (e) { this.checkStickyVisible(); @@ -217,7 +267,7 @@ /** * Event handler: recalculates position of the sticky table header. * - * @param event + * @param {jQuery.Event} event * Event being triggered. */ recalculateSticky: function (event) { diff --git a/core/misc/tableresponsive.js b/core/misc/tableresponsive.js index b4eab625c827ea5c1a4820b7b7090305242b1b38..f2ebd1893791ac35a94a5b22136a1f58069330d8 100644 --- a/core/misc/tableresponsive.js +++ b/core/misc/tableresponsive.js @@ -1,9 +1,16 @@ +/** + * @file + * Responsive table functionality. + */ + (function ($, Drupal, window) { "use strict"; /** - * Attach the tableResponsive function to Drupal.behaviors. + * Attach the tableResponsive function to {@link Drupal.behaviors}. + * + * @type {Drupal~behavior} */ Drupal.behaviors.tableResponsive = { attach: function (context, settings) { @@ -18,14 +25,18 @@ }; /** - * The TableResponsive object optimizes table presentation for all screen sizes. + * 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. + * 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 */ function TableResponsive(table) { this.table = table; @@ -51,7 +62,13 @@ /** * Extend the TableResponsive function with a list of managed tables. */ - $.extend(TableResponsive, { + $.extend(TableResponsive, /** @lends Drupal.TableResponsive */{ + + /** + * Store all created instances. + * + * @type {Array.<Drupal.TableResponsive>} + */ tables: [] }); @@ -61,24 +78,34 @@ * Columns are assumed to be hidden if their header has the class priority-low * or priority-medium. */ - $.extend(TableResponsive.prototype, { + $.extend(TableResponsive.prototype, /** @lends Drupal.TableResponsive# */{ + + /** + * @param {jQuery.Event} e + */ 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 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. + // 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 classed with either 'priority-low' or - // 'priority-medium'. + + /** + * Toggle the visibility of columns based on their priority. + * + * Columns are classed with either 'priority-low' or 'priority-medium'. + * + * @param {jQuery.Event} e + */ eventhandlerToggleColumns: function (e) { e.preventDefault(); var self = this; @@ -110,10 +137,10 @@ 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. + // 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]; @@ -134,6 +161,7 @@ } } }); + // Make the TableResponsive object available in the Drupal namespace. Drupal.TableResponsive = TableResponsive; diff --git a/core/misc/tableselect.js b/core/misc/tableselect.js index 78e8c90fd1046d9f3bbf229b7348477c580cd15d..fc8e9f2dbb999d07ce32182725d7bd6dce1c2d51 100644 --- a/core/misc/tableselect.js +++ b/core/misc/tableselect.js @@ -1,7 +1,17 @@ +/** + * @file + * Table select functionality. + */ + (function ($, Drupal) { "use strict"; + /** + * Initialize tableSelects. + * + * @type {Drupal~behavior} + */ Drupal.behaviors.tableSelect = { attach: function (context, settings) { // Select the inner-most table in case of nested tables. @@ -9,13 +19,18 @@ } }; + /** + * 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 + // 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. + // Keep track of the table, which checkbox is checked and alias the + // settings. var table = this; var checkboxes; var lastChecked; @@ -25,6 +40,10 @@ // 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 () { $(this).attr('title', state ? strings.selectNone : strings.selectAll); + + /** + * @this {HTMLElement} + */ this.checked = state; }); }; @@ -32,10 +51,20 @@ // Find all <th> with class select-all, and insert the check all checkbox. $table.find('th.select-all').prepend($('<input type="checkbox" class="form-checkbox" />').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. + // Loop through all checkboxes and set their state to the select all + // checkbox' state. checkboxes.each(function () { + + /** + * @this {HTMLElement} + */ this.checked = event.target.checked; - // Either add or remove the selected class based on the state of the check all checkbox. + // 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); }); // Update the title and the state of the check all box. @@ -45,18 +74,24 @@ // 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. + // 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 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 TR 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. + // 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. @@ -68,6 +103,11 @@ updateSelectAll((checkboxes.length === checkboxes.filter(':checked').length)); }; + /** + * @param {HTMLElement} from + * @param {HTMLElement} to + * @param {bool} state + */ 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'; @@ -80,7 +120,8 @@ continue; } $i = $(i); - // Either add or remove the selected class based on the state of the target checkbox. + // 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); diff --git a/core/misc/timezone.js b/core/misc/timezone.js index e7667b878bf6380ed314a78ee40d7ef86cec37fc..801d72839d1db5876adcaeaca7ec4f87db917b6b 100644 --- a/core/misc/timezone.js +++ b/core/misc/timezone.js @@ -1,9 +1,16 @@ +/** + * @file + * Timezone detection. + */ + (function ($) { "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) { @@ -45,10 +52,11 @@ 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. + // 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, diff --git a/core/misc/vertical-tabs.js b/core/misc/vertical-tabs.js index e7f64dc0356b8eae98c7f491baea794535b0d3c3..a381bdcf12b5e0d85d4bcb9002e9b049c504285e 100644 --- a/core/misc/vertical-tabs.js +++ b/core/misc/vertical-tabs.js @@ -1,17 +1,31 @@ +/** + * @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 ($) { "use strict"; /** - * This script transforms a set of details into a stack of vertical - * tabs. Another tab pane can be selected by clicking on the respective - * tab. + * 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} */ Drupal.behaviors.verticalTabs = { attach: function (context) { @@ -25,7 +39,8 @@ 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 + // Check if there are some details that can be converted to + // vertical-tabs. var $details = $this.find('> details'); if ($details.length === 0) { return; @@ -79,10 +94,17 @@ /** * The vertical tab object represents a single tab within a tab group. * - * @param settings - * An object with the following keys: - * - title: The name of the tab. - * - details: The jQuery object of the details element that is the tab pane. + * @constructor + * + * @param {object} settings + * @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; @@ -114,6 +136,7 @@ }; Drupal.verticalTab.prototype = { + /** * Displays the tab's content pane. */ @@ -144,15 +167,17 @@ /** * Shows a vertical tab pane. + * + * @return {Drupal.verticalTab} */ 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. + // 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. @@ -164,13 +189,15 @@ /** * Hides a vertical tab pane. + * + * @return {Drupal.verticalTab} */ 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. + // 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. @@ -191,10 +218,12 @@ /** * Theme function for a vertical tab. * - * @param settings + * @param {object} settings * An object with the following keys: - * - title: The name of the tab. - * @return + * @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