# Components

SSE's page routing model allows us to cleanly separate page-specific code in a very manageable way. But in some cases your code needs to be tied to a "component" rather than a page or path.

{% hint style="info" %}
**Component** is a significant term in Webflow, but SSE "components" aren't strictly tied to Webflow Components. We'll be discussing both here- to distinguish...

* "Components" (capitalized) will refer to Webflow Components.
* "components" (lowercase) will refer to SSE components.
  {% endhint %}

These are some examples of "components" that might use code:

* A specially configured SwiperJS setup
* A custom component you've built such as an accordion
* Specialized form validation
* A fancy multi-step form
* A cool color-picker
* Also, any Webflow Component that needs code attached to its functionality

Often, these "components" need to be reused on multiple pages, and your design team might for example duplicate a quiz or a contact form to another page at any time- ideally the dev team would not need to do *anything* for that component to automatically work on that new page.

## Goals

* Efficient code execution, only run code when it's needed
* Code isolation, e.g. the code & CSS for a multi-step form should be distinct from the rest of your source code
* Reusability. Your development work should be easy to repurpose on other projects you build.
* Webflow Component support. Take full advantage of the Webflow Team's work on components and leverage it in every way possible to maximize the finished "smart" component.
* Create a design paradigm that supports the possibility of multiple "component" instances per page.

## Implementation (v2.0+)

{% hint style="success" %}
**New in v2.0:** Components are fully implemented with automatic discovery, ComponentBase class, and automatic context detection!
{% endhint %}

### Creating a Component

Components extend `ComponentBase` for automatic element and context detection:

```typescript
import { ComponentBase, component } from '@sygnal/sse-core';

@component('accordion')
export class AccordionComponent extends ComponentBase {

  protected onPrepare(): void {
    // Synchronous setup
    // this.element and this.context automatically available
    console.log('Accordion component:', this.context.name);
  }

  protected async onLoad(): Promise<void> {
    // Asynchronous execution
    const items = this.element.querySelectorAll('.accordion-item');

    items.forEach(item => {
      item.addEventListener('click', () => {
        item.classList.toggle('open');
      });
    });
  }
}
```

### Using Components in Webflow

To use a component, add the `data-component` attribute to any element in Webflow:

1. Select the element in Webflow Designer
2. Add a custom attribute: `data-component` = `accordion`
3. The component name must match the name in the `@component` decorator

```html
<div data-component="accordion" class="accordion-wrapper">
  <!-- Your accordion HTML -->
</div>
```

### Multiple Component Instances

Components automatically support multiple instances on the same page. Each instance gets its own separate class instance:

```html
<!-- First accordion -->
<div data-component="accordion" data-component-id="main-faq">
  <!-- FAQ content -->
</div>

<!-- Second accordion -->
<div data-component="accordion" data-component-id="secondary-info">
  <!-- Info content -->
</div>
```

Both will initialize independently with their own `AccordionComponent` instance.

## Automatic Context Detection

When extending `ComponentBase`, you automatically get:

### this.element

The HTMLElement the component is bound to:

```typescript
protected async onLoad(): Promise<void> {
  // Direct access to the component's root element
  const children = this.element.querySelectorAll('.child-item');
  this.element.addEventListener('click', () => {
    console.log('Component clicked');
  });
}
```

### this.context

Component metadata automatically extracted:

```typescript
protected onPrepare(): void {
  console.log(this.context.name);           // 'accordion'
  console.log(this.context.id);             // 'main-faq'
  console.log(this.context.dataAttributes); // All data-* attributes
}
```

Available context properties:

* `this.context.name` - Component name from `data-component`
* `this.context.id` - Optional ID from `data-component-id`
* `this.context.dataAttributes` - All data-\* attributes as key-value pairs

## Accessing Page Information from Components

**New in v2.0:** Components can access the current page via the singleton pattern:

```typescript
import { ComponentBase, component, PageBase } from '@sygnal/sse-core';

@component('navigation')
export class NavigationComponent extends ComponentBase {

  protected async onLoad(): Promise<void> {
    // Access current page info
    const page = PageBase.getCurrentPage();

    if (page) {
      const info = page.getPageInfo();
      console.log('Current page ID:', info.pageId);
      console.log('Collection ID:', info.collectionId);
      console.log('Item slug:', info.itemSlug);

      // Adjust navigation based on current page
      if (info.collectionId === 'blog') {
        this.element.classList.add('blog-nav');
      }
    }
  }
}
```

Use the public `getPageInfo()` accessor (the `pageInfo` property is protected) whenever a component reads Webflow page context.

## Component Discovery and Registration

**New in v2.0:** Components are automatically discovered using the `@component` decorator!

### Automatic Discovery

Simply decorate your component class and import it:

```typescript
// src/components/accordion.ts
import { ComponentBase, component } from '@sygnal/sse-core';

@component('accordion')
export class AccordionComponent extends ComponentBase {
  // Implementation
}
```

### Register in routes.ts

Import your component files to trigger decorator registration:

```typescript
// src/routes.ts
import { RouteDispatcher, getAllPages } from "@sygnal/sse-core";
import { Site } from "./site";

// Import pages
import "./pages/home";
import "./pages/blog";

// Import components to register them
import "./components/accordion";
import "./components/navigation";
import "./components/form-validator";

export const routeDispatcher = (): RouteDispatcher => {
    const dispatcher = new RouteDispatcher(Site);
    dispatcher.routes = getAllPages();
    return dispatcher;
}
```

That's it! SSE automatically finds all `data-component` attributes in your HTML and initializes the matching components.

## Component Lifecycle

Components follow the same lifecycle as pages:

1. **onPrepare()** - Runs synchronously during `<head>` load
   * Use for quick setup that doesn't require DOM manipulation
   * Context and element are available
2. **onLoad()** - Runs asynchronously after DOM is ready
   * Use for event listeners, DOM queries, async operations
   * Full DOM access guaranteed

```typescript
@component('my-component')
export class MyComponent extends ComponentBase {

  protected onPrepare(): void {
    // Quick synchronous setup
    console.log('Component preparing:', this.context.name);
  }

  protected async onLoad(): Promise<void> {
    // Async operations, DOM manipulation
    await this.loadData();
    this.attachEventListeners();
  }

  private async loadData() {
    // Fetch data, etc.
  }

  private attachEventListeners() {
    this.element.addEventListener('click', () => {
      // Handle click
    });
  }
}
```

## Data Attributes for Configuration

Use data attributes to configure component behavior:

```html
<div
  data-component="slider"
  data-component-id="hero-slider"
  data-autoplay="true"
  data-speed="3000"
  data-loop="true"
>
  <!-- Slider content -->
</div>
```

Access in your component:

```typescript
@component('slider')
export class SliderComponent extends ComponentBase {

  protected async onLoad(): Promise<void> {
    // Access configuration from data attributes
    const config = this.context.dataAttributes;

    const autoplay = config['autoplay'] === 'true';
    const speed = parseInt(config['speed'] || '2000');
    const loop = config['loop'] === 'true';

    this.initializeSlider({ autoplay, speed, loop });
  }

  private initializeSlider(config: any) {
    // Initialize with config
  }
}
```

## Best Practices

1. **One component class per file** - Keep components organized in `/src/components/`
2. **Use descriptive names** - Component names should be clear: `accordion`, `multi-step-form`, `image-gallery`
3. **Scope your queries** - Always query within `this.element` to avoid conflicts:

   ```typescript
   // Good
   const items = this.element.querySelectorAll('.item');

   // Bad - might affect other components
   const items = document.querySelectorAll('.item');
   ```
4. **Clean up resources** - Remove event listeners if component is destroyed
5. **Use data attributes for configuration** - Keep components flexible and reusable

## Example: Complete Component

Here's a complete example of a tabs component:

```typescript
import { ComponentBase, component } from '@sygnal/sse-core';

@component('tabs')
export class TabsComponent extends ComponentBase {

  protected async onLoad(): Promise<void> {
    const tabButtons = this.element.querySelectorAll('[data-tab-button]');
    const tabPanes = this.element.querySelectorAll('[data-tab-pane]');

    tabButtons.forEach((button, index) => {
      button.addEventListener('click', () => {
        // Remove active from all
        tabButtons.forEach(btn => btn.classList.remove('active'));
        tabPanes.forEach(pane => pane.classList.remove('active'));

        // Add active to clicked
        button.classList.add('active');
        tabPanes[index]?.classList.add('active');
      });
    });

    // Activate first tab by default
    tabButtons[0]?.classList.add('active');
    tabPanes[0]?.classList.add('active');
  }
}
```

Use in Webflow:

```html
<div data-component="tabs" class="tabs-wrapper">
  <div class="tab-buttons">
    <button data-tab-button>Tab 1</button>
    <button data-tab-button>Tab 2</button>
    <button data-tab-button>Tab 3</button>
  </div>
  <div class="tab-content">
    <div data-tab-pane>Content 1</div>
    <div data-tab-pane>Content 2</div>
    <div data-tab-pane>Content 3</div>
  </div>
</div>
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://engine.sygnal.com/usage/components.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
