3 * Polyfill for HTML5 details elements.
6 (function($, Modernizr, Drupal) {
8 * The collapsible details object represents a single details element.
10 * @constructor Drupal.CollapsibleDetails
12 * @param {HTMLElement} node
13 * The details element.
15 function CollapsibleDetails(node) {
17 this.$node.data('details', this);
18 // Expand details if there are errors inside, or if it contains an
19 // element that is targeted by the URI fragment identifier.
21 window.location.hash && window.location.hash !== '#'
22 ? `, ${window.location.hash}`
24 if (this.$node.find(`.error${anchor}`).length) {
25 this.$node.attr('open', true);
27 // Initialize and setup the summary,
29 // Initialize and setup the legend.
35 /** @lends Drupal.CollapsibleDetails */ {
37 * Holds references to instantiated CollapsibleDetails objects.
39 * @type {Array.<Drupal.CollapsibleDetails>}
46 CollapsibleDetails.prototype,
47 /** @lends Drupal.CollapsibleDetails# */ {
49 * Initialize and setup summary events and markup.
51 * @fires event:summaryUpdated
53 * @listens event:summaryUpdated
56 this.$summary = $('<span class="summary"></span>');
58 .on('summaryUpdated', $.proxy(this.onSummaryUpdated, this))
59 .trigger('summaryUpdated');
63 * Initialize and setup legend markup.
66 // Turn the summary into a clickable link.
67 const $legend = this.$node.find('> summary');
69 $('<span class="details-summary-prefix visually-hidden"></span>')
70 .append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show'))
72 .after(document.createTextNode(' '));
74 // .wrapInner() does not retain bound events.
75 $('<a class="details-title"></a>')
76 .attr('href', `#${this.$node.attr('id')}`)
77 .prepend($legend.contents())
81 .append(this.$summary)
82 .on('click', $.proxy(this.onLegendClick, this));
86 * Handle legend clicks.
88 * @param {jQuery.Event} e
89 * The event triggered.
100 const text = $.trim(this.$node.drupalGetSummary());
101 this.$summary.html(text ? ` (${text})` : '');
105 * Toggle the visibility of a details element using smooth animations.
108 const isOpen = !!this.$node.attr('open');
109 const $summaryPrefix = this.$node.find(
110 '> summary span.details-summary-prefix',
113 $summaryPrefix.html(Drupal.t('Show'));
115 $summaryPrefix.html(Drupal.t('Hide'));
117 // Delay setting the attribute to emulate chrome behavior and make
118 // details-aria.js work as expected with this polyfill.
120 this.$node.attr('open', !isOpen);
127 * Polyfill HTML5 details element.
129 * @type {Drupal~behavior}
131 * @prop {Drupal~behaviorAttach} attach
132 * Attaches behavior for the details element.
134 Drupal.behaviors.collapse = {
136 if (Modernizr.details) {
139 const $collapsibleDetails = $(context)
142 .addClass('collapse-processed');
143 if ($collapsibleDetails.length) {
144 for (let i = 0; i < $collapsibleDetails.length; i++) {
145 CollapsibleDetails.instances.push(
146 new CollapsibleDetails($collapsibleDetails[i]),
154 * Open parent details elements of a targeted page fragment.
156 * Opens all (nested) details element on a hash change or fragment link click
157 * when the target is a child element, in order to make sure the targeted
158 * element is visible. Aria attributes on the summary
159 * are set by triggering the click event listener in details-aria.js.
161 * @param {jQuery.Event} e
162 * The event triggered.
163 * @param {jQuery} $target
164 * The targeted node as a jQuery object.
166 const handleFragmentLinkClickOrHashChange = (e, $target) => {
175 * Binds a listener to handle fragment link clicks and URL hash changes.
178 'formFragmentLinkClickOrHashChange.details',
179 handleFragmentLinkClickOrHashChange,
182 // Expose constructor in the public space.
183 Drupal.CollapsibleDetails = CollapsibleDetails;
184 })(jQuery, Modernizr, Drupal);