# Slots and Properties and Attributes, Oh My!

This article is the second in my [series on building Web Components](https://toddesposito.com/series/web-components), and builds on what I started in [Building A Loading Modal Component](https://toddesposito.com/building-a-loading-modal-component). If you haven't seen that one yet, you may want to give it a quick read to catch yourself up.

## Making our component more flexible

So we have a working component. Great. But, as written, it isn't flexible enough to be of use as a general purpose tool.

We'll tackle this in a couple steps. In this article, we'll dive into three aspects of Web Components: 

- **Slots** allow us to put content inside our component
- **Attributes** allow us to configure our component in the HTML tag
- **Properties** allow us to interact with our component in JavaScript

In an upcoming article, we'll discuss how to alter the styling of the component. Patience is a virtue.

## First round of improvements

Let's review our "proof of concept" page:
```HTML
<html>
<head>
  <script src="eh-loading-modal.js"></script>
</head>
<body>
  <main>some content here</main>
  <eh-loading-modal></eh-loading-modal>
  <script>
     const loader = document.getElementsByTagName('eh-loading-modal')[0]
     loader.show()
     setTimeout(() => loader.hide(), 5000)
  </script>
</body>
</html>
```

As you'll recall from the previous article, we load the Component via `<script>` tag, add it to our page via `<eh-loading-modal>` tag, and then show (and later hide) it via the JavaScript at the end of the page. 

Our Component, so far, is:
```JavaScript
window.customElements.define('eh-loading-modal',
  class EhLoadingModal extends HTMLElement {
    constructor() {
      super()
      this.attachShadow({ mode: "open" })
      this.shadowRoot.innerHTML = `
      <style>
        #loading {
          background-color: gray;
          height: 100%;
          left: 0;
          opacity: 0;
          position: fixed;
          top: 0;
          visibility: hidden;
          width: 100%;
          z-index: 1;
        }
        #loading.visible {
          opacity: 97%;
          visibility: visible;
          transition: visibility 0s linear 0s, opacity .25s 0s;
        }
        #loading .message {
          height: 100%;
          margin: 20px 2px;
          padding: 2px 16px;
          position: relative;
          text-align: center;
          top: 40%;
          vertical-align: middle;
        }
      </style>
      <div id="loading">
        <div class="message">
          <p>Loading... </p>
        </div>
      </div>
      `
    }
    show() {
      this.shadowRoot.getElementById('loading').classList.add("visible")
    }
    hide() { 
      this.shadowRoot.getElementById('loading').classList.remove("visible")
    }
})

```

## Slots

I'm sure you noticed that the component, when shown, always puts the word "Loading..." in the center of the page on a gray background. Swell. But maybe you want something else, like "Please Wait" or a nice animated image. It sure would suck to have to re-write our component every time we wanted a different message, especially since that's EXACTLY what we're trying to avoid doing in the first place.  **Slots** solve this problem.

Let's adjust our component code to add a "slot" in place of the paragraph which contains the original message. In the `constructor()` we'll update:

`<p>Loading...</p>`

to read:

`<slot>Loading...</slot>`

The `<slot>` tag identifies a place where we can slot-in (get it?) other content. If you change the component as above, and re-load your POC page, nothing will have changed.

BUT, when we update our POC page with:

```
<eh-loading-modal>
  <h2>Please Wait</h2>
  <p>We're loading your report. This could take a moment.</p>
  <img src="/static/spinner.webp"/>
</eh-loading-modal>
```

we get a different result.  Have fun playing with that, keeping in mind that any valid HTML can go inside those `<eh-loading-modal>` tags, and it will render much as you'd expect.

### Named slots
It's important to note that slots can be named and then referenced by name. Let's take an example building on our work so far.  If we were to update our component's `innerHTML` like this:

```
...
        <div class="message">
          <slot>Loading... </slot>
          <slot name="footer">(this is a footer)</slot>
        </div>
...
```

we'd now have a default "footer" on our loading page.

Then, to overwrite the content of that footer, we'd update our POC page as:
```
<eh-loading-modal>
  <h2>Please Wait</h2>
  <p>We're loading your report. This could take a moment.</p>
  <img src="/static/spinner.webp"/>
  <p slot="footer">This will go in the footer, not the main message area</p>
</eh-loading-modal>
```

The thing to remember is the `<slot>` is `name`d, whereas the element to go into the slot is `slot`ted.

## Attributes

**Attributes**, part of the HTML specification, are values applied to tags.  For example, in `<input type="checkbox" checked>` there are two attributes: 
1. `type` has the value "checkbox" 
1. `checked` has the implicit value "checked"

Maybe now you're thinking
> Why not use an **attribute** rather than a **slot** to set the content of the loading page?

First, the value of an attribute is a string, which would limit the loading message to text, without the ability to add in images and such.

Beyond that, it's a design choice. I find this:

```
<eh-loading-modal content="Wait For It..."></eh-loading-modal>
```
far less appealing and understandable what I've shown above.

### Let's add an Attribute!

Currently, our loading modal doesn't show until we tell it to with JavaScript. What if we want to build a page which immediately shows a loading message, and that message goes away when the page is ready to show whatever data it's been fetching in the background?  Why then, we'd use an Attribute in our HTML, to wit:

```
<eh-loading-modal visible>
  <h2>Please Wait</h2>
  <p>We're loading your report. This could take a moment.</p>
  <img src="/static/spinner.webp"/>
  <p slot="footer">This will go in the sticky footer, not the main message area</p>
</eh-loading-modal>
```

See the `visible` attribute added above? That's going to tell our component it should start off being visible. 

Inside our component, the outermost `div` either has or doesn't have a class of `visible` to control its visibility. By default, this `div` does NOT have this class applied. We want our component to react to the presence of the `visible` attribute by adding the `visible` class to it when initially rendered.

### connectedCallback

We have to make a small change to our component to support this new attribute. So far, we've been doing most of our work in our component's `constructor()` but to enable support for attributes, we're going to refactor much of that work into the `connectedCallback()` function. The browser calls this function every time it "mounts" a node into the DOM, at which point its attributes are available. Before then, they aren't.

```JavaScript
window.customElements.define('eh-loading-modal',
  class EhLoadingModal extends HTMLElement {
    constructor() {
      super()
      this.attachShadow({ mode: "open" })
    }
    connectedCallback() {
      const visible = this.hasAttribute('visible') ? "visible" : ""
      this.shadowRoot.innerHTML = `
...
    }
```

Now we have a `const` which is EITHER "visible" or "" (empty), depending on the existence of the `visible` attribute. Let's use it in the `innerHTML`:

```JavaScript
      this.shadowRoot.innerHTML = `
        <style>
...
        </style>
        <div id="loading" class="${visible}">
...
```

Let's update our POC page to take advantage of this new behavour:

```HTML
<html>
<head>
  <script src="eh-loading-modal.js"></script>
</head>
<body>
  <main>some content here</main>
  <eh-loading-modal visible>
    <h2>Please Wait</h2>
    <p>We're loading your report. This could take a moment.</p>
  </eh-loading-modal>
  <script>
     const loader = document.getElementsByTagName('eh-loading-modal')[0]
     setTimeout(() => loader.hide(), 5000)
  </script>
</body>
</html>
```

## Properties

What if we want to know if the component is currently visible?  **Properties** to the rescue. Properties are component values available to your page's scripts.

**PLEASE NOTE:** Properties often look like Attributes, and they can be confused, but they are different things for (often overlapping) purposes.

Let's add this code to the component:

```JavaScript
    get visible() {
      return this.hasAttribute("visible")
    }
```

Now, you can go to your browser's console and type:

`document.getElementsByTagName('eh-loading-modal')[0].visible`

You should get back `true` EVEN IF THE MODAL ISN'T CURRENTLY SHOWING! Why? Because we added the `visible` _attribute_ to the HTML, so it's ALWAYS there. 

How do we address this? We could: 
* ignore the attribute and just maintain some internal state
* inspect the classList of the controlling `div`
* add and remove the attribute like native DOM nodes do

If you toggle an HTML checkbox, you'll notice the `checked` attribute is added to or removed from the DOM node. We should probably emulate that behavior, so let's go with that last one.

### Relating Properties to Attributes

We need to connect our `visible` property to our `visible` attribute, and then use that connection to drive our component's operation.

First, we'll add a "setter" along side our "getter" for `visible`:
```JavaScript
    get visible() {
      return this.hasAttribute("visible")
    }
    set visible(v) {
      if (v) {
        this.setAttribute("visible", "")
      } else {
        this.removeAttribute("visible")
      }
    }
```

Now, in the console you can type `document.getElementsByTagName('eh-loading-modal')[0].visible = true` to add the attribute, and `document.getElementsByTagName('eh-loading-modal')[0].visible = false` to remove it.

And we should probably update `show()` and `hide()` to manipulate the attribute, since that's now how we want to effect the behavior:

```JavaScript
    show() { this.setAttribute("visible", "") }
    hide() { this.removeAttribute("visible") }
```

But this still doesn't complete the job, because just toggling the attribute on the node doesn't cause the component to DO anything.  We need to make that attribute "observed" and then attach a callback to the component which implements the intended behavior. Let's add:

```JavaScript
    static get observedAttributes() {
      return ["visible"]
    }

    attributeChangedCallback(name, oldValue, newValue) {
      switch (name) {
      case 'visible':
        if (newValue === null) {
          this.shadowRoot.getElementById('loading').classList.remove("visible")
        } else {
          this.shadowRoot.getElementById('loading').classList.add("visible")
        }
        break
      }
    }
```

The browser calls `attributeChangedCallback()` every time one of the attributes indicated by `observedAttributes()` is changed.

So when our `visible` attribute is added or removed, `attributeChangedCallback()` is called with the attribute name, the previoius value of the attribute, and the new value.

## Better, but more to do
So now we have a loading modal component which we can make visible by default via HTML attribute, fill with arbitrary content in the HTML, and  interrogate with JavaScript to see if it's visible.

That's really nice. But wouldn't it be great to alter the colors, fonts, and layout? Next time, we'll add those capabilities and discuss some design decisions which impact how useful a component will become.
