Home > front end >  querySelectorAll not selecting elements within shadowRoot
querySelectorAll not selecting elements within shadowRoot

Time:09-02

I’m trying to create a tic-tac-toe game using Webpack and Web Component.

I need to select div tag having cell class within shadowRoot at place-mark component then update innerText to O/X depending on Turn calculated by another function.

The logic is functioning fine but I cannot select right components to update HTML view so the user could see current status of selected areas.

Here's my simplified HTML code:

<div  id="board">
  <place-mark data-cell></place-mark>
</div>

Here’s my simplified component code:

const template = document.createElement("template");
template.innerHTML = `<div  />`;
export default class PlaceMark extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(template.content.cloneNode(true));}}
window.customElements.define("place-mark", PlaceMark);

Here’s my simplified JS code:

const X_CLASS = "x";
const CIRCLE_CLASS = "circle";
const cellElements = document.querySelectorAll('[data-cell]');
const board = document.getElementById("board");
let circleTurn;

function startGame() {
 cell.addEventListener("click", handleClick, { once: true });});
}

function handleClick(e) {
const cell = e.target;
const currentClass = circleTurn ? CIRCLE_CLASS : X_CLASS;
placeMark(cell, currentClass);
  if (checkWin(currentClass)) {
    endGame(false);
  } else if (isDraw()) {
    endGame(true);
  } else {
    swapTurns();
    setBoardHoverClass();
  }
}

function placeMark(cell, currentClass) {
  cell.classList.add(currentClass);
}

If you need full version of codes above, Here's my project:

https://github.com/nazaninsnr/TicTacToe

CodePudding user response:

you can simply remove shadowRoot from your code

it would give you the chance to have access to all the elements and querySelect what you want.

export default class PlaceMark extends HTMLElement {
constructor() {
super();
this.appendChild(template.content.cloneNode(true));}}
window.customElements.define("place-mark", PlaceMark);

CodePudding user response:

global querySelector code can not access DOM content within shadowRoots.

If you have an open shadowRoot, you can do:
document.querySelector("my-component").shadowRoot("[data-cell]")

This will ofcourse get hairy when you have nested shadowRoots.

You might want to change your logic to:

  • The board emits an Event
  • The 9 tiles listen for the Event, and act accordingly

working SO Snippet below:

  • click a tile to emit a tileclicked Event
  • <my-board> listens for this Event
  • and emits one Event colortile
  • all 9 tiles listen for the colortile Event
  • each tile changes its color

<my-board></my-board>
<hr>
<my-board></my-board>

<script>
  class GameComponent extends HTMLElement {
    constructor(html){
      super().attachShadow({mode: "open"}).innerHTML = html;
    }
    dispatch({ from = this,eventname,detail = {} }) {
      from.dispatchEvent(new CustomEvent(eventname, {
        composed: true, bubbles: true, detail
      }));
    }
    listen({at = this,eventname,func }) {
      at.addEventListener(eventname, func);
    }
  }
  customElements.define("my-board", class extends GameComponent {
    constructor() {
      super(`<style>:host{display:grid;grid:repeat(3,20px)/repeat(3,60px);gap:1px}</style>`  
        "green,red,blue,orange,olive,tan,teal,yellow,lime"
        .split(",").map(color => `<my-tile>${color}</my-tile>`).join(""));
    }
    connectedCallback() {
      this.listen({ at:this, eventname:"tileclicked",
                    func: (evt) => {
                                //console.log(evt.target,evt.composedPath());
                                this.dispatch({ from: this, eventname: "colortile",
                                                detail: { color: evt.detail.color }})}})
    }
  });
  customElements.define("my-tile", class extends GameComponent {
    constructor() {
      super(`<style>:host{text-align:center;cursor:pointer}</style><slot></slot>`);
    }
    connectedCallback() {
      this.onclick = () => this.dispatch({ from: this, eventname: "tileclicked",
                                           detail: { color: this.innerText }});
      this.listen({
                   at: this.getRootNode().host.closest("my-board"),
                   eventname: "colortile",
                   func: (evt) => this.style.background = evt.detail.color });
    }
  });
</script>

Playground: https://jsfiddle.net/WebComponents/Lk9r4gx2/

Notes:

  • in practice I would not set a shadowDOM on <my-tile>; then The <my-board> STYLE can style all tiles

  • you also do not need getRootNode() then to escape shadowRoot, and find the closest("my-board")

  • experiment with the disabled console.log to learn how events get re-targeted, and composedPath then saves your ...

  • Its JavaScript, you can pass a Function reference in that detail:{} property bag... that way a Tile can access methods on the Board, or vice versa.

  • Related