PageSourceSearch

https://fcrw.ru/local/templates/index/assets/js/collapsible.js

js fcrw.ru collected 2026-10-06 16:18:59 UTC 10,495 bytes, 326 lines download raw bytes

1	const collapsedClass = 'is-collapsed';
2	const expandedClass  = 'is-expanded';
3	/**
4	 * Collapsible - A plug and play plugin for expanding and
5	 * collapsing elements (i.e. accordion) on a website.
6	 *
7	 * @author Murtada al Mousawy (https://murtada.nl)
8	 */
9	(function() {
10	  'use strict';
11
12	  /**
13	   * Creates an instance of Collapsible.
14	   *
15	   * @constructor
16	   * @param {Object} options
17	   * @param {(HTMLElement|NodeList)} options.node The HTML elements that will be manipulated.
18	   * @param {HTMLElement} [options.eventNode] The HTML element on which the eventListener will be attached.
19	   * @param {Boolean} [options.isCollapsed] Assign the state of the node element.
20	   * @param {Boolean} [options.observe] Assign a MutationObserver to observe child DOM changes.
21	   * @param {Function} [options.expandCallback] Assign a callback for the [{@link Collapsible.prototype.expand} event.
22	   * @param {Function} [options.collapseCallback] Assign a callback for the {@link Collapsible.prototype.collapse} event.
23	   * @param {Function} [options.observeCallback] Assign a callback for the {@link Collapsible.prototype.initObserver} event.
24	   */
25	  var Collapsible = function(options) {
26	    // Initialize HTML nodes
27	    if (NodeList.prototype.isPrototypeOf(options.node)) {
28	      options.node.forEach(function(nodeItem) {
29	        var singleNodeOptions = options;
30	        singleNodeOptions.node = nodeItem;
31	        new Collapsible(singleNodeOptions);
32	      });
33	      return;
34	    } else if (options.node instanceof HTMLElement) {
35	      this.node = options.node;
36	      this.eventNode = (options.eventNode ? this.node.querySelector(options.eventNode) : this.node);
37
38	      this.stopBreakpoint = this.node.dataset.stopBreakpoint;
39
40	      this.isCollapsed = (typeof this.node.dataset.collapsibleCollapsed !== 'undefined'
41	                          ? true
42	                          : null);
43
44	      if (!this.isCollapsed) {
45	        this.isCollapsed = ((options.isCollapsed
46	                            && typeof options.isCollapsed === 'boolean')
47	                            ? options.isCollapsed
48	                            : false);
49	      }
50
51	      this.observe = (typeof options.observe === 'boolean' ? options.observe : false);
52	      this.expandCallback = (typeof options.expandCallback === 'function' ? options.expandCallback : null);
53	      this.collapseCallback = (typeof options.collapseCallback === 'function' ? options.collapseCallback : null);
54	      this.observeCallback = (typeof options.observeCallback === 'function' ? options.observeCallback : null);
55	      this.mutationCallback = (typeof options.mutationCallback === 'function' ? options.mutationCallback : null);
56
57	      this.init();
58	    } else {
59	      console.error(options.node, 'is not a NodeList or an instance of HTMLElement');
60	    }
61	  };
62
63	  /**
64	   * Initialize the collapsing and expanding events.
65	   */
66	  Collapsible.prototype.init = function() {
67	    this.updateHeights();
68
69	    if (this.isCollapsed) {
70	      this.node.style.height = this.collapsedHeight + 'px';
71	      this.node.classList.add(collapsedClass);
72	    } else {
73	      this.node.classList.add(expandedClass);
74	    }
75
76	    this.eventNode.addEventListener('click', function(e) {
77	        let windowWidth = window.innerWidth;
78
79	        if (!(this.stopBreakpoint === 'small-desktop' && windowWidth >= bpLG) && !(this.stopBreakpoint === 'desktop' && windowWidth >= bpXL)) {
80	            e.preventDefault();
81	        }
82
83	        this.toggleCollapse();
84	    }.bind(this));
85
86	    window.addEventListener('resize', this.updateHeights.bind(this, null));
87	    window.addEventListener('orientationchange', this.updateHeights.bind(this, null));
88
89	    // Observe children of the node
90	    if (this.observe) {
91	      this.initObserver();
92	    }
93
94	    // Attach the prototype instance to the node
95	    this.node.collapsible = this;
96	  };
97
98	  /**
99	   * Update the collapsed and expanded heights on page resize.
100	   *
101	   * @param {int} [heightDifference] Height value to add or subtract from the parent.
102	   */
103	  Collapsible.prototype.updateHeights = function(heightDifference) {
104	      let windowWidth = window.innerWidth;
105
106	      if ((this.stopBreakpoint === 'small-desktop' && windowWidth >= bpLG) || (this.stopBreakpoint === 'desktop' && windowWidth >= bpXL)) {
107	          return false;
108	      }
109
110	      heightDifference = heightDifference || 0;
111
112	      // Calculate the collapsed height
113	      this.collapsedHeight = Collapsible.parseNumber(
114	        window.getComputedStyle(this.eventNode)['height']
115	      ) + Collapsible.parseNumber(
116	        window.getComputedStyle(this.eventNode)['margin-bottom']);
117
118	      // Calculate the expanded height
119	      this.node.style.height = 'auto';
120
121	      this.expandedHeight = Collapsible.parseNumber(
122	        window.getComputedStyle(this.node)['height']
123	      );
124
125	      // Add or subtract the childNode's height difference
126	      this.expandedHeight += heightDifference;
127	      this.expandedHeight =  Math.max(this.expandedHeight, this.collapsedHeight);
128
129	      // Reset height to what it was before
130	      if (this.isCollapsed) {
131	        this.node.style.height = this.collapsedHeight + 'px';
132	      }
133
134	      this.updateParentNode(this.expandedHeight - this.collapsedHeight);
135	  };
136
137	  /**
138	   * Toggle the node state and calls the appropriate function.
139	   */
140	  Collapsible.prototype.toggleCollapse = function() {
141	    if (this.isCollapsed) {
142	      this.expand();
143	    } else {
144	      this.collapse();
145	    }
146	  };
147
148	  /**
149	   * Expand the node.
150	   */
151	  Collapsible.prototype.expand = function() {
152	    this.updateHeights();
153
154	    void this.node.offsetWidth;
155
156	    this.node.style.height = 'auto';
157	    this.node.style.height = this.expandedHeight + 'px';
158	    this.node.classList.remove(collapsedClass);
159	    this.node.classList.add(expandedClass);
160
161	    this.isCollapsed = false;
162
163	    // Create a custom event
164	    var expandEvent = new CustomEvent('toggle', {
165	      bubbles: true,
166	      detail: {
167	        action: 'expand',
168	        origin: this.eventNode
169	      }
170	    });
171
172	    this.node.dispatchEvent(expandEvent);
173
174	    // Run callback if it exists
175	    if (this.expandCallback) {
176	      this.expandCallback.bind(this, expandEvent)();
177	    }
178
179	    this.updateParentNode(this.expandedHeight - this.collapsedHeight);
180	  };
181
182	  /**
183	   * Collapse the node.
184	   */
185	  Collapsible.prototype.collapse = function() {
186	    this.node.style.height = window.getComputedStyle(this.node)['height'];
187
188	    void this.node.offsetWidth;
189
190	    this.node.style.height = this.collapsedHeight + 'px';
191	    this.node.classList.remove(expandedClass);
192	    this.node.classList.add(collapsedClass);
193
194	    this.isCollapsed = true;
195
196	    // Create a custom event
197	    var collapseEvent = new CustomEvent('toggle', {
198	      bubbles: true,
199	      detail: {
200	        action: 'collapse',
201	        origin: this.eventNode
202	      }
203	    });
204
205	    this.node.dispatchEvent(collapseEvent);
206
207	    // Run callback if it exists
208	    if (this.collapseCallback) {
209	      this.collapseCallback.bind(this, collapseEvent)();
210	    }
211
212	    this.updateParentNode(-(this.expandedHeight - this.collapsedHeight));
213	  };
214
215	  /**
216	   * Update parent heights if collapsible.
217	   *
218	   * @see {@link Collapsible.prototype.updateHeights}
219	   */
220	  Collapsible.prototype.updateParentNode = function(heightDifference) {
221	    if (this.node.parentNode && this.node.parentNode.collapsible) {
222	      this.node.parentNode.collapsible.updateHeights(heightDifference);
223	    }
224	  };
225
226	  /**
227	   * Observe the direct children list of the node.
228	   * Will adjust the expanded height automatically if necessary.
229	   */
230	  Collapsible.prototype.initObserver = function() {
231	    this.mutationObserver = new window.MutationObserver(function(mutationsList) {
232	      var mutatedNode,
233	          mutationAction;
234
235	      if (!this.isCollapsed) {
236	          // Calculate the collapsed height
237	          this.collapsedHeight = Collapsible.parseNumber(
238	            window.getComputedStyle(this.eventNode)['height']
239	          );
240
241	          // Calculate the expanded height
242	          this.node.style.height = 'auto';
243
244	          this.updateParentNode(this.expandedHeight - this.collapsedHeight);
245	      }
246
247	      // Create a custom event
248	      var mutationEvent = new CustomEvent('mutate', {
249	        bubbles: true,
250	        detail: {
251	          action: mutationAction,
252	          node: mutatedNode
253	        }
254	      });
255
256	      this.node.dispatchEvent(mutationEvent);
257
258	      // Run callback if it exists
259	      if (this.mutationCallback) {
260	        this.mutationCallback.bind(this, mutationEvent)();
261	      }
262	    }.bind(this));
263
264	    this.mutationObserver.observe(this.node, {
265	        childList: true,
266	        subtree: true
267	    });
268	  };
269
270	  // Helper functions
271	  Collapsible.parseNumber = function(numberString) {
272	    return Number.parseInt(numberString.slice(0, -2));
273	  };
274
275	  // Expose the prototype function to the global scope
276	  window.Collapsible = Collapsible;
277	})();
278
279
280	(function() {
281	    let collapsibleParents = document.querySelectorAll('[data-collapsible-parent]');
282	    let collapsibleChildren = document.querySelectorAll('[data-collapsible-child]');
283
284	    new Collapsible({
285	        node: collapsibleChildren,
286	        eventNode: '[data-collapsible-child-toggle]',
287	        isCollapsed: true,
288	        expandCallback: toggleCallback,
289	        collapseCallback: toggleCallback
290	    });
291
292	    new Collapsible({
293	        node: collapsibleParents,
294	        eventNode: '[data-collapsible-parent-toggle]',
295	        isCollapsed: false,
296	        observe: true,
297	        expandCallback: toggleCallback,
298	        collapseCallback: toggleCallback
299	    });
300
301	    // Listen to 'toggle' event
302	    collapsibleParents.forEach(function(category) {
303	        category.addEventListener('toggle', function(event) {
304	        });
305
306	        category.addEventListener('mutate', function(event) {
307	        });
308	    });
309
310	    // The callback used in options
311	    function toggleCallback(event) {
312	        let $toggle = this.eventNode;
313	        let toggleTextOn = $toggle.dataset.textOn;
314	        let toggleTextOff = $toggle.dataset.textOff;
315
316	        if (!toggleTextOn && !toggleTextOff) {
317	            return false;
318	        }
319
320	        if (event.detail.action === 'expand') {
321	            $toggle.innerHTML = toggleTextOff;
322	        } else if (event.detail.action === 'collapse') {
323	            $toggle.innerHTML = toggleTextOn;
324	        }
325	    }
326	})();

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.