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
tileclickedEvent <my-board>listens for this Event- and emits one Event
colortile - all 9 tiles listen for the
colortileEvent - 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 tilesyou also do not need
getRootNode()then to escape shadowRoot, and find theclosest("my-board")experiment with the disabled
console.logto learn how events get re-targeted, andcomposedPaththen 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.
