简体   繁体   中英

Need help iterating over DOM properly and replacing elements - Javascript

Thanks in advance for any help! I'm trying to do some recursive span tag "replaceWith" actions, replacing the span with a div and 3 child spans. I was having a huge problem with my child object(containing the spans to be replaced) being updated somehow when the first span is replaced (causing my object to grow by 2 every time), so I figured I'd try to make the object constant and then freeze or seal it. I'm not sure if that is the correct way to do this but I'm not a good enough javascript programmer to know. Anyway, I get my spans object this way:

let el = document.getElementById("container");
let nodes = el.children[0];
Object.seal(nodes);//THIS SEALS/FREEZES JUST FINE
let spans = {};//TRIED WITH AND W/O THIS JUST TO MAKE SURE THE OBJECT WAS CREATED
const spans = nodes.children;
console.log('type of spans: '+typeof spans);//RETURNS object
console.log('spans length: '+spans.length);//RETURNS spans length: 3
console.log('spans: '+JSON.stringify(spans));//RETURNS spans: {"0":{},"1":{},"2":{}}
Object.seal(spans);///RETURNS Uncaught TypeError: Cannot Seal

The HTML is simple:

<body>
    <button id="fractalize">Fractalize</button>
    <br/>
    <br/>
    <div id="container">
        <div class="sierpinski">
            <span></span>
            <span></span>
            <span></span>
        </div>
    </div>
</body>

spans returns as an object and everything but it keeps failing when using Object.freeze or Object.seal! I need to know if someone can tell me what I am doing wrong.. the spans object doesn't look any different to me than the nodes object and the nodes object freezes/seals just fine. If I can get those objects frozen then my plan is to do the following for the replacements:

for( let key in spans ) {
  if( spans.hasOwnProperty(key) ) {
    console.log(key + " -> " + JSON.stringify(spans[key]));
    let nDiv = document.createElement("div");
    nDiv.className="sierpinski";
    nDiv.innerHTML="<span></span><span></span><span></span>";
    spans[key].replaceWith(nDiv.cloneNode(true));
    nDiv.remove();
  }
}

Thanks for any insight!

edit For insight, this is what I want;

<body>
    <button id="fractalize">Fractalize</button>
    <br/>
    <br/>
    <div id="container">
        <div class="sierpinski">
            <div class="sierpinski">
                <div class="sierpinski">
                    <span></span>
                    <span></span>
                    <span></span>
                </div>
                <div class="sierpinski">
                    <span></span>
                    <span></span>
                    <span></span>
                </div>
                <div class="sierpinski">
                    <span></span>
                    <span></span>
                    <span></span>
                </div>
            </div>
            <div class="sierpinski">
                <div class="sierpinski">
                    <span></span>
                    <span></span>
                    <span></span>
                </div>
                <div class="sierpinski">
                    <span></span>
                    <span></span>
                    <span></span>
                </div>
                <div class="sierpinski">
                    <span></span>
                    <span></span>
                    <span></span>
                </div>
            </div>
            <div class="sierpinski">
                <div class="sierpinski">
                    <span></span>
                    <span></span>
                    <span></span>
                </div>
                <div class="sierpinski">
                    <span></span>
                    <span></span>
                    <span></span>
                </div>
                <div class="sierpinski">
                    <span></span>
                    <span></span>
                    <span></span>
                </div>
            </div>
        </div>
    </div>
</body>

And this is what I'm currently getting;

<body>
    <button id="fractalize">Fractalize</button>
    <br/>
    <br/>
    <div id="container">
        <div class="sierpinski">
            <div class="sierpinski">
                <span></span>
                <div class="sierpinski">
                    <span></span>
                    <div class="sierpinski">
                        <span></span>
                        <div class="sierpinski">
                            <span></span>
                            <span></span>
                            <span></span>
                        </div>
                        <span></span>
                    </div>
                    <span></span>
                </div>
                <span></span>
            </div>
            <span></span>
            <span></span>
        </div>
    </div>
</body>

Question

So do I add/remove classes or something so the code iteration has some sort of anchor? I just don't understand how I can declare a variable equal to some child elements and that not stay permanent. How does changing the DOM down the line change what's in that variable without redefining the variable??

Live and "Static" HTMLCollections / NodeLists

Using the right method to collect elements/nodes* is important for DOM manipulation. The older methods .children , .getElementsByTagName() , .getElementsByName() , .getElementsByClassName() , etc. return a Live Collection of DOM Objects. This means if any object (ie elements, ie <div> , <span> , ie not spans={} ) in this collection (aka HTMLCollection , aka NodeList ) is modified, or removed, or if a new object is added, the overall collection will change immediately . This makes many ways of recursion impossible and random.

For some reason, the MDN refer to a live collection as a HTMLCollection or a NodeList, but mentions that if using a method such as .querySelectorAll() , the NodeList isn't "live". Why it isn't referred to as a "static" collection to set it apart from a different behavior eludes me, especially if live collection is more a source of common issues such as the one you are experiencing.


DOM Objects (apples) and Object Literals (oranges)

First, please abandon the original posted (from this point referred to as OP) code. In particular this portion:

Object.seal(nodes);//THIS SEALS/FREEZES JUST FINE
let spans = {};//TRIED WITH AND W/O THIS JUST TO MAKE SURE THE OBJECT WAS CREATED
const spans = nodes.children;
console.log('type of spans: '+typeof spans);//RETURNS object
console.log('spans length: '+spans.length);//RETURNS spans length: 3
console.log('spans: '+JSON.stringify(spans));//RETURNS spans: {"0":{},"1":{},"2":{}}
Object.seal(spans);///RETURNS Uncaught TypeError: Cannot Seal

spans is not <span></span><span></span><span></span> , spans is {"0":{},"1":{},"2":{}} . The former is a HTMLCollection (or NodeList ), the latter an Object Literal , apples and oranges. Object.seal() is method for prototypical properties. Also, use var until you have more experience. let and const can be easily cripple your code if you are not mindful of scope.


Demo Outline

  1. Copy HTML fragment from <template> tag
  2. Clone and append the <template> components for each iteration
  3. Use of 3 for loops
  4. Using HTMLFormControlsCollection for user input
  5. Declaring certain values with let

Demo

Note: The layout was kept close to OP, except:

  • the use of <template>

  • target elements kept hidden inside <template>

  • didn't bother to create 4 nested levels there's 3 that should be enough

  • not going to try Sierpinski triangles so changed classes to represent books

Details commented in demo

Demo

 // Refer to HTMLFormControlsCollection var UI = document.forms.ui.elements; // Register click event to button UI.btn.addEventListener('click', generate); function generate() { // Reference to #main var main = document.getElementById('main'); // Refer to Template Tag var library = document.querySelector('.library'); var lib = library.content.cloneNode(true); /* Refer to HTMLFormControlsCollection || The user data is collected in a live collection || Note that these values are outside of the loops */ var ct = UI.ct.value; var bk = UI.bk.value; var pg = UI.pg.value; /* let declaration limits it's value to the block. || var limit's its value to the function. || In this example let declares the initial value || inside each FOR loop. If a var was used then it || would be declared outside of the loop. == || Recursion is nested 2 levels deep and on each || iteration, a component from template.library || is cloned and appended. */ for (let l = 0; l < ct; l++) { // Reference lib .category let cat = lib.querySelector('.category'); // Create a shallow clone of .category (sec) var sec = cat.cloneNode(false); // Append sec it to #main main.appendChild(sec); for (let b = 0; b < bk; b++) { // Reference lib .category .book let book = lib.querySelector('.book'); // Create shallow clone of .book (pub) var pub = book.cloneNode(false); // Append it to .category (sec) sec.appendChild(pub); for (let p = 0; p < pg; p++) { // Reference lib .category .book .page let page = lib.querySelector('.page'); // Create a deep clone of.page (copy) var copy = page.cloneNode(true); // Append it to .book (pub) pub.appendChild(copy); } // Continue to add a cloned copy to pub [pg] times } // Continue to add cloned pub to sec [bk] times } // Continue to add cloned sec to #main [ct] times }
 input { font: inherit; width: 4ch; } button { font: inherit; width: 10ch; } #main { border: 6px dotted grey; display: table } .category { background: rgba(0, 0, 0, .6); display: table-row } .category::before { content: '\\1f4da'; } .book { border: 3px solid red; display: table-cell } .book::before { content: '\\1f4d8'; } .page { border: 1px solid gold; display: inline-block; } .page::before { content: '\\1f4c3'; }
 <!doctype html> <html> <head> </head> <body> <form id='ui'> <label>Categories:&nbsp; <input id='ct' type='number' min='1' max='10' value='1'> &nbsp;Books:&nbsp; <input id='bk' type='number' min='1' max='10' value='1'> &nbsp;Pages:&nbsp; <input id='pg' type='number' min='1' max='10' value='1'> </label> <button id="btn" type='button'>Generate</button> <br/> <br/> <!-- Refer to Template Tag--> <template class='library'> <section class='category'> <article class='book'> <span class='page'></span> </article> </section> </template> <main id="main"> </main> </form> </body> </html>

References

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM