PageSourceSearch

https://empop.online/bundles/empopclient/js/vendor/vxJS/widgets/simpletabs.js

js empop.online collected 2026-09-25 20:48:03 UTC 9,236 bytes, 404 lines download raw bytes

1/**
2 * automatically structure content with a tab panel
3 * searches for .vxJS_tabThis elements
4 * .section elements within .vxJS_tabThis form the views
5 * the first h2 element of a .section is used as tab label
6 * tabs are labeled with text content of their respective h2 elements
7 * 
8 * pushState() is used when config.setHash is set and browser support suffices
9 * 
10 * @version 0.6.0 2017-07-05
11 * @author Gregor Kofler
12 *
13 * @param [{Object} HTMLElement]:
14 * 		root elemement searched for tabs, defaults to document
15 * @param [{Object} config]:
16 *		tabWrap: {String | Array} nodeName(s) of extra container element(s) for each tab
17 *		shortenLabelsTo: {Number} shorten labels to the given number of chars or preceeding word boundary
18 *		setHash: {Boolean} when true, tab ids will modify URI hash
19 *		spacersTop: {Object} additional DOM element(s) inserted before the ul element
20 *		spacersBottom: {Object} additional DOM element(s) inserted after the ul element
21 *
22 * served events: "beforeTabClick", "afterTabClick"
23 */
24
25/*jslint browser: true, plusplus: true, vars: true, white: true */
26
27vxJS.widget.simpleTabs = (function() {
28
29	"use strict";
30
31	var conf = {}, tabBars = [],
32		supportsHistory = vxJS.hasHostMethods(window.history, ["pushState", "replaceState"]);
33
34	/**
35	 * Represents a single tab.
36	 * @constructor
37	 * @param {TabBar} tabBar - the bar, the tab belongs to
38	 * @param {string} label - label on tab
39	 * @param {HTMLElement} page - the "content" of a tab page
40	 * @param {string} [id] - id of tab
41	 */
42	var Tab = function(tabBar, label, page, id) {
43		this.tabBar		= tabBar;
44		this.label		= label;
45		this.page		= page;
46		this.id			= id;
47		this.render();
48	};
49
50	Tab.prototype = {
51
52		/**
53		 * create element of a single tab element, populates Tab.element
54		 * @returns {Tab}
55		 */
56		render: function() {
57
58			var li, a, l,
59				w		= conf.tabWrap,
60				label	= conf.shortenLabelsTo ? this.label.shortenToLen(conf.shortenLabelsTo) : this.label,
61				title	= "";
62
63			if(label.length !== this.label.length) {
64				label += "...";
65				title = this.label;
66			}
67
68			a = this.id ? "a".setProp("href", conf.setHash ? "#" + this.id : "").create(label) : document.createTextNode(label);
69
70			li = "li".setProp("title", title).create();
71
72			if(!w) {
73				li.appendChild(a);
74			}
75			else if(typeof w === "string") {
76				li.appendChild(w.create(a));
77			}
78			else {
79				l = w.length;
80				while(l--) {
81					a = w[l].create(a);
82				}
83				li.appendChild(a);
84			}
85
86			this.element = li;
87			return this;
88		},
89
90		/**
91		 * focus A element in tab, when present; does not show/hide pages
92		 * @returns {Tab}
93		 */
94		focus: function() {
95			var a = this.element.getElementsByTagName("a");
96			if(a && a[0]) {
97				a[0].focus();
98			}
99			return this;
100		},
101
102		/**
103		 * show page of tab, does not hide previously shown page
104		 * @returns {Tab}
105		 */
106		show: function() {
107			this.visibilty = true;
108			vxJS.dom.addClassName(this.element, "shown");
109			this.page.style.display = "";
110			return this;
111		},
112
113		/**
114		 * hide page of tab
115		 * @returns {Tab}
116		 */
117		hide: function() {
118			this.visibilty = false;
119			vxJS.dom.removeClassName(this.element, "shown");
120			this.page.style.display = "none";
121			return this;
122		},
123		
124		/**
125		 * enable tab
126		 * @returns {Tab}
127		 */
128		enable: function() {
129			this.inactive = false;
130			vxJS.dom.removeClassName(this.element, "disabled");
131			return this;
132		},
133
134		/**
135		 * disable tab
136		 * @returns {Tab}
137		 */
138		disable: function() {
139			this.inactive = true;
140			vxJS.dom.addClassName(this.element, "disabled");
141			return this;
142		}
143
144	};
145
146	/**
147	 * represents a tab bar containing tabs
148	 * @constructor
149	 * @param {HTMLElement} container - element holding the tab bar
150	 */
151	var TabBar = function(container) {
152		this.tabs		= [];
153		this.container	= container;
154	};
155
156	TabBar.prototype = {
157
158		enable: function() {
159			this.inactive = false;
160			vxJS.dom.removeClassName(this.element, "disabled");
161			return this;
162		},
163
164		disable: function() {
165			this.inactive = true;
166			vxJS.dom.addClassName(this.element, "disabled");
167			return this;
168		},
169
170		getTabByElement: function(elem) {
171			var l = this.tabs.length;
172			while(l--) {
173				if(this.tabs[l].element === elem) {
174					return this.tabs[l];
175				}
176			}
177		},
178
179		getTabByNdx: function(ndx) {
180			return this.tabs[ndx];
181		},
182
183		getTabById: function(id) {
184			var l = this.tabs.length;
185			while(l--) {
186				if(this.tabs[l].id === id) {
187					return this.tabs[l];
188				}
189			}
190		},
191
192		getTabNdx: function(tab) {
193			var l = this.tabs.length;
194			while(l--) {
195				if(this.tabs[l] === tab) {
196					return l;
197				}
198			}
199		},
200
201		gotoTab: function(tab, pushState) {
202			var l = this.last;
203
204			if(!tab || this.tabs.indexOf(tab) === -1 || l === tab) {
205				return;
206			}
207
208			l.hide();
209			tab.show();
210
211			this.last = tab;
212			
213			if(pushState) {
214				if(tab.id && conf.setHash) {
215					if(supportsHistory) {
216						window.history.pushState(tab.id, "", "#" + tab.id);
217					}
218					else {
219						window.location = "#" + tab.id;
220					}
221				}
222			}
223
224			return this;
225		},
226
227		gotoPrevTab: function(pushState) {
228			return this.gotoTab(this.last.prevTab, pushState);
229		},
230
231		gotoNextTab: function(pushState) {
232			return this.gotoTab(this.last.nextTab, pushState);
233		},
234
235		focus: function(tab, pushState) {
236			this.gotoTab(tab, pushState);
237			tab.focus();
238			return this;
239		},
240		
241		/**
242		 * create and insert a new tab
243		 * 
244		 * page:	HTMLElement
245		 * label:	String
246		 * id:		String (optional)
247		 * 
248		 * @param {Object} tabData - { page: {HTMLElement}, label: {string}, [id : {string}] }
249		 * @param {Tab} [insertBefore] - tab before which new tab will be inserted; when omitted, tab will be appended
250		 * @returns {TabBar}
251		 */
252		insertTab: function(tabData, insertBefore) {
253
254			var tab = new Tab(this, tabData.label, tabData.page, tabData.id), prevTab;
255
256			tab.hide();
257
258			if(!insertBefore) {
259				prevTab = this.tabs[this.tabs.length - 1];
260				prevTab.nextTab = tab;
261				tab.nextTab	= null;
262				tab.prevTab	= prevTab;
263				this.tabs.push(tab);
264				this.container.insertBefore(tabData.page, prevTab.page.nextSibling);
265				this.ulElement.insertBefore(tab.element, prevTab.element.nextSibling);
266			}
267			else {
268				prevTab = insertBefore.prevTab;
269				if(prevTab) {
270					prevTab.nextTab = tab;
271				}
272				insertBefore.prevTab = tab;
273				tab.prevTab = prevTab;
274				tab.nextTab = insertBefore;
275				this.tabs.splice(this.getTabNdx(insertBefore), 0, tab);
276				this.container.insertBefore(tabData.page, insertBefore.page);
277				this.ulElement.insertBefore(tab.element, insertBefore.element);
278			}
279
280			return this;
281		},
282
283		/**
284		 * remove a tab
285		 *  
286		 * @param {Tab}
287		 * @returns {TabBar}
288		 */
289		removeTab: function(tab) {
290			var ndx = this.getTabNdx(tab);
291
292			if(tab.prevTab) {
293				tab.prevTab.nextTab = tab.nextTab;
294			}
295			if(tab.nextTab) {
296				tab.nextTab.prevTab = tab.prevTab;
297			}
298
299			this.container.removeChild(tab.page);
300			this.ulElement.removeChild(tab.element);
301			this.tabs.splice(ndx, 1);
302
303			return this;
304		}
305	};
306
307	if(supportsHistory) {
308		vxJS.event.addListener(window, "popstate", function(e) {
309			var l = tabBars.length, found;
310
311			if(e.state) {
312				while(l--) {
313					if(found = tabBars[l].getTabById(e.state)) {
314						break;
315					}
316				}
317			}
318			if(found) {
319				tabBars[l].gotoTab(found, false);
320			}
321		});
322	}
323
324	return function(root, config) {
325		var tabbed = vxJS.dom.getElementsByClassName("vxJS_tabThis", root), tbCount = tabbed.length, hash = window.location.hash.slice(1) || "";
326
327		conf = config || {};
328
329		var clickListener = function(e, bar) {
330			var tab = bar.getTabByElement((!this.nodeName || this.nodeName.toUpperCase() !== "LI") ? vxJS.dom.getParentElement(this, "li") : this);
331
332			if(!tab) {
333				return;
334			}
335			if(!bar.inactive && !tab.inactive) {
336				vxJS.event.serve(bar, "beforeTabClick");
337				bar.gotoTab(tab, true);
338				vxJS.event.serve(bar, "afterTabClick");
339			}
340
341			vxJS.event.preventDefault(e);
342		};
343
344		while(tbCount--) {
345			(function(container) {
346				var secs = vxJS.dom.getElementsByClassName("section", container),
347					ctrl = new TabBar(container), h, ul, init, i, l, id, t;
348
349				for(i = 0, l = secs.length; i < l; ++i) {
350					h = secs[i].getElementsByTagName("h2");
351
352					if(h[0]) {
353						id = h[0].id;
354						t = new Tab(ctrl, vxJS.dom.concatText(h[0]), secs[i], id);
355						t.hide();
356						h[0].parentNode.removeChild(h[0]);
357						ctrl.tabs.push(t);
358
359						if((id && id === hash) || (!init && vxJS.dom.hasClassName(secs[i], "default"))) {
360							init = t;
361						}
362					}
363				}
364
365				if(!(l = ctrl.tabs.length)) {
366					return;
367				}
368				if(!init) {
369					init = ctrl.tabs[0];
370				}
371
372				ul = "ul".create();
373
374				for(i = 0; i < l; ++i) {
375					t = ctrl.tabs;
376
377					t[i].nextTab	= t[i + 1] || null;
378					t[i].prevTab	= i - 1 >= 0 ? t[i - 1] : null;
379
380					ul.appendChild(t[i].element);
381					
382				}
383
384				init.show();
385
386				ctrl.last		= init;
387				ctrl.ulElement	= ul;
388				ctrl.element	= "div".setProp("class", "vxJS_tabBar").create([conf.spacersTop, ul, conf.spacersBottom]);
389
390				container.insertBefore(ctrl.element, container.firstChild);
391
392				vxJS.event.addListener(ctrl, "click", clickListener);
393				tabBars.push(ctrl);
394				
395				if(supportsHistory && init.id && conf.setHash) {
396					window.history.replaceState(init.id, "", "#" + init.id);
397				}
398
399			}(tabbed[tbCount]));
400		}
401
402		return tabBars;
403	};
404}());

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.