PageSourceSearch

https://portalbelohorizonte.com.br/modules/contrib/chosen/js/chosen.js?tl272o

js portalbelohorizonte.com.br collected 2026-09-24 20:50:31 UTC 11,120 bytes, 333 lines download raw bytes

1/**
2 * @file
3 * Attaches behaviors for the Chosen module.
4 */
5(function(Drupal, drupalSettings, once) {
6  'use strict';
7
8  // Update Chosen elements when state has changed.
9  document.addEventListener('state:disabled', function(e) {
10    if (e.target && e.target.matches('select')) {
11      const event = new Event('chosen:updated', { bubbles: true, cancelable: true });
12      e.target.dispatchEvent(event);
13    }
14  });
15
16  Drupal.behaviors.chosen = {
17
18    settings: {
19
20      /**
21       * Completely ignores elements that match one of these selectors.
22       *
23       * Disabled on:
24       * - Field UI
25       * - WYSIWYG elements
26       * - Tabledrag weights
27       * - Elements that have opted-out of Chosen
28       * - Elements already processed by Chosen.
29       *
30       * @type {string}
31       */
32      ignoreSelector: '#field-ui-field-storage-add-form select, #entity-form-display-edit-form select, #entity-view-display-edit-form select, .wysiwyg, .draggable select[name$="[weight]"], .draggable select[name$="[position]"], .locale-translate-filter-form select, .chosen-disable, .chosen-processed',
33
34      /**
35       * Explicit "opt-in" selector.
36       *
37       * @type {string}
38       */
39      optedInSelector: 'select.chosen-enable',
40
41      /**
42       * The default selector, overridden by drupalSettings.
43       *
44       * @type {string}
45       */
46      selector: 'select:visible',
47
48      /**
49       * Minimum options for single and multiple selects.
50       * These may be defined in drupalSettings.chosen.
51       */
52      minimum_single: 0,
53      minimum_multiple: 0,
54
55      /**
56       * Other default options for Chosen.
57       */
58      options: {},
59      minimum_width: 0,
60      use_relative_width: false,
61    },
62
63    /**
64     * Drupal attach behavior.
65     */
66    attach: function(context, settings) {
67      this.settings = this.getSettings(settings);
68      const elements = this.getElements(context);
69      // Use Drupal's once function to process elements only once
70      once('chosen', elements).forEach(function(element) {
71        // If inside a Drupal dialog, resize the dialog on open & close event.
72        if (element.closest('#drupal-modal')) {
73          element.addEventListener('chosen:showing_dropdown', (e) => {
74            const nextElement = element.nextElementSibling;
75            if (nextElement) {
76              const chosenDrop = nextElement.querySelector('.chosen-drop');
77              if (chosenDrop) {
78                chosenDrop.style.position = 'static';
79                const event = new Event('dialogContentResize', { bubbles: true, cancelable: true });
80                element.dispatchEvent(event);
81              }
82            }
83          });
84          element.addEventListener('chosen:hiding_dropdown', (e) => {
85            const nextElement = element.nextElementSibling;
86            if (nextElement) {
87              const chosenDrop = nextElement.querySelector('.chosen-drop');
88              if (chosenDrop) {
89                chosenDrop.style.position = '';
90                const event = new Event('dialogContentResize', { bubbles: true, cancelable: true });
91                element.dispatchEvent(event);
92              }
93            }
94          });
95        }
96        this.createChosen(element);
97      }.bind(this));
98    },
99
100    /**
101     * Creates a Chosen instance for a specific element.
102     *
103     * @param {HTMLElement} element
104     *   The element.
105     */
106    createChosen: function(element) {
107      const options = this.getElementOptions(element);
108      element.chosen(options);
109
110      if (options.add_helper_buttons || element.hasAttribute('chosen_add_helper_buttons')) {
111        if (element.hasAttribute('multiple')) {
112          const allButton = document.createElement('button');
113          allButton.type = 'button';
114          allButton.className = 'button chosen-helper-btn';
115          allButton.textContent = 'All';
116          allButton.addEventListener('click', function() {
117            Array.from(element.options).forEach(function(option) {
118              option.selected = true;
119            });
120            const event = new Event('chosen:updated', { bubbles: true, cancelable: true });
121            element.dispatchEvent(event);
122          });
123
124          const noneButton = document.createElement('button');
125          noneButton.type = 'button';
126          noneButton.className = 'button chosen-helper-btn';
127          noneButton.textContent = 'None';
128          noneButton.addEventListener('click', function() {
129            Array.from(element.options).forEach(function(option) {
130              option.selected = false;
131            });
132            const event = new Event('chosen:updated', { bubbles: true, cancelable: true });
133            element.dispatchEvent(event);
134          });
135
136          element.parentNode.appendChild(allButton);
137          element.parentNode.appendChild(noneButton);
138        }
139      }
140    },
141
142    /**
143     * Filter out elements that should not be converted into Chosen.
144     *
145     * @param {HTMLElement} element
146     *   The element.
147     *
148     * @return {boolean}
149     *   TRUE if the element should stay, FALSE otherwise.
150     */
151    filterElements: function(element) {
152      // Remove elements that should be ignored completely.
153      if (element.matches(this.settings.ignoreSelector)) {
154        return false;
155      }
156
157      // Zero value means no minimum.
158      const minOptions = element.hasAttribute('multiple') ? this.settings.minimum_multiple : this.settings.minimum_single;
159      return !minOptions || element.querySelectorAll('option').length >= minOptions;
160    },
161
162    /**
163     * Checks if the element is visible.
164     *
165     * @param {HTMLElement} el
166     *   A DOM element to check visibility for.
167     * @return {boolean}
168     *   True if the element is visible, false otherwise.
169     */
170    isVisible: function(el) {
171      return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
172    },
173
174    /**
175     * Parses the selector to handle :visible manually.
176     *
177     * @param {string} selector
178     *   A CSS selector string that may contain :visible.
179     * @return {Array}
180     *   An array of elements that match the selector and are visible (if :visible is present).
181     */
182    getVisibleElements: function(context, selector) {
183      const selectors = selector.split(',').map(s => s.trim());
184      let elements = [];
185
186      selectors.forEach(s => {
187        const hasVisibleFilter = s.includes(':visible');
188        const cleanSelector = s.replace(':visible', '').trim();
189
190        if (!cleanSelector) {
191          return;
192        }
193
194        let selectedElements;
195        try {
196          selectedElements = Array.from(context.querySelectorAll(cleanSelector));
197        } catch (error) {
198          return;
199        }
200
201        if (hasVisibleFilter) {
202          selectedElements = selectedElements.filter(this.isVisible);
203        }
204
205        elements = elements.concat(selectedElements);
206      });
207
208      return elements;
209    },
210
211    /**
212     * Retrieves the elements that should be converted into Chosen instances.
213     *
214     * @param {HTMLElement|Document} context
215     *   A DOM Element or Document to use as context.
216     * @param {string} [selector]
217     *   A selector to use, defaults to the default selector in the settings.
218     */
219    getElements: function(context, selector) {
220      context = context || document;
221      let elements = this.getVisibleElements(context, selector || this.settings.selector);
222
223      // Remove elements that should not be converted into Chosen.
224      elements = elements.filter(function(element) {
225        return this.filterElements(element);
226      }.bind(this));
227
228      // Add elements that have explicitly opted in to Chosen.
229      const optedInElements = Array.from(context.querySelectorAll(this.settings.optedInSelector));
230
231      // Combine elements and opted-in elements, avoiding duplicates.
232      optedInElements.forEach(function(element) {
233        if (!elements.includes(element)) {
234          elements.push(element);
235        }
236      });
237
238      return elements;
239    },
240
241    /**
242     * Retrieves options used to create a Chosen instance based on an element.
243     *
244     * @param {HTMLElement} element
245     *   The element to process.
246     *
247     * @return {Object}
248     *   The options object used to instantiate a Chosen instance with.
249     */
250    getElementOptions: function(element) {
251      const options = Object.assign({}, this.settings.options);
252      let dimension;
253      let width;
254
255      // The width default option is considered the minimum width, so this
256      // must be evaluated for every option.
257      if (this.settings.minimum_width > 0) {
258        // Given we need to manage settings as both percentage and pixel widths,
259        // we need to handle width calculations separately.
260        if (this.settings.use_relative_width) {
261          dimension = '%';
262          const elementWidth = element.getBoundingClientRect().width;
263          const parentWidth = element.parentNode.getBoundingClientRect().width;
264          width = ((elementWidth / parentWidth) * 100).toPrecision(5);
265        } else {
266          dimension = 'px';
267          width = element.getBoundingClientRect().width;
268        }
269
270        if (width < this.settings.minimum_width) {
271          options.width = this.settings.minimum_width + dimension;
272        } else {
273          options.width = width + dimension;
274        }
275      }
276
277      // Some field widgets have cardinality, so we must respect that.
278      // @see \Drupal\chosen\ChosenFormRender::preRenderSelect()
279      const cardinality = element.dataset.cardinality;
280      if (element.hasAttribute('multiple') && cardinality) {
281        options.max_selected_options = parseInt(cardinality);
282      }
283
284      return options;
285    },
286
287    /**
288     * Retrieves the settings passed from Drupal.
289     *
290     * @param {Object} [settings]
291     *   Passed Drupal settings object, if any.
292     */
293    getSettings: function(settings) {
294      // Deep merge the settings
295      return this.deepMerge({}
295, this.settings, (settings && settings.chosen) || drupalSettings.chosen || {});
296    },
297
298    /**
299     * Deep merges multiple objects into the first object.
300     * This function is used instead of Object.assign to handle nested objects.
301     *
302     * @param {Object} target
303     *   The target object to merge properties into.
304     * @param {...Object} sources
305     *   The source objects from which to copy properties.
306     * @return {Object}
307     *   The target object.
308     */
309    deepMerge: function(target, ...sources) {
310      if (!sources.length) return target;
311      const source = sources.shift();
312
313      if (typeof target !== 'object' || typeof source !== 'object') {
314        return target;
315      }
316
317      for (const key in source) {
318        if (source.hasOwnProperty(key)) {
319          if (typeof source[key] === 'object') {
320            if (!target[key]) Object.assign(target, { [key]: {} });
321            this.deepMerge(target[key], source[key]);
322          } else {
323            Object.assign(target, { [key]: source[key] });
324          }
325        }
326      }
327
328      return this.deepMerge(target, ...sources);
329    },
330
331  };
332
333})(Drupal, drupalSettings, once);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.