comparison core/misc/collapse.es6.js @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 129ea1e6d783
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
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 = location.hash && location.hash !== '#' ? `, ${location.hash}` : '';
21 if (this.$node.find(`.error${anchor}`).length) {
22 this.$node.attr('open', true);
23 }
24 // Initialize and setup the summary,
25 this.setupSummary();
26 // Initialize and setup the legend.
27 this.setupLegend();
28 }
29
30 $.extend(CollapsibleDetails, /** @lends Drupal.CollapsibleDetails */{
31
32 /**
33 * Holds references to instantiated CollapsibleDetails objects.
34 *
35 * @type {Array.<Drupal.CollapsibleDetails>}
36 */
37 instances: [],
38 });
39
40 $.extend(CollapsibleDetails.prototype, /** @lends Drupal.CollapsibleDetails# */{
41
42 /**
43 * Initialize and setup summary events and markup.
44 *
45 * @fires event:summaryUpdated
46 *
47 * @listens event:summaryUpdated
48 */
49 setupSummary() {
50 this.$summary = $('<span class="summary"></span>');
51 this.$node
52 .on('summaryUpdated', $.proxy(this.onSummaryUpdated, this))
53 .trigger('summaryUpdated');
54 },
55
56 /**
57 * Initialize and setup legend markup.
58 */
59 setupLegend() {
60 // Turn the summary into a clickable link.
61 const $legend = this.$node.find('> summary');
62
63 $('<span class="details-summary-prefix visually-hidden"></span>')
64 .append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show'))
65 .prependTo($legend)
66 .after(document.createTextNode(' '));
67
68 // .wrapInner() does not retain bound events.
69 $('<a class="details-title"></a>')
70 .attr('href', `#${this.$node.attr('id')}`)
71 .prepend($legend.contents())
72 .appendTo($legend);
73
74 $legend
75 .append(this.$summary)
76 .on('click', $.proxy(this.onLegendClick, this));
77 },
78
79 /**
80 * Handle legend clicks.
81 *
82 * @param {jQuery.Event} e
83 * The event triggered.
84 */
85 onLegendClick(e) {
86 this.toggle();
87 e.preventDefault();
88 },
89
90 /**
91 * Update summary.
92 */
93 onSummaryUpdated() {
94 const text = $.trim(this.$node.drupalGetSummary());
95 this.$summary.html(text ? ` (${text})` : '');
96 },
97
98 /**
99 * Toggle the visibility of a details element using smooth animations.
100 */
101 toggle() {
102 const isOpen = !!this.$node.attr('open');
103 const $summaryPrefix = this.$node.find('> summary span.details-summary-prefix');
104 if (isOpen) {
105 $summaryPrefix.html(Drupal.t('Show'));
106 }
107 else {
108 $summaryPrefix.html(Drupal.t('Hide'));
109 }
110 // Delay setting the attribute to emulate chrome behavior and make
111 // details-aria.js work as expected with this polyfill.
112 setTimeout(() => {
113 this.$node.attr('open', !isOpen);
114 }, 0);
115 },
116 });
117
118 /**
119 * Polyfill HTML5 details element.
120 *
121 * @type {Drupal~behavior}
122 *
123 * @prop {Drupal~behaviorAttach} attach
124 * Attaches behavior for the details element.
125 */
126 Drupal.behaviors.collapse = {
127 attach(context) {
128 if (Modernizr.details) {
129 return;
130 }
131 const $collapsibleDetails = $(context).find('details').once('collapse').addClass('collapse-processed');
132 if ($collapsibleDetails.length) {
133 for (let i = 0; i < $collapsibleDetails.length; i++) {
134 CollapsibleDetails.instances.push(new CollapsibleDetails($collapsibleDetails[i]));
135 }
136 }
137 },
138 };
139
140 /**
141 * Open parent details elements of a targeted page fragment.
142 *
143 * Opens all (nested) details element on a hash change or fragment link click
144 * when the target is a child element, in order to make sure the targeted
145 * element is visible. Aria attributes on the summary
146 * are set by triggering the click event listener in details-aria.js.
147 *
148 * @param {jQuery.Event} e
149 * The event triggered.
150 * @param {jQuery} $target
151 * The targeted node as a jQuery object.
152 */
153 const handleFragmentLinkClickOrHashChange = (e, $target) => {
154 $target.parents('details').not('[open]').find('> summary').trigger('click');
155 };
156
157 /**
158 * Binds a listener to handle fragment link clicks and URL hash changes.
159 */
160 $('body').on('formFragmentLinkClickOrHashChange.details', handleFragmentLinkClickOrHashChange);
161
162 // Expose constructor in the public space.
163 Drupal.CollapsibleDetails = CollapsibleDetails;
164 }(jQuery, Modernizr, Drupal));