How do HTMLCollections work? Can I create my own HTMLCollection? - javascript

I'm working on javascript serialization library that will allow me to serialize parts of DOM. In DOM operations, we often use HTMLCollections. Now HTMLCollection isn't just an ordinary array:
var collection = document.getElementsByTagName("p");
console.log(collection.length);
document.body.appendChild(document.createElement("p"));
console.log(collection.length);
This outputs:
Obviously not an ordinary array. Obviously, it's a matching rule on dom element that updates the list every time element child node list changes. On that, I have two questions:
Can I reliably serialize HTMLCollection not as a list of elements, but as a matching rule it represents?
Can I create HTMLCollection that is instance of HTMLCollection and follows my own matching rule, without having to re-implement the algorithm that decides when to re-update the list?

Related

Is it possible to remove an entry from a NodeList without converting it to an array or destroying the DOM node?

Okay, so I'm working on a method that accepts any form of iterable as its argument. I'd like to perform what amounts to a splice on the parameter, but - and this is the crucial bit - without changing its Symbolic Type. That is to say, I'd like the return value's Symbol to match that of the input (NodeList, DOMTokenList, HTMLCollection, etc.)
Contrived Example:
let iterableList = document.querySelectorAll('p');
// Result: Nodelist(5): p, p.body, p.body, p, p
Assumptions
Assume I wish to remove those two <p class="body"> tags (from the COLLECTION, NOT the DOM).
Assume iterableList is user-provided (e.g. I cannot simply alter the query to read p:not(.body))
Assume we do not know what kind of iterator is the input (i.e. in this example, by virtue of the fact it was a querySelectorAll that produced our results, it is a NodeList. Conversely, if it had been a getElementsByTagName it would be a HTMLCollection. Or any of a half dozen others)
Assume the return value should have the same Symbol as the input.
Assume I chose this example because the results are live nodes (and we know the subset could have come from a querySelectorAll('p:not(.body)'), so we know the subset is possible)
1. Now, obviously I can convert the symbol to an Array...
...through any number of means. I could then remove the two offending nodes, again by a variety of tactics. The problem is then my return value is an Array, and I've killed off the live node correspondence within the browser.
2. I could clone just the desired nodes...
...into a document fragment or similar, then re-query the fragment to return just the subset, but then I'd be targeting clones of the DOM nodes in question, not really those themselves, plus it would shake free any event bindings they had once had.
3a. I could insert a DOM node, append the array nodes I wish to keep, then query THAT, then put them back again, and destroy the inserted node...
... which keeps the nodes in tact (replete with any events bound to them) and even auto-remaps their xPaths to their original DOM locations...
3b. ...or even use the unique attributes of each node to attempt to dynamically construct a selector that matched only those I wished to keep...
...but because I don't know the Symbolic Type of iterator the collection is prior to receiving it, I would need a massive collection of such workarounds to conditionally handle each of the possibilities (and that's entirely notwithstanding the fact it'd be a bicycle-tire-with-a-banana solution that I'D never let through code review, either).
So I guess my question is:
Is there a way to modify, selectively clone, or even iteratively construct such an output? I don't need to perform any operation other than REMOVING one or more of the offending nodes; no edits or inserts.
A for...of statement will let one iterate them without conversion to the Array prototype, although I'm not aware of a way to call the Iterator constructors on the fly, even if I could figure out a way to move one of the live nodes from one collection to the other (the spread operator coerces the collection into an array).
Anyone know any good voodoo for this one? Or am I just SOL here?

Number of nodes returned in MutationRecord.addedNodes nodelist (mutationObserver)

MutationRecord.addedNodes returns NodeList with nodes detected as added in my document. When I use obj.appendChild() method, mutationObserver detects it and returns this ONE node in MutationRecord.addedNodes as nodelist [node]. When I use .appendChild() in loop, mutationObserver fires as many times as the number of loops amounts.
I'm just wondering if there is any case when mutationOvserver will detect more than one node added simultaneously and fire ONCE and return those nodes in MutationRecord.addedNodes as ONE nodelist [node,node,node,...]?
Judging by the spec one appendChild corresponds to a single-node addedNodes array.
addedNodes usually contains many nodes during the initial page load and, naturally, if many nodes were inserted by js code in one operation via insertAdjacentHTML, or document.write, or .innerHTML, or .outerHTML, or DocumentFragment with many nodes.

JavaScript querySelectorAll elements order

I need help with getting elements by querySelectorAll function.
When I call it and I pass a string as parameter of this function with multiple elements separated with commas, it will return me a NodeList with nodes but, they are sort by first-depth traversal pre-order (which should be as they are positioned in document body). And I have a problem when I want to call a method to return values of more elements in document saved in Array. So I call this
$('#textinput, #someotherinput').value();
where element with id #someotherinput is positioned above the element #textinput, but the result after querySelectorAll is this:
[input#someotherinput, input#textinput]
0: input#someotherinput
1: input#textinput
length: 2
__proto__: NodeList
and I need to sort them way I did when I called $(), because when I need to use values, I look just at $('#textinput, #someotherinput') and it's annoying to find out an order of the elements in document body and use returned array with values of those elements when I don't know what is saved in first (0th) position in array. And yes, I know that querySelectorAll is traversal pre-order function. So, how to sort (easy as possible) elements to MY order?
There is no simple way to determine if an element would be matched by a selector. You would have to reimplement the entire rule matching system, so the easiest way is to simply use the selector to query for the elements.
The simplest way to get the elements in the order that you like, would be to split the selector into its subselectors, do a query for each, then put the results together by eliminating duplicate element references.

Equivalent queries => identical JQuery object?

If I have <element id="blah" class="blah" attr="blah"> (edit: and I have NO other elements in the DOM, just this one)
Then are these jQuery objects absolutely equivalent:
$("#blah")
$(".blah")
$("[attr=blah]")
Is it the case that the query is executed when the object is created and after that it is irrelevant what query was used?
Edit: Does the original query used have any impact later on anything I might do with the resulting jQuery object? Are the 3 resulting objects above identical? Are they any lasting effects from the actual query I had used? (e.g., when I do something with that object later)
Each may get the element, however they do it in different ways because they are different selectors.
The id selector #
$("#blah")
This will return a jQuery object with 1 element in it (the element you list). The benefit of using an id is that it will only return your one element if it exists, and is fastest as a result of ids being expected to be unique.
The class selector .
$('.blah')
This will return a jQuery object with an array of elements in it (including the element you lsit), but also with any other element that has this class. Since there is no combination with this selector, it will be slower than a straight id lookup because it must inspect every element on the page for this class.
The attribute selector []
$("[attr=blah]")
Much like the class selector, this will return an array of elements. It also must inspect every element.
These may look the same if there is only one match when a jQuery function call is used. The reason for that happening is jQuery will look to see if there is an array of elements matched, and then internally use $.each on the set to apply the jQuery function call to them. The benefit is that this makes sets of elements responds very similarly to single elements which are wrapped by the jQuery object.
Here is a whole list of selectors jQuery supports:
http://api.jquery.com/category/selectors/
The element will match all three selectors, but it does not make the three selectors absolutely equivalent because they all have different matching criteria (one looks for an ID, one looks for a class name, and one looks for an arbitrary attribute). In particular, the class and attribute selectors can return more than one element, since unlike an ID selector they do not imply uniqueness of an element.
Even if you can guarantee that this element will be the only one matched by all three selectors, every call to $ always yields a unique jQuery object, even if the resulting jQuery objects encapsulate the same DOM element.
The query returns an array of html elements. In this specific case all queries return the same array. There is no way the original query will have an effect later.

Remove all the DOM elements with a specific tag name in Javascript

How can I remove all the elements with a specific tag name using Javascript. For example, I did the following:
var els = document.getElementsByTagName("center");
and it returned an array of all the center elements. How may I remove all these elements?
Coming from Remove all child elements of a DOM node in JavaScript and JavaScript DOM remove element I know that I can loop through els, find the parent of each element and then remove that specific node. But is there anyother way provided by javascript. Like we can do $('center').remove() in jquery and it removes all the elements with center tag. Anything similar to that in Javascript?
With the mention that you still loop over the elements (there is no way to avoid that), you can do this:
Array.prototype.slice.call(document.getElementsByTagName('center')).forEach(
function(item) {
item.remove();
// or item.parentNode.removeChild(item); for older browsers (Edge-)
});
DEMO: http://jsbin.com/OtOyUVE/1/edit
Some notes on slice:
document.getElementsByTagName doesn't return an array, it returns a live list with a length
property. That is why it is needed to first convert it into an array (Array.prototype.slice does that for any object with the length property). By doing that you eliminate the problem of the list being live (gets updated when you change the DOM) and you also get the other array functions to work (like forEach) which have a more functional syntax for looping.
"it returned an array of all the center elements."
Well, it returned an array-like object (a live NodeList or an HTMLCollection depending on the browser).
"How may I remove all these elements?"
You have to loop through the list removing them one at a time as you mentioned later in your question. If you find yourself doing that a lot, encapsulate the looping inside a function so that you don't have to repeat it.
"we can do $('center').remove() in jquery and it removes all the elements with center tag. Anything similar to that in Javascript?"
jQuery is a collection of JavaScript functions, so it can't do anything JavaScript can't do. jQuery's .remove() method (like most other jQuery methods) will loop internally, it just saves you having to think about it. So that comes back to what I already mentioned above, encapsulate the loop/remove code in a function so that you can use it from any part of your code.

Categories

Resources