X-Git-Url: http://www.aleph1.co.uk/gitweb/?p=yaffs-website;a=blobdiff_plain;f=web%2Fcore%2Fmisc%2Fcollapse.es6.js;fp=web%2Fcore%2Fmisc%2Fcollapse.es6.js;h=374af0c1c9aa22df5e142c8d88a00587007c6d96;hp=0000000000000000000000000000000000000000;hb=9917807b03b64faf00f6a1f29dcb6eafc454efa5;hpb=aea91e65e895364e460983b890e295aa5d5540a5 diff --git a/web/core/misc/collapse.es6.js b/web/core/misc/collapse.es6.js new file mode 100644 index 000000000..374af0c1c --- /dev/null +++ b/web/core/misc/collapse.es6.js @@ -0,0 +1,164 @@ +/** + * @file + * Polyfill for HTML5 details elements. + */ + +(function ($, Modernizr, Drupal) { + /** + * The collapsible details object represents a single details element. + * + * @constructor Drupal.CollapsibleDetails + * + * @param {HTMLElement} node + * The details element. + */ + function CollapsibleDetails(node) { + this.$node = $(node); + this.$node.data('details', this); + // Expand details if there are errors inside, or if it contains an + // element that is targeted by the URI fragment identifier. + const anchor = location.hash && location.hash !== '#' ? `, ${location.hash}` : ''; + if (this.$node.find(`.error${anchor}`).length) { + this.$node.attr('open', true); + } + // Initialize and setup the summary, + this.setupSummary(); + // Initialize and setup the legend. + this.setupLegend(); + } + + $.extend(CollapsibleDetails, /** @lends Drupal.CollapsibleDetails */{ + + /** + * Holds references to instantiated CollapsibleDetails objects. + * + * @type {Array.} + */ + instances: [], + }); + + $.extend(CollapsibleDetails.prototype, /** @lends Drupal.CollapsibleDetails# */{ + + /** + * Initialize and setup summary events and markup. + * + * @fires event:summaryUpdated + * + * @listens event:summaryUpdated + */ + setupSummary() { + this.$summary = $(''); + this.$node + .on('summaryUpdated', $.proxy(this.onSummaryUpdated, this)) + .trigger('summaryUpdated'); + }, + + /** + * Initialize and setup legend markup. + */ + setupLegend() { + // Turn the summary into a clickable link. + const $legend = this.$node.find('> summary'); + + $('') + .append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show')) + .prependTo($legend) + .after(document.createTextNode(' ')); + + // .wrapInner() does not retain bound events. + $('') + .attr('href', `#${this.$node.attr('id')}`) + .prepend($legend.contents()) + .appendTo($legend); + + $legend + .append(this.$summary) + .on('click', $.proxy(this.onLegendClick, this)); + }, + + /** + * Handle legend clicks. + * + * @param {jQuery.Event} e + * The event triggered. + */ + onLegendClick(e) { + this.toggle(); + e.preventDefault(); + }, + + /** + * Update summary. + */ + onSummaryUpdated() { + const text = $.trim(this.$node.drupalGetSummary()); + this.$summary.html(text ? ` (${text})` : ''); + }, + + /** + * Toggle the visibility of a details element using smooth animations. + */ + toggle() { + const isOpen = !!this.$node.attr('open'); + const $summaryPrefix = this.$node.find('> summary span.details-summary-prefix'); + if (isOpen) { + $summaryPrefix.html(Drupal.t('Show')); + } + else { + $summaryPrefix.html(Drupal.t('Hide')); + } + // Delay setting the attribute to emulate chrome behavior and make + // details-aria.js work as expected with this polyfill. + setTimeout(() => { + this.$node.attr('open', !isOpen); + }, 0); + }, + }); + + /** + * Polyfill HTML5 details element. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches behavior for the details element. + */ + Drupal.behaviors.collapse = { + attach(context) { + if (Modernizr.details) { + return; + } + const $collapsibleDetails = $(context).find('details').once('collapse').addClass('collapse-processed'); + if ($collapsibleDetails.length) { + for (let i = 0; i < $collapsibleDetails.length; i++) { + CollapsibleDetails.instances.push(new CollapsibleDetails($collapsibleDetails[i])); + } + } + }, + }; + + /** + * Open parent details elements of a targeted page fragment. + * + * Opens all (nested) details element on a hash change or fragment link click + * when the target is a child element, in order to make sure the targeted + * element is visible. Aria attributes on the summary + * are set by triggering the click event listener in details-aria.js. + * + * @param {jQuery.Event} e + * The event triggered. + * @param {jQuery} $target + * The targeted node as a jQuery object. + */ + const handleFragmentLinkClickOrHashChange = (e, $target) => { + $target.parents('details').not('[open]').find('> summary').trigger('click'); + }; + + /** + * Binds a listener to handle fragment link clicks and URL hash changes. + */ + $('body').on('formFragmentLinkClickOrHashChange.details', handleFragmentLinkClickOrHashChange); + + // Expose constructor in the public space. + Drupal.CollapsibleDetails = CollapsibleDetails; +}(jQuery, Modernizr, Drupal));