Click the word game
Tags: litelement, webcomponents, example, education
Published: 2021-11-09 20:30:12

Web components pose an existential threat to the educational technology industry [hard stop]. By producing free, complex, and platform portable functionality, we can liberate educational experiences of traditionally entrenched, poorly constructed ecosystems that universities pay millions for.

For today's example of what's possible I built a simple "click the word" game for my students as a codepen. Here's the code and then we'll step into notable concepts of this you could use in your own projects

{% codepen https://codepen.io/btopro/pen/dyzKzMG %}

# Implementation
```html
<x-ample answers="implemented,because,is,going">This is a whole bunch of text that is going to be in the middle here but yet we won't see it because of the slot not being implemented</x-ample>
```
The implementation has the answer in it (OH NOZE!) but at run-time, reads this into a variable and deletes the answer from the dom.

![Inspecting the element](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pc1drfu94itufeu1ofbo.png)

There's two ways I accomplish this. The 1st is reading in the Light DOM as data via `.innerText`. Because we're just trying to mark words we don't need the nodes that might live there.

```js
this.textAry = this.innerText.split(/\s+/g);
```
Now we've got an array of text like `["word", "another"]` as we `.split` using a regex to ensure all spaces / end-lines get picked up.

We do a similar practice with the answer. To hide the answer from the attributes in the DOM we listen for `answers` to change in our `updated()` [life cycle of LitElement](https://lit.dev/docs/components/lifecycle/#reactive-update-cycle-completing) and split on `,` in order to generate another `Array`.

```js
updated(changedProperties) {
    changedProperties.forEach((oldValue, propName) => {
      if (propName === "answers" && this[propName]) {
        this.validAnswers = this[propName].split(",");
        // unset so that people can't cheat
        this.answers = null;
      }
    });
  }
```
Then when we want to make evaluations we use validAnswers as opposed to answers. Setting this to `null` we wipe it from the Light DOM.

# Rebuilding the "game" aspect
So now we've got an array of values that are correct, and an array of original text.

```js
 updated(changedProperties) {
    changedProperties.forEach((oldValue, propName) => {
      if (propName === "textAry") {
        this.rebuildContents(this[propName]);
      }
    });
  }
rebuildContents(ary) {
    // wipe out inner
    this.shadowRoot.querySelector("#area").innerHTML = "";
    ary.forEach((word) => {
      let span = document.createElement("span");
      span.innerText = word;
      span.setAttribute("tabindex", "-1");
      span.addEventListener("click", this.clickWord.bind(this));
      this.shadowRoot.querySelector("#area").appendChild(span);
    });
  }
```
`.rebuildContents` takes an array of text and does the following:
- Wipes a shadowRoot `id` marked as `area`
- Creates a `span` element
- Sets `.innerText` to the word we're on in the array
- Sets a tabindex so that keyboard users can focus the element
- add's an event listener for clicking.

When a span get's clicked, we toggle a `data-selected` attribute on it which we can then visualize using a basic css selector
```css
span[data-selected] {
  outline: 2px solid purple;
}
```
![CSS applied to selected items](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6p7o8zqymw824zen7uun.png)

Now we can toggle by "click" the status of each word so that we know what someone wants to mark for testing correctness in identification.

# Testing answers
When we tap our Test button, we capture this event and then walk the shadowRoot for what the user selected previously. This attribute can then be used to do a `.querySelectorAll` and obtain what the user clicked previously without us actually having stored that information!
![HTML structure showing span tags that have a attribute set](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vyaimt7suqy3htud3rx3.png)

```js
  testAnswers(e) {
    const selected = this.shadowRoot.querySelectorAll("#area span[data-selected]");
    console.log(selected);
    for (var i=0; i < selected.length; i++) {
      const el = selected[i];
      if (this.validAnswers.includes(el.innerText)) {
        el.setAttribute("data-status", "correct");
      }
      else {
        el.setAttribute("data-status", "incorrect");        
      }
    }
  }
```
We then loop through these `span[data-selected]` elements that matched our query and see if their `innerText` was included in the initial `validAnswers` array. If we get a match, we set (and visualize) correctness, otherwise we show incorrect.

While this element isn't finished, it's a great example of the transformative nature of modern standards. A previously unapproachable problem requiring Flash or Meta's R_act framework can now be achieved with a slim library like [Lit](https://lit.dev) and honestly could be VanillaJS with minor tweaks.

In project EdtechJoker my students explore problems like this. Using the Web component standard we can liberate pedagogical approaches like this to empower people to educate others regardless of access to overpriced platforms.