Chris@0
|
1 /**
|
Chris@0
|
2 * @file
|
Chris@0
|
3 * Some basic behaviors and utility functions for Views UI.
|
Chris@0
|
4 */
|
Chris@0
|
5
|
Chris@17
|
6 (function($, Drupal, drupalSettings) {
|
Chris@0
|
7 /**
|
Chris@0
|
8 * @namespace
|
Chris@0
|
9 */
|
Chris@0
|
10 Drupal.viewsUi = {};
|
Chris@0
|
11
|
Chris@0
|
12 /**
|
Chris@0
|
13 * Improve the user experience of the views edit interface.
|
Chris@0
|
14 *
|
Chris@0
|
15 * @type {Drupal~behavior}
|
Chris@0
|
16 *
|
Chris@0
|
17 * @prop {Drupal~behaviorAttach} attach
|
Chris@0
|
18 * Attaches toggling of SQL rewrite warning on the corresponding checkbox.
|
Chris@0
|
19 */
|
Chris@0
|
20 Drupal.behaviors.viewsUiEditView = {
|
Chris@0
|
21 attach() {
|
Chris@0
|
22 // Only show the SQL rewrite warning when the user has chosen the
|
Chris@0
|
23 // corresponding checkbox.
|
Chris@17
|
24 $('[data-drupal-selector="edit-query-options-disable-sql-rewrite"]').on(
|
Chris@17
|
25 'click',
|
Chris@17
|
26 () => {
|
Chris@17
|
27 $('.sql-rewrite-warning').toggleClass('js-hide');
|
Chris@17
|
28 },
|
Chris@17
|
29 );
|
Chris@0
|
30 },
|
Chris@0
|
31 };
|
Chris@0
|
32
|
Chris@0
|
33 /**
|
Chris@0
|
34 * In the add view wizard, use the view name to prepopulate form fields such
|
Chris@0
|
35 * as page title and menu link.
|
Chris@0
|
36 *
|
Chris@0
|
37 * @type {Drupal~behavior}
|
Chris@0
|
38 *
|
Chris@0
|
39 * @prop {Drupal~behaviorAttach} attach
|
Chris@0
|
40 * Attaches behavior for prepopulating page title and menu links, based on
|
Chris@0
|
41 * view name.
|
Chris@0
|
42 */
|
Chris@0
|
43 Drupal.behaviors.viewsUiAddView = {
|
Chris@0
|
44 attach(context) {
|
Chris@0
|
45 const $context = $(context);
|
Chris@0
|
46 // Set up regular expressions to allow only numbers, letters, and dashes.
|
Chris@0
|
47 const exclude = new RegExp('[^a-z0-9\\-]+', 'g');
|
Chris@0
|
48 const replace = '-';
|
Chris@0
|
49 let suffix;
|
Chris@0
|
50
|
Chris@0
|
51 // The page title, block title, and menu link fields can all be
|
Chris@0
|
52 // prepopulated with the view name - no regular expression needed.
|
Chris@17
|
53 const $fields = $context.find(
|
Chris@17
|
54 '[id^="edit-page-title"], [id^="edit-block-title"], [id^="edit-page-link-properties-title"]',
|
Chris@17
|
55 );
|
Chris@0
|
56 if ($fields.length) {
|
Chris@0
|
57 if (!this.fieldsFiller) {
|
Chris@0
|
58 this.fieldsFiller = new Drupal.viewsUi.FormFieldFiller($fields);
|
Chris@17
|
59 } else {
|
Chris@0
|
60 // After an AJAX response, this.fieldsFiller will still have event
|
Chris@0
|
61 // handlers bound to the old version of the form fields (which don't
|
Chris@0
|
62 // exist anymore). The event handlers need to be unbound and then
|
Chris@0
|
63 // rebound to the new markup. Note that jQuery.live is difficult to
|
Chris@0
|
64 // make work in this case because the IDs of the form fields change
|
Chris@0
|
65 // on every AJAX response.
|
Chris@0
|
66 this.fieldsFiller.rebind($fields);
|
Chris@0
|
67 }
|
Chris@0
|
68 }
|
Chris@0
|
69
|
Chris@0
|
70 // Prepopulate the path field with a URLified version of the view name.
|
Chris@0
|
71 const $pathField = $context.find('[id^="edit-page-path"]');
|
Chris@0
|
72 if ($pathField.length) {
|
Chris@0
|
73 if (!this.pathFiller) {
|
Chris@17
|
74 this.pathFiller = new Drupal.viewsUi.FormFieldFiller(
|
Chris@17
|
75 $pathField,
|
Chris@17
|
76 exclude,
|
Chris@17
|
77 replace,
|
Chris@17
|
78 );
|
Chris@17
|
79 } else {
|
Chris@0
|
80 this.pathFiller.rebind($pathField);
|
Chris@0
|
81 }
|
Chris@0
|
82 }
|
Chris@0
|
83
|
Chris@0
|
84 // Populate the RSS feed field with a URLified version of the view name,
|
Chris@0
|
85 // and an .xml suffix (to make it unique).
|
Chris@17
|
86 const $feedField = $context.find(
|
Chris@17
|
87 '[id^="edit-page-feed-properties-path"]',
|
Chris@17
|
88 );
|
Chris@0
|
89 if ($feedField.length) {
|
Chris@0
|
90 if (!this.feedFiller) {
|
Chris@0
|
91 suffix = '.xml';
|
Chris@17
|
92 this.feedFiller = new Drupal.viewsUi.FormFieldFiller(
|
Chris@17
|
93 $feedField,
|
Chris@17
|
94 exclude,
|
Chris@17
|
95 replace,
|
Chris@17
|
96 suffix,
|
Chris@17
|
97 );
|
Chris@17
|
98 } else {
|
Chris@0
|
99 this.feedFiller.rebind($feedField);
|
Chris@0
|
100 }
|
Chris@0
|
101 }
|
Chris@0
|
102 },
|
Chris@0
|
103 };
|
Chris@0
|
104
|
Chris@0
|
105 /**
|
Chris@0
|
106 * Constructor for the {@link Drupal.viewsUi.FormFieldFiller} object.
|
Chris@0
|
107 *
|
Chris@0
|
108 * Prepopulates a form field based on the view name.
|
Chris@0
|
109 *
|
Chris@0
|
110 * @constructor
|
Chris@0
|
111 *
|
Chris@0
|
112 * @param {jQuery} $target
|
Chris@0
|
113 * A jQuery object representing the form field or fields to prepopulate.
|
Chris@0
|
114 * @param {bool} [exclude=false]
|
Chris@0
|
115 * A regular expression representing characters to exclude from
|
Chris@0
|
116 * the target field.
|
Chris@0
|
117 * @param {string} [replace='']
|
Chris@0
|
118 * A string to use as the replacement value for disallowed characters.
|
Chris@0
|
119 * @param {string} [suffix='']
|
Chris@0
|
120 * A suffix to append at the end of the target field content.
|
Chris@0
|
121 */
|
Chris@17
|
122 Drupal.viewsUi.FormFieldFiller = function($target, exclude, replace, suffix) {
|
Chris@0
|
123 /**
|
Chris@0
|
124 *
|
Chris@0
|
125 * @type {jQuery}
|
Chris@0
|
126 */
|
Chris@0
|
127 this.source = $('#edit-label');
|
Chris@0
|
128
|
Chris@0
|
129 /**
|
Chris@0
|
130 *
|
Chris@0
|
131 * @type {jQuery}
|
Chris@0
|
132 */
|
Chris@0
|
133 this.target = $target;
|
Chris@0
|
134
|
Chris@0
|
135 /**
|
Chris@0
|
136 *
|
Chris@0
|
137 * @type {bool}
|
Chris@0
|
138 */
|
Chris@0
|
139 this.exclude = exclude || false;
|
Chris@0
|
140
|
Chris@0
|
141 /**
|
Chris@0
|
142 *
|
Chris@0
|
143 * @type {string}
|
Chris@0
|
144 */
|
Chris@0
|
145 this.replace = replace || '';
|
Chris@0
|
146
|
Chris@0
|
147 /**
|
Chris@0
|
148 *
|
Chris@0
|
149 * @type {string}
|
Chris@0
|
150 */
|
Chris@0
|
151 this.suffix = suffix || '';
|
Chris@0
|
152
|
Chris@0
|
153 // Create bound versions of this instance's object methods to use as event
|
Chris@0
|
154 // handlers. This will let us easily unbind those specific handlers later
|
Chris@0
|
155 // on. NOTE: jQuery.proxy will not work for this because it assumes we want
|
Chris@0
|
156 // only one bound version of an object method, whereas we need one version
|
Chris@0
|
157 // per object instance.
|
Chris@0
|
158 const self = this;
|
Chris@0
|
159
|
Chris@0
|
160 /**
|
Chris@0
|
161 * Populate the target form field with the altered source field value.
|
Chris@0
|
162 *
|
Chris@0
|
163 * @return {*}
|
Chris@0
|
164 * The result of the _populate call, which should be undefined.
|
Chris@0
|
165 */
|
Chris@17
|
166 this.populate = function() {
|
Chris@0
|
167 return self._populate.call(self);
|
Chris@0
|
168 };
|
Chris@0
|
169
|
Chris@0
|
170 /**
|
Chris@0
|
171 * Stop prepopulating the form fields.
|
Chris@0
|
172 *
|
Chris@0
|
173 * @return {*}
|
Chris@0
|
174 * The result of the _unbind call, which should be undefined.
|
Chris@0
|
175 */
|
Chris@17
|
176 this.unbind = function() {
|
Chris@0
|
177 return self._unbind.call(self);
|
Chris@0
|
178 };
|
Chris@0
|
179
|
Chris@0
|
180 this.bind();
|
Chris@0
|
181 // Object constructor; no return value.
|
Chris@0
|
182 };
|
Chris@0
|
183
|
Chris@17
|
184 $.extend(
|
Chris@17
|
185 Drupal.viewsUi.FormFieldFiller.prototype,
|
Chris@17
|
186 /** @lends Drupal.viewsUi.FormFieldFiller# */ {
|
Chris@17
|
187 /**
|
Chris@17
|
188 * Bind the form-filling behavior.
|
Chris@17
|
189 */
|
Chris@17
|
190 bind() {
|
Chris@17
|
191 this.unbind();
|
Chris@17
|
192 // Populate the form field when the source changes.
|
Chris@17
|
193 this.source.on('keyup.viewsUi change.viewsUi', this.populate);
|
Chris@17
|
194 // Quit populating the field as soon as it gets focus.
|
Chris@17
|
195 this.target.on('focus.viewsUi', this.unbind);
|
Chris@17
|
196 },
|
Chris@0
|
197
|
Chris@17
|
198 /**
|
Chris@17
|
199 * Get the source form field value as altered by the passed-in parameters.
|
Chris@17
|
200 *
|
Chris@17
|
201 * @return {string}
|
Chris@17
|
202 * The source form field value.
|
Chris@17
|
203 */
|
Chris@17
|
204 getTransliterated() {
|
Chris@17
|
205 let from = this.source.val();
|
Chris@17
|
206 if (this.exclude) {
|
Chris@17
|
207 from = from.toLowerCase().replace(this.exclude, this.replace);
|
Chris@17
|
208 }
|
Chris@17
|
209 return from;
|
Chris@17
|
210 },
|
Chris@17
|
211
|
Chris@17
|
212 /**
|
Chris@17
|
213 * Populate the target form field with the altered source field value.
|
Chris@17
|
214 */
|
Chris@17
|
215 _populate() {
|
Chris@17
|
216 const transliterated = this.getTransliterated();
|
Chris@17
|
217 const suffix = this.suffix;
|
Chris@17
|
218 this.target.each(function(i) {
|
Chris@17
|
219 // Ensure that the maxlength is not exceeded by prepopulating the field.
|
Chris@17
|
220 const maxlength = $(this).attr('maxlength') - suffix.length;
|
Chris@17
|
221 $(this).val(transliterated.substr(0, maxlength) + suffix);
|
Chris@17
|
222 });
|
Chris@17
|
223 },
|
Chris@17
|
224
|
Chris@17
|
225 /**
|
Chris@17
|
226 * Stop prepopulating the form fields.
|
Chris@17
|
227 */
|
Chris@17
|
228 _unbind() {
|
Chris@17
|
229 this.source.off('keyup.viewsUi change.viewsUi', this.populate);
|
Chris@17
|
230 this.target.off('focus.viewsUi', this.unbind);
|
Chris@17
|
231 },
|
Chris@17
|
232
|
Chris@17
|
233 /**
|
Chris@17
|
234 * Bind event handlers to new form fields, after they're replaced via Ajax.
|
Chris@17
|
235 *
|
Chris@17
|
236 * @param {jQuery} $fields
|
Chris@17
|
237 * Fields to rebind functionality to.
|
Chris@17
|
238 */
|
Chris@17
|
239 rebind($fields) {
|
Chris@17
|
240 this.target = $fields;
|
Chris@17
|
241 this.bind();
|
Chris@17
|
242 },
|
Chris@0
|
243 },
|
Chris@17
|
244 );
|
Chris@0
|
245
|
Chris@0
|
246 /**
|
Chris@0
|
247 * Adds functionality for the add item form.
|
Chris@0
|
248 *
|
Chris@0
|
249 * @type {Drupal~behavior}
|
Chris@0
|
250 *
|
Chris@0
|
251 * @prop {Drupal~behaviorAttach} attach
|
Chris@0
|
252 * Attaches the functionality in {@link Drupal.viewsUi.AddItemForm} to the
|
Chris@0
|
253 * forms in question.
|
Chris@0
|
254 */
|
Chris@0
|
255 Drupal.behaviors.addItemForm = {
|
Chris@0
|
256 attach(context) {
|
Chris@0
|
257 const $context = $(context);
|
Chris@0
|
258 let $form = $context;
|
Chris@0
|
259 // The add handler form may have an id of views-ui-add-handler-form--n.
|
Chris@0
|
260 if (!$context.is('form[id^="views-ui-add-handler-form"]')) {
|
Chris@0
|
261 $form = $context.find('form[id^="views-ui-add-handler-form"]');
|
Chris@0
|
262 }
|
Chris@0
|
263 if ($form.once('views-ui-add-handler-form').length) {
|
Chris@0
|
264 // If we we have an unprocessed views-ui-add-handler-form, let's
|
Chris@0
|
265 // instantiate.
|
Chris@0
|
266 new Drupal.viewsUi.AddItemForm($form);
|
Chris@0
|
267 }
|
Chris@0
|
268 },
|
Chris@0
|
269 };
|
Chris@0
|
270
|
Chris@0
|
271 /**
|
Chris@0
|
272 * Constructs a new AddItemForm.
|
Chris@0
|
273 *
|
Chris@0
|
274 * @constructor
|
Chris@0
|
275 *
|
Chris@0
|
276 * @param {jQuery} $form
|
Chris@0
|
277 * The form element used.
|
Chris@0
|
278 */
|
Chris@17
|
279 Drupal.viewsUi.AddItemForm = function($form) {
|
Chris@0
|
280 /**
|
Chris@0
|
281 *
|
Chris@0
|
282 * @type {jQuery}
|
Chris@0
|
283 */
|
Chris@0
|
284 this.$form = $form;
|
Chris@17
|
285 this.$form
|
Chris@17
|
286 .find('.views-filterable-options :checkbox')
|
Chris@17
|
287 .on('click', $.proxy(this.handleCheck, this));
|
Chris@0
|
288
|
Chris@0
|
289 /**
|
Chris@0
|
290 * Find the wrapper of the displayed text.
|
Chris@0
|
291 */
|
Chris@0
|
292 this.$selected_div = this.$form.find('.views-selected-options').parent();
|
Chris@0
|
293 this.$selected_div.hide();
|
Chris@0
|
294
|
Chris@0
|
295 /**
|
Chris@0
|
296 *
|
Chris@0
|
297 * @type {Array}
|
Chris@0
|
298 */
|
Chris@0
|
299 this.checkedItems = [];
|
Chris@0
|
300 };
|
Chris@0
|
301
|
Chris@0
|
302 /**
|
Chris@0
|
303 * Handles a checkbox check.
|
Chris@0
|
304 *
|
Chris@0
|
305 * @param {jQuery.Event} event
|
Chris@0
|
306 * The event triggered.
|
Chris@0
|
307 */
|
Chris@17
|
308 Drupal.viewsUi.AddItemForm.prototype.handleCheck = function(event) {
|
Chris@0
|
309 const $target = $(event.target);
|
Chris@17
|
310 const label = $.trim(
|
Chris@17
|
311 $target
|
Chris@17
|
312 .closest('td')
|
Chris@17
|
313 .next()
|
Chris@17
|
314 .html(),
|
Chris@17
|
315 );
|
Chris@0
|
316 // Add/remove the checked item to the list.
|
Chris@0
|
317 if ($target.is(':checked')) {
|
Chris@0
|
318 this.$selected_div.show().css('display', 'block');
|
Chris@0
|
319 this.checkedItems.push(label);
|
Chris@17
|
320 } else {
|
Chris@0
|
321 const position = $.inArray(label, this.checkedItems);
|
Chris@0
|
322 // Delete the item from the list and make sure that the list doesn't have
|
Chris@0
|
323 // undefined items left.
|
Chris@0
|
324 for (let i = 0; i < this.checkedItems.length; i++) {
|
Chris@0
|
325 if (i === position) {
|
Chris@0
|
326 this.checkedItems.splice(i, 1);
|
Chris@0
|
327 i--;
|
Chris@0
|
328 break;
|
Chris@0
|
329 }
|
Chris@0
|
330 }
|
Chris@0
|
331 // Hide it again if none item is selected.
|
Chris@0
|
332 if (this.checkedItems.length === 0) {
|
Chris@0
|
333 this.$selected_div.hide();
|
Chris@0
|
334 }
|
Chris@0
|
335 }
|
Chris@0
|
336 this.refreshCheckedItems();
|
Chris@0
|
337 };
|
Chris@0
|
338
|
Chris@0
|
339 /**
|
Chris@0
|
340 * Refresh the display of the checked items.
|
Chris@0
|
341 */
|
Chris@17
|
342 Drupal.viewsUi.AddItemForm.prototype.refreshCheckedItems = function() {
|
Chris@0
|
343 // Perhaps we should precache the text div, too.
|
Chris@17
|
344 this.$selected_div
|
Chris@17
|
345 .find('.views-selected-options')
|
Chris@0
|
346 .html(this.checkedItems.join(', '))
|
Chris@0
|
347 .trigger('dialogContentResize');
|
Chris@0
|
348 };
|
Chris@0
|
349
|
Chris@0
|
350 /**
|
Chris@0
|
351 * The input field items that add displays must be rendered as `<input>`
|
Chris@0
|
352 * elements. The following behavior detaches the `<input>` elements from the
|
Chris@0
|
353 * DOM, wraps them in an unordered list, then appends them to the list of
|
Chris@0
|
354 * tabs.
|
Chris@0
|
355 *
|
Chris@0
|
356 * @type {Drupal~behavior}
|
Chris@0
|
357 *
|
Chris@0
|
358 * @prop {Drupal~behaviorAttach} attach
|
Chris@0
|
359 * Fixes the input elements needed.
|
Chris@0
|
360 */
|
Chris@0
|
361 Drupal.behaviors.viewsUiRenderAddViewButton = {
|
Chris@0
|
362 attach(context) {
|
Chris@0
|
363 // Build the add display menu and pull the display input buttons into it.
|
Chris@17
|
364 const $menu = $(context)
|
Chris@17
|
365 .find('#views-display-menu-tabs')
|
Chris@17
|
366 .once('views-ui-render-add-view-button');
|
Chris@0
|
367 if (!$menu.length) {
|
Chris@0
|
368 return;
|
Chris@0
|
369 }
|
Chris@0
|
370
|
Chris@17
|
371 const $addDisplayDropdown = $(
|
Chris@17
|
372 `<li class="add"><a href="#"><span class="icon add"></span>${Drupal.t(
|
Chris@17
|
373 'Add',
|
Chris@17
|
374 )}</a><ul class="action-list" style="display:none;"></ul></li>`,
|
Chris@17
|
375 );
|
Chris@0
|
376 const $displayButtons = $menu.nextAll('input.add-display').detach();
|
Chris@14
|
377 $displayButtons
|
Chris@14
|
378 .appendTo($addDisplayDropdown.find('.action-list'))
|
Chris@14
|
379 .wrap('<li>')
|
Chris@14
|
380 .parent()
|
Chris@14
|
381 .eq(0)
|
Chris@14
|
382 .addClass('first')
|
Chris@14
|
383 .end()
|
Chris@14
|
384 .eq(-1)
|
Chris@14
|
385 .addClass('last');
|
Chris@0
|
386 // Remove the 'Add ' prefix from the button labels since they're being
|
Chris@0
|
387 // placed in an 'Add' dropdown. @todo This assumes English, but so does
|
Chris@0
|
388 // $addDisplayDropdown above. Add support for translation.
|
Chris@17
|
389 $displayButtons.each(function() {
|
Chris@0
|
390 const label = $(this).val();
|
Chris@0
|
391 if (label.substr(0, 4) === 'Add ') {
|
Chris@0
|
392 $(this).val(label.substr(4));
|
Chris@0
|
393 }
|
Chris@0
|
394 });
|
Chris@0
|
395 $addDisplayDropdown.appendTo($menu);
|
Chris@0
|
396
|
Chris@0
|
397 // Add the click handler for the add display button.
|
Chris@17
|
398 $menu.find('li.add > a').on('click', function(event) {
|
Chris@0
|
399 event.preventDefault();
|
Chris@0
|
400 const $trigger = $(this);
|
Chris@0
|
401 Drupal.behaviors.viewsUiRenderAddViewButton.toggleMenu($trigger);
|
Chris@0
|
402 });
|
Chris@0
|
403 // Add a mouseleave handler to close the dropdown when the user mouses
|
Chris@0
|
404 // away from the item. We use mouseleave instead of mouseout because
|
Chris@0
|
405 // the user is going to trigger mouseout when she moves from the trigger
|
Chris@0
|
406 // link to the sub menu items.
|
Chris@0
|
407 // We use the live binder because the open class on this item will be
|
Chris@0
|
408 // toggled on and off and we want the handler to take effect in the cases
|
Chris@0
|
409 // that the class is present, but not when it isn't.
|
Chris@17
|
410 $('li.add', $menu).on('mouseleave', function(event) {
|
Chris@0
|
411 const $this = $(this);
|
Chris@0
|
412 const $trigger = $this.children('a[href="#"]');
|
Chris@0
|
413 if ($this.children('.action-list').is(':visible')) {
|
Chris@0
|
414 Drupal.behaviors.viewsUiRenderAddViewButton.toggleMenu($trigger);
|
Chris@0
|
415 }
|
Chris@0
|
416 });
|
Chris@0
|
417 },
|
Chris@0
|
418 };
|
Chris@0
|
419
|
Chris@0
|
420 /**
|
Chris@0
|
421 * Toggle menu visibility.
|
Chris@0
|
422 *
|
Chris@0
|
423 * @param {jQuery} $trigger
|
Chris@0
|
424 * The element where the toggle was triggered.
|
Chris@0
|
425 *
|
Chris@0
|
426 *
|
Chris@0
|
427 * @note [@jessebeach] I feel like the following should be a more generic
|
Chris@0
|
428 * function and not written specifically for this UI, but I'm not sure
|
Chris@0
|
429 * where to put it.
|
Chris@0
|
430 */
|
Chris@17
|
431 Drupal.behaviors.viewsUiRenderAddViewButton.toggleMenu = function($trigger) {
|
Chris@0
|
432 $trigger.parent().toggleClass('open');
|
Chris@0
|
433 $trigger.next().slideToggle('fast');
|
Chris@0
|
434 };
|
Chris@0
|
435
|
Chris@0
|
436 /**
|
Chris@0
|
437 * Add search options to the views ui.
|
Chris@0
|
438 *
|
Chris@0
|
439 * @type {Drupal~behavior}
|
Chris@0
|
440 *
|
Chris@0
|
441 * @prop {Drupal~behaviorAttach} attach
|
Chris@0
|
442 * Attaches {@link Drupal.viewsUi.OptionsSearch} to the views ui filter
|
Chris@0
|
443 * options.
|
Chris@0
|
444 */
|
Chris@0
|
445 Drupal.behaviors.viewsUiSearchOptions = {
|
Chris@0
|
446 attach(context) {
|
Chris@0
|
447 const $context = $(context);
|
Chris@0
|
448 let $form = $context;
|
Chris@0
|
449 // The add handler form may have an id of views-ui-add-handler-form--n.
|
Chris@0
|
450 if (!$context.is('form[id^="views-ui-add-handler-form"]')) {
|
Chris@0
|
451 $form = $context.find('form[id^="views-ui-add-handler-form"]');
|
Chris@0
|
452 }
|
Chris@0
|
453 // Make sure we don't add more than one event handler to the same form.
|
Chris@0
|
454 if ($form.once('views-ui-filter-options').length) {
|
Chris@0
|
455 new Drupal.viewsUi.OptionsSearch($form);
|
Chris@0
|
456 }
|
Chris@0
|
457 },
|
Chris@0
|
458 };
|
Chris@0
|
459
|
Chris@0
|
460 /**
|
Chris@0
|
461 * Constructor for the viewsUi.OptionsSearch object.
|
Chris@0
|
462 *
|
Chris@0
|
463 * The OptionsSearch object filters the available options on a form according
|
Chris@0
|
464 * to the user's search term. Typing in "taxonomy" will show only those
|
Chris@0
|
465 * options containing "taxonomy" in their label.
|
Chris@0
|
466 *
|
Chris@0
|
467 * @constructor
|
Chris@0
|
468 *
|
Chris@0
|
469 * @param {jQuery} $form
|
Chris@0
|
470 * The form element.
|
Chris@0
|
471 */
|
Chris@17
|
472 Drupal.viewsUi.OptionsSearch = function($form) {
|
Chris@0
|
473 /**
|
Chris@0
|
474 *
|
Chris@0
|
475 * @type {jQuery}
|
Chris@0
|
476 */
|
Chris@0
|
477 this.$form = $form;
|
Chris@0
|
478
|
Chris@0
|
479 // Click on the title checks the box.
|
Chris@17
|
480 this.$form.on('click', 'td.title', event => {
|
Chris@0
|
481 const $target = $(event.currentTarget);
|
Chris@17
|
482 $target
|
Chris@17
|
483 .closest('tr')
|
Chris@17
|
484 .find('input')
|
Chris@17
|
485 .trigger('click');
|
Chris@0
|
486 });
|
Chris@0
|
487
|
Chris@17
|
488 const searchBoxSelector =
|
Chris@17
|
489 '[data-drupal-selector="edit-override-controls-options-search"]';
|
Chris@17
|
490 const controlGroupSelector =
|
Chris@17
|
491 '[data-drupal-selector="edit-override-controls-group"]';
|
Chris@17
|
492 this.$form.on(
|
Chris@17
|
493 'formUpdated',
|
Chris@17
|
494 `${searchBoxSelector},${controlGroupSelector}`,
|
Chris@17
|
495 $.proxy(this.handleFilter, this),
|
Chris@17
|
496 );
|
Chris@0
|
497
|
Chris@0
|
498 this.$searchBox = this.$form.find(searchBoxSelector);
|
Chris@0
|
499 this.$controlGroup = this.$form.find(controlGroupSelector);
|
Chris@0
|
500
|
Chris@0
|
501 /**
|
Chris@0
|
502 * Get a list of option labels and their corresponding divs and maintain it
|
Chris@0
|
503 * in memory, so we have as little overhead as possible at keyup time.
|
Chris@0
|
504 */
|
Chris@0
|
505 this.options = this.getOptions(this.$form.find('.filterable-option'));
|
Chris@0
|
506
|
Chris@0
|
507 // Trap the ENTER key in the search box so that it doesn't submit the form.
|
Chris@17
|
508 this.$searchBox.on('keypress', event => {
|
Chris@0
|
509 if (event.which === 13) {
|
Chris@0
|
510 event.preventDefault();
|
Chris@0
|
511 }
|
Chris@0
|
512 });
|
Chris@0
|
513 };
|
Chris@0
|
514
|
Chris@17
|
515 $.extend(
|
Chris@17
|
516 Drupal.viewsUi.OptionsSearch.prototype,
|
Chris@17
|
517 /** @lends Drupal.viewsUi.OptionsSearch# */ {
|
Chris@17
|
518 /**
|
Chris@17
|
519 * Assemble a list of all the filterable options on the form.
|
Chris@17
|
520 *
|
Chris@17
|
521 * @param {jQuery} $allOptions
|
Chris@17
|
522 * A jQuery object representing the rows of filterable options to be
|
Chris@17
|
523 * shown and hidden depending on the user's search terms.
|
Chris@17
|
524 *
|
Chris@17
|
525 * @return {Array}
|
Chris@17
|
526 * An array of all the filterable options.
|
Chris@17
|
527 */
|
Chris@17
|
528 getOptions($allOptions) {
|
Chris@17
|
529 let $title;
|
Chris@17
|
530 let $description;
|
Chris@17
|
531 let $option;
|
Chris@17
|
532 const options = [];
|
Chris@17
|
533 const length = $allOptions.length;
|
Chris@17
|
534 for (let i = 0; i < length; i++) {
|
Chris@17
|
535 $option = $($allOptions[i]);
|
Chris@17
|
536 $title = $option.find('.title');
|
Chris@17
|
537 $description = $option.find('.description');
|
Chris@17
|
538 options[i] = {
|
Chris@17
|
539 // Search on the lowercase version of the title text + description.
|
Chris@17
|
540 searchText: `${$title
|
Chris@17
|
541 .text()
|
Chris@17
|
542 .toLowerCase()} ${$description.text().toLowerCase()}`,
|
Chris@17
|
543 // Maintain a reference to the jQuery object for each row, so we don't
|
Chris@17
|
544 // have to create a new object inside the performance-sensitive keyup
|
Chris@17
|
545 // handler.
|
Chris@17
|
546 $div: $option,
|
Chris@17
|
547 };
|
Chris@17
|
548 }
|
Chris@17
|
549 return options;
|
Chris@17
|
550 },
|
Chris@0
|
551
|
Chris@17
|
552 /**
|
Chris@17
|
553 * Filter handler for the search box and type select that hides or shows the relevant
|
Chris@17
|
554 * options.
|
Chris@17
|
555 *
|
Chris@17
|
556 * @param {jQuery.Event} event
|
Chris@17
|
557 * The formUpdated event.
|
Chris@17
|
558 */
|
Chris@17
|
559 handleFilter(event) {
|
Chris@17
|
560 // Determine the user's search query. The search text has been converted
|
Chris@17
|
561 // to lowercase.
|
Chris@17
|
562 const search = this.$searchBox.val().toLowerCase();
|
Chris@17
|
563 const words = search.split(' ');
|
Chris@17
|
564 // Get selected Group
|
Chris@17
|
565 const group = this.$controlGroup.val();
|
Chris@17
|
566
|
Chris@17
|
567 // Search through the search texts in the form for matching text.
|
Chris@17
|
568 this.options.forEach(option => {
|
Chris@17
|
569 function hasWord(word) {
|
Chris@17
|
570 return option.searchText.indexOf(word) !== -1;
|
Chris@17
|
571 }
|
Chris@17
|
572
|
Chris@17
|
573 let found = true;
|
Chris@17
|
574 // Each word in the search string has to match the item in order for the
|
Chris@17
|
575 // item to be shown.
|
Chris@17
|
576 if (search) {
|
Chris@17
|
577 found = words.every(hasWord);
|
Chris@17
|
578 }
|
Chris@17
|
579 if (found && group !== 'all') {
|
Chris@17
|
580 found = option.$div.hasClass(group);
|
Chris@17
|
581 }
|
Chris@17
|
582
|
Chris@17
|
583 option.$div.toggle(found);
|
Chris@17
|
584 });
|
Chris@17
|
585
|
Chris@17
|
586 // Adapt dialog to content size.
|
Chris@17
|
587 $(event.target).trigger('dialogContentResize');
|
Chris@17
|
588 },
|
Chris@0
|
589 },
|
Chris@17
|
590 );
|
Chris@0
|
591
|
Chris@0
|
592 /**
|
Chris@0
|
593 * Preview functionality in the views edit form.
|
Chris@0
|
594 *
|
Chris@0
|
595 * @type {Drupal~behavior}
|
Chris@0
|
596 *
|
Chris@0
|
597 * @prop {Drupal~behaviorAttach} attach
|
Chris@0
|
598 * Attaches the preview functionality to the view edit form.
|
Chris@0
|
599 */
|
Chris@0
|
600 Drupal.behaviors.viewsUiPreview = {
|
Chris@0
|
601 attach(context) {
|
Chris@0
|
602 // Only act on the edit view form.
|
Chris@17
|
603 const $contextualFiltersBucket = $(context).find(
|
Chris@17
|
604 '.views-display-column .views-ui-display-tab-bucket.argument',
|
Chris@17
|
605 );
|
Chris@0
|
606 if ($contextualFiltersBucket.length === 0) {
|
Chris@0
|
607 return;
|
Chris@0
|
608 }
|
Chris@0
|
609
|
Chris@0
|
610 // If the display has no contextual filters, hide the form where you
|
Chris@0
|
611 // enter the contextual filters for the live preview. If it has contextual
|
Chris@0
|
612 // filters, show the form.
|
Chris@17
|
613 const $contextualFilters = $contextualFiltersBucket.find(
|
Chris@17
|
614 '.views-display-setting a',
|
Chris@17
|
615 );
|
Chris@0
|
616 if ($contextualFilters.length) {
|
Chris@17
|
617 $('#preview-args')
|
Chris@17
|
618 .parent()
|
Chris@17
|
619 .show();
|
Chris@17
|
620 } else {
|
Chris@17
|
621 $('#preview-args')
|
Chris@17
|
622 .parent()
|
Chris@17
|
623 .hide();
|
Chris@0
|
624 }
|
Chris@0
|
625
|
Chris@0
|
626 // Executes an initial preview.
|
Chris@17
|
627 if (
|
Chris@17
|
628 $('#edit-displays-live-preview')
|
Chris@17
|
629 .once('edit-displays-live-preview')
|
Chris@17
|
630 .is(':checked')
|
Chris@17
|
631 ) {
|
Chris@17
|
632 $('#preview-submit')
|
Chris@17
|
633 .once('edit-displays-live-preview')
|
Chris@17
|
634 .trigger('click');
|
Chris@0
|
635 }
|
Chris@0
|
636 },
|
Chris@0
|
637 };
|
Chris@0
|
638
|
Chris@0
|
639 /**
|
Chris@0
|
640 * Rearranges the filters.
|
Chris@0
|
641 *
|
Chris@0
|
642 * @type {Drupal~behavior}
|
Chris@0
|
643 *
|
Chris@0
|
644 * @prop {Drupal~behaviorAttach} attach
|
Chris@17
|
645 * Attach handlers to make it possible to rearrange the filters in the form
|
Chris@0
|
646 * in question.
|
Chris@0
|
647 * @see Drupal.viewsUi.RearrangeFilterHandler
|
Chris@0
|
648 */
|
Chris@0
|
649 Drupal.behaviors.viewsUiRearrangeFilter = {
|
Chris@0
|
650 attach(context) {
|
Chris@0
|
651 // Only act on the rearrange filter form.
|
Chris@17
|
652 if (
|
Chris@17
|
653 typeof Drupal.tableDrag === 'undefined' ||
|
Chris@17
|
654 typeof Drupal.tableDrag['views-rearrange-filters'] === 'undefined'
|
Chris@17
|
655 ) {
|
Chris@0
|
656 return;
|
Chris@0
|
657 }
|
Chris@0
|
658 const $context = $(context);
|
Chris@17
|
659 const $table = $context
|
Chris@17
|
660 .find('#views-rearrange-filters')
|
Chris@17
|
661 .once('views-rearrange-filters');
|
Chris@17
|
662 const $operator = $context
|
Chris@17
|
663 .find('.js-form-item-filter-groups-operator')
|
Chris@17
|
664 .once('views-rearrange-filters');
|
Chris@0
|
665 if ($table.length) {
|
Chris@0
|
666 new Drupal.viewsUi.RearrangeFilterHandler($table, $operator);
|
Chris@0
|
667 }
|
Chris@0
|
668 },
|
Chris@0
|
669 };
|
Chris@0
|
670
|
Chris@0
|
671 /**
|
Chris@0
|
672 * Improve the UI of the rearrange filters dialog box.
|
Chris@0
|
673 *
|
Chris@0
|
674 * @constructor
|
Chris@0
|
675 *
|
Chris@0
|
676 * @param {jQuery} $table
|
Chris@0
|
677 * The table in the filter form.
|
Chris@0
|
678 * @param {jQuery} $operator
|
Chris@0
|
679 * The filter groups operator element.
|
Chris@0
|
680 */
|
Chris@17
|
681 Drupal.viewsUi.RearrangeFilterHandler = function($table, $operator) {
|
Chris@0
|
682 /**
|
Chris@0
|
683 * Keep a reference to the `<table>` being altered and to the div containing
|
Chris@0
|
684 * the filter groups operator dropdown (if it exists).
|
Chris@0
|
685 */
|
Chris@0
|
686 this.table = $table;
|
Chris@0
|
687
|
Chris@0
|
688 /**
|
Chris@0
|
689 *
|
Chris@0
|
690 * @type {jQuery}
|
Chris@0
|
691 */
|
Chris@0
|
692 this.operator = $operator;
|
Chris@0
|
693
|
Chris@0
|
694 /**
|
Chris@0
|
695 *
|
Chris@0
|
696 * @type {bool}
|
Chris@0
|
697 */
|
Chris@0
|
698 this.hasGroupOperator = this.operator.length > 0;
|
Chris@0
|
699
|
Chris@0
|
700 /**
|
Chris@0
|
701 * Keep a reference to all draggable rows within the table.
|
Chris@0
|
702 *
|
Chris@0
|
703 * @type {jQuery}
|
Chris@0
|
704 */
|
Chris@0
|
705 this.draggableRows = $table.find('.draggable');
|
Chris@0
|
706
|
Chris@0
|
707 /**
|
Chris@0
|
708 * Keep a reference to the buttons for adding and removing filter groups.
|
Chris@0
|
709 *
|
Chris@0
|
710 * @type {jQuery}
|
Chris@0
|
711 */
|
Chris@0
|
712 this.addGroupButton = $('input#views-add-group');
|
Chris@0
|
713
|
Chris@0
|
714 /**
|
Chris@0
|
715 * @type {jQuery}
|
Chris@0
|
716 */
|
Chris@0
|
717 this.removeGroupButtons = $table.find('input.views-remove-group');
|
Chris@0
|
718
|
Chris@0
|
719 // Add links that duplicate the functionality of the (hidden) add and remove
|
Chris@0
|
720 // buttons.
|
Chris@0
|
721 this.insertAddRemoveFilterGroupLinks();
|
Chris@0
|
722
|
Chris@0
|
723 // When there is a filter groups operator dropdown on the page, create
|
Chris@0
|
724 // duplicates of the dropdown between each pair of filter groups.
|
Chris@0
|
725 if (this.hasGroupOperator) {
|
Chris@0
|
726 /**
|
Chris@0
|
727 * @type {jQuery}
|
Chris@0
|
728 */
|
Chris@0
|
729 this.dropdowns = this.duplicateGroupsOperator();
|
Chris@0
|
730 this.syncGroupsOperators();
|
Chris@0
|
731 }
|
Chris@0
|
732
|
Chris@0
|
733 // Add methods to the tableDrag instance to account for operator cells
|
Chris@0
|
734 // (which span multiple rows), the operator labels next to each filter
|
Chris@0
|
735 // (e.g., "And" or "Or"), the filter groups, and other special aspects of
|
Chris@0
|
736 // this tableDrag instance.
|
Chris@0
|
737 this.modifyTableDrag();
|
Chris@0
|
738
|
Chris@0
|
739 // Initialize the operator labels (e.g., "And" or "Or") that are displayed
|
Chris@0
|
740 // next to the filters in each group, and bind a handler so that they change
|
Chris@0
|
741 // based on the values of the operator dropdown within that group.
|
Chris@0
|
742 this.redrawOperatorLabels();
|
Chris@17
|
743 $table
|
Chris@17
|
744 .find('.views-group-title select')
|
Chris@0
|
745 .once('views-rearrange-filter-handler')
|
Chris@17
|
746 .on(
|
Chris@17
|
747 'change.views-rearrange-filter-handler',
|
Chris@17
|
748 $.proxy(this, 'redrawOperatorLabels'),
|
Chris@17
|
749 );
|
Chris@0
|
750
|
Chris@0
|
751 // Bind handlers so that when a "Remove" link is clicked, we:
|
Chris@0
|
752 // - Update the rowspans of cells containing an operator dropdown (since
|
Chris@0
|
753 // they need to change to reflect the number of rows in each group).
|
Chris@0
|
754 // - Redraw the operator labels next to the filters in the group (since the
|
Chris@0
|
755 // filter that is currently displayed last in each group is not supposed
|
Chris@0
|
756 // to have a label display next to it).
|
Chris@17
|
757 $table
|
Chris@17
|
758 .find('a.views-groups-remove-link')
|
Chris@0
|
759 .once('views-rearrange-filter-handler')
|
Chris@17
|
760 .on(
|
Chris@17
|
761 'click.views-rearrange-filter-handler',
|
Chris@17
|
762 $.proxy(this, 'updateRowspans'),
|
Chris@17
|
763 )
|
Chris@17
|
764 .on(
|
Chris@17
|
765 'click.views-rearrange-filter-handler',
|
Chris@17
|
766 $.proxy(this, 'redrawOperatorLabels'),
|
Chris@17
|
767 );
|
Chris@0
|
768 };
|
Chris@0
|
769
|
Chris@17
|
770 $.extend(
|
Chris@17
|
771 Drupal.viewsUi.RearrangeFilterHandler.prototype,
|
Chris@17
|
772 /** @lends Drupal.viewsUi.RearrangeFilterHandler# */ {
|
Chris@17
|
773 /**
|
Chris@17
|
774 * Insert links that allow filter groups to be added and removed.
|
Chris@17
|
775 */
|
Chris@17
|
776 insertAddRemoveFilterGroupLinks() {
|
Chris@17
|
777 // Insert a link for adding a new group at the top of the page, and make
|
Chris@17
|
778 // it match the action link styling used in a typical page.html.twig.
|
Chris@17
|
779 // Since Drupal does not provide a theme function for this markup this is
|
Chris@17
|
780 // the best we can do.
|
Chris@17
|
781 $(
|
Chris@17
|
782 `<ul class="action-links"><li><a id="views-add-group-link" href="#">${this.addGroupButton.val()}</a></li></ul>`,
|
Chris@17
|
783 )
|
Chris@17
|
784 .prependTo(this.table.parent())
|
Chris@17
|
785 // When the link is clicked, dynamically click the hidden form button
|
Chris@17
|
786 // for adding a new filter group.
|
Chris@17
|
787 .once('views-rearrange-filter-handler')
|
Chris@17
|
788 .find('#views-add-group-link')
|
Chris@17
|
789 .on(
|
Chris@17
|
790 'click.views-rearrange-filter-handler',
|
Chris@17
|
791 $.proxy(this, 'clickAddGroupButton'),
|
Chris@17
|
792 );
|
Chris@0
|
793
|
Chris@17
|
794 // Find each (visually hidden) button for removing a filter group and
|
Chris@17
|
795 // insert a link next to it.
|
Chris@17
|
796 const length = this.removeGroupButtons.length;
|
Chris@17
|
797 let i;
|
Chris@17
|
798 for (i = 0; i < length; i++) {
|
Chris@17
|
799 const $removeGroupButton = $(this.removeGroupButtons[i]);
|
Chris@17
|
800 const buttonId = $removeGroupButton.attr('id');
|
Chris@17
|
801 $(
|
Chris@17
|
802 `<a href="#" class="views-remove-group-link">${Drupal.t(
|
Chris@17
|
803 'Remove group',
|
Chris@17
|
804 )}</a>`,
|
Chris@17
|
805 )
|
Chris@17
|
806 .insertBefore($removeGroupButton)
|
Chris@17
|
807 // When the link is clicked, dynamically click the corresponding form
|
Chris@17
|
808 // button.
|
Chris@17
|
809 .once('views-rearrange-filter-handler')
|
Chris@17
|
810 .on(
|
Chris@17
|
811 'click.views-rearrange-filter-handler',
|
Chris@17
|
812 { buttonId },
|
Chris@17
|
813 $.proxy(this, 'clickRemoveGroupButton'),
|
Chris@17
|
814 );
|
Chris@17
|
815 }
|
Chris@17
|
816 },
|
Chris@0
|
817
|
Chris@0
|
818 /**
|
Chris@17
|
819 * Dynamically click the button that adds a new filter group.
|
Chris@0
|
820 *
|
Chris@17
|
821 * @param {jQuery.Event} event
|
Chris@17
|
822 * The event triggered.
|
Chris@0
|
823 */
|
Chris@17
|
824 clickAddGroupButton(event) {
|
Chris@17
|
825 this.addGroupButton.trigger('mousedown');
|
Chris@17
|
826 event.preventDefault();
|
Chris@17
|
827 },
|
Chris@17
|
828
|
Chris@17
|
829 /**
|
Chris@17
|
830 * Dynamically click a button for removing a filter group.
|
Chris@17
|
831 *
|
Chris@17
|
832 * @param {jQuery.Event} event
|
Chris@17
|
833 * Event being triggered, with event.data.buttonId set to the ID of the
|
Chris@17
|
834 * form button that should be clicked.
|
Chris@17
|
835 */
|
Chris@17
|
836 clickRemoveGroupButton(event) {
|
Chris@17
|
837 this.table.find(`#${event.data.buttonId}`).trigger('mousedown');
|
Chris@17
|
838 event.preventDefault();
|
Chris@17
|
839 },
|
Chris@17
|
840
|
Chris@17
|
841 /**
|
Chris@17
|
842 * Move the groups operator so that it's between the first two groups, and
|
Chris@17
|
843 * duplicate it between any subsequent groups.
|
Chris@17
|
844 *
|
Chris@17
|
845 * @return {jQuery}
|
Chris@17
|
846 * An operator element.
|
Chris@17
|
847 */
|
Chris@17
|
848 duplicateGroupsOperator() {
|
Chris@17
|
849 let newRow;
|
Chris@17
|
850 let titleRow;
|
Chris@17
|
851
|
Chris@17
|
852 const titleRows = $('tr.views-group-title').once(
|
Chris@17
|
853 'duplicateGroupsOperator',
|
Chris@17
|
854 );
|
Chris@17
|
855
|
Chris@17
|
856 if (!titleRows.length) {
|
Chris@17
|
857 return this.operator;
|
Chris@17
|
858 }
|
Chris@17
|
859
|
Chris@17
|
860 // Get rid of the explanatory text around the operator; its placement is
|
Chris@17
|
861 // explanatory enough.
|
Chris@17
|
862 this.operator
|
Chris@17
|
863 .find('label')
|
Chris@17
|
864 .add('div.description')
|
Chris@17
|
865 .addClass('visually-hidden');
|
Chris@17
|
866 this.operator.find('select').addClass('form-select');
|
Chris@17
|
867
|
Chris@17
|
868 // Keep a list of the operator dropdowns, so we can sync their behavior
|
Chris@17
|
869 // later.
|
Chris@17
|
870 const dropdowns = this.operator;
|
Chris@17
|
871
|
Chris@17
|
872 // Move the operator to a new row just above the second group.
|
Chris@17
|
873 titleRow = $('tr#views-group-title-2');
|
Chris@17
|
874 newRow = $(
|
Chris@17
|
875 '<tr class="filter-group-operator-row"><td colspan="5"></td></tr>',
|
Chris@17
|
876 );
|
Chris@17
|
877 newRow.find('td').append(this.operator);
|
Chris@17
|
878 newRow.insertBefore(titleRow);
|
Chris@17
|
879 const length = titleRows.length;
|
Chris@17
|
880 // Starting with the third group, copy the operator to a new row above the
|
Chris@17
|
881 // group title.
|
Chris@17
|
882 for (let i = 2; i < length; i++) {
|
Chris@17
|
883 titleRow = $(titleRows[i]);
|
Chris@17
|
884 // Make a copy of the operator dropdown and put it in a new table row.
|
Chris@17
|
885 const fakeOperator = this.operator.clone();
|
Chris@17
|
886 fakeOperator.attr('id', '');
|
Chris@17
|
887 newRow = $(
|
Chris@17
|
888 '<tr class="filter-group-operator-row"><td colspan="5"></td></tr>',
|
Chris@17
|
889 );
|
Chris@17
|
890 newRow.find('td').append(fakeOperator);
|
Chris@17
|
891 newRow.insertBefore(titleRow);
|
Chris@17
|
892 dropdowns.add(fakeOperator);
|
Chris@17
|
893 }
|
Chris@17
|
894
|
Chris@17
|
895 return dropdowns;
|
Chris@17
|
896 },
|
Chris@17
|
897
|
Chris@17
|
898 /**
|
Chris@17
|
899 * Make the duplicated groups operators change in sync with each other.
|
Chris@17
|
900 */
|
Chris@17
|
901 syncGroupsOperators() {
|
Chris@17
|
902 if (this.dropdowns.length < 2) {
|
Chris@17
|
903 // We only have one dropdown (or none at all), so there's nothing to
|
Chris@17
|
904 // sync.
|
Chris@17
|
905 return;
|
Chris@17
|
906 }
|
Chris@17
|
907
|
Chris@17
|
908 this.dropdowns.on('change', $.proxy(this, 'operatorChangeHandler'));
|
Chris@17
|
909 },
|
Chris@17
|
910
|
Chris@17
|
911 /**
|
Chris@17
|
912 * Click handler for the operators that appear between filter groups.
|
Chris@17
|
913 *
|
Chris@17
|
914 * Forces all operator dropdowns to have the same value.
|
Chris@17
|
915 *
|
Chris@17
|
916 * @param {jQuery.Event} event
|
Chris@17
|
917 * The event triggered.
|
Chris@17
|
918 */
|
Chris@17
|
919 operatorChangeHandler(event) {
|
Chris@17
|
920 const $target = $(event.target);
|
Chris@17
|
921 const operators = this.dropdowns.find('select').not($target);
|
Chris@17
|
922
|
Chris@17
|
923 // Change the other operators to match this new value.
|
Chris@17
|
924 operators.val($target.val());
|
Chris@17
|
925 },
|
Chris@17
|
926
|
Chris@17
|
927 /**
|
Chris@17
|
928 * @method
|
Chris@17
|
929 */
|
Chris@17
|
930 modifyTableDrag() {
|
Chris@17
|
931 const tableDrag = Drupal.tableDrag['views-rearrange-filters'];
|
Chris@17
|
932 const filterHandler = this;
|
Chris@17
|
933
|
Chris@17
|
934 /**
|
Chris@17
|
935 * Override the row.onSwap method from tabledrag.js.
|
Chris@17
|
936 *
|
Chris@17
|
937 * When a row is dragged to another place in the table, several things
|
Chris@17
|
938 * need to occur.
|
Chris@17
|
939 * - The row needs to be moved so that it's within one of the filter
|
Chris@17
|
940 * groups.
|
Chris@17
|
941 * - The operator cells that span multiple rows need their rowspan
|
Chris@17
|
942 * attributes updated to reflect the number of rows in each group.
|
Chris@17
|
943 * - The operator labels that are displayed next to each filter need to
|
Chris@17
|
944 * be redrawn, to account for the row's new location.
|
Chris@17
|
945 */
|
Chris@17
|
946 tableDrag.row.prototype.onSwap = function() {
|
Chris@17
|
947 if (filterHandler.hasGroupOperator) {
|
Chris@17
|
948 // Make sure the row that just got moved (this.group) is inside one
|
Chris@17
|
949 // of the filter groups (i.e. below an empty marker row or a
|
Chris@17
|
950 // draggable). If it isn't, move it down one.
|
Chris@17
|
951 const thisRow = $(this.group);
|
Chris@17
|
952 const previousRow = thisRow.prev('tr');
|
Chris@17
|
953 if (
|
Chris@17
|
954 previousRow.length &&
|
Chris@17
|
955 !previousRow.hasClass('group-message') &&
|
Chris@17
|
956 !previousRow.hasClass('draggable')
|
Chris@17
|
957 ) {
|
Chris@17
|
958 // Move the dragged row down one.
|
Chris@17
|
959 const next = thisRow.next();
|
Chris@17
|
960 if (next.is('tr')) {
|
Chris@17
|
961 this.swap('after', next);
|
Chris@17
|
962 }
|
Chris@17
|
963 }
|
Chris@17
|
964 filterHandler.updateRowspans();
|
Chris@17
|
965 }
|
Chris@17
|
966 // Redraw the operator labels that are displayed next to each filter, to
|
Chris@17
|
967 // account for the row's new location.
|
Chris@17
|
968 filterHandler.redrawOperatorLabels();
|
Chris@17
|
969 };
|
Chris@17
|
970
|
Chris@17
|
971 /**
|
Chris@17
|
972 * Override the onDrop method from tabledrag.js.
|
Chris@17
|
973 */
|
Chris@17
|
974 tableDrag.onDrop = function() {
|
Chris@17
|
975 // If the tabledrag change marker (i.e., the "*") has been inserted
|
Chris@17
|
976 // inside a row after the operator label (i.e., "And" or "Or")
|
Chris@17
|
977 // rearrange the items so the operator label continues to appear last.
|
Chris@17
|
978 const changeMarker = $(this.oldRowElement).find('.tabledrag-changed');
|
Chris@17
|
979 if (changeMarker.length) {
|
Chris@17
|
980 // Search for occurrences of the operator label before the change
|
Chris@17
|
981 // marker, and reverse them.
|
Chris@17
|
982 const operatorLabel = changeMarker.prevAll('.views-operator-label');
|
Chris@17
|
983 if (operatorLabel.length) {
|
Chris@17
|
984 operatorLabel.insertAfter(changeMarker);
|
Chris@0
|
985 }
|
Chris@0
|
986 }
|
Chris@17
|
987
|
Chris@17
|
988 // Make sure the "group" dropdown is properly updated when rows are
|
Chris@17
|
989 // dragged into an empty filter group. This is borrowed heavily from
|
Chris@17
|
990 // the block.js implementation of tableDrag.onDrop().
|
Chris@17
|
991 const groupRow = $(this.rowObject.element)
|
Chris@17
|
992 .prevAll('tr.group-message')
|
Chris@17
|
993 .get(0);
|
Chris@17
|
994 const groupName = groupRow.className.replace(
|
Chris@17
|
995 /([^ ]+[ ]+)*group-([^ ]+)-message([ ]+[^ ]+)*/,
|
Chris@17
|
996 '$2',
|
Chris@17
|
997 );
|
Chris@17
|
998 const groupField = $(
|
Chris@17
|
999 'select.views-group-select',
|
Chris@17
|
1000 this.rowObject.element,
|
Chris@17
|
1001 );
|
Chris@18
|
1002 if (!groupField.is(`.views-group-select-${groupName}`)) {
|
Chris@17
|
1003 const oldGroupName = groupField
|
Chris@17
|
1004 .attr('class')
|
Chris@17
|
1005 .replace(
|
Chris@17
|
1006 /([^ ]+[ ]+)*views-group-select-([^ ]+)([ ]+[^ ]+)*/,
|
Chris@17
|
1007 '$2',
|
Chris@17
|
1008 );
|
Chris@17
|
1009 groupField
|
Chris@17
|
1010 .removeClass(`views-group-select-${oldGroupName}`)
|
Chris@17
|
1011 .addClass(`views-group-select-${groupName}`);
|
Chris@17
|
1012 groupField.val(groupName);
|
Chris@17
|
1013 }
|
Chris@17
|
1014 };
|
Chris@17
|
1015 },
|
Chris@0
|
1016
|
Chris@0
|
1017 /**
|
Chris@17
|
1018 * Redraw the operator labels that are displayed next to each filter.
|
Chris@0
|
1019 */
|
Chris@17
|
1020 redrawOperatorLabels() {
|
Chris@17
|
1021 for (let i = 0; i < this.draggableRows.length; i++) {
|
Chris@17
|
1022 // Within the row, the operator labels are displayed inside the first
|
Chris@17
|
1023 // table cell (next to the filter name).
|
Chris@17
|
1024 const $draggableRow = $(this.draggableRows[i]);
|
Chris@17
|
1025 const $firstCell = $draggableRow.find('td').eq(0);
|
Chris@17
|
1026 if ($firstCell.length) {
|
Chris@17
|
1027 // The value of the operator label ("And" or "Or") is taken from the
|
Chris@17
|
1028 // first operator dropdown we encounter, going backwards from the
|
Chris@17
|
1029 // current row. This dropdown is the one associated with the current
|
Chris@17
|
1030 // row's filter group.
|
Chris@17
|
1031 const operatorValue = $draggableRow
|
Chris@17
|
1032 .prevAll('.views-group-title')
|
Chris@17
|
1033 .find('option:selected')
|
Chris@17
|
1034 .html();
|
Chris@17
|
1035 const operatorLabel = `<span class="views-operator-label">${operatorValue}</span>`;
|
Chris@17
|
1036 // If the next visible row after this one is a draggable filter row,
|
Chris@17
|
1037 // display the operator label next to the current row. (Checking for
|
Chris@17
|
1038 // visibility is necessary here since the "Remove" links hide the
|
Chris@17
|
1039 // removed row but don't actually remove it from the document).
|
Chris@17
|
1040 const $nextRow = $draggableRow.nextAll(':visible').eq(0);
|
Chris@17
|
1041 const $existingOperatorLabel = $firstCell.find(
|
Chris@17
|
1042 '.views-operator-label',
|
Chris@17
|
1043 );
|
Chris@17
|
1044 if ($nextRow.hasClass('draggable')) {
|
Chris@17
|
1045 // If an operator label was already there, replace it with the new
|
Chris@17
|
1046 // one.
|
Chris@17
|
1047 if ($existingOperatorLabel.length) {
|
Chris@17
|
1048 $existingOperatorLabel.replaceWith(operatorLabel);
|
Chris@17
|
1049 }
|
Chris@17
|
1050 // Otherwise, append the operator label to the end of the table
|
Chris@17
|
1051 // cell.
|
Chris@17
|
1052 else {
|
Chris@17
|
1053 $firstCell.append(operatorLabel);
|
Chris@17
|
1054 }
|
Chris@17
|
1055 }
|
Chris@17
|
1056 // If the next row doesn't contain a filter, then this is the last row
|
Chris@17
|
1057 // in the group. We don't want to display the operator there (since
|
Chris@17
|
1058 // operators should only display between two related filters, e.g.
|
Chris@17
|
1059 // "filter1 AND filter2 AND filter3"). So we remove any existing label
|
Chris@17
|
1060 // that this row has.
|
Chris@17
|
1061 else {
|
Chris@17
|
1062 $existingOperatorLabel.remove();
|
Chris@17
|
1063 }
|
Chris@0
|
1064 }
|
Chris@0
|
1065 }
|
Chris@17
|
1066 },
|
Chris@0
|
1067
|
Chris@17
|
1068 /**
|
Chris@17
|
1069 * Update the rowspan attribute of each cell containing an operator
|
Chris@17
|
1070 * dropdown.
|
Chris@17
|
1071 */
|
Chris@17
|
1072 updateRowspans() {
|
Chris@17
|
1073 let $row;
|
Chris@17
|
1074 let $currentEmptyRow;
|
Chris@17
|
1075 let draggableCount;
|
Chris@17
|
1076 let $operatorCell;
|
Chris@17
|
1077 const rows = $(this.table).find('tr');
|
Chris@17
|
1078 const length = rows.length;
|
Chris@17
|
1079 for (let i = 0; i < length; i++) {
|
Chris@17
|
1080 $row = $(rows[i]);
|
Chris@17
|
1081 if ($row.hasClass('views-group-title')) {
|
Chris@17
|
1082 // This row is a title row.
|
Chris@17
|
1083 // Keep a reference to the cell containing the dropdown operator.
|
Chris@17
|
1084 $operatorCell = $row.find('td.group-operator');
|
Chris@17
|
1085 // Assume this filter group is empty, until we find otherwise.
|
Chris@17
|
1086 draggableCount = 0;
|
Chris@17
|
1087 $currentEmptyRow = $row.next('tr');
|
Chris@17
|
1088 $currentEmptyRow
|
Chris@17
|
1089 .removeClass('group-populated')
|
Chris@17
|
1090 .addClass('group-empty');
|
Chris@17
|
1091 // The cell with the dropdown operator should span the title row and
|
Chris@17
|
1092 // the "this group is empty" row.
|
Chris@17
|
1093 $operatorCell.attr('rowspan', 2);
|
Chris@17
|
1094 } else if ($row.hasClass('draggable') && $row.is(':visible')) {
|
Chris@17
|
1095 // We've found a visible filter row, so we now know the group isn't
|
Chris@17
|
1096 // empty.
|
Chris@17
|
1097 draggableCount++;
|
Chris@17
|
1098 $currentEmptyRow
|
Chris@17
|
1099 .removeClass('group-empty')
|
Chris@17
|
1100 .addClass('group-populated');
|
Chris@17
|
1101 // The operator cell should span all draggable rows, plus the title.
|
Chris@17
|
1102 $operatorCell.attr('rowspan', draggableCount + 1);
|
Chris@0
|
1103 }
|
Chris@0
|
1104 }
|
Chris@17
|
1105 },
|
Chris@0
|
1106 },
|
Chris@17
|
1107 );
|
Chris@0
|
1108
|
Chris@0
|
1109 /**
|
Chris@0
|
1110 * Add a select all checkbox, which checks each checkbox at once.
|
Chris@0
|
1111 *
|
Chris@0
|
1112 * @type {Drupal~behavior}
|
Chris@0
|
1113 *
|
Chris@0
|
1114 * @prop {Drupal~behaviorAttach} attach
|
Chris@0
|
1115 * Attaches select all functionality to the views filter form.
|
Chris@0
|
1116 */
|
Chris@0
|
1117 Drupal.behaviors.viewsFilterConfigSelectAll = {
|
Chris@0
|
1118 attach(context) {
|
Chris@0
|
1119 const $context = $(context);
|
Chris@0
|
1120
|
Chris@17
|
1121 const $selectAll = $context
|
Chris@17
|
1122 .find('.js-form-item-options-value-all')
|
Chris@17
|
1123 .once('filterConfigSelectAll');
|
Chris@0
|
1124 const $selectAllCheckbox = $selectAll.find('input[type=checkbox]');
|
Chris@17
|
1125 const $checkboxes = $selectAll
|
Chris@17
|
1126 .closest('.form-checkboxes')
|
Chris@17
|
1127 .find(
|
Chris@17
|
1128 '.js-form-type-checkbox:not(.js-form-item-options-value-all) input[type="checkbox"]',
|
Chris@17
|
1129 );
|
Chris@0
|
1130
|
Chris@0
|
1131 if ($selectAll.length) {
|
Chris@17
|
1132 // Show the select all checkbox.
|
Chris@0
|
1133 $selectAll.show();
|
Chris@17
|
1134 $selectAllCheckbox.on('click', function() {
|
Chris@0
|
1135 // Update all checkbox beside the select all checkbox.
|
Chris@0
|
1136 $checkboxes.prop('checked', $(this).is(':checked'));
|
Chris@0
|
1137 });
|
Chris@0
|
1138
|
Chris@0
|
1139 // Uncheck the select all checkbox if any of the others are unchecked.
|
Chris@17
|
1140 $checkboxes.on('click', function() {
|
Chris@0
|
1141 if ($(this).is('checked') === false) {
|
Chris@0
|
1142 $selectAllCheckbox.prop('checked', false);
|
Chris@0
|
1143 }
|
Chris@0
|
1144 });
|
Chris@0
|
1145 }
|
Chris@0
|
1146 },
|
Chris@0
|
1147 };
|
Chris@0
|
1148
|
Chris@0
|
1149 /**
|
Chris@0
|
1150 * Remove icon class from elements that are themed as buttons or dropbuttons.
|
Chris@0
|
1151 *
|
Chris@0
|
1152 * @type {Drupal~behavior}
|
Chris@0
|
1153 *
|
Chris@0
|
1154 * @prop {Drupal~behaviorAttach} attach
|
Chris@0
|
1155 * Removes the icon class from certain views elements.
|
Chris@0
|
1156 */
|
Chris@0
|
1157 Drupal.behaviors.viewsRemoveIconClass = {
|
Chris@0
|
1158 attach(context) {
|
Chris@14
|
1159 $(context)
|
Chris@14
|
1160 .find('.dropbutton')
|
Chris@14
|
1161 .once('dropbutton-icon')
|
Chris@14
|
1162 .find('.icon')
|
Chris@14
|
1163 .removeClass('icon');
|
Chris@0
|
1164 },
|
Chris@0
|
1165 };
|
Chris@0
|
1166
|
Chris@0
|
1167 /**
|
Chris@0
|
1168 * Change "Expose filter" buttons into checkboxes.
|
Chris@0
|
1169 *
|
Chris@0
|
1170 * @type {Drupal~behavior}
|
Chris@0
|
1171 *
|
Chris@0
|
1172 * @prop {Drupal~behaviorAttach} attach
|
Chris@0
|
1173 * Changes buttons into checkboxes via {@link Drupal.viewsUi.Checkboxifier}.
|
Chris@0
|
1174 */
|
Chris@0
|
1175 Drupal.behaviors.viewsUiCheckboxify = {
|
Chris@0
|
1176 attach(context, settings) {
|
Chris@17
|
1177 const $buttons = $(
|
Chris@17
|
1178 '[data-drupal-selector="edit-options-expose-button-button"], [data-drupal-selector="edit-options-group-button-button"]',
|
Chris@17
|
1179 ).once('views-ui-checkboxify');
|
Chris@0
|
1180 const length = $buttons.length;
|
Chris@0
|
1181 let i;
|
Chris@0
|
1182 for (i = 0; i < length; i++) {
|
Chris@0
|
1183 new Drupal.viewsUi.Checkboxifier($buttons[i]);
|
Chris@0
|
1184 }
|
Chris@0
|
1185 },
|
Chris@0
|
1186 };
|
Chris@0
|
1187
|
Chris@0
|
1188 /**
|
Chris@0
|
1189 * Change the default widget to select the default group according to the
|
Chris@0
|
1190 * selected widget for the exposed group.
|
Chris@0
|
1191 *
|
Chris@0
|
1192 * @type {Drupal~behavior}
|
Chris@0
|
1193 *
|
Chris@0
|
1194 * @prop {Drupal~behaviorAttach} attach
|
Chris@0
|
1195 * Changes the default widget based on user input.
|
Chris@0
|
1196 */
|
Chris@0
|
1197 Drupal.behaviors.viewsUiChangeDefaultWidget = {
|
Chris@0
|
1198 attach(context) {
|
Chris@0
|
1199 const $context = $(context);
|
Chris@0
|
1200
|
Chris@0
|
1201 function changeDefaultWidget(event) {
|
Chris@0
|
1202 if ($(event.target).prop('checked')) {
|
Chris@17
|
1203 $context
|
Chris@17
|
1204 .find('input.default-radios')
|
Chris@17
|
1205 .parent()
|
Chris@17
|
1206 .hide();
|
Chris@17
|
1207 $context
|
Chris@17
|
1208 .find('td.any-default-radios-row')
|
Chris@17
|
1209 .parent()
|
Chris@17
|
1210 .hide();
|
Chris@17
|
1211 $context
|
Chris@17
|
1212 .find('input.default-checkboxes')
|
Chris@17
|
1213 .parent()
|
Chris@17
|
1214 .show();
|
Chris@17
|
1215 } else {
|
Chris@17
|
1216 $context
|
Chris@17
|
1217 .find('input.default-checkboxes')
|
Chris@17
|
1218 .parent()
|
Chris@17
|
1219 .hide();
|
Chris@17
|
1220 $context
|
Chris@17
|
1221 .find('td.any-default-radios-row')
|
Chris@17
|
1222 .parent()
|
Chris@17
|
1223 .show();
|
Chris@17
|
1224 $context
|
Chris@17
|
1225 .find('input.default-radios')
|
Chris@17
|
1226 .parent()
|
Chris@17
|
1227 .show();
|
Chris@0
|
1228 }
|
Chris@0
|
1229 }
|
Chris@0
|
1230
|
Chris@0
|
1231 // Update on widget change.
|
Chris@17
|
1232 $context
|
Chris@17
|
1233 .find('input[name="options[group_info][multiple]"]')
|
Chris@0
|
1234 .on('change', changeDefaultWidget)
|
Chris@0
|
1235 // Update the first time the form is rendered.
|
Chris@0
|
1236 .trigger('change');
|
Chris@0
|
1237 },
|
Chris@0
|
1238 };
|
Chris@0
|
1239
|
Chris@0
|
1240 /**
|
Chris@0
|
1241 * Attaches expose filter button to a checkbox that triggers its click event.
|
Chris@0
|
1242 *
|
Chris@0
|
1243 * @constructor
|
Chris@0
|
1244 *
|
Chris@0
|
1245 * @param {HTMLElement} button
|
Chris@0
|
1246 * The DOM object representing the button to be checkboxified.
|
Chris@0
|
1247 */
|
Chris@17
|
1248 Drupal.viewsUi.Checkboxifier = function(button) {
|
Chris@0
|
1249 this.$button = $(button);
|
Chris@0
|
1250 this.$parent = this.$button.parent('div.views-expose, div.views-grouped');
|
Chris@0
|
1251 this.$input = this.$parent.find('input:checkbox, input:radio');
|
Chris@0
|
1252 // Hide the button and its description.
|
Chris@0
|
1253 this.$button.hide();
|
Chris@0
|
1254 this.$parent.find('.exposed-description, .grouped-description').hide();
|
Chris@0
|
1255
|
Chris@0
|
1256 this.$input.on('click', $.proxy(this, 'clickHandler'));
|
Chris@0
|
1257 };
|
Chris@0
|
1258
|
Chris@0
|
1259 /**
|
Chris@0
|
1260 * When the checkbox is checked or unchecked, simulate a button press.
|
Chris@0
|
1261 *
|
Chris@0
|
1262 * @param {jQuery.Event} e
|
Chris@0
|
1263 * The event triggered.
|
Chris@0
|
1264 */
|
Chris@17
|
1265 Drupal.viewsUi.Checkboxifier.prototype.clickHandler = function(e) {
|
Chris@17
|
1266 this.$button.trigger('click').trigger('submit');
|
Chris@0
|
1267 };
|
Chris@0
|
1268
|
Chris@0
|
1269 /**
|
Chris@0
|
1270 * Change the Apply button text based upon the override select state.
|
Chris@0
|
1271 *
|
Chris@0
|
1272 * @type {Drupal~behavior}
|
Chris@0
|
1273 *
|
Chris@0
|
1274 * @prop {Drupal~behaviorAttach} attach
|
Chris@0
|
1275 * Attaches behavior to change the Apply button according to the current
|
Chris@0
|
1276 * state.
|
Chris@0
|
1277 */
|
Chris@0
|
1278 Drupal.behaviors.viewsUiOverrideSelect = {
|
Chris@0
|
1279 attach(context) {
|
Chris@17
|
1280 $(context)
|
Chris@17
|
1281 .find('[data-drupal-selector="edit-override-dropdown"]')
|
Chris@17
|
1282 .once('views-ui-override-button-text')
|
Chris@17
|
1283 .each(function() {
|
Chris@17
|
1284 // Closures! :(
|
Chris@17
|
1285 const $context = $(context);
|
Chris@17
|
1286 const $submit = $context.find('[id^=edit-submit]');
|
Chris@17
|
1287 const oldValue = $submit.val();
|
Chris@0
|
1288
|
Chris@17
|
1289 $submit
|
Chris@17
|
1290 .once('views-ui-override-button-text')
|
Chris@17
|
1291 .on('mouseup', function() {
|
Chris@17
|
1292 $(this).val(oldValue);
|
Chris@17
|
1293 return true;
|
Chris@17
|
1294 });
|
Chris@0
|
1295
|
Chris@17
|
1296 $(this)
|
Chris@17
|
1297 .on('change', function() {
|
Chris@17
|
1298 const $this = $(this);
|
Chris@17
|
1299 if ($this.val() === 'default') {
|
Chris@17
|
1300 $submit.val(Drupal.t('Apply (all displays)'));
|
Chris@17
|
1301 } else if ($this.val() === 'default_revert') {
|
Chris@17
|
1302 $submit.val(Drupal.t('Revert to default'));
|
Chris@17
|
1303 } else {
|
Chris@17
|
1304 $submit.val(Drupal.t('Apply (this display)'));
|
Chris@17
|
1305 }
|
Chris@17
|
1306 const $dialog = $context.closest('.ui-dialog-content');
|
Chris@17
|
1307 $dialog.trigger('dialogButtonsChange');
|
Chris@17
|
1308 })
|
Chris@17
|
1309 .trigger('change');
|
Chris@17
|
1310 });
|
Chris@0
|
1311 },
|
Chris@0
|
1312 };
|
Chris@0
|
1313
|
Chris@0
|
1314 /**
|
Chris@0
|
1315 * Functionality for the remove link in the views UI.
|
Chris@0
|
1316 *
|
Chris@0
|
1317 * @type {Drupal~behavior}
|
Chris@0
|
1318 *
|
Chris@0
|
1319 * @prop {Drupal~behaviorAttach} attach
|
Chris@0
|
1320 * Attaches behavior for the remove view and remove display links.
|
Chris@0
|
1321 */
|
Chris@0
|
1322 Drupal.behaviors.viewsUiHandlerRemoveLink = {
|
Chris@0
|
1323 attach(context) {
|
Chris@0
|
1324 const $context = $(context);
|
Chris@0
|
1325 // Handle handler deletion by looking for the hidden checkbox and hiding
|
Chris@0
|
1326 // the row.
|
Chris@17
|
1327 $context
|
Chris@17
|
1328 .find('a.views-remove-link')
|
Chris@17
|
1329 .once('views')
|
Chris@17
|
1330 .on('click', function(event) {
|
Chris@17
|
1331 const id = $(this)
|
Chris@17
|
1332 .attr('id')
|
Chris@17
|
1333 .replace('views-remove-link-', '');
|
Chris@17
|
1334 $context.find(`#views-row-${id}`).hide();
|
Chris@17
|
1335 $context.find(`#views-removed-${id}`).prop('checked', true);
|
Chris@17
|
1336 event.preventDefault();
|
Chris@17
|
1337 });
|
Chris@0
|
1338
|
Chris@0
|
1339 // Handle display deletion by looking for the hidden checkbox and hiding
|
Chris@0
|
1340 // the row.
|
Chris@17
|
1341 $context
|
Chris@17
|
1342 .find('a.display-remove-link')
|
Chris@17
|
1343 .once('display')
|
Chris@17
|
1344 .on('click', function(event) {
|
Chris@17
|
1345 const id = $(this)
|
Chris@17
|
1346 .attr('id')
|
Chris@17
|
1347 .replace('display-remove-link-', '');
|
Chris@17
|
1348 $context.find(`#display-row-${id}`).hide();
|
Chris@17
|
1349 $context.find(`#display-removed-${id}`).prop('checked', true);
|
Chris@17
|
1350 event.preventDefault();
|
Chris@17
|
1351 });
|
Chris@0
|
1352 },
|
Chris@0
|
1353 };
|
Chris@17
|
1354 })(jQuery, Drupal, drupalSettings);
|