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 tilesyou also do not need
getRootNode()
then to escape shadowRoot, and find theclosest("my-board")
experiment with the disabled
console.log
to learn how events get re-targeted, andcomposedPath
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.