# Hooks Reference

Entity Builder provides hooks for extending functionality without creating full extension plugins.

## Available Hooks

### hook_eb_ui_grid_provider_info

Registers a grid provider for the Entity Builder UI.

**Location:** `eb_ui` module

**Purpose:** Register alternative UI providers for definition editing.

**Example:**

```php
<?php

namespace Drupal\my_module\Hook;

use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\StringTranslation\StringTranslationTrait;

/**
 * Hook implementations for my_module.
 */
class MyModuleHooks {

  use StringTranslationTrait;

  /**
   * Implements hook_eb_ui_grid_provider_info().
   */
  #[Hook('eb_ui_grid_provider_info')]
  public function gridProviderInfo(): array {
    return [
      'my_grid' => [
        'id' => 'my_grid',
        'label' => $this->t('My Custom Grid'),
        'form_class' => '\Drupal\my_module\Form\MyGridForm',
        'library' => 'my_module/grid',
        'theme_class' => 'my-theme-class',
      ],
    ];
  }

}
```

**Provider Properties:**

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `id` | string | Yes | Unique provider ID |
| `label` | string | Yes | Human-readable label |
| `form_class` | string | Yes | Form class implementing the UI |
| `library` | string | Yes | Library to attach |
| `theme_class` | string | No | CSS class for theming |

## Using Hooks with PHP Attributes

Drupal 11 uses PHP attributes for hook registration:

```php
<?php

namespace Drupal\my_module\Hook;

use Drupal\Core\Hook\Attribute\Hook;

class MyModuleHooks {

  #[Hook('eb_ui_grid_provider_info')]
  public function gridProviderInfo(): array {
    // Implementation
  }

}
```

Register the hook class in your services file:

```yaml
# my_module.services.yml
services:
  Drupal\my_module\Hook\MyModuleHooks:
    class: Drupal\my_module\Hook\MyModuleHooks
    autowire: true
```

## Creating a Custom Grid Provider

### Step 1: Implement the Hook

```php
#[Hook('eb_ui_grid_provider_info')]
public function gridProviderInfo(): array {
  return [
    'my_provider' => [
      'id' => 'my_provider',
      'label' => $this->t('My Custom Provider'),
      'form_class' => '\Drupal\my_module\Form\MyProviderForm',
      'library' => 'my_module/provider',
    ],
  ];
}
```

### Step 2: Create the Form Class

```php
<?php

namespace Drupal\my_module\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\eb\Entity\EbDefinition;

/**
 * Custom grid provider form.
 */
class MyProviderForm extends FormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormId(): string {
    return 'my_provider_form';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state, ?EbDefinition $eb_definition = NULL): array {
    // Get current definition data
    $data = $eb_definition ? [
      'bundle_definitions' => $eb_definition->get('bundle_definitions') ?? [],
      'field_definitions' => $eb_definition->get('field_definitions') ?? [],
    ] : [];

    // Build your custom interface
    $form['my_interface'] = [
      '#type' => 'container',
      '#attributes' => ['id' => 'my-provider-container'],
    ];

    // Hidden field to store data
    $form['definition_data'] = [
      '#type' => 'hidden',
      '#default_value' => json_encode($data),
    ];

    // Attach your JavaScript library
    $form['#attached']['library'][] = 'my_module/provider';
    $form['#attached']['drupalSettings']['myProvider'] = [
      'data' => $data,
    ];

    $form['actions'] = [
      '#type' => 'actions',
    ];

    $form['actions']['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Save'),
      '#button_type' => 'primary',
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state): void {
    // Process submitted data
    $data = json_decode($form_state->getValue('definition_data'), TRUE);

    // Update the definition entity
    $definition = $form_state->get('eb_definition');
    if ($definition) {
      $definition->set('bundle_definitions', $data['bundle_definitions'] ?? []);
      $definition->set('field_definitions', $data['field_definitions'] ?? []);
      $definition->save();

      $this->messenger()->addStatus($this->t('Definition saved.'));
    }
  }

}
```

### Step 3: Create the JavaScript Library

```yaml
# my_module.libraries.yml
provider:
  version: VERSION
  js:
    js/my-provider.js: {}
  css:
    theme:
      css/my-provider.css: {}
  dependencies:
    - core/drupal
    - core/drupalSettings
    - core/once
```

```javascript
// js/my-provider.js
(function (Drupal, drupalSettings, once) {
  'use strict';

  Drupal.behaviors.myProvider = {
    attach: function (context, settings) {
      once('my-provider', '#my-provider-container', context).forEach(function (container) {
        // Initialize your custom interface
        const data = drupalSettings.myProvider?.data || {};

        // Build your UI and update hidden field when data changes.
        function updateData(newData) {
          const hiddenField = document.querySelector('[name="definition_data"]');
          if (hiddenField) {
            hiddenField.value = JSON.stringify(newData);
          }
        }
      });
    }
  };
})(Drupal, drupalSettings, once);
```

### Step 4: Add Route Priority (Optional)

If your provider needs route priority:

```php
<?php

namespace Drupal\my_module\Access;

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\eb_ui\Service\GridProviderManager;

/**
 * Access checker for my provider routes.
 */
class MyProviderAccess {

  /**
   * Constructor.
   */
  public function __construct(
    protected GridProviderManager $providerManager,
  ) {}

  /**
   * Checks access.
   */
  public function access(AccountInterface $account): AccessResultInterface {
    $provider = $this->providerManager->getActiveProvider();

    // Allow if our provider is active
    return AccessResult::allowedIf(
      $provider && $provider['id'] === 'my_provider'
    )->addCacheContexts(['user.permissions']);
  }

}
```

## Using eb_ui's Shared API

Grid providers can use eb_ui's shared API endpoints:

### Validation Endpoint

```javascript
async function validateData(data) {
  const response = await fetch('/eb/api/validate', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': drupalSettings.csrfToken,
      'X-Requested-With': 'XMLHttpRequest',
    },
    body: JSON.stringify(data),
  });

  return response.json();
}
```

### Preview Endpoint

```javascript
async function previewOperations(data) {
  const response = await fetch('/eb/api/preview', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': drupalSettings.csrfToken,
      'X-Requested-With': 'XMLHttpRequest',
    },
    body: JSON.stringify(data),
  });

  return response.json();
}
```

### Entity Discovery Endpoints

```javascript
// Get bundles for entity type
async function getBundles(entityType) {
  const response = await fetch(`/eb/api/bundles/${entityType}`, {
    headers: { 'X-Requested-With': 'XMLHttpRequest' },
  });
  return response.json();
}

// Get field configuration
async function getEntityConfig(entityType, bundle) {
  const response = await fetch(`/eb/api/entity-config/${entityType}/${bundle}`, {
    headers: { 'X-Requested-With': 'XMLHttpRequest' },
  });
  return response.json();
}
```

## Best Practices

1. **Use shared endpoints** - Leverage eb_ui's validation and preview APIs
2. **Follow Drupal patterns** - Use Drupal behaviors and once()
3. **Handle errors gracefully** - Show user-friendly error messages
4. **Maintain data consistency** - Keep hidden field in sync with UI state
5. **Add accessibility** - Follow WCAG guidelines for custom interfaces
