1

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>
Mike Thiel
  • 57
  • 7
  • you can only mod js objects, not host object like dom elements. – dandavis Sep 26 '17 at 19:01
  • This is a classic [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). You might make more progress by asking about the original issue "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)" instead of about the freeze/seal workaround... What exactly was going wrong there? It's not quite clear from the question. – Daniel Beck Sep 26 '17 at 19:13
  • @DanielBeck I don't know how to add code to comments, so let me edit original question. – Mike Thiel Sep 26 '17 at 22:17
  • @dandavis Thanks, but then tell me please what the difference is between Object.seal(el.children[0]) vs Object.seal(el.children[0].children) ? because the first seals/freezes fine but the second does not. – Mike Thiel Sep 26 '17 at 22:32
  • and, fyi, I understand why it's behaving like it is.. the index of the nested elements keeps changing because I keep adding more span elements to the DOM.. however, I don't know how to stop that behavior and have my DOM manipulation contained to the first child elements in succession at a time. – Mike Thiel Sep 26 '17 at 22:37
  • one is an object, one is a collection getter, but again, host object behavior w/seal/freeze/etc is not consistently implemented. you're barking up the wrong tree to be sure. – dandavis Sep 27 '17 at 00:18
  • 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?? – Mike Thiel Sep 27 '17 at 00:25
  • Freezing, sealing, laminating, sautéing an Object doesn't help with simple DOM manipulation. `spans` is not ``, `spans` is `{"0":{},"1":{},"2":{}}`. You think you have the former but you are using the latter. Doing `typeof` to verify an Object is a madman's game in JavaScript do not go down that road. Forget about your approach it is like knowing that a apple tree needs soil and water so you throw the apple seeds and fertilizer into a swimming pool. – zer00ne Sep 28 '17 at 21:02
  • 1
    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 (i.e. elements, i.e. `
    `, ``, i.e. **not** `spans={}`) in this collection (a.k.a. HTMLCollection, a.k.a. NodeList) is modified, or removed, or if a new object is added, the over all **collection will change immediately**. This makes many ways of recursion impossible and random.
    – zer00ne Sep 28 '17 at 21:24

1 Answers1

1

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 (i.e. elements, i.e. <div>, <span>, i.e. not spans={}) in this collection (a.k.a. HTMLCollection, a.k.a. 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

zer00ne
  • 41,936
  • 6
  • 41
  • 68
  • You, sir, are an amazing individual. Thank you so much for that insanely detailed response! I actually understand now and am going through all the reference links as well. Thank you!! – Mike Thiel Sep 29 '17 at 18:43
  • No problem at all. I can tell by reading your code that you had seriously read a lot, and had the logic part down as well. The only thing you lacked was knowledge that comes with experience. Happy coding, sir. :) – zer00ne Sep 29 '17 at 19:29
  • BTW, Google [`sierpinski javascript canvas`](https://www.google.com/search?q=%60sierpinski+javascript+canvas%60&oq=%60sierpinski+javascript+canvas%60&aqs=chrome..69i57&sourceid=chrome&ie=UTF-8) and you'll find a ton of examples and tutorials. – zer00ne Sep 29 '17 at 19:36
  • I actually did see all those canvas examples.. I just really didn't want to do it with canvas! I was trying to find a way to do it all with basic elements.. divs/spans/whatever.. You helped tremendously, thank you again! – Mike Thiel Sep 29 '17 at 22:03