Added the Search API Synonym module to deal specifically with licence and license...
[yaffs-website] / web / core / misc / collapse.es6.js
1 /**
2  * @file
3  * Polyfill for HTML5 details elements.
4  */
5
6 (function($, Modernizr, Drupal) {
7   /**
8    * The collapsible details object represents a single details element.
9    *
10    * @constructor Drupal.CollapsibleDetails
11    *
12    * @param {HTMLElement} node
13    *   The details element.
14    */
15   function CollapsibleDetails(node) {
16     this.$node = $(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.
20     const anchor =
21       window.location.hash && window.location.hash !== '#'
22         ? `, ${window.location.hash}`
23         : '';
24     if (this.$node.find(`.error${anchor}`).length) {
25       this.$node.attr('open', true);
26     }
27     // Initialize and setup the summary,
28     this.setupSummary();
29     // Initialize and setup the legend.
30     this.setupLegend();
31   }
32
33   $.extend(
34     CollapsibleDetails,
35     /** @lends Drupal.CollapsibleDetails */ {
36       /**
37        * Holds references to instantiated CollapsibleDetails objects.
38        *
39        * @type {Array.<Drupal.CollapsibleDetails>}
40        */
41       instances: [],
42     },
43   );
44
45   $.extend(
46     CollapsibleDetails.prototype,
47     /** @lends Drupal.CollapsibleDetails# */ {
48       /**
49        * Initialize and setup summary events and markup.
50        *
51        * @fires event:summaryUpdated
52        *
53        * @listens event:summaryUpdated
54        */
55       setupSummary() {
56         this.$summary = $('<span class="summary"></span>');
57         this.$node
58           .on('summaryUpdated', $.proxy(this.onSummaryUpdated, this))
59           .trigger('summaryUpdated');
60       },
61
62       /**
63        * Initialize and setup legend markup.
64        */
65       setupLegend() {
66         // Turn the summary into a clickable link.
67         const $legend = this.$node.find('> summary');
68
69         $('<span class="details-summary-prefix visually-hidden"></span>')
70           .append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show'))
71           .prependTo($legend)
72           .after(document.createTextNode(' '));
73
74         // .wrapInner() does not retain bound events.
75         $('<a class="details-title"></a>')
76           .attr('href', `#${this.$node.attr('id')}`)
77           .prepend($legend.contents())
78           .appendTo($legend);
79
80         $legend
81           .append(this.$summary)
82           .on('click', $.proxy(this.onLegendClick, this));
83       },
84
85       /**
86        * Handle legend clicks.
87        *
88        * @param {jQuery.Event} e
89        *   The event triggered.
90        */
91       onLegendClick(e) {
92         this.toggle();
93         e.preventDefault();
94       },
95
96       /**
97        * Update summary.
98        */
99       onSummaryUpdated() {
100         const text = $.trim(this.$node.drupalGetSummary());
101         this.$summary.html(text ? ` (${text})` : '');
102       },
103
104       /**
105        * Toggle the visibility of a details element using smooth animations.
106        */
107       toggle() {
108         const isOpen = !!this.$node.attr('open');
109         const $summaryPrefix = this.$node.find(
110           '> summary span.details-summary-prefix',
111         );
112         if (isOpen) {
113           $summaryPrefix.html(Drupal.t('Show'));
114         } else {
115           $summaryPrefix.html(Drupal.t('Hide'));
116         }
117         // Delay setting the attribute to emulate chrome behavior and make
118         // details-aria.js work as expected with this polyfill.
119         setTimeout(() => {
120           this.$node.attr('open', !isOpen);
121         }, 0);
122       },
123     },
124   );
125
126   /**
127    * Polyfill HTML5 details element.
128    *
129    * @type {Drupal~behavior}
130    *
131    * @prop {Drupal~behaviorAttach} attach
132    *   Attaches behavior for the details element.
133    */
134   Drupal.behaviors.collapse = {
135     attach(context) {
136       if (Modernizr.details) {
137         return;
138       }
139       const $collapsibleDetails = $(context)
140         .find('details')
141         .once('collapse')
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]),
147           );
148         }
149       }
150     },
151   };
152
153   /**
154    * Open parent details elements of a targeted page fragment.
155    *
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.
160    *
161    * @param {jQuery.Event} e
162    *   The event triggered.
163    * @param {jQuery} $target
164    *   The targeted node as a jQuery object.
165    */
166   const handleFragmentLinkClickOrHashChange = (e, $target) => {
167     $target
168       .parents('details')
169       .not('[open]')
170       .find('> summary')
171       .trigger('click');
172   };
173
174   /**
175    * Binds a listener to handle fragment link clicks and URL hash changes.
176    */
177   $('body').on(
178     'formFragmentLinkClickOrHashChange.details',
179     handleFragmentLinkClickOrHashChange,
180   );
181
182   // Expose constructor in the public space.
183   Drupal.CollapsibleDetails = CollapsibleDetails;
184 })(jQuery, Modernizr, Drupal);