NodeList.js
hace que el uso de las API DOM nativas en una Array
de Nodes
sea tan fácil como jQuery
con los beneficios de ser extremadamente pequeño, alrededor de 4k minificado , y el navegador como una dependencia (esa es la parte más interesante) .
Lo primero que notarás es que estoy usando $$
, la razón por la que elegí esto para seleccionar DOM Nodes
es porque si abres tus herramientas de desarrollo y escribes lo siguiente:
$$ ( 'div' ) ; // Will return a NodeList
NodeList.js
: HTML
que manipularemos de la siguiente manera: < body >
< div id =" container " class =" cont " >
< div class =" child " > </ div >
< div class =" child " > </ div >
< div class =" child " > </ div >
< div class =" child " > </ div >
< div class =" child " > </ div >
< div class =" child " > </ div >
< div class =" child " > </ div >
< div class =" child " > </ div >
< div class =" child " > </ div >
< div class =" child " > </ div >
</ div >
</ body >
#container
: Cada uno de los siguientes devuelve una Array of Nodes
(también conocida como mi NodeList
, no la NodeList
nativa del navegador)
// Method 1
$$ ( '#container div' ) ;
// Method 2
$$ ( '#container' ) . children ;
// Method 3
$$ ( 'div div' ) ;
Si pasa una cadena de consulta, hay un segundo argumento que puede pasar como alcance:
let container = document . getElementById ( 'container' ) ;
$$ ( 'div' , container ) ;
Lo que equivaldría a:
// Just this doesn't return my NodeList, but the browser's NodeList
container . querySelectorAll ( 'div' ) ;
Puedes pasar nodos como argumentos:
$$(document, document.body); // returns NodeList
También puede pasar 1 Array
de nodos o una NodeList
o una HTMLCollection
No se aplanará, para aplanar use concat()
:
$$([document, document.body]); // returns NodeList
Node
:Cómo lo harías normalmente:
let children = document . getElementsByClassName ( 'child' ) ;
Ahora obtendrías propiedades en los hijos del #container
:
for ( let i = 0 , l = children . length ; i < l ; i ++ ) {
children [ i ] . id ; // ''
children [ i ] . nodeName ; // 'DIV'
children [ i ] . className ; // 'child'
}
Así es como lo harías con nodeList.js
:
$$ ( '.child' ) . id ; // ['', '' ... x10]
$$ ( '.child' ) . nodeName ; // ['DIV', 'DIV' ... x10]
$$ ( '.child' ) . className ; // ['child', 'child' ... x10]
Por lo tanto, leerías cada propiedad tal como lo harías con un solo Node
:)
Observe cómo devuelve una Array
del valor de la propiedad, lo que significa que puede seleccionarlas por index
y usar cualquier Array Methods
en ellas, lo verá cuando llegue a la parte del bucle.
node
: Sigamos usando la variable children
, así es como establecerías las propiedades de las children
:
for ( let i = 0 , l = children . length ; i < l ; i ++ ) {
children [ i ] . className = 'containerChild' ;
children [ i ] . textContent = 'This is some text' ;
}
Así es como lo harías con NodeList.js
:
$$ ( '.child' ) . className = 'containerChild' ;
$$ ( '.child' ) . textContent = 'This is some text' ;
node
: Todavía usando la variable children
:
Agreguemos un detector de eventos a cada nodo, aunque lo mejor sería event delegation
, pero por el bien de este ejemplo:
for ( let i = 0 , l = children . length ; i < l ; i ++ ) {
children [ i ] . addEventListener ( 'click' , function ( ) {
console . log ( this , 'was clicked' ) ;
} ) ;
}
Así es como lo harías con NodeList.js
:
$$ ( '.child' ) . addEventListener ( 'click' , function ( ) {
console . log ( this , 'was clicked' ) ;
} ) ;
Genial, ¿verdad? Puedes usar cualquier Native DOM method
:
Establezcamos algunos atributos:
$$ ( '.child' ) . setAttribute ( 'class' , 'child div' ) ;
// For setting the class you could just do:
$$ ( '.child' ) . className = 'child div' ;
Haciendo clic en los elementos:
$$ ( '.child' ) . click ( ) ;
Eliminando los elementos:
$$ ( '.child' ) . remove ( ) ;
Creo que estás entendiendo el punto: cualquier Native DOM Method
que herede cada Node/Element
puedes simplemente llamarlo en NodeList
y se llamará en cada elemento.
Por cierto: todos los métodos DOM
que normalmente devolverían undefined
cuando se llaman en un solo Node
devolverán la misma NodeList
para permitir el encadenamiento de métodos. Como setAttribute()
.
Usando un bucle for y ES6
for-of
:
Simplemente eliminaremos los nodos del DOM
como ejemplos:
let nodes = $$ ( '.child' ) ;
for ( let i = 0 , l = nodes . length ; i < l ; i ++ ) {
nodes [ i ] . remove ( ) ;
}
for ( let node of nodes ) {
node . remove ( ) ;
}
Usando forEach
:
// Removes all Nodes and returns same the NodeList to allow method chaining
$$ ( '.child' ) . forEach ( function ( node ) {
node . remove ( ) ;
} ) ;
// But Just do:
$$ ( '.child' ) . remove ( ) ;
Recorriendo las propiedades:
// Returns Array of style objects (CSSStyleDeclaration)
let styles = $$ ( '.child' ) . style ;
for ( let i = 0 , l = styles . length ; i < l ; i ++ ) {
styles [ i ] . color = 'red' ;
}
for ( let style of styles ) {
style . color = 'red' ;
}
styles . forEach ( function ( style ) {
style . color = 'red' ;
} ) ;
// OR loop through the nodes themselves
let nodes = $$ ( '.child' ) ;
for ( let i = 0 , l = nodes . length ; i < l ; i ++ ) {
nodes [ i ] . style . color = 'red' ;
}
for ( let node of nodes ) {
node . style . color = 'red' ;
}
nodes . forEach ( function ( node ) {
node . style . color = 'red' ;
} ) ;
// Returns NodeList containing first Node
$$ ( '.child' ) . slice ( 0 , 1 ) ;
El mapeo es fácil: simplemente obtenga la propiedad tal como lo haría en un solo nodo.
// Returns an Array of the id of each Node in the NodeList
$$ ( '#container' ) . id ;
// No need for
$$ ( '#container' ) . map ( function ( element ) {
return element . id ;
} ) ;
// Map() Checks if Array is fully populated with nodes so returns a NodeList populated with firstChld nodes
$$ ( '#container div' ) . map ( function ( div ) {
return div . firstChild ;
} ) ;
// Maps the firstChild node and removes it, and returns the NodeList of firstChild Nodes
$$ ( '#container' ) . map ( function ( div ) {
return div . firstChild ;
} ) . remove ( ) ;
// Or:
$$ ( '#container' ) . firstChild . remove ( ) ;
// Filter out the #container div
$$ ( 'div' ) . filter ( function ( div ) {
return ! div . matches ( '#container' ) ;
} ) ;
No se me ocurre un mejor ejemplo para usar Reducir en una NodeList (pero es posible)
let unique = $$ ( 'div' ) . reduce ( function ( set , div ) {
set . add ( div . parentElement ) ;
return set ;
} , new Set ( ) ) ;
También está reduceRight()
Todos los siguientes métodos concat()
devuelven una nueva NodeList
concatenada (sin afectar la NodeList
a la que se llama concat()
)
let divs = $$ ( 'div' ) ;
// Method 1 passing a Node
let divsAndBody = divs . concat ( document . body ) ;
// Method 2 passing an Array of Nodes
let divsAndBody = divs . concat ( [ document . body ] ) ;
// Method 3 passing a NodeList
let divsAndBody = divs . concat ( $$ ( 'body' ) ) ;
// Method 4 passing an Array of NodeList
let divsAndBody = divs . concat ( [ $$ ( 'body' ) ] ) ;
// Method 5 passing multiple Nodes as arguments
let divsAndBodyAndHTML = divs . concat ( document . body , document . documentHTML ) ;
// Method 6 passing multiple Arrays of Nodes as arguments
let divsAndBodyAndHTML = divs . concat ( [ document . body ] , [ document . documentHTML ] ) ;
// Method 7 passing multiple Arrays of NodeList as are arguments
let divsAndBodyAndHTML = divs . concat ( [ $$ ( 'body' ) ] , [ $$ ( 'html' ) ] ) ;
Concat()
es recursivo, por lo que puedes pasar un Array
que sea tan profundo como desees.
Ahora, si pasa algo que no sea un Node
, NodeList
, HTMLCollections
, Array
o un Deep Array of Arrays
que contenga algo que no sea Node
, NodeList
, HTMLCollections
, Array
arrojará un Error
.
let divs = $$ ( 'div' ) ;
// Pushes the document.body element, and returns the same NodeList to allow method chaining.
divs . push ( document . body ) ;
let divs = $$ ( 'div' ) ;
// Removes last Node in the NodeList and returns a NodeList of the removed Nodes
divs . pop ( ) ;
pop()
toma un argumento opcional de cuántos Nodes
POP
// Removes last 2 Nodes in the NodeList and returns a NodeList of the removed Nodes
divs . pop ( 2 ) ;
let divs = $$ ( 'div' ) ;
// Removes first Node in the NodeList and returns a NodeList of the removed Nodes
divs . shift ( ) ;
shift()
también toma un argumento opcional de cuántos Nodes
cambiar
// Removes first 2 Nodes in the NodeList and returns a NodeList of the removed Nodes
divs . shift ( 2 ) ;
let divs = $$ ( 'div' ) ;
// Inserts/unshifts the document.body into the beginning of the NodeList and returns the same NodeList to allow method chaining.
divs . unshift ( document . body ) ;
Reemplacemos el primer elemento que sería #container con document.body
let divs = $$ ( 'div' ) ;
// Removes the first Element, inserts document.body in its place and returns a NodeList of the spliced Nodes
divs . splice ( 0 , 1 , document . body ) ;
let divs = $$ ( '.child' ) ;
// Gives each div a data-index attribute
divs . forEach ( function ( div , index ) {
div . dataset . index = index ;
} ) ;
// Reverse the NodeList and returns the same NodeList
divs . sort ( function ( div1 , div2 ) {
return div2 . dataset . index - div1 . dataset . index ;
} ) ;
// Returns the same NodeList, but reversed
$$ ( 'div' ) . reverse ( ) ;
No puse un método join
para NodeLists
porque sería inútil en los Nodos reales:
// Returns "[object HTMLDivElement], [object HTMLDivElement] ..."
$$ ( '.child' ) . join ( ) ;
Por lo tanto, aún puedes usarlo al mapear propiedades:
// Returns "child,child,child,child,child,child,child,child,child,child"
$$ ( '.child' ) . className . join ( ) ;
// Returns true if passed Node is included in the NodeList
$$ ( 'body' ) . includes ( document . body ) ;
// Returns body element: <body>
$$ ( 'body' ) . find ( function ( el ) {
return el === el ;
} ) ;
// Returns 0
$$ ( 'body' ) . findIndex ( function ( el ) {
return el === el ;
} ) ;
Es posible que haya métodos DOM
que tengan el mismo nombre que los de Array.prototype
en el futuro, o tal vez simplemente desee convertir NodeList
en un Array
para poder usarlo como un Array
nativo :
asArray
$$ ( 'body' ) . asArray ; // returns Array
$$ ( 'body' ) . asArray . forEach ( function ( ) { ... } ) ; // uses native Array method therefore you cannot chain
Bien, ¿qué tal si tratamos con elementos que tienen propiedades únicas? Al igual que HTMLAnchorElement(s)
tienen la propiedad href
que no se hereda de HTMLElement
. No hay HTMLAnchorElements
en este ejemplo, pero así es como lo manejará.
// Returns undefined because it's a unique property that every element does not inherit
$$ ( 'a' ) . href
// Returns an Array of href values
$$ ( 'a' ) . get ( 'href' ) ;
Get()
también se puede utilizar en una Array
de propiedades:
// Returns an Array of the value of each node.style.color
$$ ( '.child' ) . style . get ( 'color' ) ;
// Sets the href property of each Node in NodeList
$$ ( 'a' ) . set ( 'href' , 'https://www.example.com/' ) ;
set()
solo establecerá las propiedades en los Nodes
cuyas propiedades no estén indefinidas:
$$ ( 'div, a' ) . set ( 'href' , 'https://www.example.com/' ) ;
href
solo se establecerá en los elementos <a>
y no en los <div>
s
set()
también se puede utilizar en una Array
de propiedades:
// Sets each element's color to red and returns the Array of styles back
$$ ( '.child' ) . style . set ( 'color' , 'red' ) ;
También puede establecer varias propiedades:
$$ ( '.child' ) . set ( {
textContent : 'Hello World' ,
className : 'class1 class2'
} ) ;
Lo mismo con las propiedades asignadas:
$$ ( '.child' ) . style . set ( {
color : 'red' ,
background : 'black'
} ) ;
Recuerda que puedes encadenar:
$$ ( '.child' ) . set ( {
textContent : 'Hello World' ,
className : 'class1 class2'
} ) . style . set ( {
color : 'red' ,
background : 'black'
} ) ;
Hay métodos que son exclusivos de ciertos elementos. Así es como llamarías a esos métodos:
$$ ( 'video' ) . call ( 'pause' ) ;
O simplemente podrías recorrer los elementos y llamar a los métodos.
¿Qué pasa con el paso de argumentos?
// Returns Array of `CanvasRenderingContext2D`
$$ ( 'canvas' ) . call ( 'getContext' , '2d' ) ;
Si el método llamado en cualquiera de los elementos devuelve algo, call()
devolverá una Array
de esos elementos devueltos; de lo contrario, se devolverá NodeList
para permitir el encadenamiento de métodos.
El método item(index)
del navegador hace lo mismo que NodeList[index]
pero en el mío devuelve ese Node
como mi NodeList
(si conoce jQuery
es lo mismo que el método eq()
de jQuery)
// returns the <html> element
$$ ( 'html, body' ) [ 0 ] ;
// returns my NodeList [<html>]
$$ ( 'html, body' ) . item ( 0 ) ;
Esto es para que puedas seguir usando las mismas propiedades/métodos de mi NodeList, en lugar de tener que slice
un Node
owner
: Todo lo que hace la propiedad propietaria es devolverle la NodeList
desde la que se asignó la propiedad:
var elms = $$ ( '.child' ) ;
elms . style . owner === elms ; // true
Entonces puedo hacer todo tipo de cosas:
Recuerde que style
de mapeo devuelve una Array
de CSSStyleDeclarations
$$ ( '.child' ) . style ;
Esto le devolverá la NodeList
desde la que se asignó style
:
var childs = $$ ( '.child' ) ;
childs . style . owner === childs ; // true
Si conoce jQuery
es lo mismo que su propiedad prevObj
.
$$ . NL . myMethod = function ( ) {
// You'll have to write your own loop here if you want to call this on each Node or use:
this . forEach ( function ( node ) {
// do something with each node
} ) ;
}
Navegador | Versión |
---|---|
fuegofox | 6+ |
Safari | 5.0.5+ |
Cromo | 6+ |
ES DECIR | 9+ |
Ópera | 11+ |
Atención: debe darse cuenta de que mi biblioteca depende del navegador que está ejecutando (lo cual es fantástico, por lo que se actualiza automáticamente cuando el navegador actualiza el DOM
con nuevas propiedades/métodos), lo que significa: digamos que la propiedad hidden
no existe en el navegador. API DOM
que no puedes hacer: $$('.child').hidden = true;