# Signy from scratch

Source: https://signy.dev/documentation/1/overview/signy-from-scratch/

Build a shopping list, one idea at a time. No framework experience needed.

This page is for you if you know some HTML and a little JavaScript and have never used a UI library. You will build a small shopping list. Each step adds one idea, and every example on the page is complete: copy it into your file and it works.

## 1. What a UI library does for you

A page often has to change when data changes: a counter goes up, an item appears in a list. In plain JavaScript you find the element and change it yourself, every time.

```js
const root = document.querySelector("#scratch-plain");
root.innerHTML = `<button>Add an apple</button> <span>0 apples</span>`;

let apples = 0;
const label = root.querySelector("span");

root.querySelector("button").addEventListener("click", () => {
  apples = apples + 1;
  label.textContent = `${apples} apples`; // you must remember this line
});
```

That works for one number. With ten numbers shown in ten places, you have to remember to update each of them, and it is easy to miss one. Signy turns that around: you change the data, and the page follows by itself.

## 2. One HTML file

You need no installation and no build tool. Create a file called `shopping.html`, paste this in, and open it in your browser.

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Shopping list</title>
    <script src="https://cdn.jsdelivr.net/npm/signy@1/dist/signy.global.min.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <script>
      // The code of each step goes here
    </script>
  </body>
</html>
```

The first script tag loads Signy and gives you a global object called `Signy`. Every example below starts by taking what it needs from it, such as `const { Signal, html } = Signy;`. In your file, replace the id in the last line of each example with `"#app"`.

## 3. A signal is a box with a value

A **signal** holds one value and remembers who reads it. You read it with `.value` and change it by giving `.value` a new value. Here is the apple counter again.

```js
const { Component, Signal, application, html } = Signy;

class Apples extends Component {
  apples = new Signal(0);

  render() {
    return html`
      <button onClick=${() => (this.apples.value = this.apples.value + 1)}>Add an apple</button>
      <span>${() => this.apples.value} apples</span>
    `;
  }
}

application(Apples).mount("#scratch-signal");
```

There is no line that updates the text. The text reads the signal, so Signy updates it whenever the signal changes. That is the whole idea.

**The one rule.** Write `${() => this.apples.value}`, with `() =>` in front. That little arrow makes a function, and Signy calls it again when the value changes. Without it, `${this.apples.value}` puts the value in once and it never changes again.

## 4. A component is a piece of the page

The example above is a **component**: a JavaScript class that describes one piece of the page. If you have not used classes:

- `class Apples extends Component { ... }` creates a new kind of component, based on the one Signy provides.
- `apples = new Signal(0);` is a **field**: a value each component carries. Inside the class you reach it as `this.apples`.
- `render()` returns the HTML of the component. Signy calls it **once**, when the component appears. It never calls it again: the functions inside keep the page up to date.
- `application(Apples).mount("#app")` puts the component into the element with the id `app`.

The HTML is written between backticks after `html`. That is a **template**. The `${...}` parts put JavaScript values into it.

## 5. Reacting to clicks and typing

`onClick=${...}` runs a function when the element is clicked, and `onInput=${...}` runs one each time the user types. The function gets an **event**, and `event.currentTarget.value` is the text in the field.

```js
const { Component, Signal, application, html } = Signy;

class NewItem extends Component {
  text = new Signal("");

  render() {
    return html`
      <label>
        Item
        <input onInput=${(event) => (this.text.value = event.currentTarget.value)} />
      </label>
      <p>You are typing: ${() => this.text.value}</p>
    `;
  }
}

application(NewItem).mount("#scratch-events");
```

Notice `<input />` ends with `/>`. In a template, elements that have no closing tag, such as `input`, `img` and `br`, need that slash.

## 6. Showing and hiding

To show one thing or another, use `Show`. It takes a `when` function, what to show when it is true (`children`) and what to show otherwise (`fallback`).

```js
const { Component, Signal, application, html, Show } = Signy;

class Basket extends Component {
  open = new Signal(false);

  render() {
    return html`
      <button onClick=${() => (this.open.value = !this.open.value)}>
        ${() => (this.open.value ? "Close the basket" : "Open the basket")}
      </button>
      ${Show({
        when: () => this.open.value,
        fallback: html`<p>The basket is closed.</p>`,
        children: html`<p>There is bread and cheese inside.</p>`,
      })}
    `;
  }
}

application(Basket).mount("#scratch-show");
```

## 7. Lists

A signal can hold a list. To change it, give the signal a **new** list. Changing the old list in place, with `push()`, does not tell anyone. `For` shows one row per item and updates only the rows that changed.

```js
const { Component, Signal, application, html, For } = Signy;

let nextId = 3;

class ShoppingList extends Component {
  items = new Signal([
    { id: 1, name: "Bread" },
    { id: 2, name: "Cheese" },
  ]);

  add(form) {
    const name = form.elements.name.value.trim();
    if (!name) return;
    this.items.value = [...this.items.value, { id: nextId++, name }];
    form.reset();
  }

  remove(id) {
    this.items.value = this.items.value.filter((item) => item.id !== id);
  }

  render() {
    return html`
      <form class="group" onSubmit=${(event) => { event.preventDefault(); this.add(event.currentTarget); }}>
        <input name="name" aria-label="New item" placeholder="Milk" />
        <button>Add</button>
      </form>
      <ul>
        ${For({
          each: () => this.items.value,
          keyBy: "id",
          fallback: html`<li>Nothing to buy</li>`,
          children: (item) => html`
            <li>
              ${item.name}
              <button class="small outlined" onClick=${() => this.remove(item.id)}>Remove</button>
            </li>
          `,
        })}
      </ul>
    `;
  }
}

application(ShoppingList).mount("#scratch-list");
```

- `[...this.items.value, newItem]` makes a new list with everything from the old one plus the new item.
- `filter()` makes a new list without the removed item.
- Each item has an `id`, and `keyBy: "id"` tells `For` to use it. That way `For` knows which row is which, even after you add or remove items.

## 8. Values computed from other values

The number of items comes from the list. Instead of keeping it in a second signal, compute it with `Computed`. It updates when the list does.

```js
const { Component, Signal, Computed, application, html, For } = Signy;

class Cart extends Component {
  items = new Signal([
    { id: 1, name: "Bread", price: 2.5 },
    { id: 2, name: "Cheese", price: 4 },
  ]);
  total = new Computed(() => this.items.value.reduce((sum, item) => sum + item.price, 0));

  addMilk() {
    this.items.value = [...this.items.value, { id: Date.now(), name: "Milk", price: 1.2 }];
  }

  render() {
    return html`
      <ul>
        ${For({ each: () => this.items.value, keyBy: "id", children: (item) => html`<li>${item.name}: ${item.price} EUR</li>` })}
      </ul>
      <p>${() => this.items.value.length} items, ${() => this.total.value.toFixed(2)} EUR in total</p>
      <button onClick=${() => this.addMilk()}>Add milk</button>
    `;
  }
}

application(Cart).mount("#scratch-computed");
```

## 9. Splitting into components

When a component grows, move a part into its own component and use it like a tag: `<${Total} />`. Values you pass on the tag are its **props**, and it reads them as `this.props`.

A prop is read once, like everything in `render()`. For a value that changes, pass a function and call it inside `() =>`. What you put between the opening and closing tag arrives as `this.props.children`.

```js
const { Component, Signal, application, html } = Signy;

class Total extends Component {
  render() {
    return html`<p><strong>${this.props.label}:</strong> ${() => this.props.count()} items</p>`;
  }
}

class Panel extends Component {
  render() {
    return html`<section class="card">${this.props.children}</section>`;
  }
}

class Shop extends Component {
  count = new Signal(2);

  render() {
    return html`
      <${Panel}>
        <${Total} label="In the basket" count=${() => this.count.value} />
        <button onClick=${() => (this.count.value = this.count.value + 1)}>Add one</button>
      </${Panel}>
    `;
  }
}

application(Shop).mount("#scratch-split");
```

## 10. When something does not work

| What you see | Why | Fix |
| --- | --- | --- |
| The number on the page never changes | The value is put in once | Write `${() => this.count.value}`, not `${this.count.value}` |
| The list does not change after `push()` | The signal still holds the same list, so nobody is told | Give it a new list: `this.items.value = [...this.items.value, item]` |
| A list made with `.map()` never changes | `.map()` runs once, when the page is built | Use `For` |
| The input keeps its text after you clear the signal | The field's text belongs to the browser once someone typed | Reset the form with `form.reset()`, or set the field through a [ref](https://signy.dev/documentation/1/components/refs-and-forms/index.html.md) |
| The page is empty, or half of it is missing | An `<input>` or `<br>` without the slash | Write `<input />` and `<br />` |
| Nothing shows at all | An error in your code | Open the browser console (F12) and read the first red message |

## 11. Where to go next

- [How Signy works](https://signy.dev/documentation/1/overview/how-signy-works/index.html.md) explains why the rules above are what they are.
- [Signals](https://signy.dev/documentation/1/reactivity/signals/index.html.md), [Components](https://signy.dev/documentation/1/components/components/index.html.md) and [For and Index](https://signy.dev/documentation/1/templates/for-and-index/index.html.md) go into detail on what you used here.
- [Examples](https://signy.dev/examples/index.html.md) show whole apps, a todo list among them, with all their code.
