Files
testdrive-jsui/tutorials/Tutorial 3 Bidirectional Data Binding.md
2025-11-03 21:36:45 +01:00

5.4 KiB
Raw Blame History

Tutorial 3: Bi-Directional Data Binding

This third tutorial builds directly on your hello-world component and introduces two-way interaction (bi-directional data binding).

Youll learn how to let the user type into an input field and see the greeting update live — while continuing to follow a TDD-first approach.


🧭 1. Goal

The <hello-world> component should display a greeting and an input box. Typing into the input should update the greeting in real time. The name property should still be readable/writable programmatically.


🧪 2. Step 1 — Write the failing test first

Create a new test file: src/components/hello-world/hello-world.input.test.js

import "./hello-world.js";

describe("<hello-world> (interactive input)", () => {
  it("renders an input element and shows default greeting", () => {
    const el = document.createElement("hello-world");
    document.body.appendChild(el);

    const input = el.shadowRoot.querySelector("input");
    const greeting = el.shadowRoot.querySelector(".greeting");

    expect(input).to.exist;
    expect(greeting.textContent.trim()).to.equal("Hello, World!");
  });

  it("updates greeting when user types into input", async () => {
    const el = document.createElement("hello-world");
    document.body.appendChild(el);

    const input = el.shadowRoot.querySelector("input");
    input.value = "Agent";
    input.dispatchEvent(new Event("input"));
    await el.updateComplete;

    const greeting = el.shadowRoot.querySelector(".greeting");
    expect(greeting.textContent.trim()).to.equal("Hello, Agent!");
  });

  it("reflects property changes in input value", async () => {
    const el = document.createElement("hello-world");
    document.body.appendChild(el);

    el.name = "Nova";
    await el.updateComplete;

    const input = el.shadowRoot.querySelector("input");
    expect(input.value).to.equal("Nova");
  });
});

Run tests:

npm test

They will all fail — as expected.


🧩 3. Step 2 — Implement the feature

Edit src/components/hello-world/hello-world.js and replace the render logic:

import { LitElement, html, css } from "lit";

export class HelloWorld extends LitElement {
  static properties = {
    name: { type: String }
  };

  constructor() {
    super();
    this.name = "World";
  }

  static styles = css`
    .container {
      font-family: system-ui, sans-serif;
      font-size: 1.5rem;
      color: #007acc;
      padding: 1rem;
      text-align: center;
    }
    input {
      font-size: 1rem;
      margin-top: 1rem;
      padding: 0.3rem 0.6rem;
      border: 1px solid #ccc;
      border-radius: 6px;
      width: 60%;
      text-align: center;
    }
  `;

  render() {
    return html`
      <div class="container">
        <div class="greeting">Hello, ${this.name}!</div>
        <input
          type="text"
          .value=${this.name}
          @input=${this._onInput}
          aria-label="Name input"
        />
      </div>
    `;
  }

  _onInput(event) {
    this.name = event.target.value;
  }
}

customElements.define("hello-world", HelloWorld);

🧪 4. Step 3 — Run the tests again

npm test

All three tests should now pass.


5. Step 4 — Try it live

Update src/index.html to:

<hello-world></hello-world>

Run:

npm run dev

In your browser:

  • Youll see “Hello, World!”
  • Type “Coulomb” in the input box.
  • The greeting updates instantly: Hello, Coulomb!

🧠 6. Step 5 — Explore two-way binding manually

Open DevTools Console and run:

document.querySelector("hello-world").name = "Bernd";

The input value updates automatically, and the greeting reflects the change too. Thats the power of Lits reactive updates combined with the native DOM event loop.


📚 7. Step 6 — Optional Storybook story

src/components/hello-world/hello-world.interactive.stories.js

import "./hello-world.js";

export default {
  title: "UI/Hello World (Interactive)"
};

export const Interactive = () => `<hello-world></hello-world>`;

When you add Storybook later, this story will provide a live, interactive playground.


🔍 8. Key TDD lessons learned

Concept Explanation
Event testing Simulate user input with dispatchEvent(new Event('input'))
Reactive updates Use await el.updateComplete to wait for re-render
Two-way binding Property changes update DOM; DOM events update property
Isolation jsdom tests confirm behavior without running a browser

9. Summary

Youve now completed:

  1. Static rendering (Hello World!)
  2. Reactive property rendering (Hello, ${name}!)
  3. Bi-directional interaction (live input → UI → property → UI)

You can now test-drive any UI component using the same methodology.


Continue to the fourth tutorial on component composition — i.e., building a small dashboard that uses multiple custom components together, still under test Control.

xxx