Commit | Line | Data |
---|---|---|
4bbf6ff1 AE |
1 | /** |
2 | * Provides helper functions to traverse the DOM. | |
3 | * | |
4 | * @author Alexander Ebert | |
c839bd49 | 5 | * @copyright 2001-2018 WoltLab GmbH |
4bbf6ff1 | 6 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
58d7e8f8 | 7 | * @module WoltLabSuite/Core/Dom/Traverse |
4bbf6ff1 | 8 | */ |
431e4cb4 | 9 | define([], function() { |
565853e8 TD |
10 | "use strict"; |
11 | ||
4bbf6ff1 AE |
12 | /** @const */ var NONE = 0; |
13 | /** @const */ var SELECTOR = 1; | |
14 | /** @const */ var CLASS_NAME = 2; | |
15 | /** @const */ var TAG_NAME = 3; | |
16 | ||
17 | var _probe = [ | |
18 | function(el, none) { return true; }, | |
431e4cb4 | 19 | function(el, selector) { return el.matches(selector); }, |
4bbf6ff1 AE |
20 | function(el, className) { return el.classList.contains(className); }, |
21 | function(el, tagName) { return el.nodeName === tagName; } | |
22 | ]; | |
23 | ||
c318ad3b | 24 | var _children = function(el, type, value) { |
21adc182 AE |
25 | if (!(el instanceof Element)) { |
26 | throw new TypeError("Expected a valid element as first argument."); | |
27 | } | |
28 | ||
c318ad3b AE |
29 | var children = []; |
30 | ||
31 | for (var i = 0; i < el.childElementCount; i++) { | |
32 | if (_probe[type](el.children[i], value)) { | |
33 | children.push(el.children[i]); | |
34 | } | |
35 | } | |
36 | ||
37 | return children; | |
38 | }; | |
39 | ||
a76a4654 | 40 | var _parent = function(el, type, value, untilElement) { |
21adc182 AE |
41 | if (!(el instanceof Element)) { |
42 | throw new TypeError("Expected a valid element as first argument."); | |
43 | } | |
44 | ||
4bbf6ff1 AE |
45 | el = el.parentNode; |
46 | ||
47 | while (el instanceof Element) { | |
a76a4654 AE |
48 | if (el === untilElement) { |
49 | return null; | |
50 | } | |
51 | ||
4bbf6ff1 AE |
52 | if (_probe[type](el, value)) { |
53 | return el; | |
54 | } | |
55 | ||
56 | el = el.parentNode; | |
57 | } | |
58 | ||
59 | return null; | |
60 | }; | |
61 | ||
62 | var _sibling = function(el, siblingType, type, value) { | |
21adc182 AE |
63 | if (!(el instanceof Element)) { |
64 | throw new TypeError("Expected a valid element as first argument."); | |
65 | } | |
66 | ||
4bbf6ff1 AE |
67 | if (el instanceof Element) { |
68 | if (el[siblingType] !== null && _probe[type](el[siblingType], value)) { | |
69 | return el[siblingType]; | |
70 | } | |
71 | } | |
72 | ||
73 | return null; | |
74 | }; | |
75 | ||
76 | /** | |
58d7e8f8 | 77 | * @exports WoltLabSuite/Core/Dom/Traverse |
4bbf6ff1 | 78 | */ |
431e4cb4 | 79 | return { |
fa033f4f AE |
80 | /** |
81 | * Examines child elements and returns the first child matching the given selector. | |
82 | * | |
83 | * @param {Element} el element | |
84 | * @param {string} selector CSS selector to match child elements against | |
85 | * @return {(Element|null)} null if there is no child node matching the selector | |
86 | */ | |
87 | childBySel: function(el, selector) { | |
88 | return _children(el, SELECTOR, selector)[0] || null; | |
89 | }, | |
90 | ||
91 | /** | |
92 | * Examines child elements and returns the first child that has the given CSS class set. | |
93 | * | |
94 | * @param {Element} el element | |
95 | * @param {string} className CSS class name | |
96 | * @return {(Element|null)} null if there is no child node with given CSS class | |
97 | */ | |
98 | childByClass: function(el, className) { | |
99 | return _children(el, CLASS_NAME, className)[0] || null; | |
100 | }, | |
101 | ||
102 | /** | |
103 | * Examines child elements and returns the first child which equals the given tag. | |
104 | * | |
105 | * @param {Element} el element | |
106 | * @param {string} tagName element tag name | |
107 | * @return {(Element|null)} null if there is no child node which equals given tag | |
108 | */ | |
109 | childByTag: function(el, tagName) { | |
110 | return _children(el, TAG_NAME, tagName)[0] || null; | |
111 | }, | |
112 | ||
c318ad3b AE |
113 | /** |
114 | * Examines child elements and returns all children matching the given selector. | |
115 | * | |
116 | * @param {Element} el element | |
117 | * @param {string} selector CSS selector to match child elements against | |
118 | * @return {array<Element>} list of children matching the selector | |
119 | */ | |
120 | childrenBySel: function(el, selector) { | |
121 | return _children(el, SELECTOR, selector); | |
122 | }, | |
123 | ||
124 | /** | |
125 | * Examines child elements and returns all children that have the given CSS class set. | |
126 | * | |
127 | * @param {Element} el element | |
128 | * @param {string} className CSS class name | |
129 | * @return {array<Element>} list of children with the given class | |
130 | */ | |
131 | childrenByClass: function(el, className) { | |
132 | return _children(el, CLASS_NAME, className); | |
133 | }, | |
134 | ||
135 | /** | |
136 | * Examines child elements and returns all children which equal the given tag. | |
137 | * | |
138 | * @param {Element} el element | |
139 | * @param {string} tagName element tag name | |
140 | * @return {array<Element>} list of children equaling the tag name | |
141 | */ | |
142 | childrenByTag: function(el, tagName) { | |
143 | return _children(el, TAG_NAME, tagName); | |
144 | }, | |
145 | ||
4bbf6ff1 AE |
146 | /** |
147 | * Examines parent nodes and returns the first parent that matches the given selector. | |
148 | * | |
149 | * @param {Element} el child element | |
150 | * @param {string} selector CSS selector to match parent nodes against | |
a76a4654 | 151 | * @param {Element=} untilElement stop when reaching this element |
4bbf6ff1 AE |
152 | * @return {(Element|null)} null if no parent node matched the selector |
153 | */ | |
a76a4654 AE |
154 | parentBySel: function(el, selector, untilElement) { |
155 | return _parent(el, SELECTOR, selector, untilElement); | |
4bbf6ff1 AE |
156 | }, |
157 | ||
158 | /** | |
159 | * Examines parent nodes and returns the first parent that has the given CSS class set. | |
160 | * | |
161 | * @param {Element} el child element | |
162 | * @param {string} className CSS class name | |
a76a4654 | 163 | * @param {Element=} untilElement stop when reaching this element |
4bbf6ff1 AE |
164 | * @return {(Element|null)} null if there is no parent node with given class |
165 | */ | |
a76a4654 AE |
166 | parentByClass: function(el, className, untilElement) { |
167 | return _parent(el, CLASS_NAME, className, untilElement); | |
4bbf6ff1 AE |
168 | }, |
169 | ||
170 | /** | |
171 | * Examines parent nodes and returns the first parent which equals the given tag. | |
172 | * | |
173 | * @param {Element} el child element | |
174 | * @param {string} tagName element tag name | |
a76a4654 | 175 | * @param {Element=} untilElement stop when reaching this element |
4bbf6ff1 AE |
176 | * @return {(Element|null)} null if there is no parent node of given tag type |
177 | */ | |
a76a4654 AE |
178 | parentByTag: function(el, tagName, untilElement) { |
179 | return _parent(el, TAG_NAME, tagName, untilElement); | |
4bbf6ff1 AE |
180 | }, |
181 | ||
182 | /** | |
183 | * Returns the next element sibling. | |
184 | * | |
185 | * @param {Element} el element | |
186 | * @return {(Element|null)} null if there is no next sibling element | |
187 | */ | |
188 | next: function(el) { | |
189 | return _sibling(el, 'nextElementSibling', NONE, null); | |
190 | }, | |
191 | ||
192 | /** | |
193 | * Returns the next element sibling that matches the given selector. | |
194 | * | |
195 | * @param {Element} el element | |
196 | * @param {string} selector CSS selector to match parent nodes against | |
197 | * @return {(Element|null)} null if there is no next sibling element or it does not match the selector | |
198 | */ | |
199 | nextBySel: function(el, selector) { | |
200 | return _sibling(el, 'nextElementSibling', SELECTOR, selector); | |
201 | }, | |
202 | ||
203 | /** | |
204 | * Returns the next element sibling with given CSS class. | |
205 | * | |
206 | * @param {Element} el element | |
207 | * @param {string} className CSS class name | |
208 | * @return {(Element|null)} null if there is no next sibling element or it does not have the class set | |
209 | */ | |
210 | nextByClass: function(el, className) { | |
211 | return _sibling(el, 'nextElementSibling', CLASS_NAME, className); | |
212 | }, | |
213 | ||
214 | /** | |
215 | * Returns the next element sibling with given CSS class. | |
216 | * | |
217 | * @param {Element} el element | |
431e4cb4 | 218 | * @param {string} tagName element tag name |
4bbf6ff1 AE |
219 | * @return {(Element|null)} null if there is no next sibling element or it does not have the class set |
220 | */ | |
221 | nextByTag: function(el, tagName) { | |
431e4cb4 | 222 | return _sibling(el, 'nextElementSibling', TAG_NAME, tagName); |
4bbf6ff1 AE |
223 | }, |
224 | ||
225 | /** | |
226 | * Returns the previous element sibling. | |
227 | * | |
228 | * @param {Element} el element | |
229 | * @return {(Element|null)} null if there is no previous sibling element | |
230 | */ | |
231 | prev: function(el) { | |
232 | return _sibling(el, 'previousElementSibling', NONE, null); | |
233 | }, | |
234 | ||
235 | /** | |
236 | * Returns the previous element sibling that matches the given selector. | |
237 | * | |
238 | * @param {Element} el element | |
239 | * @param {string} selector CSS selector to match parent nodes against | |
240 | * @return {(Element|null)} null if there is no previous sibling element or it does not match the selector | |
241 | */ | |
242 | prevBySel: function(el, selector) { | |
243 | return _sibling(el, 'previousElementSibling', SELECTOR, selector); | |
244 | }, | |
245 | ||
246 | /** | |
247 | * Returns the previous element sibling with given CSS class. | |
248 | * | |
249 | * @param {Element} el element | |
250 | * @param {string} className CSS class name | |
251 | * @return {(Element|null)} null if there is no previous sibling element or it does not have the class set | |
252 | */ | |
253 | prevByClass: function(el, className) { | |
254 | return _sibling(el, 'previousElementSibling', CLASS_NAME, className); | |
255 | }, | |
256 | ||
257 | /** | |
258 | * Returns the previous element sibling with given CSS class. | |
259 | * | |
260 | * @param {Element} el element | |
431e4cb4 | 261 | * @param {string} tagName element tag name |
4bbf6ff1 AE |
262 | * @return {(Element|null)} null if there is no previous sibling element or it does not have the class set |
263 | */ | |
264 | prevByTag: function(el, tagName) { | |
431e4cb4 | 265 | return _sibling(el, 'previousElementSibling', TAG_NAME, tagName); |
4bbf6ff1 AE |
266 | } |
267 | }; | |
565853e8 | 268 | }); |