# Controllers

> Learn how to manipulate Flexiboard components via their controllers.

Source: https://www.flexiboards.dev/docs/controllers

Framework: Svelte

A controller exposes a board, target, or widget's state and actions. Choose access based on where your code runs:

| Task                                                      | Access                                                                                                        |
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| Keep a controller for a parent component's event handlers | `onfirstcreate`                                                                                               |
| Render from a surrounding widget's state                  | The framework-specific context helper or hook below                                                           |
| Set the first layout, including SSR                       | `initialLayout` in [server-rendering configuration](https://www.flexiboards.dev/docs/guides/server-side-rendering#server-stored-layouts) |
| Replace a layout after initialization                     | `importLayout()` on a stored controller                                                                       |

The examples in the access sections are excerpts. Insert your existing targets and widgets where indicated; they focus on how to obtain the controller.

## Method 1: `onfirstcreate` callback

Components with controllers accept `onfirstcreate`. The callback receives the controller once. Use it to keep a reference for later actions. Timing depends on the adapter, as described below.

```svelte
<script lang="ts">
	import { FlexiBoard, type FlexiBoardController } from '@flexiboards/svelte';

	let board = $state<FlexiBoardController>();

	function rememberBoard(controller: FlexiBoardController) {
		board = controller;
	}
</script>

<FlexiBoard onfirstcreate={rememberBoard}>
	<!-- ... -->
</FlexiBoard>
```

The callback runs during component setup, including SSR. Keep browser-only work in client event handlers or effects. Use `initialLayout` for data that must appear in the server-rendered board.

## Method 2: `bind:controller` prop

Bind `controller` to a state variable when the parent needs the instance. Guard reads before initialization.

Here's an example of using it to access the controller of a `FlexiBoard`:

```svelte
<script lang="ts">
	import { FlexiBoard, type FlexiBoardController } from '@flexiboards/svelte';

	let boardController = $state<FlexiBoardController>();

	function clearBoard() {
		boardController?.clear();
	}
</script>

<button onclick={clearBoard}>Clear board</button>
<FlexiBoard bind:controller={boardController}>
	<!-- Existing targets and widgets. -->
</FlexiBoard>
```

The button handler runs on the client after initialization. For setup-time controller access, use `onfirstcreate`; for server-provided layout data, use `initialLayout`.

By the time the `onfirstcreate` callback fires, any variable bound to `controller` already holds the controller instance. If you prefer, you can read that variable rather than the `controller` parameter passed to the callback.

## Method 3: context helper

From any component rendered _inside_ a Flexiboards component, you can reach the surrounding widget's controller through the `getFlexiwidgetCtx()` helper. It uses the [Svelte Context API](https://svelte.dev/docs/svelte/context) under the hood, so it must be called from the top level of a component.

```svelte
<!-- my-widget-content.svelte -->
<script lang="ts">
	import { getFlexiwidgetCtx } from '@flexiboards/svelte';

	const widget = getFlexiwidgetCtx();
</script>

<div class:opacity-50={widget.isGrabbed}>...</div>
```

## Changing the board from code

Once you hold a controller you can change the board without a drag. Placement actions run the grid rules. Successful layout mutations notify `onLayoutChange`; import and export have separate behavior shown below.

| Call                           | What it does                                                                                                                        |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| `widget.delete()`              | Removes the widget from its target. Fires `onWidgetDelete`.                                                                         |
| `widget.moveTo({ x, y })`      | Moves the widget within its target. Returns `false` and leaves it in place if the grid refuses the spot.                            |
| `widget.moveTo({ target })`    | Moves the widget to another target, wherever that target's grid puts it. Pass `x` and `y` too to choose the cell.                   |
| `target.createWidget(config)`  | Adds a widget. Returns `undefined` if it cannot be placed.                                                                          |
| `target.clear()`               | Deletes every widget in the target.                                                                                                 |
| `board.clear()`                | Deletes every widget in every target.                                                                                               |
| `board.importLayout(layout)`   | Replaces widgets in targets named by the saved layout, bare or in a `{ version, layout }` envelope. Does not fire `onLayoutChange`. |
| `board.exportLayoutEnvelope()` | The layout with its format version, the shape to persist. See [Exporting & Importing](https://www.flexiboards.dev/docs/guides/exporting-importing-boards).     |

This excerpt assumes `done` and `doing` are target controllers from the same board:

```ts
// Move the first "done" card back into "doing", at the top.
const card = done.widgets.values().next().value;
card?.moveTo({ target: doing, x: 0, y: 0 });
```

`moveTo` bypasses `canDrop`. Validate application permissions before calling it; grid placement rules still apply. A refused move returns `false`. A successful placement notifies even if the coordinates are unchanged. Clears notify only if widgets are removed.

## Reacting to interactions

Use `canDrop` for validation, grab and enter/leave callbacks for interaction progress, and drop/resize callbacks for committed changes. `onLayoutChange` reports the committed layout in a batched microtask before animations settle.

These configuration excerpts log accepted moves between targets and deletions. Keep your existing target declarations inside the board:

```svelte
<script lang="ts">
	import { FlexiBoard, type FlexiBoardConfiguration } from '@flexiboards/svelte';

	const config: FlexiBoardConfiguration = {
		onWidgetDrop: ({ widget, sourceTarget, target }) => {
			if (sourceTarget !== target) {
				console.info('Moved', widget.userProvidedId ?? widget.id, 'to', target.key);
			}
		},
		onWidgetDelete: ({ widget }) => console.info('Deleted', widget.userProvidedId ?? widget.id),
		// Only the "done" column accepts cards that are marked complete.
		canDrop: ({ widget, target }) => target.key !== 'done' || widget.metadata?.complete === true
	};
</script>

<FlexiBoard {config}><!-- Existing targets and widgets. --></FlexiBoard>
```

| Callback                                     | Fires when                                                                                                                                                                           |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `onWidgetGrab`                               | The user picks a widget up, by pointer or keyboard.                                                                                                                                  |
| `onWidgetDrop`                               | A move commits, before its animation settles. `sourceTarget` is where it came from; it is `undefined` for a widget that arrived through a `FlexiAdd`.                                |
| `onWidgetCancel`                             | The user presses Escape, or lets go where nothing accepts the widget. The widget is back where it started.                                                                           |
| `onWidgetDelete`                             | A widget is dropped on a `FlexiDelete`, or `widget.delete()` is called.                                                                                                              |
| `onWidgetResize`                             | A resize the user was making commits.                                                                                                                                                |
| `onWidgetEnterTarget`, `onWidgetLeaveTarget` | A widget being moved is carried over a target, or leaves it. Useful for styling a column while it is the candidate.                                                                  |
| `canDrop`                                    | While the user hovers and again on release. Return `false` to refuse; the drop preview shows the rejection and the widget returns to its origin.                                     |
| `onLayoutChange`                             | Accepted drops/resizes and programmatic creation, movement, or deletion. Batched in a microtask with committed coordinates. Hover, validation, import, and export do not trigger it. |

`canDrop` runs alongside the grid's own rules (bounds, collisions, size limits), which apply whether or not you provide it. A target's own configuration can carry a `canDrop` too, for rules that belong to one list rather than the board; both must agree.

## Controller APIs

Each controller's properties and methods are listed on its component's page: [FlexiBoard](https://www.flexiboards.dev/docs/components/board#flexiboardcontroller), [FlexiTarget](https://www.flexiboards.dev/docs/components/target#flexitargetcontroller), and [FlexiWidget](https://www.flexiboards.dev/docs/components/widget#flexiwidgetcontroller).
