Milk Admin

data-fetch System

This system allows transforming links and containers with the attribute data-fetch="get" or data-fetch="post" into asynchronous fetch() calls. The request is sent automatically (on click for links, on page load for divs), and the JSON response is handled in a unified way (modals, offcanvas, toasts, HTML replacement...).

Two modes available:
  • Links (<a>): Fetch triggered on click
  • Containers (<div>): Fetch triggered automatically on page load

1. data-fetch Links

Transform any link into an asynchronous fetch call that triggers on click.

<a href="/example/url?param=1" data-fetch="get" class="btn btn-primary">
    Fetch GET example
</a>

<a href="/example/url?user=42&active=1" data-fetch="post" class="btn btn-secondary">
    Fetch POST example
</a>
Demo Fetch Links

Clicking these buttons will trigger a fetch call instead of navigation:

Fetch GET Fetch POST

When a link is clicked:

  • It is given the disabled class (Bootstrap-compatible) to prevent double clicks.
  • A fetch() call is executed (GET or POST).
  • The response must always be in JSON format.
  • Depending on the returned data, it can open modals, offcanvas, or toasts.
  • Once the request is completed, the link is re-enabled.

Example JSON Response

{
    "success": true,
    "msg": "Operation completed successfully",
    "modal": {
        "title": "Success",
        "body": "The operation was completed successfully.",
        "footer": "<button class='btn btn-primary' data-bs-dismiss='modal'>Close</button>"
    }
}

If the JSON response contains these properties, they are automatically handled:

  • msg → shows a toast if window.toasts exists
  • modal → opens a modal via window.modal.show()
  • offcanvas_end → opens an offcanvas via window.offcanvasEnd.show()

This is used in the builder title as an option in addButton

You can call initFetchLinks() again after a dynamic DOM update to reinitialize new links added via AJAX.

Redirects are also supported:

{
    "success": true,
    "redirect": "/example/url"
}

2. data-fetch Containers (Divs)

Containers with data-fetch and data-url attributes automatically load content via fetch when the page loads. This is perfect for loading tables, dashboards, or any dynamic content without blocking the initial page render.

Basic Usage

<div id="tableContainer"
     data-fetch="post"
     data-url="?page=posts&action=get-table">
    <!-- Content will be loaded here automatically -->
</div>
✅ Advantages:
  • Faster page load: The page renders immediately, content loads progressively
  • Non-blocking: Multiple containers load sequentially without blocking each other
  • Automatic: No JavaScript code needed, just add the attributes
  • Clean HTML: Attributes are removed after loading to keep DOM clean

How it Works

  1. Page finishes loading (DOMContentLoaded)
  2. After 500ms, the first div with data-fetch starts loading
  3. Each subsequent div loads 500ms after the previous one
  4. The response (JSON) is handled by jsonAction() with the div as container
  5. The data-fetch and data-url attributes are removed to prevent re-loading

Sequential Loading

If you have multiple containers, they load in sequence with 500ms delay between each:

<!-- Loads at 500ms -->
<div data-fetch="post" data-url="?page=stats&action=get-summary"></div>

<!-- Loads at 1000ms (1s) -->
<div data-fetch="post" data-url="?page=posts&action=get-table"></div>

<!-- Loads at 1500ms (1.5s) -->
<div data-fetch="post" data-url="?page=comments&action=get-recent"></div>
⏱️ Loading Timeline:
  • T+0ms: Page loads
  • T+500ms: First container starts loading
  • T+1000ms: Second container starts loading
  • T+1500ms: Third container starts loading
  • And so on...

Controller Response

The controller should return JSON with html to replace the container content:

<?php
#[RequestAction('get-table')]
public function getTable() {
    $table = TableBuilder::create($this->model, 'idTable')
        ->activeFetch()
        ->asLink('title', '?page='.$this->page.'&action=edit&id=%id%')
        ->setDefaultActions();

    if ($table->isInsideRequest()) {
        // When table updates itself (pagination, sorting)
        Response::Json(['html' => $table->render()]);
    } else {
        // When loaded initially or from external fetch
        Response::Json([
            'modal' => [
                'title' => $this->title,
                'body' => $table->render(),
                'size' => 'xl'
            ]
        ]);
    }
}

Alternative: HTML Replacement

You can also return HTML that replaces the container directly:

<?php
Response::Json([
    'html' => '<div class="table-responsive">...</div>'
]);

The html property in the response will replace the container's outerHTML.

Error Handling

If the fetch fails, the container will show an error message:

<div class="alert alert-danger">Failed to load content</div>

The data-fetch and data-url attributes are removed even on error to prevent infinite retry loops.

Loading Indicator

If window.plugin_loading is available, it will be shown during the fetch operation and hidden when complete.

Manual Re-initialization

If you dynamically add containers after page load, you can manually trigger the initialization:

// After adding new containers to DOM
window.initFetchDiv();

Complete Example: Dashboard with Multiple Sections

<div class="container-fluid">
    <div class="row">
        <!-- Stats cards - loads first at 500ms -->
        <div class="col-12 mb-4"
             data-fetch="post"
             data-url="?page=dashboard&action=get-stats">
            <div class="text-center py-5">
                <div class="spinner-border" role="status"></div>
                <p>Loading statistics...</p>
            </div>
        </div>

        <!-- Recent posts table - loads second at 1000ms -->
        <div class="col-md-6 mb-4"
             data-fetch="post"
             data-url="?page=posts&action=get-recent">
            <div class="text-center py-5">
                <div class="spinner-border" role="status"></div>
                <p>Loading recent posts...</p>
            </div>
        </div>

        <!-- Activity log - loads third at 1500ms -->
        <div class="col-md-6 mb-4"
             data-fetch="post"
             data-url="?page=activity&action=get-log">
            <div class="text-center py-5">
                <div class="spinner-border" role="status"></div>
                <p>Loading activity...</p>
            </div>
        </div>
    </div>
</div>
💡 Pro Tip: Include loading indicators (spinners) in the initial HTML. They'll be replaced when the content loads, providing better UX.

Comparison: Links vs Containers

Feature Links (<a data-fetch>) Containers (<div data-fetch>)
Trigger Click event Automatic on page load
URL Source href attribute data-url attribute
Loading Timing Immediate on click Sequential (500ms delay between each)
Disabled State Yes (adds disabled class) No
Common Use Cases Edit buttons, Delete actions, Form submissions Tables, Dashboards, Stats, Dynamic content
Response Container link.parentNode The div itself
Attributes Removed No Yes (after loading)

Summary

✅ Quick Reference:
  • Links: <a href="..." data-fetch="post"> - Triggers on click
  • Containers: <div data-fetch="post" data-url="..."> - Auto-loads on page load
  • Methods: get or post
  • Response: Always JSON format
  • Handler: jsonAction(data, container)
  • Re-init: window.initFetchLinks() and window.initFetchDiv()
Loading...