-
Notifications
You must be signed in to change notification settings - Fork 0
/
teinte.tree.js
292 lines (268 loc) · 10.8 KB
/
teinte.tree.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
/**
<h1>Tree, automatic table of contents and other tools for clickable trees</h1>
LGPL http://www.gnu.org/licenses/lgpl.html
© 2005 Frederic.Glorieux@fictif.org et École nationale des chartes
© 2012 Frederic.Glorieux@fictif.org
© 2013 Frederic.Glorieux@fictif.org et LABEX OBVIL
<p>
Manage show/hide trees. No dependancies to a javascript library.
The HTML structure handled is a multilevel list with nested ul in li.
Relies the more as possible on CSS, and toggles between
classes, especially to display groups of items (ul).
<b>li.plus</b> means there is more items inside (ul inside are hidden).
<b>li.minus</b> means it's possible to hide items inside.
First usecase is to generate an autotoc on the header hierarchy of HTML page.
All the events on such trees are also available for user written table of contents.
The method Tree.load() (to use on an body.onload() event) may be used to to add events
to all li.plus and li.minus.
</p>
<pre>
<body>
<h1>Title of page</h1>
...
<h2 id="header_id">1) header</h2>
...
<h2>2) header</h2>
...
<div id="nav ">
<-- Generated by javascript -->
<ul class="tree">
<li class="plus"><a href="#h_1">Title of page</a>
<ul>
<li><a href="#header_id">1) header</a></li>
<li><a href="#h_3">2) header</a></li>
</ul>
</li>
</ul>
</div>
<!-- all li.plus and li.minus will become clickable -->
<script src="../teipot/js/Tree.js">//</script>
</pre>
*/
if (!document.createElementNS) document.createElementNS = function (uri, name) {
return document.createElement(name);
};
var Tree = {
/** default class name for root ul */
TREE: "tree",
/** default class name for li with hidden list */
MORE: "more",
/** default class name for li with visible list */
LESS: "less",
/** default class name for clicked link */
HERE: "here",
/** default class name of */
ASIDE: "sidebar",
/** the pannel element */
aside: null,
/** default id where to find titles */
MAIN: "main",
/** the main element */
main: null,
/** default id where to put an autotoc */
TOC: "toc",
/**
* Ititialisation of the object
*/
ini: function () {
if (Tree.reLessmore) return;
Tree.reLessmore = new RegExp(" *(" + Tree.LESS + "|" + Tree.MORE + ") *", "gi");
Tree.reHere = new RegExp(" *(" + Tree.HERE + ") *", "gi");
// case seems sometimes significant
Tree.reHereDel = new RegExp(' *' + Tree.HERE + ' *', "gi");
},
/**
* What can be done for document.onload
*/
loaded: false,
load: function () {
const els = document.getElementsByClassName(Tree.TREE);
for (var maxj = els.length, j = 0; j < maxj; j++) {
Tree.treeprep(els[j]);
}
},
/**
* Add events to a a recursive list
*/
treeprep: function (ul) {
if (!ul) return false;
if (ul.nodeType != 1) ul = document.getElementById(ul);
if (!ul) return false;
if (ul.className.indexOf("tree") == -1) return false;
var nodeset = ul.getElementsByTagName("li");
for (var i = 0; i < nodeset.length; i++) {
target = "";
li = nodeset[i];
// if item as children, hide them
if (li.getElementsByTagName("ul").length && li.className.search(Tree.reLessmore) == -1) {
li.className = Tree.MORE + " " + li.className;
}
li.ul = ul; // set the root ul
li.onclick = function (e) { return Tree.click(this, e); }
// hilite current link in this item
var links = li.getElementsByTagName("a");
if (!links.length) continue;
// this loop will go inside all links of the <li>, especially children <ul>, bad
// for(var j=0; j < links.length; j++) {
a = links[0];
parent = a;
// for a folder with no link, do not hilite here
while (parent = parent.parentNode) {
if (parent.nodeName.toLowerCase() != 'li') continue;
if (parent == li) break;
a = null;
}
if (!a) continue;
// now, check if item should be opened
target = a.getAttribute('href'); // should return unresolved URI like written in source file
if (location.pathname != a.pathname) continue; // not same path, go away
keep = true; // at least, correct path, but check if hash or query could be better
if (Tree.lastHere && a.hash && !location.hash) continue; // hash requested, no hash in URI, let first item found
if (Tree.lastHere && a.hash && a.hash != location.hash) continue; // hash requested, not good hash in URI, let first item found
if (Tree.lastHere && a.search && !location.search) continue; // query param requested, no query parma in URI, let first item found
if (a.search && location.search) {
search = a.search.replace('?', '&') + '&';
lost = location.search.replace('?', '&') + '&';
found = lost.indexOf(search);
if (Tree.lastHere && found < 0) continue; // query param not found, let first item found
}
/*
// class on li or a ?
if (a.className.indexOf(Tree.HERE) != -1) a.className=a.className.replace(Tree.reHereDel, '');
a.className += " "+Tree.HERE;
*/
// close parents of first found
if (Tree.lastHere) {
Tree.lastHere.className = Tree.lastHere.className.replace(Tree.reHereDel, '');
Tree.close(Tree.lastHere);
}
// open parents
Tree.open(li);
li.className = li.className.replace(Tree.reHereDel, '') + " " + Tree.HERE;
if (li.focus) li.focus();
Tree.lastHere = li;
}
// add a class for CSS to say this list is set (before display none things)
if (ul.className.indexOf("treejs") != -1);
else { ul.className = ul.className + " treejs"; }
// changing global class should have resized object
// check if link is visible, if not, scroll to it
/*
if (Tree.top(Tree.lastHere) > Tree.winHeight()) {
lastid = window.location.hash;
if (lastid == '#') lastid=null;
Tree.lastHere.id = "lasthere";
window.location.hash = '#' + Tree.lastHere.id;
window.location.hash = lastid;
}
*/
},
/**
* Change ClassName of a <li> onclick to open/close
*/
click: function (li, e) {
if (Tree.className != null) li = this;
if (!li) return false;
var ret = false;
var className = "";
var here = "";
if (li.className.indexOf(Tree.LESS) > -1) className = " " + Tree.MORE;
else if (li.className.indexOf(Tree.MORE) > -1) className = " " + Tree.LESS;
// if click in a link, force open
var ev = e || event,
src = ev.target || ev.srcElement;
while (src && src.tagName.toLowerCase() != "li") {
if (src.tagName.toLowerCase() == 'a') {
// if link, hilite the parent li
className = " " + Tree.LESS;
here = true;
ret = true;
break;
}
src = src.parentNode;
}
// unset the last of this list, and set this one
if (here) {
// set it at document level
if (Tree.lastHere) Tree.lastHere.className = Tree.lastHere.className.replace(Tree.reHereDel, '')
li.className = li.className.replace(Tree.reHereDel, '') + " " + Tree.HERE;
Tree.lastHere = li;
}
// change class only if already less or more
if (li.className.search(Tree.reLessmore) > -1) li.className = li.className.replace(Tree.reLessmore, ' ') + className;
// alert(li.className+" "+className);
// stop propagation
if (ev && document.all) ev.cancelBubble = true;
if (ev && ev.stopPropagation) ev.stopPropagation();
return ret;
},
/**
* Recursively open li ancestors
*/
open: function () {
var li; // don't forget or may produce some strange var collapse
for (i = arguments.length - 1; i >= 0; i--) {
li = arguments[i];
if (li.className == null) li = document.getElementById(arguments[i]);
if (!li) continue;
while (li && li.tagName.toLowerCase() == 'li') {
// avoid icon in front of single item
if (li.className.match(Tree.reLessmore) || li.getElementsByTagName('UL').length > 0)
li.className = (li.className.replace(Tree.reLessmore, ' ') + " " + Tree.LESS).trim();
li = li.parentNode.parentNode; // get a possible li ancestor (jump an ul container)
}
}
},
/**
* Recursively close li ancestors
*/
close: function () {
var li; // don't forget or may produce some strange var collapse
for (i = arguments.length - 1; i >= 0; i--) {
li = arguments[i];
if (li.className == null) li = document.getElementById(arguments[i]);
if (!li) continue;
while (li && li.tagName.toLowerCase() == 'li') {
if (li.className.match(Tree.reLessmore) || li.getElementsByTagName('UL').length > 0)
li.className = (li.className.replace(Tree.reLessmore, ' ') + " " + Tree.MORE).trim();
li = li.parentNode.parentNode; // get a possible li ancestor (jump an ul container)
}
}
},
/**
* Hilite an anchor element, now replaced by the :target CSS selector
*/
hash: function (id) {
// id maybe an Event
if (id && id.stopPropagation) id = null;
// if another element has been hilited
if (!this.window.anchorLast);
else {
this.window.anchorLast.className = this.window.anchorLast.className.replace(/ *mark */g, '');
}
if (!id) {
id = window.location.hash;
if (id.indexOf('#') != 0) return false;
id = id.substring(1);
}
var o = document.getElementById(id);
// if (!o) take from anchors array ?
if (!o) return false;
if (o.className.indexOf("mark") > -1) return false;
o.className += " mark";
this.window.anchorLast = o;
// here it's OK, but an event scroll the page to its right place after
return false;
}
}
Tree.ini();
// if loaded as bottom script, create trees ?
if (window.document.body) {
Tree.load();
} else if (window.addEventListener) {
window.addEventListener('load', Tree.load, false);
} else if (window.attachEvent) {
if (!Tree.loaded) window.attachEvent('onload', Tree.load);
// window.attachEvent('onload', Tree.autotoc);
window.attachEvent('onload', Notes.load);
}