# Form Edit

Grid can be connected to a separate form for editing selected records. This pattern is useful when records have many fields or when you want a clear separation between viewing and editing.

```tsx
import { createModel } from "cx/data";
import {
  Controller,
  expr,
  hasValue,
  KeySelection,
  LabelsLeftLayout,
} from "cx/ui";
import { stopPropagation } from "cx/util";
import { Button, Checkbox, Grid, TextField } from "cx/widgets";

interface Person {
  id: number;
  fullName: string;
  phone: string;
  city: string;
  notified: boolean;
}

interface PageModel {
  records: Person[];
  selectedId: number | null;
  form: Person | null;
  $record: Person;
}

const m = createModel<PageModel>();

class PageController extends Controller {
  onInit() {
    this.store.set(m.records, [
      {
        id: 1,
        fullName: "Alice Johnson",
        phone: "555-0101",
        city: "New York",
        notified: true,
      },
      {
        id: 2,
        fullName: "Bob Smith",
        phone: "555-0102",
        city: "Los Angeles",
        notified: false,
      },
      {
        id: 3,
        fullName: "Carol White",
        phone: "555-0103",
        city: "Chicago",
        notified: true,
      },
      {
        id: 4,
        fullName: "David Brown",
        phone: "555-0104",
        city: "Houston",
        notified: false,
      },
      {
        id: 5,
        fullName: "Eva Green",
        phone: "555-0105",
        city: "Phoenix",
        notified: true,
      },
    ]);

    this.addTrigger("loadForm", [m.selectedId, m.records], (id, records) => {
      const record = records?.find((r: Person) => r.id === id);
      this.store.set(m.form, record || null);
    });
  }

  saveRecord() {
    const form = this.store.get(m.form);
    if (!form) return;
    this.store.update(m.records, (records) =>
      records.map((r) => (r.id === form.id ? form : r)),
    );
  }

  addRecord() {
    const newRecord: Person = {
      id: Date.now(),
      fullName: "New Entry",
      phone: "",
      city: "",
      notified: false,
    };
    this.store.update(m.records, (records) => [...records, newRecord]);
    this.store.set(m.selectedId, newRecord.id);
  }

  removeRecord(id: number) {
    this.store.update(m.records, (records) =>
      records.filter((r) => r.id !== id),
    );
    if (this.store.get(m.selectedId) === id) {
      this.store.set(m.selectedId, null);
    }
  }
}

export default (
  <div controller={PageController}>
    <Grid
      records={m.records}
      selection={{ type: KeySelection, bind: m.selectedId, keyField: "id" }}
      columns={[
        { header: "Name", field: "fullName", sortable: true },
        { header: "Phone", field: "phone" },
        { header: "City", field: "city", sortable: true },
        {
          header: "Notified",
          field: "notified",
          value: expr(m.$record.notified, (n) => (n ? "Yes" : "No")),
        },
        {
          header: "Actions",
          style: "padding: 2px",
          children: (
            <Button
              mod="hollow"
              onMouseDown={stopPropagation}
              onClick={(e, instance) => {
                const id = instance.store.get(m.$record.id);
                instance.getControllerByType(PageController).removeRecord(id);
              }}
            >
              Remove
            </Button>
          ),
        },
      ]}
      recordAlias={m.$record}
    />

    <p class="mt-4">
      <Button
        onClick={(e, instance) =>
          instance.getControllerByType(PageController).addRecord()
        }
      >
        Add
      </Button>
    </p>

    <hr class="my-6" />

    <LabelsLeftLayout visible={hasValue(m.form)}>
      <strong text={m.form.fullName} class="mb-4" />
      <TextField label="Name" value={m.form.fullName} />
      <TextField label="Phone" value={m.form.phone} />
      <TextField label="City" value={m.form.city} />
      <Checkbox label="Notified" value={m.form.notified} />
      <Button
        onClick={(e, instance) =>
          instance.getControllerByType(PageController).saveRecord()
        }
      >
        Save
      </Button>
    </LabelsLeftLayout>
  </div>
);

```

Click a row to select it. The form below displays the selected record's data for editing.

## Loading Form Data

Use a trigger to load the selected record into the form:

```tsx
this.addTrigger("loadForm", [m.selectedId, m.records], (id, records) => {
  const record = records?.find((r) => r.id === id);
  this.store.set(m.form, record || null);
});
```

## Saving Changes

Update the records array when the form is saved using `updateArray`:

```tsx
import { updateArray } from "cx/data";

saveRecord() {
  const form = this.store.get(m.form);
  if (!form) return;
  this.store.update(m.records, (records) =>
    updateArray(records, () => form, (r) => r.id === form.id)
  );
}
```

## Preventing Row Selection on Button Click

When using action buttons inside a selectable grid, clicking the button also selects the row. Use `stopPropagation` on `onMouseDown` to prevent this:

```tsx
import { stopPropagation } from "cx/util";

{
  header: "Actions",
  children: (
    <Button
      onMouseDown={stopPropagation}
      onClick={(e, instance) => {
        instance.getControllerByType(PageController).removeRecord(id);
      }}
    >
      Remove
    </Button>
  ),
}
```

## When to Use

Form editing is ideal when:
- Records have many fields that don't fit in a grid
- You want explicit Save/Cancel actions
- Complex validation is needed before saving
- The form needs additional context or related data

See also: [Cell Editing](/docs/tables/cell-editing), [Row Editing](/docs/tables/row-editing), [Inline Edit](/docs/tables/inline-edit)