npm package: @freeseller/wappler-nested-form-repeat
Description: > This instruction and the examples below were generated with the help of AI. > They should be treated as a starting point, not as a complete specification. > To fully unlock the component potential, it is important to understand its logic and experiment 
Author: Vladimir Busov
Latest version: 0.5.0

# @freeseller/wappler-nested-form-repeat

> This instruction and the examples below were generated with the help of AI.
> They should be treated as a starting point, not as a complete specification.
> To fully unlock the component potential, it is important to understand its logic and experiment with it in real Wappler forms and nested structures.
>
> The author already uses this component to build a dynamic article constructor and continues to improve and evolve it.

Nested Form Repeat for Wappler.

This component is based on the standard **Form Repeat** component, but is adapted for nested form structures.

## Main differences from standard Form Repeat

- built on top of the standard Wappler **Form Repeat** logic
- supports nested repeaters
- parent/child paths are built from the component **`name`** values, not from component ids
- field names are **not auto-generated** like in the standard Form Repeat
- field names must be defined explicitly, usually with `dmx-bind:name`
- this makes the component more flexible for nested arrays and nested objects
- uses renamed actions:
  - `nfrAdd(data)`
  - `nfrAddChild(name, parentIndex, data)`
  - `nfrRemove(index)`
  - `nfrMove(oldIndex, newIndex)`
  - `nfrMoveToStart(index)`
  - `nfrMoveToEnd(index)`
  - `nfrMoveBefore(index)`
  - `nfrMoveAfter(index)`
  - `nfrReset()`
- `duplicate` from the original component is not exposed as part of the intended public workflow
- parent repeater should have an **id** when you want to call it directly
- child repeaters do **not** need an id when they are controlled through `$repeat`

---

## Core concepts

### `name` is required
Each repeater must have a `name`.

The component uses `name` values to build nested paths.

Examples:
- parent repeater `name="entries"`
- child repeater `name="blocks"`

This leads to payload paths like:
- `entries[0]`
- `entries[0][blocks][0]`

### `id` is only needed for direct parent actions
The parent repeater usually has an `id` so you can call actions directly:

```html
<div is="dmx-nested-form-repeat"
     id="entries_repeat"
     name="entries"
     dmx-bind:items="server_data.entries">
</div>
```

Then you can call:

```html
<button dmx-on:click="entries_repeat.nfrAdd()">Add row</button>
<button dmx-on:click="entries_repeat.nfrReset()">Reset</button>
```

Child repeaters do not need an id when they are accessed through `$repeat`.

### `$value`
`$value` is the data scope of the current row.

Examples:
- `$value.title`
- `$value.blocks`
- `$value.meta.code`

### `$itempath`
`$itempath` is the full path to the current row.

Examples:
- `entries[0]`
- `entries[0][blocks][1]`

### `$parentpath`
`$parentpath` is the path without the current row index.

Examples:
- if `$itempath` is `entries[0]` then `$parentpath` is `entries`
- if `$itempath` is `entries[0][blocks][1]` then `$parentpath` is `entries[0][blocks]`

---

## Important difference from standard Form Repeat

The standard Form Repeat automatically rewrites field names from static template fields.

This component is more flexible:
- you define field names yourself
- usually with `dmx-bind:name`
- this allows arrays and objects to be mixed in nested structures

So instead of relying on automatic name generation, you write the exact structure you want.

---

## How actions are called

### Parent level
Use the parent component id directly.

```html
<button dmx-on:click="entries_repeat.nfrAdd()">Add parent row</button>
<button dmx-on:click="entries_repeat.nfrAdd({title: 'New entry'})">Add parent row with data</button>
<button dmx-on:click="entries_repeat.nfrReset()">Reset</button>
```

### Nested level
Inside a row, use `$repeat`.

Remove current row:

```html
<button dmx-on:click="$repeat.nfrRemove($formindex)">Remove row</button>
```

Add a child row into nested repeater `blocks` of the current parent row:

```html
<button dmx-on:click="$repeat.nfrAddChild('blocks', $formindex, {type: 'text'})">
  Add text block
</button>
```

This means:
- use current row context
- find child repeater with `name="blocks"`
- use `$formindex` as the parent row index
- insert the provided data into the new child item

---

## Example 1: Nested array structure

### Markup

```html
<div is="dmx-nested-form-repeat"
     id="entries_repeat"
     name="entries"
     dmx-bind:items="server_data.entries">

  <div>
    <input dmx-bind:name="$itempath + '[record_id]'" type="hidden" dmx-bind:value="'entry_' + $formindex">
    <input dmx-bind:name="$itempath + '[title]'" type="text" dmx-bind:value="$value.title">

    <button type="button" dmx-on:click="$repeat.nfrAddChild('blocks', $formindex, {type: 'text'})">
      Add text block
    </button>

    <div is="dmx-nested-form-repeat"
         name="blocks"
         dmx-bind:items="$value.blocks">

      <div>
        <input dmx-bind:name="$itempath + '[type]'" type="hidden" value="text">
        <textarea dmx-bind:name="$itempath + '[content]'" dmx-bind:value="$value.content"></textarea>
      </div>
    </div>
  </div>
</div>

<button dmx-on:click="entries_repeat.nfrAdd({title: 'New entry'})">
  Add parent row
</button>
```

### Example server data

```json
{
  "entries": [
    {
      "record_id": "entry_0",
      "title": "First entry",
      "blocks": [
        {
          "type": "text",
          "content": "Hello"
        }
      ]
    }
  ]
}
```

### Example payload sent to the server

```json
{
  "entries": [
    {
      "record_id": "entry_0",
      "title": "First entry",
      "blocks": [
        {
          "type": "text",
          "content": "Hello"
        }
      ]
    },
    {
      "record_id": "entry_1",
      "title": "New entry"
    }
  ]
}
```

---

## Example 2: Nested object structure with `$parentpath`

Use `$parentpath` when a field should be written under the parent object path instead of the current array item path.

In this example the parent row contains two child repeaters:
- `blocks` builds a nested array
- `seo_fields` writes values into a nested object

### Markup

```html
<div is="dmx-nested-form-repeat"
     id="articles_repeat"
     name="articles"
     dmx-bind:items="server_data.articles">

  <div>
    <input dmx-bind:name="$itempath + '[title]'" type="text" dmx-bind:value="$value.title">

    <div is="dmx-nested-form-repeat"
         name="blocks"
         dmx-bind:items="$value.blocks">

      <div>
        <input dmx-bind:name="$itempath + '[type]'" type="hidden" value="text">
        <textarea dmx-bind:name="$itempath + '[content]'" dmx-bind:value="$value.content"></textarea>
      </div>
    </div>

    <div is="dmx-nested-form-repeat"
         name="seo_fields"
         dmx-bind:items="$value.seo_fields">

      <div>
        <input dmx-bind:name="$itempath + '[field]'" type="hidden" dmx-bind:value="$value.field">
        <input dmx-bind:name="$parentpath + '[seo][' + $value.field + ']'"
               type="text"
               dmx-bind:value="$value.value">
      </div>
    </div>
  </div>
</div>
```

### Example server data

```json
{
  "articles": [
    {
      "title": "Article A",
      "blocks": [
        {
          "type": "text",
          "content": "Intro"
        }
      ],
      "seo_fields": [
        { "field": "title", "value": "SEO Title" },
        { "field": "description", "value": "SEO Description" },
        { "field": "keywords", "value": "seo, example" }
      ]
    }
  ]
}
```

### Example payload sent to the server

```json
{
  "articles": [
    {
      "title": "Article A",
      "blocks": [
        {
          "type": "text",
          "content": "Intro"
        }
      ],
      "seo_fields": [
        { "field": "title", "value": "SEO Title" },
        { "field": "description", "value": "SEO Description" },
        { "field": "keywords", "value": "seo, example" }
      ],
      "seo": {
        "title": "SEO Title",
        "description": "SEO Description",
        "keywords": "seo, example"
      }
    }
  ]
}
```

This is useful when you need both:
- row-based nested editing
- direct object mapping under the parent item

---

## Adding data programmatically

### Add parent item with data

```html
<button dmx-on:click="entries_repeat.nfrAdd({
  record_id: 'entry_new',
  title: 'Created from action'
})">
  Add parent with data
</button>
```

### Add child item with data

```html
<button dmx-on:click="$repeat.nfrAddChild('blocks', $formindex, {
  type: 'text',
  content: 'Inserted block'
})">
  Add child with data
</button>
```

---

## Notes

- `name` is required on every repeater
- paths are built from repeater `name` values, not from component ids
- use parent repeater actions directly through its `id`
- use child repeater actions through `$repeat` inside the current row
- parent repeaters should have an `id` if you want to call them directly
- child repeaters do not need an `id` when they are discovered by `name`
- `$value` represents the current row data scope
- `$itempath` is the full path to the current row
- `$parentpath` is the path to the parent container without the current row index
- field names should be defined explicitly, usually with `dmx-bind:name`

---

## Recommended pattern

- use `name` for each repeater collection name
- use `$value` to access current row data
- use `dmx-bind:name` for fields so you control the exact payload structure
- use `$repeat.nfrRemove($formindex)` inside row context
- use the parent component id for top-level add/reset actions

---

## Package

```json
{
  "name": "@freeseller/wappler-nested-form-repeat",
  "version": "0.5.0"
}
```