PageSourceSearch

https://thejns.org/assets/shared/zbe047af3/js/dist/collapser.js

js thejns.org collected 2026-10-01 09:07:21 UTC 7,095 bytes, 228 lines download raw bytes

1'use strict';
2
3/**
4 *  The Collapser object can have mutliple instances,
5 *  and should work with any nested ul/li...
6 *
7 *  This function is used in both the loader and baseapp - so if you make
8 *  changes, be sure to check both for potential problems.
9 *
10 *  Example structure:
11 *
12 *  <div class="taxonomyTree">
13 *    <ul>
14 *      <li>
15 *        <a class="toggle">
16 *        <a class="taxonomyNode" id="0001">My Node's Wonderful Name</a>
17 * 		  <ul>
18 *          <li>
19 *           ...
20 *           </li>
21 *           <li>
22 *           </li>
23 *         </ul>
24 *       </li>
25 *       <li>
26 *        ...
27 *       </li>
28 *     </ul>
29 *   </div>
30 */
31(function ($) {
32	var COLLAPSER = function COLLAPSER(collapser, shutAll, openAll) {
33		var c = this.collapser = collapser = $(collapser);
34		var CPR = this;
35
36		shutAll = shutAll === undefined ? true : shutAll; // make sure this is set if it is not passed
37		openAll = openAll === undefined ? false : openAll; // make sure this is set if it is not passed
38
39		// if there are any open/close all links
40		c.on('click', '.openAll a', function () {
41			CPR.openAll();
42			return false;
43		});
44
45		c.on('click', '.shutAll a', function () {
46			CPR.shutAll();
47			return false;
48		});
49
50		c.addClass('collapser');
51
52		var a = this.allLis = c.find("ul,[data-toc-role='ul']").not('.actions-list').parentsUntil(c, "li,[data-toc-role='li']");
53
54		if (a.length == 0) {
55			c.addClass('collapser-flat'); // signifies that a collapser was added, but it is flat (eg. no expandable/collapsable nodes)
56			return; // nothing left to do, there are no expandable elements
57		}
58
59		if (CPR.getToggle(a).length > 0) {
60			c.addClass('collapser-with-toggles');
61		} else {
62			c.addClass('collapser-without-toggles');
63		}
64
65		// attach the listener to the <li> expandable tag
66		a.each(function () {
67			var li = $(this);
68			if (!li.data('COLLAPSER')) {
69				var handleToggle = function handleToggle(event, origin) {
70					var t = $(this != null ? this : origin); // this
71					// event target
72					var tg = $(event.target);
73					// event target node name
74					var tgnn = tg.prop('nodeName');
75					var toggle = CPR.getToggle(t);
76					// closest expandable li
77					var cli = tg.closest("li.expandable,[data-toc-role='li'].expandable");
78					// the closests "stop" tag, meaning that if they are clicked, stop propagtion... TODO: make this overridable in a plugin!
79					var cstop = tg.closest("input, label, a, li:not(.expandable),[data-toc-role='li']:not(.expandable)");
80					// the distance to the closest LI
81					var dcli = tg.parentsUntil(cli).length;
82					// the distance to the closest "stop" tag.
83					var dcstop = tg.parentsUntil(cstop).length;
84
85					// this is necessary so that if an "a" tag (for example) is within a collapser, it doesn't also collapse/expand the collapser!
86					if (
87					// there is no expandable section, so give up
88					!cli ||
89					// if the target is also the closest stop element... but it is not explicitly a toggle, then we need to stop propagation!
90					tg.is(cstop) && !tg.is(toggle) ||
91					// if somewhere between the target and the collapsing element, there is a stop element, then don't collapse either
92					dcstop < dcli && !cstop.is(toggle)) {
93						event.stopPropagation();
94						// if the closest li is not an expandable one, or if what was clicked was a link, stop propagation
95						return;
96					}
97
98					CPR.toggleSection(t);
99					return false;
100				};
101
102				li.data('COLLAPSER', c);
103				var toggleBtn = CPR.getToggle(li);
104				if (toggleBtn) {
105					toggleBtn.on('click', function (event) {
106						if (li.hasClass('expandable')) {
107							if (li.hasClass('open')) {
108								toggleBtn.removeClass('c-Icon--remove').addClass('c-Icon--add');
109							} else {
110								toggleBtn.removeClass('c-Icon--add').addClass('c-Icon--remove');
111							}
112						}
113					});
114				}
115
116				li.on('click', handleToggle);
117				li.on('keyup', function (event) {
118					if (event.key == "Enter") {
119						// Enter key 
120						return handleToggle(event, this);
121					}
122				});
123			}
124		});
125
126		a.addClass('expandable');
127		if (shutAll) {
128			CPR.shutAll();
129			c.find("li.active-section,[data-toc-role='li'].active-section").each(function (li) {
130				CPR.openSection(this);
131			});
132		} else if (openAll) {
133			CPR.openAll();
134		} else {
135			// we have to make sure that if it is not "open" or "shut", we set them
136			CPR.openSection(a.not('shut').not('open'));
137		}
138	};
139
140	var CP = COLLAPSER.prototype;
141
142	CP.openAll = function () {
143		this.openSection(this.allLis);
144	};
145
146	CP.shutAll = function () {
147		this.shutSection(this.allLis);
148	};
149
150	CP.setSection = function (section, addClass, removeClass, text) {
151		section = $(section);
152		var toggle = this.getToggle(section);
153		var span = toggle.find('span').first();
154
155		section.addClass(addClass).removeClass(removeClass);
156
157		// The toggle is the reverse state of the parent
158		if (addClass === 'open') {
159			toggle.addClass('ico-toggle-' + removeClass + ' c-Icon--remove').removeClass('ico-toggle-' + addClass + ' c-Icon--add');
160		} else {
161			toggle.addClass('ico-toggle-' + removeClass + ' c-Icon--add').removeClass('ico-toggle-' + addClass + ' c-Icon--remove');
162		}
163		span.html(text);
164	};
165
166	CP.openSection = function (section) {
167		this.setSection(section, 'open', 'shut', 'Close section');
168	};
169
170	CP.shutSection = function (section) {
171		this.setSection(section, 'shut', 'open', 'Open section');
172	};
173
174	CP.getToggle = function (section) {
175		return section.find('.toggle:first');
176	};
177
178	CP.toggleSection = function (section) {
179		var s = $(section);
180		if (s.hasClass('open')) {
181			this.shutSection(s);
182		} else {
183			this.openSection(s);
184		}
185	};
186
187	$(function () {
188		$('.taxonomyTree').each(function () {
189			var t = $(this),
190			    s = t.hasClass('all-shut'),
191			    o = t.hasClass('all-open'),
192			    c = new COLLAPSER(this, s, o);
193			c.openSection($(this).find('.active').closest("li,[data-toc-role='li']").parents('.expandable'));
194			var t = $(this);
195			if (t.hasClass('relatedTaxonomyTree')) {
196				c.openAll();
197			}
198			$.data(this, 'COLLAPSER', c);
199		});
200
201		$('.toc-menu').each(function () {
202			if ($(this).find('.issue-toc').length <= 0) {
203				//Do not add collapser to issue toc main content
204				var t = $(this),
205				    s = t.hasClass('all-shut'),
206				    c = new COLLAPSER(this, s);
207
208				if (t.find('.book-toc').length > 0) {
209					// this is book TOCs...
210					//t.find(".expandable").prepend("<a class='toggle minustoggle'><span>toggle</span></a>");
211					c.shutSection(t.find("span:contains('Front Matter')").parents('.expandable'));
212					c.shutSection(t.find("span:contains('Back Matter')").parents('.expandable'));
213				} else if (t.find('.journal-toc').length > 0) {
214					// journal TOCs
215					var curr = t.find('.current');
216					c.openSection(curr.closest("li,[data-toc-role='li']").parents('.expandable'));
217					// By default, always expand the first Volume if no other volumes are selected
218					if (!curr) {
219						c.openSection(t.find('.js-first').closest("li,[data-toc-role='li']"));
220					}
221				}
222				c.openSection($(this).find('.active').closest("li,[data-toc-role='li']").parents('.expandable'));
223
224				$.data(this, 'COLLAPSER', c);
225			}
226		});
227	});
228})(jQuery);

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.