1 |
|
2 |
|
3 |
|
4 | const ArrayProto = Array.prototype
|
5 | const nodeError = new Error('Passed arguments must be of Node')
|
6 | let blurEvent
|
7 | let blurList = []
|
8 | let Events = []
|
9 |
|
10 | function isNode (val) { return val instanceof window.Node }
|
11 | function isNodeList (val) {
|
12 | return val instanceof window.NodeList ||
|
13 | val instanceof window.HTMLCollection ||
|
14 | val instanceof Array
|
15 | }
|
16 |
|
17 | class NodeList {
|
18 | constructor (args) {
|
19 | let nodes = args
|
20 | if (args[0] === window) {
|
21 | nodes = [window]
|
22 | } else if (typeof args[0] === 'string') {
|
23 | nodes = (args[1] || document).querySelectorAll(args[0])
|
24 | if (args[1]) { this.owner = args[1] }
|
25 | } else if (0 in args && !isNode(args[0]) && args[0] && 'length' in args[0]) {
|
26 | nodes = args[0]
|
27 | if (args[1]) { this.owner = args[1] }
|
28 | }
|
29 | if (nodes) {
|
30 | for (const i in nodes) {
|
31 | if (Object.prototype.hasOwnProperty.call(nodes, i)) {
|
32 | this[i] = nodes[i]
|
33 | }
|
34 | }
|
35 | this.length = nodes.length
|
36 | } else {
|
37 | this.length = 0
|
38 | }
|
39 | }
|
40 |
|
41 | concat (...args) {
|
42 | const nodes = ArrayProto.slice.call(this)
|
43 | function flatten (arr) {
|
44 | ArrayProto.forEach.call(arr, el => {
|
45 | if (isNode(el)) {
|
46 | if (!~nodes.indexOf(el)) nodes.push(el)
|
47 | } else if (isNodeList(el)) {
|
48 | flatten(el)
|
49 | }
|
50 | })
|
51 | }
|
52 | ArrayProto.forEach.call(args, arg => {
|
53 | if (isNode(arg)) {
|
54 | if (!~nodes.indexOf(arg)) nodes.push(arg)
|
55 | } else if (isNodeList(arg)) {
|
56 | flatten(arg)
|
57 | } else {
|
58 | throw Error('Concat arguments must be of a Node, NodeList, HTMLCollection, or Array of (Node, NodeList, HTMLCollection, Array)')
|
59 | }
|
60 | })
|
61 | return NodeListJS(nodes, this)
|
62 | }
|
63 | delete () {
|
64 | const notRemoved = flatten(this).filter(el => {
|
65 | if (el.remove) {
|
66 | el.remove()
|
67 | } else if (el.parentNode) {
|
68 | el.parentNode.removeChild(el)
|
69 | }
|
70 | return document.body.contains(el)
|
71 | })
|
72 | if (notRemoved.length) console.warn('NodeList: Some nodes could not be deleted.')
|
73 | return notRemoved
|
74 | }
|
75 | each (...args) {
|
76 | ArrayProto.forEach.apply(this, args)
|
77 | return this
|
78 | }
|
79 | filter (...args) {
|
80 | return NodeListJS(ArrayProto.filter.apply(this, args), this)
|
81 | }
|
82 | find (element) {
|
83 | const nodes = []
|
84 | flatten(this).forEach(node => { ArrayProto.push.apply(nodes, node.querySelectorAll(element)) })
|
85 | return flatten(nodes, this.owner)
|
86 | }
|
87 | findChildren (element) {
|
88 | if (element) return this.find(element).filter(el => this.includes(el.parentElement))
|
89 | return flatten(this.map(el => el.children))
|
90 | }
|
91 | forEach (...args) {
|
92 | ArrayProto.forEach.apply(this, args)
|
93 | return this
|
94 | }
|
95 | includes (element, index) {
|
96 | return ~this.indexOf(element, index)
|
97 | }
|
98 | map (...args) {
|
99 | const mapped = ArrayProto.map.apply(this, args)
|
100 | return mapped.some(el => (isNode(el) || isNodeList(el))) ? flatten(mapped, this) : mapped
|
101 | }
|
102 | parent () {
|
103 | return flatten(this.map(el => el.parentNode), this)
|
104 | }
|
105 | pop (amount) {
|
106 | if (typeof amount !== 'number') { amount = 1 }
|
107 | const nodes = []
|
108 | const pop = ArrayProto.pop.bind(this)
|
109 | while (amount--) nodes.push(pop())
|
110 | return NodeListJS(nodes, this)
|
111 | }
|
112 | push (...args) {
|
113 | ArrayProto.forEach.call(args, arg => {
|
114 | if (!isNode(arg)) throw nodeError
|
115 | if (!~this.indexOf(arg)) ArrayProto.push.call(this, arg)
|
116 | })
|
117 | return this
|
118 | }
|
119 | shift (amount) {
|
120 | if (typeof amount !== 'number') { amount = 1 }
|
121 | const nodes = []
|
122 | while (amount--) nodes.push(ArrayProto.shift.call(this))
|
123 | return nodes.length === 1 ? nodes[0] : NodeListJS(nodes, this)
|
124 | }
|
125 | slice (...args) {
|
126 | return NodeListJS(ArrayProto.slice.apply(this, args), this)
|
127 | }
|
128 | splice (...args) {
|
129 | for (let i = 2, l = args.length; i < l; i++) {
|
130 | if (!isNode(args[i])) throw nodeError
|
131 | }
|
132 | ArrayProto.splice.apply(this, args)
|
133 | return this
|
134 | }
|
135 | unshift (...args) {
|
136 | const unshift = ArrayProto.unshift.bind(this)
|
137 | ArrayProto.forEach.call(args, arg => {
|
138 | if (!isNode(arg)) throw nodeError
|
139 | if (!~this.indexOf(arg)) unshift(arg)
|
140 | })
|
141 | return this
|
142 | }
|
143 |
|
144 | addClass (classes) {
|
145 | return this.toggleClass(classes, true)
|
146 | }
|
147 | removeClass (classes) {
|
148 | return this.toggleClass(classes, false)
|
149 | }
|
150 | toggleClass (classes, value) {
|
151 | let method
|
152 | if (typeof value === 'undefined' || value === null) {
|
153 | method = 'toggle'
|
154 | } else if (value) {
|
155 | method = 'add'
|
156 | } else {
|
157 | method = 'remove'
|
158 | }
|
159 |
|
160 | if (typeof classes === 'string') {
|
161 | classes = classes.trim().replace(/\s+/, ' ').split(' ')
|
162 | }
|
163 | this.each(el => {
|
164 | let list = el.className.trim().replace(/\s+/, ' ').split(' ')
|
165 | classes.forEach(c => {
|
166 | const hasClass = ~list.indexOf(c)
|
167 | if (!hasClass && method !== 'remove') list.push(c)
|
168 | if (hasClass && method !== 'add') { list = list.filter(ele => (ele !== c)) }
|
169 | })
|
170 | el.className = list.join(' ')
|
171 | })
|
172 | return this
|
173 | }
|
174 |
|
175 | get (prop) {
|
176 | const arr = []
|
177 | this.each(el => {
|
178 | if (el !== null) { el = el[prop] }
|
179 | arr.push(el)
|
180 | })
|
181 | return flatten(arr, this)
|
182 | }
|
183 | set (prop, value) {
|
184 | if (prop.constructor === Object) {
|
185 | this.each(el => {
|
186 | if (el) {
|
187 | for (const key in prop) {
|
188 | if (key in el) { el[key] = prop[key] }
|
189 | }
|
190 | }
|
191 | })
|
192 | } else {
|
193 | this.each(el => {
|
194 | if (prop in el) { el[prop] = value }
|
195 | })
|
196 | }
|
197 | return this
|
198 | }
|
199 | call (...args) {
|
200 | const method = ArrayProto.shift.call(args)
|
201 | const arr = []
|
202 | let returnThis = true
|
203 | this.each(el => {
|
204 | if (el && el[method] instanceof Function) {
|
205 | el = el[method].apply(el, args)
|
206 | arr.push(el)
|
207 | if (returnThis && typeof el !== 'undefined') {
|
208 | returnThis = false
|
209 | }
|
210 | } else {
|
211 | arr.push(null)
|
212 | }
|
213 | })
|
214 | return returnThis ? this : flatten(arr, this)
|
215 | }
|
216 | item (index) {
|
217 | return NodeListJS([this[index]], this)
|
218 | }
|
219 | get asArray () {
|
220 | return ArrayProto.slice.call(this)
|
221 | }
|
222 |
|
223 |
|
224 | on (events, selector, callback) {
|
225 | if (typeof events === 'string') { events = events.trim().replace(/\s+/, ' ').split(' ') }
|
226 | if (!this || !this.length) return this
|
227 | if (typeof callback === 'undefined') {
|
228 | callback = selector
|
229 | selector = null
|
230 | }
|
231 | if (!callback) return this
|
232 | const fn = callback
|
233 | callback = selector ? function (e) {
|
234 | const els = NodeListJS(selector, this)
|
235 | if (!els.length) { return }
|
236 | els.some(el => {
|
237 | const target = el.contains(e.target)
|
238 | if (target) fn.call(el, e, el)
|
239 | return target
|
240 | })
|
241 | } : function (e) {
|
242 | fn.apply(this, [e, this])
|
243 | }
|
244 | this.each(el => {
|
245 | events.forEach(event => {
|
246 | if (el === window || isNode(el)) {
|
247 | el.addEventListener(event, callback, false)
|
248 | Events.push({
|
249 | el,
|
250 | event,
|
251 | callback
|
252 | })
|
253 | }
|
254 | })
|
255 | })
|
256 | return this
|
257 | }
|
258 | off (events, callback) {
|
259 | if (events instanceof Function) {
|
260 | callback = events
|
261 | events = null
|
262 | }
|
263 | if (typeof events === 'string' && callback instanceof Function) {
|
264 | this.each(el => {
|
265 | events.split(' ').forEach(event => {
|
266 | Events.forEach((e, i) => {
|
267 | if (Events[i] && Events[i].el === el &&
|
268 | Events[i].event === event && Events[i].callback === callback) {
|
269 | Events[i].el.removeEventListener(Events[i].event, Events[i].callback)
|
270 | delete Events[i]
|
271 | }
|
272 | })
|
273 | })
|
274 | })
|
275 | } else if (typeof events === 'string') {
|
276 | this.each(el => {
|
277 | events.split(' ').forEach(event => {
|
278 | Events.forEach((e, i) => {
|
279 | if (Events[i] && Events[i].el === el && Events[i].event === event) {
|
280 | Events[i].el.removeEventListener(Events[i].event, Events[i].callback)
|
281 | delete Events[i]
|
282 | }
|
283 | })
|
284 | })
|
285 | })
|
286 | } else if (callback instanceof Function) {
|
287 | this.each(el => {
|
288 | Events.forEach(e => {
|
289 | if (Events[e] && Events[e].el === el && Events[e].callback === callback) {
|
290 | Events[e].el.removeEventListener(Events[e].event, Events[e].callback)
|
291 | delete Events[e]
|
292 | }
|
293 | })
|
294 | })
|
295 | } else {
|
296 | this.each(el => {
|
297 | Events.forEach(e => {
|
298 | if (Events[e] && Events[e].el === el) {
|
299 | Events[e].el.removeEventListener(Events[e].event, Events[e].callback)
|
300 | delete Events[e]
|
301 | }
|
302 | })
|
303 | })
|
304 | }
|
305 | Events = Events.filter(el => (typeof el !== 'undefined'))
|
306 | return this
|
307 | }
|
308 | onBlur (callback) {
|
309 | if (!this || !this.length) return this
|
310 | if (!callback) return this
|
311 | this.each(el => { blurList.push({ el, callback }) })
|
312 | if (!blurEvent) {
|
313 | blurEvent = e => {
|
314 | blurList.forEach(item => {
|
315 | const target = item.el.contains(e.target) || item.el === e.target
|
316 | if (!target) item.callback.call(item.el, e, item.el)
|
317 | })
|
318 | }
|
319 | document.addEventListener('click', blurEvent, false)
|
320 | document.addEventListener('touchstart', blurEvent, false)
|
321 | }
|
322 | return this
|
323 | }
|
324 | offBlur (callback) {
|
325 | this.each(el => {
|
326 | blurList = blurList.filter(blur => {
|
327 | if (blur && blur.el === el && (!callback || blur.callback === callback)) {
|
328 | return false
|
329 | }
|
330 | return el
|
331 | })
|
332 | })
|
333 | return this
|
334 | }
|
335 | }
|
336 |
|
337 | const NL = NodeList.prototype
|
338 |
|
339 | function flatten (arr, owner) {
|
340 | const list = []
|
341 | ArrayProto.forEach.call(arr, el => {
|
342 | if (isNode(el)) {
|
343 | if (!~list.indexOf(el)) list.push(el)
|
344 | } else if (isNodeList(el)) {
|
345 | for (const id in el) list.push(el[id])
|
346 | } else if (el !== null) {
|
347 | arr.get = NL.get
|
348 | arr.set = NL.set
|
349 | arr.call = NL.call
|
350 | arr.owner = owner
|
351 | return arr
|
352 | }
|
353 | })
|
354 | return NodeListJS(list, owner)
|
355 | }
|
356 |
|
357 | Object.getOwnPropertyNames(ArrayProto).forEach(key => {
|
358 | if (key !== 'join' && key !== 'copyWithin' && key !== 'fill' && typeof NL[key] === 'undefined') {
|
359 | NL[key] = ArrayProto[key]
|
360 | }
|
361 | })
|
362 | if (window.Symbol && window.Symbol.iterator) {
|
363 | NL.values = ArrayProto[window.Symbol.iterator]
|
364 | NL[window.Symbol.iterator] = NL.values
|
365 | }
|
366 | const div = document.createElement('div')
|
367 | function setterGetter (prop) {
|
368 | if (NL[prop]) return
|
369 | if (div[prop] instanceof Function) {
|
370 | NL[prop] = (...args) => {
|
371 | const arr = []
|
372 | let returnThis = true
|
373 | for (const i in NL) {
|
374 | let el = NL[i]
|
375 | if (el && el[prop] instanceof Function) {
|
376 | el = el[prop].apply(el, args)
|
377 | arr.push(el)
|
378 | if (returnThis && typeof el !== 'undefined') {
|
379 | returnThis = false
|
380 | }
|
381 | } else {
|
382 | arr.push(null)
|
383 | }
|
384 | }
|
385 | return returnThis ? this : flatten(arr, this)
|
386 | }
|
387 | } else {
|
388 | Object.defineProperty(NL, prop, {
|
389 | get () {
|
390 | const arr = []
|
391 | this.each(el => {
|
392 | if (el !== null) { el = el[prop] }
|
393 | arr.push(el)
|
394 | })
|
395 | return flatten(arr, this)
|
396 | },
|
397 | set (value) {
|
398 | this.each(el => {
|
399 | if (el && prop in el) { el[prop] = value }
|
400 | })
|
401 | }
|
402 | })
|
403 | }
|
404 | }
|
405 | for (const prop in div) setterGetter(prop)
|
406 |
|
407 | function NodeListJS (...args) { return new NodeList(args) }
|
408 | window.NL = NodeListJS
|
409 |
|
410 | export default NodeListJS
|