# Record Field Type - Documentation

## Overview

The Record field type is a repeater-like field that allows you to create multiple entries, each containing a set of subfields. It's useful for managing structured, repeating data like:
- Event schedules (multiple time slots with details)
- Team members (multiple people with names, roles, photos)
- Contact information (multiple offices/locations)
- Product specifications (multiple attributes)

## Architecture

### Data Structure

The Record field uses umeta_id-based storage for queryable subfields:

**1. Record Field (Structure with umeta_ids)**
```javascript
[
  { name: 123, role: 124, photo: 125 },
  { name: 126, role: 127, photo: 128 }
]
```

**2. Subfield Data (Multiple Meta Entries)**
Each subfield value is saved as a separate meta entry with `single=false`:
- `team_members_name` with values "John Doe" (umeta_id: 123) and "Jane Smith" (umeta_id: 126)
- `team_members_role` with values "Developer" (umeta_id: 124) and "Designer" (umeta_id: 127)
- `team_members_photo` with values 100 (umeta_id: 125) and 101 (umeta_id: 128)

This approach enables:
- ✅ **Index-free Queries**: Search for "John" without knowing which record
- ✅ **Simple Meta Keys**: No complex `field__0__subfield` naming
- ✅ **Individual umeta_ids**: Each value has its own database row and ID
- ✅ **Position Preserved**: Record structure maintains relationships via umeta_ids

### How It Works

1. **Record Container**: Stores array of objects with umeta_id references for each subfield
2. **Subfield Storage**: Each subfield value gets its own meta row (single=false)
3. **Meta Key Pattern**: `{recordKey}_{subfieldKey}` (e.g., `team_members_name`)
4. **Queryable**: Search for any value without knowing the record index
5. **REST API**: Automatically returns `{subfieldKey}__umeta_ids` arrays for mapping

### Database Schema

For a Record field named `team_members` with subfields `name` and `role`:

**Post Meta Table:**
```
umeta_id | meta_key              | meta_value
---------|----------------------|--------------------------------
100      | team_members         | [{"name":123,"role":124},{"name":125,"role":126}]
123      | team_members_name    | John Doe
124      | team_members_role    | Developer
125      | team_members_name    | Jane Smith
126      | team_members_role    | Designer
```

Each subfield value is a separate row. The Record structure stores the umeta_ids to maintain relationships.

## PHP Registration

### Basic Example

```php
use MioDataModel;

$dm = MioDataModel::getInstance();

// Register the Record field
$dm->registerPostMeta('team_members', [
    'type' => 'array',
    'fieldType' => 'record',
    'label' => 'Team Members',
    'panel' => 'team-panel',
    'postTypes' => ['page', 'post'],
    'show_in_rest' => true,
    'single' => true,
    'fields' => [
        [
            'key' => 'name',
            'label' => 'Name',
            'fieldType' => 'text'
        ],
        [
            'key' => 'role',
            'label' => 'Role',
            'fieldType' => 'text'
        ],
        [
            'key' => 'photo',
            'label' => 'Photo',
            'fieldType' => 'media'
        ],
        [
            'key' => 'bio',
            'label' => 'Biography',
            'fieldType' => 'textarea'
        ]
    ]
]);
```

### Advanced Example with Multiple Field Types

```php
$dm->registerPostMeta('event_schedule', [
    'type' => 'array',
    'fieldType' => 'record',
    'label' => 'Event Schedule',
    'panel' => 'event-details',
    'postTypes' => ['mio_event'],
    'show_in_rest' => true,
    'single' => true,
    'fields' => [
        [
            'key' => 'session_time',
            'label' => 'Session Time',
            'fieldType' => 'datetime'
        ],
        [
            'key' => 'session_title',
            'label' => 'Title',
            'fieldType' => 'text'
        ],
        [
            'key' => 'description',
            'label' => 'Description',
            'fieldType' => 'textarea'
        ],
        [
            'key' => 'location',
            'label' => 'Location',
            'fieldType' => 'latlng'
        ],
        [
            'key' => 'featured_image',
            'label' => 'Featured Image',
            'fieldType' => 'media'
        ],
        [
            'key' => 'related_speakers',
            'label' => 'Speakers',
            'fieldType' => 'related-posts',
            'relatedPostTypes' => ['mio_person']
        ]
    ]
]);
```

## Supported Subfield Types

The Record field supports the following field types as subfields:

- `text` - Single line text input
- `textarea` - Multi-line text area
- `media` - Image/media upload
- `gallery` - Multiple image selection
- `datetime` - Date and time picker
- `url` - URL with label (markdown format)
- `latlng` - Latitude/longitude coordinates
- `term` - Taxonomy term selector
- `related-posts` - Related posts selector

**Note:** Record fields cannot be nested (you cannot have a Record field within a Record field).

## Frontend Usage

### Retrieving Record Data

```php
<?php
// Method 1: Get the record structure (contains umeta_ids)
$team_members = get_post_meta(get_the_ID(), 'team_members', true);

if ($team_members && is_array($team_members)) {
    foreach ($team_members as $index => $record) {
        // Get subfield values using umeta_ids
        $name_umeta_id = $record['name'] ?? 0;
        $role_umeta_id = $record['role'] ?? 0;
        $photo_umeta_id = $record['photo'] ?? 0;
        
        // Retrieve actual values from postmeta
        $name_values = get_post_meta(get_the_ID(), 'team_members_name', false);
        $role_values = get_post_meta(get_the_ID(), 'team_members_role', false);
        $photo_values = get_post_meta(get_the_ID(), 'team_members_photo', false);
        
        $name = $name_values[$index] ?? '';
        $role = $role_values[$index] ?? '';
        $photo_id = $photo_values[$index] ?? 0;
        
        echo "<div class='team-member'>";
        if ($photo_id) {
            echo wp_get_attachment_image($photo_id, 'thumbnail');
        }
        echo "<h3>" . esc_html($name) . "</h3>";
        echo "<p class='role'>" . esc_html($role) . "</p>";
        echo "</div>";
    }
}
?>
```

### Helper Function Example

```php
/**
 * Get all records for a field with values properly retrieved
 */
function mio_get_records($post_id, $field_key) {
    $records = get_post_meta($post_id, $field_key, true);
    
    if (!$records || !is_array($records)) {
        return [];
    }
    
    // Get all subfield keys from first record
    $first_record = reset($records);
    $subfield_keys = array_keys($first_record);
    
    // Fetch all subfield values
    $subfield_values = [];
    foreach ($subfield_keys as $subfield_key) {
        $meta_key = "{$field_key}_{$subfield_key}";
        $subfield_values[$subfield_key] = get_post_meta($post_id, $meta_key, false);
    }
    
    // Build complete records
    $complete_records = [];
    foreach ($records as $index => $record) {
        $complete_record = [];
        foreach ($subfield_keys as $subfield_key) {
            $complete_record[$subfield_key] = $subfield_values[$subfield_key][$index] ?? '';
        }
        $complete_records[] = $complete_record;
    }
    
    return $complete_records;
}

// Usage
$team = mio_get_records(get_the_ID(), 'team_members');
foreach ($team as $member) {
    echo "<h3>" . esc_html($member['name']) . "</h3>";
    echo "<p>" . esc_html($member['role']) . "</p>";
}
```

## REST API Access

Record fields are accessible via the WordPress REST API when `show_in_rest` is set to `true`:

```javascript
// Fetch post with record fields
fetch('/wp-json/wp/v2/posts/123')
  .then(response => response.json())
  .then(post => {
    const teamMembers = post.meta.team_members;
    
    // Subfield values come as arrays with __umeta_ids
    const names = post.meta.team_members_name;
    const roles = post.meta.team_members_role;
    
    // Iterate through records
    teamMembers.forEach((record, index) => {
      console.log(names[index], roles[index]);
    });
  });
```

## UI Features

### Editor Interface

The Record field provides a clean, collapsible interface in the WordPress block editor:

- **Add Record Button**: Blue button at the bottom to add new records
- **Remove Record Button**: Red trash icon to delete specific records
- **Collapsible Panels**: Each record is wrapped in a PanelBody for organization
- **Subfield Organization**: Subfields are visually grouped within each record

### User Experience

- Records are numbered sequentially (Record 1, Record 2, etc.)
- First record is open by default for easier initial data entry
- Empty state message guides users to add their first record
- Subfields retain their specific UI components (date pickers, media uploaders, etc.)

## Best Practices

1. **Field Naming**: Use descriptive, lowercase keys with underscores (e.g., `team_members`, `event_schedule`)

2. **Subfield Keys**: Keep subfield keys short and descriptive (e.g., `name`, `title`, `photo`)

3. **Data Validation**: Implement validation in your PHP backend for production use

4. **Performance**: For large datasets with many records, consider pagination or limiting the number of records

5. **Querying**: Use direct meta queries on subfield keys without indices (e.g., `team_members_name` not `team_members__0__name`)

6. **Helper Functions**: Use the provided `mio_get_records()` helper to retrieve complete record data with all subfield values

## Limitations

- Record fields cannot be nested (no records within records)
- Maximum recommended records per field: ~50 (for performance)
- All subfields in a record share the same parent field configuration

## Troubleshooting

### Records Not Saving

- Verify `show_in_rest` is set to `true`
- Check that `type` is set to `'array'`
- Ensure post type supports `'custom-fields'`

### Subfields Not Displaying

- Confirm subfield `fieldType` is supported
- Check that subfield configuration includes required properties (`key`, `label`, `fieldType`)

### Metadata Not Loading

- Verify post meta is registered correctly
- Check browser console for JavaScript errors
- Ensure WordPress REST API is accessible
with `type => 'array'`
- Check that `show_in_rest` is set to `true`
- Ensure the field key is correct
- Check browser console for JavaScript errors