UNPKG

11.4 kBJavaScriptView Raw
1/**
2 * https://github.com/yuche/vue-strap/blob/master/src/utils/NodeList.js
3 */
4const ArrayProto = Array.prototype
5const nodeError = new Error('Passed arguments must be of Node')
6let blurEvent
7let blurList = []
8let Events = []
9
10function isNode (val) { return val instanceof window.Node }
11function isNodeList (val) {
12 return val instanceof window.NodeList ||
13 val instanceof window.HTMLCollection ||
14 val instanceof Array
15}
16
17class 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 // event handlers
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
337const NL = NodeList.prototype
338
339function 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
357Object.getOwnPropertyNames(ArrayProto).forEach(key => {
358 if (key !== 'join' && key !== 'copyWithin' && key !== 'fill' && typeof NL[key] === 'undefined') {
359 NL[key] = ArrayProto[key]
360 }
361})
362if (window.Symbol && window.Symbol.iterator) {
363 NL.values = ArrayProto[window.Symbol.iterator]
364 NL[window.Symbol.iterator] = NL.values
365}
366const div = document.createElement('div')
367function 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}
405for (const prop in div) setterGetter(prop)
406
407function NodeListJS (...args) { return new NodeList(args) }
408window.NL = NodeListJS
409
410export default NodeListJS