Chris@0
|
1 /**
|
Chris@0
|
2 * @file
|
Chris@0
|
3 * Dropbutton feature.
|
Chris@0
|
4 */
|
Chris@0
|
5
|
Chris@17
|
6 (function($, Drupal) {
|
Chris@0
|
7 /**
|
Chris@0
|
8 * A DropButton presents an HTML list as a button with a primary action.
|
Chris@0
|
9 *
|
Chris@0
|
10 * All secondary actions beyond the first in the list are presented in a
|
Chris@0
|
11 * dropdown list accessible through a toggle arrow associated with the button.
|
Chris@0
|
12 *
|
Chris@0
|
13 * @constructor Drupal.DropButton
|
Chris@0
|
14 *
|
Chris@0
|
15 * @param {HTMLElement} dropbutton
|
Chris@0
|
16 * A DOM element.
|
Chris@0
|
17 * @param {object} settings
|
Chris@0
|
18 * A list of options including:
|
Chris@0
|
19 * @param {string} settings.title
|
Chris@0
|
20 * The text inside the toggle link element. This text is hidden
|
Chris@0
|
21 * from visual UAs.
|
Chris@0
|
22 */
|
Chris@0
|
23 function DropButton(dropbutton, settings) {
|
Chris@0
|
24 // Merge defaults with settings.
|
Chris@17
|
25 const options = $.extend(
|
Chris@17
|
26 { title: Drupal.t('List additional actions') },
|
Chris@17
|
27 settings,
|
Chris@17
|
28 );
|
Chris@0
|
29 const $dropbutton = $(dropbutton);
|
Chris@0
|
30
|
Chris@0
|
31 /**
|
Chris@0
|
32 * @type {jQuery}
|
Chris@0
|
33 */
|
Chris@0
|
34 this.$dropbutton = $dropbutton;
|
Chris@0
|
35
|
Chris@0
|
36 /**
|
Chris@0
|
37 * @type {jQuery}
|
Chris@0
|
38 */
|
Chris@0
|
39 this.$list = $dropbutton.find('.dropbutton');
|
Chris@0
|
40
|
Chris@0
|
41 /**
|
Chris@0
|
42 * Find actions and mark them.
|
Chris@0
|
43 *
|
Chris@0
|
44 * @type {jQuery}
|
Chris@0
|
45 */
|
Chris@0
|
46 this.$actions = this.$list.find('li').addClass('dropbutton-action');
|
Chris@0
|
47
|
Chris@0
|
48 // Add the special dropdown only if there are hidden actions.
|
Chris@0
|
49 if (this.$actions.length > 1) {
|
Chris@0
|
50 // Identify the first element of the collection.
|
Chris@0
|
51 const $primary = this.$actions.slice(0, 1);
|
Chris@0
|
52 // Identify the secondary actions.
|
Chris@0
|
53 const $secondary = this.$actions.slice(1);
|
Chris@0
|
54 $secondary.addClass('secondary-action');
|
Chris@0
|
55 // Add toggle link.
|
Chris@0
|
56 $primary.after(Drupal.theme('dropbuttonToggle', options));
|
Chris@0
|
57 // Bind mouse events.
|
Chris@17
|
58 this.$dropbutton.addClass('dropbutton-multiple').on({
|
Chris@17
|
59 /**
|
Chris@17
|
60 * Adds a timeout to close the dropdown on mouseleave.
|
Chris@17
|
61 *
|
Chris@17
|
62 * @ignore
|
Chris@17
|
63 */
|
Chris@17
|
64 'mouseleave.dropbutton': $.proxy(this.hoverOut, this),
|
Chris@0
|
65
|
Chris@17
|
66 /**
|
Chris@17
|
67 * Clears timeout when mouseout of the dropdown.
|
Chris@17
|
68 *
|
Chris@17
|
69 * @ignore
|
Chris@17
|
70 */
|
Chris@17
|
71 'mouseenter.dropbutton': $.proxy(this.hoverIn, this),
|
Chris@0
|
72
|
Chris@17
|
73 /**
|
Chris@17
|
74 * Similar to mouseleave/mouseenter, but for keyboard navigation.
|
Chris@17
|
75 *
|
Chris@17
|
76 * @ignore
|
Chris@17
|
77 */
|
Chris@17
|
78 'focusout.dropbutton': $.proxy(this.focusOut, this),
|
Chris@0
|
79
|
Chris@17
|
80 /**
|
Chris@17
|
81 * @ignore
|
Chris@17
|
82 */
|
Chris@17
|
83 'focusin.dropbutton': $.proxy(this.focusIn, this),
|
Chris@17
|
84 });
|
Chris@17
|
85 } else {
|
Chris@0
|
86 this.$dropbutton.addClass('dropbutton-single');
|
Chris@0
|
87 }
|
Chris@0
|
88 }
|
Chris@0
|
89
|
Chris@0
|
90 /**
|
Chris@17
|
91 * Delegated callback for opening and closing dropbutton secondary actions.
|
Chris@17
|
92 *
|
Chris@17
|
93 * @function Drupal.DropButton~dropbuttonClickHandler
|
Chris@17
|
94 *
|
Chris@17
|
95 * @param {jQuery.Event} e
|
Chris@17
|
96 * The event triggered.
|
Chris@17
|
97 */
|
Chris@17
|
98 function dropbuttonClickHandler(e) {
|
Chris@17
|
99 e.preventDefault();
|
Chris@17
|
100 $(e.target)
|
Chris@17
|
101 .closest('.dropbutton-wrapper')
|
Chris@17
|
102 .toggleClass('open');
|
Chris@17
|
103 }
|
Chris@17
|
104
|
Chris@17
|
105 /**
|
Chris@17
|
106 * Process elements with the .dropbutton class on page load.
|
Chris@17
|
107 *
|
Chris@17
|
108 * @type {Drupal~behavior}
|
Chris@17
|
109 *
|
Chris@17
|
110 * @prop {Drupal~behaviorAttach} attach
|
Chris@17
|
111 * Attaches dropButton behaviors.
|
Chris@17
|
112 */
|
Chris@17
|
113 Drupal.behaviors.dropButton = {
|
Chris@17
|
114 attach(context, settings) {
|
Chris@17
|
115 const $dropbuttons = $(context)
|
Chris@17
|
116 .find('.dropbutton-wrapper')
|
Chris@17
|
117 .once('dropbutton');
|
Chris@17
|
118 if ($dropbuttons.length) {
|
Chris@17
|
119 // Adds the delegated handler that will toggle dropdowns on click.
|
Chris@17
|
120 const $body = $('body').once('dropbutton-click');
|
Chris@17
|
121 if ($body.length) {
|
Chris@17
|
122 $body.on('click', '.dropbutton-toggle', dropbuttonClickHandler);
|
Chris@17
|
123 }
|
Chris@17
|
124 // Initialize all buttons.
|
Chris@17
|
125 const il = $dropbuttons.length;
|
Chris@17
|
126 for (let i = 0; i < il; i++) {
|
Chris@17
|
127 DropButton.dropbuttons.push(
|
Chris@17
|
128 new DropButton($dropbuttons[i], settings.dropbutton),
|
Chris@17
|
129 );
|
Chris@17
|
130 }
|
Chris@17
|
131 }
|
Chris@17
|
132 },
|
Chris@17
|
133 };
|
Chris@17
|
134
|
Chris@17
|
135 /**
|
Chris@0
|
136 * Extend the DropButton constructor.
|
Chris@0
|
137 */
|
Chris@17
|
138 $.extend(
|
Chris@17
|
139 DropButton,
|
Chris@17
|
140 /** @lends Drupal.DropButton */ {
|
Chris@17
|
141 /**
|
Chris@17
|
142 * Store all processed DropButtons.
|
Chris@17
|
143 *
|
Chris@17
|
144 * @type {Array.<Drupal.DropButton>}
|
Chris@17
|
145 */
|
Chris@17
|
146 dropbuttons: [],
|
Chris@17
|
147 },
|
Chris@17
|
148 );
|
Chris@0
|
149
|
Chris@0
|
150 /**
|
Chris@0
|
151 * Extend the DropButton prototype.
|
Chris@0
|
152 */
|
Chris@17
|
153 $.extend(
|
Chris@17
|
154 DropButton.prototype,
|
Chris@17
|
155 /** @lends Drupal.DropButton# */ {
|
Chris@17
|
156 /**
|
Chris@17
|
157 * Toggle the dropbutton open and closed.
|
Chris@17
|
158 *
|
Chris@17
|
159 * @param {bool} [show]
|
Chris@17
|
160 * Force the dropbutton to open by passing true or to close by
|
Chris@17
|
161 * passing false.
|
Chris@17
|
162 */
|
Chris@17
|
163 toggle(show) {
|
Chris@17
|
164 const isBool = typeof show === 'boolean';
|
Chris@17
|
165 show = isBool ? show : !this.$dropbutton.hasClass('open');
|
Chris@17
|
166 this.$dropbutton.toggleClass('open', show);
|
Chris@17
|
167 },
|
Chris@0
|
168
|
Chris@17
|
169 /**
|
Chris@17
|
170 * @method
|
Chris@17
|
171 */
|
Chris@17
|
172 hoverIn() {
|
Chris@17
|
173 // Clear any previous timer we were using.
|
Chris@17
|
174 if (this.timerID) {
|
Chris@17
|
175 window.clearTimeout(this.timerID);
|
Chris@17
|
176 }
|
Chris@17
|
177 },
|
Chris@17
|
178
|
Chris@17
|
179 /**
|
Chris@17
|
180 * @method
|
Chris@17
|
181 */
|
Chris@17
|
182 hoverOut() {
|
Chris@17
|
183 // Wait half a second before closing.
|
Chris@17
|
184 this.timerID = window.setTimeout($.proxy(this, 'close'), 500);
|
Chris@17
|
185 },
|
Chris@17
|
186
|
Chris@17
|
187 /**
|
Chris@17
|
188 * @method
|
Chris@17
|
189 */
|
Chris@17
|
190 open() {
|
Chris@17
|
191 this.toggle(true);
|
Chris@17
|
192 },
|
Chris@17
|
193
|
Chris@17
|
194 /**
|
Chris@17
|
195 * @method
|
Chris@17
|
196 */
|
Chris@17
|
197 close() {
|
Chris@17
|
198 this.toggle(false);
|
Chris@17
|
199 },
|
Chris@17
|
200
|
Chris@17
|
201 /**
|
Chris@17
|
202 * @param {jQuery.Event} e
|
Chris@17
|
203 * The event triggered.
|
Chris@17
|
204 */
|
Chris@17
|
205 focusOut(e) {
|
Chris@17
|
206 this.hoverOut.call(this, e);
|
Chris@17
|
207 },
|
Chris@17
|
208
|
Chris@17
|
209 /**
|
Chris@17
|
210 * @param {jQuery.Event} e
|
Chris@17
|
211 * The event triggered.
|
Chris@17
|
212 */
|
Chris@17
|
213 focusIn(e) {
|
Chris@17
|
214 this.hoverIn.call(this, e);
|
Chris@17
|
215 },
|
Chris@0
|
216 },
|
Chris@17
|
217 );
|
Chris@0
|
218
|
Chris@17
|
219 $.extend(
|
Chris@17
|
220 Drupal.theme,
|
Chris@17
|
221 /** @lends Drupal.theme */ {
|
Chris@17
|
222 /**
|
Chris@17
|
223 * A toggle is an interactive element often bound to a click handler.
|
Chris@17
|
224 *
|
Chris@17
|
225 * @param {object} options
|
Chris@17
|
226 * Options object.
|
Chris@17
|
227 * @param {string} [options.title]
|
Chris@17
|
228 * The button text.
|
Chris@17
|
229 *
|
Chris@17
|
230 * @return {string}
|
Chris@17
|
231 * A string representing a DOM fragment.
|
Chris@17
|
232 */
|
Chris@17
|
233 dropbuttonToggle(options) {
|
Chris@17
|
234 return `<li class="dropbutton-toggle"><button type="button"><span class="dropbutton-arrow"><span class="visually-hidden">${
|
Chris@17
|
235 options.title
|
Chris@17
|
236 }</span></span></button></li>`;
|
Chris@17
|
237 },
|
Chris@0
|
238 },
|
Chris@17
|
239 );
|
Chris@0
|
240
|
Chris@0
|
241 // Expose constructor in the public space.
|
Chris@0
|
242 Drupal.DropButton = DropButton;
|
Chris@17
|
243 })(jQuery, Drupal);
|