diff options
Diffstat (limited to 'core/modules/contextual/js')
20 files changed, 1187 insertions, 584 deletions
diff --git a/core/modules/contextual/js/contextual.es6.js b/core/modules/contextual/js/contextual.es6.js new file mode 100644 index 000000000000..558ea105cc2f --- /dev/null +++ b/core/modules/contextual/js/contextual.es6.js @@ -0,0 +1,256 @@ +/** + * @file + * Attaches behaviors for the Contextual module. + */ + +(function ($, Drupal, drupalSettings, _, Backbone, JSON, storage) { + + 'use strict'; + + var options = $.extend(drupalSettings.contextual, + // Merge strings on top of drupalSettings so that they are not mutable. + { + strings: { + open: Drupal.t('Open'), + close: Drupal.t('Close') + } + } + ); + + // Clear the cached contextual links whenever the current user's set of + // permissions changes. + var cachedPermissionsHash = storage.getItem('Drupal.contextual.permissionsHash'); + var permissionsHash = drupalSettings.user.permissionsHash; + if (cachedPermissionsHash !== permissionsHash) { + if (typeof permissionsHash === 'string') { + _.chain(storage).keys().each(function (key) { + if (key.substring(0, 18) === 'Drupal.contextual.') { + storage.removeItem(key); + } + }); + } + storage.setItem('Drupal.contextual.permissionsHash', permissionsHash); + } + + /** + * Initializes a contextual link: updates its DOM, sets up model and views. + * + * @param {jQuery} $contextual + * A contextual links placeholder DOM element, containing the actual + * contextual links as rendered by the server. + * @param {string} html + * The server-side rendered HTML for this contextual link. + */ + function initContextual($contextual, html) { + var $region = $contextual.closest('.contextual-region'); + var contextual = Drupal.contextual; + + $contextual + // Update the placeholder to contain its rendered contextual links. + .html(html) + // Use the placeholder as a wrapper with a specific class to provide + // positioning and behavior attachment context. + .addClass('contextual') + // Ensure a trigger element exists before the actual contextual links. + .prepend(Drupal.theme('contextualTrigger')); + + // Set the destination parameter on each of the contextual links. + var destination = 'destination=' + Drupal.encodePath(drupalSettings.path.currentPath); + $contextual.find('.contextual-links a').each(function () { + var url = this.getAttribute('href'); + var glue = (url.indexOf('?') === -1) ? '?' : '&'; + this.setAttribute('href', url + glue + destination); + }); + + // Create a model and the appropriate views. + var model = new contextual.StateModel({ + title: $region.find('h2').eq(0).text().trim() + }); + var viewOptions = $.extend({el: $contextual, model: model}, options); + contextual.views.push({ + visual: new contextual.VisualView(viewOptions), + aural: new contextual.AuralView(viewOptions), + keyboard: new contextual.KeyboardView(viewOptions) + }); + contextual.regionViews.push(new contextual.RegionView( + $.extend({el: $region, model: model}, options)) + ); + + // Add the model to the collection. This must happen after the views have + // been associated with it, otherwise collection change event handlers can't + // trigger the model change event handler in its views. + contextual.collection.add(model); + + // Let other JavaScript react to the adding of a new contextual link. + $(document).trigger('drupalContextualLinkAdded', { + $el: $contextual, + $region: $region, + model: model + }); + + // Fix visual collisions between contextual link triggers. + adjustIfNestedAndOverlapping($contextual); + } + + /** + * Determines if a contextual link is nested & overlapping, if so: adjusts it. + * + * This only deals with two levels of nesting; deeper levels are not touched. + * + * @param {jQuery} $contextual + * A contextual links placeholder DOM element, containing the actual + * contextual links as rendered by the server. + */ + function adjustIfNestedAndOverlapping($contextual) { + var $contextuals = $contextual + // @todo confirm that .closest() is not sufficient + .parents('.contextual-region').eq(-1) + .find('.contextual'); + + // Early-return when there's no nesting. + if ($contextuals.length === 1) { + return; + } + + // If the two contextual links overlap, then we move the second one. + var firstTop = $contextuals.eq(0).offset().top; + var secondTop = $contextuals.eq(1).offset().top; + if (firstTop === secondTop) { + var $nestedContextual = $contextuals.eq(1); + + // Retrieve height of nested contextual link. + var height = 0; + var $trigger = $nestedContextual.find('.trigger'); + // Elements with the .visually-hidden class have no dimensions, so this + // class must be temporarily removed to the calculate the height. + $trigger.removeClass('visually-hidden'); + height = $nestedContextual.height(); + $trigger.addClass('visually-hidden'); + + // Adjust nested contextual link's position. + $nestedContextual.css({top: $nestedContextual.position().top + height}); + } + } + + /** + * Attaches outline behavior for regions associated with contextual links. + * + * Events + * Contextual triggers an event that can be used by other scripts. + * - drupalContextualLinkAdded: Triggered when a contextual link is added. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the outline behavior to the right context. + */ + Drupal.behaviors.contextual = { + attach: function (context) { + var $context = $(context); + + // Find all contextual links placeholders, if any. + var $placeholders = $context.find('[data-contextual-id]').once('contextual-render'); + if ($placeholders.length === 0) { + return; + } + + // Collect the IDs for all contextual links placeholders. + var ids = []; + $placeholders.each(function () { + ids.push($(this).attr('data-contextual-id')); + }); + + // Update all contextual links placeholders whose HTML is cached. + var uncachedIDs = _.filter(ids, function initIfCached(contextualID) { + var html = storage.getItem('Drupal.contextual.' + contextualID); + if (html && html.length) { + // Initialize after the current execution cycle, to make the AJAX + // request for retrieving the uncached contextual links as soon as + // possible, but also to ensure that other Drupal behaviors have had + // the chance to set up an event listener on the Backbone collection + // Drupal.contextual.collection. + window.setTimeout(function () { + initContextual($context.find('[data-contextual-id="' + contextualID + '"]'), html); + }); + return false; + } + return true; + }); + + // Perform an AJAX request to let the server render the contextual links + // for each of the placeholders. + if (uncachedIDs.length > 0) { + $.ajax({ + url: Drupal.url('contextual/render'), + type: 'POST', + data: {'ids[]': uncachedIDs}, + dataType: 'json', + success: function (results) { + _.each(results, function (html, contextualID) { + // Store the metadata. + storage.setItem('Drupal.contextual.' + contextualID, html); + // If the rendered contextual links are empty, then the current + // user does not have permission to access the associated links: + // don't render anything. + if (html.length > 0) { + // Update the placeholders to contain its rendered contextual + // links. Usually there will only be one placeholder, but it's + // possible for multiple identical placeholders exist on the + // page (probably because the same content appears more than + // once). + $placeholders = $context.find('[data-contextual-id="' + contextualID + '"]'); + + // Initialize the contextual links. + for (var i = 0; i < $placeholders.length; i++) { + initContextual($placeholders.eq(i), html); + } + } + }); + } + }); + } + } + }; + + /** + * Namespace for contextual related functionality. + * + * @namespace + */ + Drupal.contextual = { + + /** + * The {@link Drupal.contextual.View} instances associated with each list + * element of contextual links. + * + * @type {Array} + */ + views: [], + + /** + * The {@link Drupal.contextual.RegionView} instances associated with each + * contextual region element. + * + * @type {Array} + */ + regionViews: [] + }; + + /** + * A Backbone.Collection of {@link Drupal.contextual.StateModel} instances. + * + * @type {Backbone.Collection} + */ + Drupal.contextual.collection = new Backbone.Collection([], {model: Drupal.contextual.StateModel}); + + /** + * A trigger is an interactive element often bound to a click handler. + * + * @return {string} + * A string representing a DOM fragment. + */ + Drupal.theme.contextualTrigger = function () { + return '<button class="trigger visually-hidden focusable" type="button"></button>'; + }; + +})(jQuery, Drupal, drupalSettings, _, Backbone, window.JSON, window.sessionStorage); diff --git a/core/modules/contextual/js/contextual.js b/core/modules/contextual/js/contextual.js index 558ea105cc2f..7eb42ec3de0d 100644 --- a/core/modules/contextual/js/contextual.js +++ b/core/modules/contextual/js/contextual.js @@ -1,24 +1,22 @@ /** - * @file - * Attaches behaviors for the Contextual module. - */ +* DO NOT EDIT THIS FILE. +* All changes should be applied to ./modules/contextual/js/contextual.es6.js +* See the following change record for more information, +* https://www.drupal.org/node/2873849 +* @preserve +**/ (function ($, Drupal, drupalSettings, _, Backbone, JSON, storage) { 'use strict'; - var options = $.extend(drupalSettings.contextual, - // Merge strings on top of drupalSettings so that they are not mutable. - { - strings: { - open: Drupal.t('Open'), - close: Drupal.t('Close') - } + var options = $.extend(drupalSettings.contextual, { + strings: { + open: Drupal.t('Open'), + close: Drupal.t('Close') } - ); + }); - // Clear the cached contextual links whenever the current user's set of - // permissions changes. var cachedPermissionsHash = storage.getItem('Drupal.contextual.permissionsHash'); var permissionsHash = drupalSettings.user.permissionsHash; if (cachedPermissionsHash !== permissionsHash) { @@ -32,143 +30,81 @@ storage.setItem('Drupal.contextual.permissionsHash', permissionsHash); } - /** - * Initializes a contextual link: updates its DOM, sets up model and views. - * - * @param {jQuery} $contextual - * A contextual links placeholder DOM element, containing the actual - * contextual links as rendered by the server. - * @param {string} html - * The server-side rendered HTML for this contextual link. - */ function initContextual($contextual, html) { var $region = $contextual.closest('.contextual-region'); var contextual = Drupal.contextual; - $contextual - // Update the placeholder to contain its rendered contextual links. - .html(html) - // Use the placeholder as a wrapper with a specific class to provide - // positioning and behavior attachment context. - .addClass('contextual') - // Ensure a trigger element exists before the actual contextual links. - .prepend(Drupal.theme('contextualTrigger')); + $contextual.html(html).addClass('contextual').prepend(Drupal.theme('contextualTrigger')); - // Set the destination parameter on each of the contextual links. var destination = 'destination=' + Drupal.encodePath(drupalSettings.path.currentPath); $contextual.find('.contextual-links a').each(function () { var url = this.getAttribute('href'); - var glue = (url.indexOf('?') === -1) ? '?' : '&'; + var glue = url.indexOf('?') === -1 ? '?' : '&'; this.setAttribute('href', url + glue + destination); }); - // Create a model and the appropriate views. var model = new contextual.StateModel({ title: $region.find('h2').eq(0).text().trim() }); - var viewOptions = $.extend({el: $contextual, model: model}, options); + var viewOptions = $.extend({ el: $contextual, model: model }, options); contextual.views.push({ visual: new contextual.VisualView(viewOptions), aural: new contextual.AuralView(viewOptions), keyboard: new contextual.KeyboardView(viewOptions) }); - contextual.regionViews.push(new contextual.RegionView( - $.extend({el: $region, model: model}, options)) - ); + contextual.regionViews.push(new contextual.RegionView($.extend({ el: $region, model: model }, options))); - // Add the model to the collection. This must happen after the views have - // been associated with it, otherwise collection change event handlers can't - // trigger the model change event handler in its views. contextual.collection.add(model); - // Let other JavaScript react to the adding of a new contextual link. $(document).trigger('drupalContextualLinkAdded', { $el: $contextual, $region: $region, model: model }); - // Fix visual collisions between contextual link triggers. adjustIfNestedAndOverlapping($contextual); } - /** - * Determines if a contextual link is nested & overlapping, if so: adjusts it. - * - * This only deals with two levels of nesting; deeper levels are not touched. - * - * @param {jQuery} $contextual - * A contextual links placeholder DOM element, containing the actual - * contextual links as rendered by the server. - */ function adjustIfNestedAndOverlapping($contextual) { - var $contextuals = $contextual - // @todo confirm that .closest() is not sufficient - .parents('.contextual-region').eq(-1) - .find('.contextual'); + var $contextuals = $contextual.parents('.contextual-region').eq(-1).find('.contextual'); - // Early-return when there's no nesting. if ($contextuals.length === 1) { return; } - // If the two contextual links overlap, then we move the second one. var firstTop = $contextuals.eq(0).offset().top; var secondTop = $contextuals.eq(1).offset().top; if (firstTop === secondTop) { var $nestedContextual = $contextuals.eq(1); - // Retrieve height of nested contextual link. var height = 0; var $trigger = $nestedContextual.find('.trigger'); - // Elements with the .visually-hidden class have no dimensions, so this - // class must be temporarily removed to the calculate the height. + $trigger.removeClass('visually-hidden'); height = $nestedContextual.height(); $trigger.addClass('visually-hidden'); - // Adjust nested contextual link's position. - $nestedContextual.css({top: $nestedContextual.position().top + height}); + $nestedContextual.css({ top: $nestedContextual.position().top + height }); } } - /** - * Attaches outline behavior for regions associated with contextual links. - * - * Events - * Contextual triggers an event that can be used by other scripts. - * - drupalContextualLinkAdded: Triggered when a contextual link is added. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches the outline behavior to the right context. - */ Drupal.behaviors.contextual = { - attach: function (context) { + attach: function attach(context) { var $context = $(context); - // Find all contextual links placeholders, if any. var $placeholders = $context.find('[data-contextual-id]').once('contextual-render'); if ($placeholders.length === 0) { return; } - // Collect the IDs for all contextual links placeholders. var ids = []; $placeholders.each(function () { ids.push($(this).attr('data-contextual-id')); }); - // Update all contextual links placeholders whose HTML is cached. var uncachedIDs = _.filter(ids, function initIfCached(contextualID) { var html = storage.getItem('Drupal.contextual.' + contextualID); if (html && html.length) { - // Initialize after the current execution cycle, to make the AJAX - // request for retrieving the uncached contextual links as soon as - // possible, but also to ensure that other Drupal behaviors have had - // the chance to set up an event listener on the Backbone collection - // Drupal.contextual.collection. window.setTimeout(function () { initContextual($context.find('[data-contextual-id="' + contextualID + '"]'), html); }); @@ -177,30 +113,19 @@ return true; }); - // Perform an AJAX request to let the server render the contextual links - // for each of the placeholders. if (uncachedIDs.length > 0) { $.ajax({ url: Drupal.url('contextual/render'), type: 'POST', - data: {'ids[]': uncachedIDs}, + data: { 'ids[]': uncachedIDs }, dataType: 'json', - success: function (results) { + success: function success(results) { _.each(results, function (html, contextualID) { - // Store the metadata. storage.setItem('Drupal.contextual.' + contextualID, html); - // If the rendered contextual links are empty, then the current - // user does not have permission to access the associated links: - // don't render anything. + if (html.length > 0) { - // Update the placeholders to contain its rendered contextual - // links. Usually there will only be one placeholder, but it's - // possible for multiple identical placeholders exist on the - // page (probably because the same content appears more than - // once). $placeholders = $context.find('[data-contextual-id="' + contextualID + '"]'); - // Initialize the contextual links. for (var i = 0; i < $placeholders.length; i++) { initContextual($placeholders.eq(i), html); } @@ -212,45 +137,15 @@ } }; - /** - * Namespace for contextual related functionality. - * - * @namespace - */ Drupal.contextual = { - - /** - * The {@link Drupal.contextual.View} instances associated with each list - * element of contextual links. - * - * @type {Array} - */ views: [], - /** - * The {@link Drupal.contextual.RegionView} instances associated with each - * contextual region element. - * - * @type {Array} - */ regionViews: [] }; - /** - * A Backbone.Collection of {@link Drupal.contextual.StateModel} instances. - * - * @type {Backbone.Collection} - */ - Drupal.contextual.collection = new Backbone.Collection([], {model: Drupal.contextual.StateModel}); + Drupal.contextual.collection = new Backbone.Collection([], { model: Drupal.contextual.StateModel }); - /** - * A trigger is an interactive element often bound to a click handler. - * - * @return {string} - * A string representing a DOM fragment. - */ Drupal.theme.contextualTrigger = function () { return '<button class="trigger visually-hidden focusable" type="button"></button>'; }; - -})(jQuery, Drupal, drupalSettings, _, Backbone, window.JSON, window.sessionStorage); +})(jQuery, Drupal, drupalSettings, _, Backbone, window.JSON, window.sessionStorage);
\ No newline at end of file diff --git a/core/modules/contextual/js/contextual.toolbar.es6.js b/core/modules/contextual/js/contextual.toolbar.es6.js new file mode 100644 index 000000000000..b5a9053490c0 --- /dev/null +++ b/core/modules/contextual/js/contextual.toolbar.es6.js @@ -0,0 +1,77 @@ +/** + * @file + * Attaches behaviors for the Contextual module's edit toolbar tab. + */ + +(function ($, Drupal, Backbone) { + + 'use strict'; + + var strings = { + tabbingReleased: Drupal.t('Tabbing is no longer constrained by the Contextual module.'), + tabbingConstrained: Drupal.t('Tabbing is constrained to a set of @contextualsCount and the edit mode toggle.'), + pressEsc: Drupal.t('Press the esc key to exit.') + }; + + /** + * Initializes a contextual link: updates its DOM, sets up model and views. + * + * @param {HTMLElement} context + * A contextual links DOM element as rendered by the server. + */ + function initContextualToolbar(context) { + if (!Drupal.contextual || !Drupal.contextual.collection) { + return; + } + + var contextualToolbar = Drupal.contextualToolbar; + var model = contextualToolbar.model = new contextualToolbar.StateModel({ + // Checks whether localStorage indicates we should start in edit mode + // rather than view mode. + // @see Drupal.contextualToolbar.VisualView.persist + isViewing: localStorage.getItem('Drupal.contextualToolbar.isViewing') !== 'false' + }, { + contextualCollection: Drupal.contextual.collection + }); + + var viewOptions = { + el: $('.toolbar .toolbar-bar .contextual-toolbar-tab'), + model: model, + strings: strings + }; + new contextualToolbar.VisualView(viewOptions); + new contextualToolbar.AuralView(viewOptions); + } + + /** + * Attaches contextual's edit toolbar tab behavior. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches contextual toolbar behavior on a contextualToolbar-init event. + */ + Drupal.behaviors.contextualToolbar = { + attach: function (context) { + if ($('body').once('contextualToolbar-init').length) { + initContextualToolbar(context); + } + } + }; + + /** + * Namespace for the contextual toolbar. + * + * @namespace + */ + Drupal.contextualToolbar = { + + /** + * The {@link Drupal.contextualToolbar.StateModel} instance. + * + * @type {?Drupal.contextualToolbar.StateModel} + */ + model: null + }; + +})(jQuery, Drupal, Backbone); diff --git a/core/modules/contextual/js/contextual.toolbar.js b/core/modules/contextual/js/contextual.toolbar.js index b5a9053490c0..291a5a68c300 100644 --- a/core/modules/contextual/js/contextual.toolbar.js +++ b/core/modules/contextual/js/contextual.toolbar.js @@ -1,7 +1,10 @@ /** - * @file - * Attaches behaviors for the Contextual module's edit toolbar tab. - */ +* DO NOT EDIT THIS FILE. +* All changes should be applied to ./modules/contextual/js/contextual.toolbar.es6.js +* See the following change record for more information, +* https://www.drupal.org/node/2873849 +* @preserve +**/ (function ($, Drupal, Backbone) { @@ -13,12 +16,6 @@ pressEsc: Drupal.t('Press the esc key to exit.') }; - /** - * Initializes a contextual link: updates its DOM, sets up model and views. - * - * @param {HTMLElement} context - * A contextual links DOM element as rendered by the server. - */ function initContextualToolbar(context) { if (!Drupal.contextual || !Drupal.contextual.collection) { return; @@ -26,9 +23,6 @@ var contextualToolbar = Drupal.contextualToolbar; var model = contextualToolbar.model = new contextualToolbar.StateModel({ - // Checks whether localStorage indicates we should start in edit mode - // rather than view mode. - // @see Drupal.contextualToolbar.VisualView.persist isViewing: localStorage.getItem('Drupal.contextualToolbar.isViewing') !== 'false' }, { contextualCollection: Drupal.contextual.collection @@ -43,35 +37,15 @@ new contextualToolbar.AuralView(viewOptions); } - /** - * Attaches contextual's edit toolbar tab behavior. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches contextual toolbar behavior on a contextualToolbar-init event. - */ Drupal.behaviors.contextualToolbar = { - attach: function (context) { + attach: function attach(context) { if ($('body').once('contextualToolbar-init').length) { initContextualToolbar(context); } } }; - /** - * Namespace for the contextual toolbar. - * - * @namespace - */ Drupal.contextualToolbar = { - - /** - * The {@link Drupal.contextualToolbar.StateModel} instance. - * - * @type {?Drupal.contextualToolbar.StateModel} - */ model: null }; - -})(jQuery, Drupal, Backbone); +})(jQuery, Drupal, Backbone);
\ No newline at end of file diff --git a/core/modules/contextual/js/models/StateModel.es6.js b/core/modules/contextual/js/models/StateModel.es6.js new file mode 100644 index 000000000000..465d717d5636 --- /dev/null +++ b/core/modules/contextual/js/models/StateModel.es6.js @@ -0,0 +1,132 @@ +/** + * @file + * A Backbone Model for the state of a contextual link's trigger, list & region. + */ + +(function (Drupal, Backbone) { + + 'use strict'; + + /** + * Models the state of a contextual link's trigger, list & region. + * + * @constructor + * + * @augments Backbone.Model + */ + Drupal.contextual.StateModel = Backbone.Model.extend(/** @lends Drupal.contextual.StateModel# */{ + + /** + * @type {object} + * + * @prop {string} title + * @prop {bool} regionIsHovered + * @prop {bool} hasFocus + * @prop {bool} isOpen + * @prop {bool} isLocked + */ + defaults: /** @lends Drupal.contextual.StateModel# */{ + + /** + * The title of the entity to which these contextual links apply. + * + * @type {string} + */ + title: '', + + /** + * Represents if the contextual region is being hovered. + * + * @type {bool} + */ + regionIsHovered: false, + + /** + * Represents if the contextual trigger or options have focus. + * + * @type {bool} + */ + hasFocus: false, + + /** + * Represents if the contextual options for an entity are available to + * be selected (i.e. whether the list of options is visible). + * + * @type {bool} + */ + isOpen: false, + + /** + * When the model is locked, the trigger remains active. + * + * @type {bool} + */ + isLocked: false + }, + + /** + * Opens or closes the contextual link. + * + * If it is opened, then also give focus. + * + * @return {Drupal.contextual.StateModel} + * The current contextual state model. + */ + toggleOpen: function () { + var newIsOpen = !this.get('isOpen'); + this.set('isOpen', newIsOpen); + if (newIsOpen) { + this.focus(); + } + return this; + }, + + /** + * Closes this contextual link. + * + * Does not call blur() because we want to allow a contextual link to have + * focus, yet be closed for example when hovering. + * + * @return {Drupal.contextual.StateModel} + * The current contextual state model. + */ + close: function () { + this.set('isOpen', false); + return this; + }, + + /** + * Gives focus to this contextual link. + * + * Also closes + removes focus from every other contextual link. + * + * @return {Drupal.contextual.StateModel} + * The current contextual state model. + */ + focus: function () { + this.set('hasFocus', true); + var cid = this.cid; + this.collection.each(function (model) { + if (model.cid !== cid) { + model.close().blur(); + } + }); + return this; + }, + + /** + * Removes focus from this contextual link, unless it is open. + * + * @return {Drupal.contextual.StateModel} + * The current contextual state model. + */ + blur: function () { + if (!this.get('isOpen')) { + this.set('hasFocus', false); + } + return this; + } + + }); + +})(Drupal, Backbone); diff --git a/core/modules/contextual/js/models/StateModel.js b/core/modules/contextual/js/models/StateModel.js index 465d717d5636..ac9a89582486 100644 --- a/core/modules/contextual/js/models/StateModel.js +++ b/core/modules/contextual/js/models/StateModel.js @@ -1,78 +1,29 @@ /** - * @file - * A Backbone Model for the state of a contextual link's trigger, list & region. - */ +* DO NOT EDIT THIS FILE. +* All changes should be applied to ./modules/contextual/js/models/StateModel.es6.js +* See the following change record for more information, +* https://www.drupal.org/node/2873849 +* @preserve +**/ (function (Drupal, Backbone) { 'use strict'; - /** - * Models the state of a contextual link's trigger, list & region. - * - * @constructor - * - * @augments Backbone.Model - */ - Drupal.contextual.StateModel = Backbone.Model.extend(/** @lends Drupal.contextual.StateModel# */{ - - /** - * @type {object} - * - * @prop {string} title - * @prop {bool} regionIsHovered - * @prop {bool} hasFocus - * @prop {bool} isOpen - * @prop {bool} isLocked - */ - defaults: /** @lends Drupal.contextual.StateModel# */{ - - /** - * The title of the entity to which these contextual links apply. - * - * @type {string} - */ + Drupal.contextual.StateModel = Backbone.Model.extend({ + defaults: { title: '', - /** - * Represents if the contextual region is being hovered. - * - * @type {bool} - */ regionIsHovered: false, - /** - * Represents if the contextual trigger or options have focus. - * - * @type {bool} - */ hasFocus: false, - /** - * Represents if the contextual options for an entity are available to - * be selected (i.e. whether the list of options is visible). - * - * @type {bool} - */ isOpen: false, - /** - * When the model is locked, the trigger remains active. - * - * @type {bool} - */ isLocked: false }, - /** - * Opens or closes the contextual link. - * - * If it is opened, then also give focus. - * - * @return {Drupal.contextual.StateModel} - * The current contextual state model. - */ - toggleOpen: function () { + toggleOpen: function toggleOpen() { var newIsOpen = !this.get('isOpen'); this.set('isOpen', newIsOpen); if (newIsOpen) { @@ -81,29 +32,12 @@ return this; }, - /** - * Closes this contextual link. - * - * Does not call blur() because we want to allow a contextual link to have - * focus, yet be closed for example when hovering. - * - * @return {Drupal.contextual.StateModel} - * The current contextual state model. - */ - close: function () { + close: function close() { this.set('isOpen', false); return this; }, - /** - * Gives focus to this contextual link. - * - * Also closes + removes focus from every other contextual link. - * - * @return {Drupal.contextual.StateModel} - * The current contextual state model. - */ - focus: function () { + focus: function focus() { this.set('hasFocus', true); var cid = this.cid; this.collection.each(function (model) { @@ -114,13 +48,7 @@ return this; }, - /** - * Removes focus from this contextual link, unless it is open. - * - * @return {Drupal.contextual.StateModel} - * The current contextual state model. - */ - blur: function () { + blur: function blur() { if (!this.get('isOpen')) { this.set('hasFocus', false); } @@ -128,5 +56,4 @@ } }); - -})(Drupal, Backbone); +})(Drupal, Backbone);
\ No newline at end of file diff --git a/core/modules/contextual/js/toolbar/models/StateModel.es6.js b/core/modules/contextual/js/toolbar/models/StateModel.es6.js new file mode 100644 index 000000000000..d9159e4e97f6 --- /dev/null +++ b/core/modules/contextual/js/toolbar/models/StateModel.es6.js @@ -0,0 +1,119 @@ +/** + * @file + * A Backbone Model for the state of Contextual module's edit toolbar tab. + */ + +(function (Drupal, Backbone) { + + 'use strict'; + + Drupal.contextualToolbar.StateModel = Backbone.Model.extend(/** @lends Drupal.contextualToolbar.StateModel# */{ + + /** + * @type {object} + * + * @prop {bool} isViewing + * @prop {bool} isVisible + * @prop {number} contextualCount + * @prop {Drupal~TabbingContext} tabbingContext + */ + defaults: /** @lends Drupal.contextualToolbar.StateModel# */{ + + /** + * Indicates whether the toggle is currently in "view" or "edit" mode. + * + * @type {bool} + */ + isViewing: true, + + /** + * Indicates whether the toggle should be visible or hidden. Automatically + * calculated, depends on contextualCount. + * + * @type {bool} + */ + isVisible: false, + + /** + * Tracks how many contextual links exist on the page. + * + * @type {number} + */ + contextualCount: 0, + + /** + * A TabbingContext object as returned by {@link Drupal~TabbingManager}: + * the set of tabbable elements when edit mode is enabled. + * + * @type {?Drupal~TabbingContext} + */ + tabbingContext: null + }, + + /** + * Models the state of the edit mode toggle. + * + * @constructs + * + * @augments Backbone.Model + * + * @param {object} attrs + * Attributes for the backbone model. + * @param {object} options + * An object with the following option: + * @param {Backbone.collection} options.contextualCollection + * The collection of {@link Drupal.contextual.StateModel} models that + * represent the contextual links on the page. + */ + initialize: function (attrs, options) { + // Respond to new/removed contextual links. + this.listenTo(options.contextualCollection, 'reset remove add', this.countContextualLinks); + this.listenTo(options.contextualCollection, 'add', this.lockNewContextualLinks); + + // Automatically determine visibility. + this.listenTo(this, 'change:contextualCount', this.updateVisibility); + + // Whenever edit mode is toggled, lock all contextual links. + this.listenTo(this, 'change:isViewing', function (model, isViewing) { + options.contextualCollection.each(function (contextualModel) { + contextualModel.set('isLocked', !isViewing); + }); + }); + }, + + /** + * Tracks the number of contextual link models in the collection. + * + * @param {Drupal.contextual.StateModel} contextualModel + * The contextual links model that was added or removed. + * @param {Backbone.Collection} contextualCollection + * The collection of contextual link models. + */ + countContextualLinks: function (contextualModel, contextualCollection) { + this.set('contextualCount', contextualCollection.length); + }, + + /** + * Lock newly added contextual links if edit mode is enabled. + * + * @param {Drupal.contextual.StateModel} contextualModel + * The contextual links model that was added. + * @param {Backbone.Collection} [contextualCollection] + * The collection of contextual link models. + */ + lockNewContextualLinks: function (contextualModel, contextualCollection) { + if (!this.get('isViewing')) { + contextualModel.set('isLocked', true); + } + }, + + /** + * Automatically updates visibility of the view/edit mode toggle. + */ + updateVisibility: function () { + this.set('isVisible', this.get('contextualCount') > 0); + } + + }); + +})(Drupal, Backbone); diff --git a/core/modules/contextual/js/toolbar/models/StateModel.js b/core/modules/contextual/js/toolbar/models/StateModel.js index d9159e4e97f6..93eed97b5b55 100644 --- a/core/modules/contextual/js/toolbar/models/StateModel.js +++ b/core/modules/contextual/js/toolbar/models/StateModel.js @@ -1,79 +1,32 @@ /** - * @file - * A Backbone Model for the state of Contextual module's edit toolbar tab. - */ +* DO NOT EDIT THIS FILE. +* All changes should be applied to ./modules/contextual/js/toolbar/models/StateModel.es6.js +* See the following change record for more information, +* https://www.drupal.org/node/2873849 +* @preserve +**/ (function (Drupal, Backbone) { 'use strict'; - Drupal.contextualToolbar.StateModel = Backbone.Model.extend(/** @lends Drupal.contextualToolbar.StateModel# */{ - - /** - * @type {object} - * - * @prop {bool} isViewing - * @prop {bool} isVisible - * @prop {number} contextualCount - * @prop {Drupal~TabbingContext} tabbingContext - */ - defaults: /** @lends Drupal.contextualToolbar.StateModel# */{ - - /** - * Indicates whether the toggle is currently in "view" or "edit" mode. - * - * @type {bool} - */ + Drupal.contextualToolbar.StateModel = Backbone.Model.extend({ + defaults: { isViewing: true, - /** - * Indicates whether the toggle should be visible or hidden. Automatically - * calculated, depends on contextualCount. - * - * @type {bool} - */ isVisible: false, - /** - * Tracks how many contextual links exist on the page. - * - * @type {number} - */ contextualCount: 0, - /** - * A TabbingContext object as returned by {@link Drupal~TabbingManager}: - * the set of tabbable elements when edit mode is enabled. - * - * @type {?Drupal~TabbingContext} - */ tabbingContext: null }, - /** - * Models the state of the edit mode toggle. - * - * @constructs - * - * @augments Backbone.Model - * - * @param {object} attrs - * Attributes for the backbone model. - * @param {object} options - * An object with the following option: - * @param {Backbone.collection} options.contextualCollection - * The collection of {@link Drupal.contextual.StateModel} models that - * represent the contextual links on the page. - */ - initialize: function (attrs, options) { - // Respond to new/removed contextual links. + initialize: function initialize(attrs, options) { this.listenTo(options.contextualCollection, 'reset remove add', this.countContextualLinks); this.listenTo(options.contextualCollection, 'add', this.lockNewContextualLinks); - // Automatically determine visibility. this.listenTo(this, 'change:contextualCount', this.updateVisibility); - // Whenever edit mode is toggled, lock all contextual links. this.listenTo(this, 'change:isViewing', function (model, isViewing) { options.contextualCollection.each(function (contextualModel) { contextualModel.set('isLocked', !isViewing); @@ -81,39 +34,19 @@ }); }, - /** - * Tracks the number of contextual link models in the collection. - * - * @param {Drupal.contextual.StateModel} contextualModel - * The contextual links model that was added or removed. - * @param {Backbone.Collection} contextualCollection - * The collection of contextual link models. - */ - countContextualLinks: function (contextualModel, contextualCollection) { + countContextualLinks: function countContextualLinks(contextualModel, contextualCollection) { this.set('contextualCount', contextualCollection.length); }, - /** - * Lock newly added contextual links if edit mode is enabled. - * - * @param {Drupal.contextual.StateModel} contextualModel - * The contextual links model that was added. - * @param {Backbone.Collection} [contextualCollection] - * The collection of contextual link models. - */ - lockNewContextualLinks: function (contextualModel, contextualCollection) { + lockNewContextualLinks: function lockNewContextualLinks(contextualModel, contextualCollection) { if (!this.get('isViewing')) { contextualModel.set('isLocked', true); } }, - /** - * Automatically updates visibility of the view/edit mode toggle. - */ - updateVisibility: function () { + updateVisibility: function updateVisibility() { this.set('isVisible', this.get('contextualCount') > 0); } }); - -})(Drupal, Backbone); +})(Drupal, Backbone);
\ No newline at end of file diff --git a/core/modules/contextual/js/toolbar/views/AuralView.es6.js b/core/modules/contextual/js/toolbar/views/AuralView.es6.js new file mode 100644 index 000000000000..d684ffb9e63e --- /dev/null +++ b/core/modules/contextual/js/toolbar/views/AuralView.es6.js @@ -0,0 +1,104 @@ +/** + * @file + * A Backbone View that provides the aural view of the edit mode toggle. + */ + +(function ($, Drupal, Backbone, _) { + + 'use strict'; + + Drupal.contextualToolbar.AuralView = Backbone.View.extend(/** @lends Drupal.contextualToolbar.AuralView# */{ + + /** + * Tracks whether the tabbing constraint announcement has been read once. + * + * @type {bool} + */ + announcedOnce: false, + + /** + * Renders the aural view of the edit mode toggle (screen reader support). + * + * @constructs + * + * @augments Backbone.View + * + * @param {object} options + * Options for the view. + */ + initialize: function (options) { + this.options = options; + + this.listenTo(this.model, 'change', this.render); + this.listenTo(this.model, 'change:isViewing', this.manageTabbing); + + $(document).on('keyup', _.bind(this.onKeypress, this)); + }, + + /** + * @inheritdoc + * + * @return {Drupal.contextualToolbar.AuralView} + * The current contextual toolbar aural view. + */ + render: function () { + // Render the state. + this.$el.find('button').attr('aria-pressed', !this.model.get('isViewing')); + + return this; + }, + + /** + * Limits tabbing to the contextual links and edit mode toolbar tab. + */ + manageTabbing: function () { + var tabbingContext = this.model.get('tabbingContext'); + // Always release an existing tabbing context. + if (tabbingContext) { + tabbingContext.release(); + Drupal.announce(this.options.strings.tabbingReleased); + } + // Create a new tabbing context when edit mode is enabled. + if (!this.model.get('isViewing')) { + tabbingContext = Drupal.tabbingManager.constrain($('.contextual-toolbar-tab, .contextual')); + this.model.set('tabbingContext', tabbingContext); + this.announceTabbingConstraint(); + this.announcedOnce = true; + } + }, + + /** + * Announces the current tabbing constraint. + */ + announceTabbingConstraint: function () { + var strings = this.options.strings; + Drupal.announce(Drupal.formatString(strings.tabbingConstrained, { + '@contextualsCount': Drupal.formatPlural(Drupal.contextual.collection.length, '@count contextual link', '@count contextual links') + })); + Drupal.announce(strings.pressEsc); + }, + + /** + * Responds to esc and tab key press events. + * + * @param {jQuery.Event} event + * The keypress event. + */ + onKeypress: function (event) { + // The first tab key press is tracked so that an annoucement about tabbing + // constraints can be raised if edit mode is enabled when the page is + // loaded. + if (!this.announcedOnce && event.keyCode === 9 && !this.model.get('isViewing')) { + this.announceTabbingConstraint(); + // Set announce to true so that this conditional block won't run again. + this.announcedOnce = true; + } + // Respond to the ESC key. Exit out of edit mode. + if (event.keyCode === 27) { + this.model.set('isViewing', true); + } + } + + }); + +})(jQuery, Drupal, Backbone, _); diff --git a/core/modules/contextual/js/toolbar/views/AuralView.js b/core/modules/contextual/js/toolbar/views/AuralView.js index d684ffb9e63e..132342f5801e 100644 --- a/core/modules/contextual/js/toolbar/views/AuralView.js +++ b/core/modules/contextual/js/toolbar/views/AuralView.js @@ -1,32 +1,19 @@ /** - * @file - * A Backbone View that provides the aural view of the edit mode toggle. - */ +* DO NOT EDIT THIS FILE. +* All changes should be applied to ./modules/contextual/js/toolbar/views/AuralView.es6.js +* See the following change record for more information, +* https://www.drupal.org/node/2873849 +* @preserve +**/ (function ($, Drupal, Backbone, _) { 'use strict'; - Drupal.contextualToolbar.AuralView = Backbone.View.extend(/** @lends Drupal.contextualToolbar.AuralView# */{ - - /** - * Tracks whether the tabbing constraint announcement has been read once. - * - * @type {bool} - */ + Drupal.contextualToolbar.AuralView = Backbone.View.extend({ announcedOnce: false, - /** - * Renders the aural view of the edit mode toggle (screen reader support). - * - * @constructs - * - * @augments Backbone.View - * - * @param {object} options - * Options for the view. - */ - initialize: function (options) { + initialize: function initialize(options) { this.options = options; this.listenTo(this.model, 'change', this.render); @@ -35,30 +22,20 @@ $(document).on('keyup', _.bind(this.onKeypress, this)); }, - /** - * @inheritdoc - * - * @return {Drupal.contextualToolbar.AuralView} - * The current contextual toolbar aural view. - */ - render: function () { - // Render the state. + render: function render() { this.$el.find('button').attr('aria-pressed', !this.model.get('isViewing')); return this; }, - /** - * Limits tabbing to the contextual links and edit mode toolbar tab. - */ - manageTabbing: function () { + manageTabbing: function manageTabbing() { var tabbingContext = this.model.get('tabbingContext'); - // Always release an existing tabbing context. + if (tabbingContext) { tabbingContext.release(); Drupal.announce(this.options.strings.tabbingReleased); } - // Create a new tabbing context when edit mode is enabled. + if (!this.model.get('isViewing')) { tabbingContext = Drupal.tabbingManager.constrain($('.contextual-toolbar-tab, .contextual')); this.model.set('tabbingContext', tabbingContext); @@ -67,10 +44,7 @@ } }, - /** - * Announces the current tabbing constraint. - */ - announceTabbingConstraint: function () { + announceTabbingConstraint: function announceTabbingConstraint() { var strings = this.options.strings; Drupal.announce(Drupal.formatString(strings.tabbingConstrained, { '@contextualsCount': Drupal.formatPlural(Drupal.contextual.collection.length, '@count contextual link', '@count contextual links') @@ -78,27 +52,17 @@ Drupal.announce(strings.pressEsc); }, - /** - * Responds to esc and tab key press events. - * - * @param {jQuery.Event} event - * The keypress event. - */ - onKeypress: function (event) { - // The first tab key press is tracked so that an annoucement about tabbing - // constraints can be raised if edit mode is enabled when the page is - // loaded. + onKeypress: function onKeypress(event) { if (!this.announcedOnce && event.keyCode === 9 && !this.model.get('isViewing')) { this.announceTabbingConstraint(); - // Set announce to true so that this conditional block won't run again. + this.announcedOnce = true; } - // Respond to the ESC key. Exit out of edit mode. + if (event.keyCode === 27) { this.model.set('isViewing', true); } } }); - -})(jQuery, Drupal, Backbone, _); +})(jQuery, Drupal, Backbone, _);
\ No newline at end of file diff --git a/core/modules/contextual/js/toolbar/views/VisualView.es6.js b/core/modules/contextual/js/toolbar/views/VisualView.es6.js new file mode 100644 index 000000000000..d1d413502d32 --- /dev/null +++ b/core/modules/contextual/js/toolbar/views/VisualView.es6.js @@ -0,0 +1,84 @@ +/** + * @file + * A Backbone View that provides the visual view of the edit mode toggle. + */ + +(function (Drupal, Backbone) { + + 'use strict'; + + Drupal.contextualToolbar.VisualView = Backbone.View.extend(/** @lends Drupal.contextualToolbar.VisualView# */{ + + /** + * Events for the Backbone view. + * + * @return {object} + * A mapping of events to be used in the view. + */ + events: function () { + // Prevents delay and simulated mouse events. + var touchEndToClick = function (event) { + event.preventDefault(); + event.target.click(); + }; + + return { + click: function () { + this.model.set('isViewing', !this.model.get('isViewing')); + }, + touchend: touchEndToClick + }; + }, + + /** + * Renders the visual view of the edit mode toggle. + * + * Listens to mouse & touch and handles edit mode toggle interactions. + * + * @constructs + * + * @augments Backbone.View + */ + initialize: function () { + this.listenTo(this.model, 'change', this.render); + this.listenTo(this.model, 'change:isViewing', this.persist); + }, + + /** + * @inheritdoc + * + * @return {Drupal.contextualToolbar.VisualView} + * The current contextual toolbar visual view. + */ + render: function () { + // Render the visibility. + this.$el.toggleClass('hidden', !this.model.get('isVisible')); + // Render the state. + this.$el.find('button').toggleClass('is-active', !this.model.get('isViewing')); + + return this; + }, + + /** + * Model change handler; persists the isViewing value to localStorage. + * + * `isViewing === true` is the default, so only stores in localStorage when + * it's not the default value (i.e. false). + * + * @param {Drupal.contextualToolbar.StateModel} model + * A {@link Drupal.contextualToolbar.StateModel} model. + * @param {bool} isViewing + * The value of the isViewing attribute in the model. + */ + persist: function (model, isViewing) { + if (!isViewing) { + localStorage.setItem('Drupal.contextualToolbar.isViewing', 'false'); + } + else { + localStorage.removeItem('Drupal.contextualToolbar.isViewing'); + } + } + + }); + +})(Drupal, Backbone); diff --git a/core/modules/contextual/js/toolbar/views/VisualView.js b/core/modules/contextual/js/toolbar/views/VisualView.js index d1d413502d32..f666a95c2180 100644 --- a/core/modules/contextual/js/toolbar/views/VisualView.js +++ b/core/modules/contextual/js/toolbar/views/VisualView.js @@ -1,84 +1,50 @@ /** - * @file - * A Backbone View that provides the visual view of the edit mode toggle. - */ +* DO NOT EDIT THIS FILE. +* All changes should be applied to ./modules/contextual/js/toolbar/views/VisualView.es6.js +* See the following change record for more information, +* https://www.drupal.org/node/2873849 +* @preserve +**/ (function (Drupal, Backbone) { 'use strict'; - Drupal.contextualToolbar.VisualView = Backbone.View.extend(/** @lends Drupal.contextualToolbar.VisualView# */{ - - /** - * Events for the Backbone view. - * - * @return {object} - * A mapping of events to be used in the view. - */ - events: function () { - // Prevents delay and simulated mouse events. - var touchEndToClick = function (event) { + Drupal.contextualToolbar.VisualView = Backbone.View.extend({ + events: function events() { + var touchEndToClick = function touchEndToClick(event) { event.preventDefault(); event.target.click(); }; return { - click: function () { + click: function click() { this.model.set('isViewing', !this.model.get('isViewing')); }, touchend: touchEndToClick }; }, - /** - * Renders the visual view of the edit mode toggle. - * - * Listens to mouse & touch and handles edit mode toggle interactions. - * - * @constructs - * - * @augments Backbone.View - */ - initialize: function () { + initialize: function initialize() { this.listenTo(this.model, 'change', this.render); this.listenTo(this.model, 'change:isViewing', this.persist); }, - /** - * @inheritdoc - * - * @return {Drupal.contextualToolbar.VisualView} - * The current contextual toolbar visual view. - */ - render: function () { - // Render the visibility. + render: function render() { this.$el.toggleClass('hidden', !this.model.get('isVisible')); - // Render the state. + this.$el.find('button').toggleClass('is-active', !this.model.get('isViewing')); return this; }, - /** - * Model change handler; persists the isViewing value to localStorage. - * - * `isViewing === true` is the default, so only stores in localStorage when - * it's not the default value (i.e. false). - * - * @param {Drupal.contextualToolbar.StateModel} model - * A {@link Drupal.contextualToolbar.StateModel} model. - * @param {bool} isViewing - * The value of the isViewing attribute in the model. - */ - persist: function (model, isViewing) { + persist: function persist(model, isViewing) { if (!isViewing) { localStorage.setItem('Drupal.contextualToolbar.isViewing', 'false'); - } - else { + } else { localStorage.removeItem('Drupal.contextualToolbar.isViewing'); } } }); - -})(Drupal, Backbone); +})(Drupal, Backbone);
\ No newline at end of file diff --git a/core/modules/contextual/js/views/AuralView.es6.js b/core/modules/contextual/js/views/AuralView.es6.js new file mode 100644 index 000000000000..8ba2e33e347b --- /dev/null +++ b/core/modules/contextual/js/views/AuralView.es6.js @@ -0,0 +1,55 @@ +/** + * @file + * A Backbone View that provides the aural view of a contextual link. + */ + +(function (Drupal, Backbone) { + + 'use strict'; + + Drupal.contextual.AuralView = Backbone.View.extend(/** @lends Drupal.contextual.AuralView# */{ + + /** + * Renders the aural view of a contextual link (i.e. screen reader support). + * + * @constructs + * + * @augments Backbone.View + * + * @param {object} options + * Options for the view. + */ + initialize: function (options) { + this.options = options; + + this.listenTo(this.model, 'change', this.render); + + // Use aria-role form so that the number of items in the list is spoken. + this.$el.attr('role', 'form'); + + // Initial render. + this.render(); + }, + + /** + * @inheritdoc + */ + render: function () { + var isOpen = this.model.get('isOpen'); + + // Set the hidden property of the links. + this.$el.find('.contextual-links') + .prop('hidden', !isOpen); + + // Update the view of the trigger. + this.$el.find('.trigger') + .text(Drupal.t('@action @title configuration options', { + '@action': (!isOpen) ? this.options.strings.open : this.options.strings.close, + '@title': this.model.get('title') + })) + .attr('aria-pressed', isOpen); + } + + }); + +})(Drupal, Backbone); diff --git a/core/modules/contextual/js/views/AuralView.js b/core/modules/contextual/js/views/AuralView.js index 8ba2e33e347b..5cbefcdd785d 100644 --- a/core/modules/contextual/js/views/AuralView.js +++ b/core/modules/contextual/js/views/AuralView.js @@ -1,55 +1,36 @@ /** - * @file - * A Backbone View that provides the aural view of a contextual link. - */ +* DO NOT EDIT THIS FILE. +* All changes should be applied to ./modules/contextual/js/views/AuralView.es6.js +* See the following change record for more information, +* https://www.drupal.org/node/2873849 +* @preserve +**/ (function (Drupal, Backbone) { 'use strict'; - Drupal.contextual.AuralView = Backbone.View.extend(/** @lends Drupal.contextual.AuralView# */{ - - /** - * Renders the aural view of a contextual link (i.e. screen reader support). - * - * @constructs - * - * @augments Backbone.View - * - * @param {object} options - * Options for the view. - */ - initialize: function (options) { + Drupal.contextual.AuralView = Backbone.View.extend({ + initialize: function initialize(options) { this.options = options; this.listenTo(this.model, 'change', this.render); - // Use aria-role form so that the number of items in the list is spoken. this.$el.attr('role', 'form'); - // Initial render. this.render(); }, - /** - * @inheritdoc - */ - render: function () { + render: function render() { var isOpen = this.model.get('isOpen'); - // Set the hidden property of the links. - this.$el.find('.contextual-links') - .prop('hidden', !isOpen); - - // Update the view of the trigger. - this.$el.find('.trigger') - .text(Drupal.t('@action @title configuration options', { - '@action': (!isOpen) ? this.options.strings.open : this.options.strings.close, - '@title': this.model.get('title') - })) - .attr('aria-pressed', isOpen); + this.$el.find('.contextual-links').prop('hidden', !isOpen); + + this.$el.find('.trigger').text(Drupal.t('@action @title configuration options', { + '@action': !isOpen ? this.options.strings.open : this.options.strings.close, + '@title': this.model.get('title') + })).attr('aria-pressed', isOpen); } }); - -})(Drupal, Backbone); +})(Drupal, Backbone);
\ No newline at end of file diff --git a/core/modules/contextual/js/views/KeyboardView.es6.js b/core/modules/contextual/js/views/KeyboardView.es6.js new file mode 100644 index 000000000000..9c247730e312 --- /dev/null +++ b/core/modules/contextual/js/views/KeyboardView.es6.js @@ -0,0 +1,61 @@ +/** + * @file + * A Backbone View that provides keyboard interaction for a contextual link. + */ + +(function (Drupal, Backbone) { + + 'use strict'; + + Drupal.contextual.KeyboardView = Backbone.View.extend(/** @lends Drupal.contextual.KeyboardView# */{ + + /** + * @type {object} + */ + events: { + 'focus .trigger': 'focus', + 'focus .contextual-links a': 'focus', + 'blur .trigger': function () { this.model.blur(); }, + 'blur .contextual-links a': function () { + // Set up a timeout to allow a user to tab between the trigger and the + // contextual links without the menu dismissing. + var that = this; + this.timer = window.setTimeout(function () { + that.model.close().blur(); + }, 150); + } + }, + + /** + * Provides keyboard interaction for a contextual link. + * + * @constructs + * + * @augments Backbone.View + */ + initialize: function () { + + /** + * The timer is used to create a delay before dismissing the contextual + * links on blur. This is only necessary when keyboard users tab into + * contextual links without edit mode (i.e. without TabbingManager). + * That means that if we decide to disable tabbing of contextual links + * without edit mode, all this timer logic can go away. + * + * @type {NaN|number} + */ + this.timer = NaN; + }, + + /** + * Sets focus on the model; Clears the timer that dismisses the links. + */ + focus: function () { + // Clear the timeout that might have been set by blurring a link. + window.clearTimeout(this.timer); + this.model.focus(); + } + + }); + +})(Drupal, Backbone); diff --git a/core/modules/contextual/js/views/KeyboardView.js b/core/modules/contextual/js/views/KeyboardView.js index 9c247730e312..1539971d6833 100644 --- a/core/modules/contextual/js/views/KeyboardView.js +++ b/core/modules/contextual/js/views/KeyboardView.js @@ -1,24 +1,23 @@ /** - * @file - * A Backbone View that provides keyboard interaction for a contextual link. - */ +* DO NOT EDIT THIS FILE. +* All changes should be applied to ./modules/contextual/js/views/KeyboardView.es6.js +* See the following change record for more information, +* https://www.drupal.org/node/2873849 +* @preserve +**/ (function (Drupal, Backbone) { 'use strict'; - Drupal.contextual.KeyboardView = Backbone.View.extend(/** @lends Drupal.contextual.KeyboardView# */{ - - /** - * @type {object} - */ + Drupal.contextual.KeyboardView = Backbone.View.extend({ events: { 'focus .trigger': 'focus', 'focus .contextual-links a': 'focus', - 'blur .trigger': function () { this.model.blur(); }, - 'blur .contextual-links a': function () { - // Set up a timeout to allow a user to tab between the trigger and the - // contextual links without the menu dismissing. + 'blur .trigger': function blurTrigger() { + this.model.blur(); + }, + 'blur .contextual-links a': function blurContextualLinksA() { var that = this; this.timer = window.setTimeout(function () { that.model.close().blur(); @@ -26,36 +25,14 @@ } }, - /** - * Provides keyboard interaction for a contextual link. - * - * @constructs - * - * @augments Backbone.View - */ - initialize: function () { - - /** - * The timer is used to create a delay before dismissing the contextual - * links on blur. This is only necessary when keyboard users tab into - * contextual links without edit mode (i.e. without TabbingManager). - * That means that if we decide to disable tabbing of contextual links - * without edit mode, all this timer logic can go away. - * - * @type {NaN|number} - */ + initialize: function initialize() { this.timer = NaN; }, - /** - * Sets focus on the model; Clears the timer that dismisses the links. - */ - focus: function () { - // Clear the timeout that might have been set by blurring a link. + focus: function focus() { window.clearTimeout(this.timer); this.model.focus(); } }); - -})(Drupal, Backbone); +})(Drupal, Backbone);
\ No newline at end of file diff --git a/core/modules/contextual/js/views/RegionView.es6.js b/core/modules/contextual/js/views/RegionView.es6.js new file mode 100644 index 000000000000..e960db36bfd6 --- /dev/null +++ b/core/modules/contextual/js/views/RegionView.es6.js @@ -0,0 +1,57 @@ +/** + * @file + * A Backbone View that renders the visual view of a contextual region element. + */ + +(function (Drupal, Backbone, Modernizr) { + + 'use strict'; + + Drupal.contextual.RegionView = Backbone.View.extend(/** @lends Drupal.contextual.RegionView# */{ + + /** + * Events for the Backbone view. + * + * @return {object} + * A mapping of events to be used in the view. + */ + events: function () { + var mapping = { + mouseenter: function () { this.model.set('regionIsHovered', true); }, + mouseleave: function () { + this.model.close().blur().set('regionIsHovered', false); + } + }; + // We don't want mouse hover events on touch. + if (Modernizr.touchevents) { + mapping = {}; + } + return mapping; + }, + + /** + * Renders the visual view of a contextual region element. + * + * @constructs + * + * @augments Backbone.View + */ + initialize: function () { + this.listenTo(this.model, 'change:hasFocus', this.render); + }, + + /** + * @inheritdoc + * + * @return {Drupal.contextual.RegionView} + * The current contextual region view. + */ + render: function () { + this.$el.toggleClass('focus', this.model.get('hasFocus')); + + return this; + } + + }); + +})(Drupal, Backbone, Modernizr); diff --git a/core/modules/contextual/js/views/RegionView.js b/core/modules/contextual/js/views/RegionView.js index e960db36bfd6..d37e10f3c283 100644 --- a/core/modules/contextual/js/views/RegionView.js +++ b/core/modules/contextual/js/views/RegionView.js @@ -1,57 +1,41 @@ /** - * @file - * A Backbone View that renders the visual view of a contextual region element. - */ +* DO NOT EDIT THIS FILE. +* All changes should be applied to ./modules/contextual/js/views/RegionView.es6.js +* See the following change record for more information, +* https://www.drupal.org/node/2873849 +* @preserve +**/ (function (Drupal, Backbone, Modernizr) { 'use strict'; - Drupal.contextual.RegionView = Backbone.View.extend(/** @lends Drupal.contextual.RegionView# */{ - - /** - * Events for the Backbone view. - * - * @return {object} - * A mapping of events to be used in the view. - */ - events: function () { + Drupal.contextual.RegionView = Backbone.View.extend({ + events: function events() { var mapping = { - mouseenter: function () { this.model.set('regionIsHovered', true); }, - mouseleave: function () { + mouseenter: function mouseenter() { + this.model.set('regionIsHovered', true); + }, + mouseleave: function mouseleave() { this.model.close().blur().set('regionIsHovered', false); } }; - // We don't want mouse hover events on touch. + if (Modernizr.touchevents) { mapping = {}; } return mapping; }, - /** - * Renders the visual view of a contextual region element. - * - * @constructs - * - * @augments Backbone.View - */ - initialize: function () { + initialize: function initialize() { this.listenTo(this.model, 'change:hasFocus', this.render); }, - /** - * @inheritdoc - * - * @return {Drupal.contextual.RegionView} - * The current contextual region view. - */ - render: function () { + render: function render() { this.$el.toggleClass('focus', this.model.get('hasFocus')); return this; } }); - -})(Drupal, Backbone, Modernizr); +})(Drupal, Backbone, Modernizr);
\ No newline at end of file diff --git a/core/modules/contextual/js/views/VisualView.es6.js b/core/modules/contextual/js/views/VisualView.es6.js new file mode 100644 index 000000000000..b22bb373dd8d --- /dev/null +++ b/core/modules/contextual/js/views/VisualView.es6.js @@ -0,0 +1,80 @@ +/** + * @file + * A Backbone View that provides the visual view of a contextual link. + */ + +(function (Drupal, Backbone, Modernizr) { + + 'use strict'; + + Drupal.contextual.VisualView = Backbone.View.extend(/** @lends Drupal.contextual.VisualView# */{ + + /** + * Events for the Backbone view. + * + * @return {object} + * A mapping of events to be used in the view. + */ + events: function () { + // Prevents delay and simulated mouse events. + var touchEndToClick = function (event) { + event.preventDefault(); + event.target.click(); + }; + var mapping = { + 'click .trigger': function () { this.model.toggleOpen(); }, + 'touchend .trigger': touchEndToClick, + 'click .contextual-links a': function () { this.model.close().blur(); }, + 'touchend .contextual-links a': touchEndToClick + }; + // We only want mouse hover events on non-touch. + if (!Modernizr.touchevents) { + mapping.mouseenter = function () { this.model.focus(); }; + } + return mapping; + }, + + /** + * Renders the visual view of a contextual link. Listens to mouse & touch. + * + * @constructs + * + * @augments Backbone.View + */ + initialize: function () { + this.listenTo(this.model, 'change', this.render); + }, + + /** + * @inheritdoc + * + * @return {Drupal.contextual.VisualView} + * The current contextual visual view. + */ + render: function () { + var isOpen = this.model.get('isOpen'); + // The trigger should be visible when: + // - the mouse hovered over the region, + // - the trigger is locked, + // - and for as long as the contextual menu is open. + var isVisible = this.model.get('isLocked') || this.model.get('regionIsHovered') || isOpen; + + this.$el + // The open state determines if the links are visible. + .toggleClass('open', isOpen) + // Update the visibility of the trigger. + .find('.trigger').toggleClass('visually-hidden', !isVisible); + + // Nested contextual region handling: hide any nested contextual triggers. + if ('isOpen' in this.model.changed) { + this.$el.closest('.contextual-region') + .find('.contextual .trigger:not(:first)') + .toggle(!isOpen); + } + + return this; + } + + }); + +})(Drupal, Backbone, Modernizr); diff --git a/core/modules/contextual/js/views/VisualView.js b/core/modules/contextual/js/views/VisualView.js index b22bb373dd8d..c54f50f87189 100644 --- a/core/modules/contextual/js/views/VisualView.js +++ b/core/modules/contextual/js/views/VisualView.js @@ -1,80 +1,57 @@ /** - * @file - * A Backbone View that provides the visual view of a contextual link. - */ +* DO NOT EDIT THIS FILE. +* All changes should be applied to ./modules/contextual/js/views/VisualView.es6.js +* See the following change record for more information, +* https://www.drupal.org/node/2873849 +* @preserve +**/ (function (Drupal, Backbone, Modernizr) { 'use strict'; - Drupal.contextual.VisualView = Backbone.View.extend(/** @lends Drupal.contextual.VisualView# */{ - - /** - * Events for the Backbone view. - * - * @return {object} - * A mapping of events to be used in the view. - */ - events: function () { - // Prevents delay and simulated mouse events. - var touchEndToClick = function (event) { + Drupal.contextual.VisualView = Backbone.View.extend({ + events: function events() { + var touchEndToClick = function touchEndToClick(event) { event.preventDefault(); event.target.click(); }; var mapping = { - 'click .trigger': function () { this.model.toggleOpen(); }, + 'click .trigger': function clickTrigger() { + this.model.toggleOpen(); + }, 'touchend .trigger': touchEndToClick, - 'click .contextual-links a': function () { this.model.close().blur(); }, + 'click .contextual-links a': function clickContextualLinksA() { + this.model.close().blur(); + }, 'touchend .contextual-links a': touchEndToClick }; - // We only want mouse hover events on non-touch. + if (!Modernizr.touchevents) { - mapping.mouseenter = function () { this.model.focus(); }; + mapping.mouseenter = function () { + this.model.focus(); + }; } return mapping; }, - /** - * Renders the visual view of a contextual link. Listens to mouse & touch. - * - * @constructs - * - * @augments Backbone.View - */ - initialize: function () { + initialize: function initialize() { this.listenTo(this.model, 'change', this.render); }, - /** - * @inheritdoc - * - * @return {Drupal.contextual.VisualView} - * The current contextual visual view. - */ - render: function () { + render: function render() { var isOpen = this.model.get('isOpen'); - // The trigger should be visible when: - // - the mouse hovered over the region, - // - the trigger is locked, - // - and for as long as the contextual menu is open. + var isVisible = this.model.get('isLocked') || this.model.get('regionIsHovered') || isOpen; - this.$el - // The open state determines if the links are visible. - .toggleClass('open', isOpen) - // Update the visibility of the trigger. - .find('.trigger').toggleClass('visually-hidden', !isVisible); + this.$el.toggleClass('open', isOpen).find('.trigger').toggleClass('visually-hidden', !isVisible); - // Nested contextual region handling: hide any nested contextual triggers. if ('isOpen' in this.model.changed) { - this.$el.closest('.contextual-region') - .find('.contextual .trigger:not(:first)') - .toggle(!isOpen); + this.$el.closest('.contextual-region').find('.contextual .trigger:not(:first)').toggle(!isOpen); } return this; } }); - -})(Drupal, Backbone, Modernizr); +})(Drupal, Backbone, Modernizr);
\ No newline at end of file |