jazzypants1989 / jessquery

Modern JavaScript is pretty good, but typing `document.querySelector()` is a pain. This is a tiny library that makes DOM manipulation easy. jQuery is around 30kb, while this is only around 3.5kb. Lots of JSDoc comments so it's self-documenting and works great with TypeScript.
119 stars 1 forks source link

Currently, when cloning another DomProxyCollection or NodeList, $$.become only uses the first element found. #3

Open jazzypants1989 opened 1 year ago

jazzypants1989 commented 1 year ago

To clarify, cloneTo works perfectly for this situation, but I really want this library to emphasize flexibility.

I know this is probably an easy fix, but I have spent an absolutely insane amount of time debugging this compared to the rest of this library. I tried implementing some behavior where the replacement elements were either cycled or original elements were removed if there were not enough replacements... I had it working for a moment, but I re-factored stuff and I have NO idea when I broke it.

Honestly, the logic of taking every element in a collection and replacing it with every element in another collection gets REALLY confusing once you start to think about it. There's so many different ways that could really work. Do you want each element to contain EVERY element in the other collection, or are you trying to match elements one by one? I could see why you would want either behavior depending on the situation, but this is very hard to make happen.

If you want to see the unwanted behavior, just look at the test: become3.html. Here it is in full:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Become Method Test</title>
  </head>
  <body>
    <div id="testContainer"></div>

    <div id="cycle">
      <h2>Cycle Match Test:</h2>
      <div class="toBeReplaced">To Be Replaced 1</div>
      <div class="toBeReplaced">To Be Replaced 2</div>
      <div class="toBeReplaced">To Be Replaced 3</div>
      <div class="replacementElement">Replacement Element 1</div>
      <div class="replacementElement">Replacement Element 2</div>
    </div>
    <!-- Result: FAILED! 
    Replacement Element 1
    Replacement Element 1
    Replacement Element 1
    Replacement Element 1
    Replacement Element 2
    -->

    <div id="remove">
      <h2>Remove Match Test:</h2>
      <div class="toBeReplaced">To Be Replaced 1</div>
      <div class="toBeReplaced">To Be Replaced 2</div>
      <div class="toBeReplaced">To Be Replaced 3</div>
      <div class="replacementElement">Replacement Element 1</div>
      <div class="replacementElement">Replacement Element 2</div>
    </div>
    <!-- 
    So, think about it: 
    - Do you want **EACH** div.toBeReplaced to hold both replacementElements? 
    - Do you want to match element for element? 
    - So, what happens to div.toBeReplaced #3? 
    - This makes my head hurt.
    -->

    <script type="module">
      import { $, $$ } from "../index.js"

      function runTests() {
        const output = document.createElement("div")
        document.getElementById("testContainer").appendChild(output)

        const toBeReplacedElsCycle = $$("#cycle .toBeReplaced")
        const replacementElsCycle = document.querySelectorAll(
          "#cycle .replacementElement"
        )
        const toBeReplacedElsRemove = $$("#remove .toBeReplaced")
        const replacementElsRemove = document.querySelectorAll(
          "#remove .replacementElement"
        )

        // Cycle Match Test
        toBeReplacedElsCycle.become(replacementElsCycle, {
          mode: "clone",
          match: "cycle", // vestige of the removed feature
        })

        if (
          document.querySelectorAll("#cycle .replacementElement").length === 6
        ) {
          output.innerHTML += `<p>Cycle Match Test: <span style="color: green;">Passed</span></p>`
        } else {
          console.log(replacementElsCycle.raw)
          output.innerHTML += `<p>Cycle Match Test: <span style="color: red;">Failed</span></p>`
        }

        // Remove Match Test
        toBeReplacedElsRemove.become(replacementElsRemove, {
          mode: "clone",
          match: "remove",
        })

        if (
          document.querySelectorAll("#remove .replacementElement").length === 4
        ) {
          output.innerHTML += `<p>Remove Match Test: <span style="color: green;">Passed</span></p>`
        } else {
          console.log(replacementElsRemove)
          output.innerHTML += `<p>Remove Match Test: <span style="color: red;">Failed</span></p>`
        }
      }

      runTests()
    </script>
  </body>
</html>

If anyone wants me to love them forever, just help me figure this out: PLEASE!

jazzypants1989 commented 1 year ago

This will be the death of me. I'm still not even sure what I want to happen when trying to insert make a NodeList become another NodeList. This is my most recent attempt:


export function become(
  elements,
  replacements,
  options = { mode: "clone", match: "cycle" }
) {
  const children = Array.isArray(elements) ? elements : [elements].flat()
  const replacementsArray = Array.isArray(replacements)
    ? replacements
    : [replacements].flat()

  let i = 0

  const matchModes = {
    cycle: () => {
      return () => replacementsArray[i++ % replacementsArray.length]
    },
    repeat: () => {
      return () => {
        if (i < replacementsArray.length - 1) i++
        return replacementsArray[i]
      }
    },
    default: () => {
      return () => {
        if (i < replacementsArray.length) {
          return replacementsArray[i++]
        }
      }
    },
  }

  const matchMode = matchModes[options.match] || matchModes.default

  const getReplacement = matchMode()

  children.forEach((child) => {
    const replacement = getReplacement()
    if (replacement) {
      const parent = child.parentElement
      modifyDOM(parent, replacement, options)
      child.remove()
    }
  })
}

I can't reasonably spend any more time on this issue... Someone save me!