aura

Kanban

Arguably the most complex component in Aura, the Kanban component provides a robust way to render and manage a kanban board. It includes both frontend and backend components that work together seamlessly. The frontend handles the interactive UI using Muuri.js for drag-and-drop functionality, while the backend component integrates with Livewire to manage data persistence and event handling.

Attributes

  • static - (bool) When set, columns become static and cannot be dragged (items within columns remain draggable).

Basic Usage

To Do
Drink a cup of coffee
Make another cup of coffee
Done
Make a cup of coffee

Copy to clipboard

<aura:board>
    <aura:board.column>
        <aura:board.column.header>
            To Do
        </aura:board.column.header>
        <aura:board.column.content>
            <aura:board.card>
                Drink a cup of coffee
            </aura:board.card>
            <aura:board.card>
                Make another cup of coffee
            </aura:board.card>
        </aura:board.column.content>
    </aura:board.column>
    <aura:board.column>
        <aura:board.column.header>
            Done
        </aura:board.column.header>
        <aura:board.column.content>
            <aura:board.card>
                Make a cup of coffee
            </aura:board.card>
        </aura:board.column.content>
    </aura:board.column>
</aura:board>

Static Columns

When the static attribute is set, the columns become static and cannot be dragged. However, items within the columns remain draggable.

To Do
Drink a cup of coffee
Make another cup of coffee
Done
Make a cup of coffee

Copy to clipboard

<aura:board static>
    <!-- ... -->
</aura:board>

Caveats

The root element requires some specific CSS considerations. It needs a parent with explicitly defined dimensions ( non-computed height and width) to render correctly:

Copy to clipboard


<div class="w-[1080px] h-96">
    <aura:board>
        ...
    </aura:board>
</div>

For filling the aura:main component, use this workaround:

Copy to clipboard


<div class="h-full w-full">
    <div class="h-full w-full min-h-96 flex relative">
        <div class="absolute inset-0">
            <aura:board>
                ...
            </aura:board>
        </div>
    </div>
</div>

Backend

The backend is powered by an abstract Livewire component, BoardComponent, which serves as the bridge between the frontend kanban board and your application's data storage. It handles all events dispatched from the frontend and provides methods to persist changes.

Implementation

To use the Kanban component, you must extend BoardComponent and implement its abstract methods. Here's a complete example:

Copy to clipboard

namespace App\Http\Livewire;

use App\Models\KanbanColumn;
use App\Models\KanbanItem;
use Livewire\Component;
use Aura\Livewire\BoardComponent;

class KanbanBoard extends BoardComponent
{
    public $columns = [];
    
    public function mount()
    {
        // Load initial data
        $this->columns = KanbanColumn::with('items')->get()->toArray();
    }

    protected function updateItemPosition(string $itemId, string $toColumnId, int &$toIndex): void
    {
        $item = KanbanItem::findOrFail($itemId);
        $item->column_id = $toColumnId;
        $item->position = $toIndex;
        $item->save();

        // Reorder other items in the column if needed
        KanbanItem::where('column_id', $toColumnId)
            ->where('id', '!=', $itemId)
            ->orderBy('position')
            ->get()
            ->each(function ($item, $index) use ($toIndex) {
                $item->position = ($index >= $toIndex) ? $index + 1 : $index;
                $item->save();
            });
    }

    protected function updateColumnPosition(string $columnId, int &$toIndex): void
    {
        $column = KanbanColumn::findOrFail($columnId);
        $column->position = $toIndex;
        $column->save();

        // Reorder other columns
        KanbanColumn::where('id', '!=', $columnId)
            ->orderBy('position')
            ->get()
            ->each(function ($column, $index) use ($toIndex) {
                $column->position = ($index >= $toIndex) ? $index + 1 : $index;
                $column->save();
            });
    }

    protected function createItem(string $columnId, int &$index, array $itemData): string
    {
        $item = KanbanItem::create([
            'column_id' => $columnId,
            'title' => $itemData['title'] ?? 'New Item',
            'position' => $index,
        ]);

        return "<div data-aura-board-item data-aura-board-item-id='{$item->id}'>{$item->title}</div>";
    }

    protected function createColumn(int &$index, array $columnData): string
    {
        $column = KanbanColumn::create([
            'title' => $columnData['title'] ?? 'New Column',
            'position' => $index,
        ]);

        return "
            <div data-aura-board-column data-aura-board-column-id='{$column->id}'>
                <div data-aura-board-column-header>{$column->title}</div>
                <div data-aura-board-column-content></div>
            </div>
        ";
    }

    protected function removeItem(string $itemId): void
    {
        KanbanItem::findOrFail($itemId)->delete();
    }

    protected function removeColumn(string $columnId): void
    {
        $column = KanbanColumn::findOrFail($columnId);
        $column->items()->delete(); // Delete associated items
        $column->delete();
    }

    // Optional: Render method if you're using a custom view
    public function render()
    {
        return view('livewire.kanban-board', [
            'columns' => $this->columns,
        ]);
    }
}

Required Methods

The BoardComponent abstract class requires you to implement these methods:

  • updateItemPosition(string $itemId, string $toColumnId, int &$toIndex): void

    • Updates an item's position when dragged to a new column or position.
    • Parameters: Item ID, target column ID, and target index (passed by reference).
  • updateColumnPosition(string $columnId, int &$toIndex): void

    • Updates a column's position when reordered.
    • Parameters: Column ID and target index (passed by reference).
  • createItem(string $columnId, int &$index, array $itemData): string

    • Creates a new item and returns its HTML representation.
    • Parameters: Target column ID, index, and item data array.
    • Must return HTML matching the data-aura-board-item structure.
  • createColumn(int &$index, array $columnData): string

    • Creates a new column and returns its HTML representation.
    • Parameters: Index and column data array.
    • Must return HTML matching the data-aura-board-column structure.
  • removeItem(string $itemId): void

    • Removes an item from the backend storage.
    • Parameter: Item ID.
  • removeColumn(string $columnId): void

    • Removes a column and optionally its items from the backend.
    • Parameter: Column ID.

Events

The backend listens for and dispatches these Livewire events:

  • moved-board-item → Calls updateItemPosition
  • moved-column → Calls updateColumnPosition
  • add-board-item → Calls createItem
  • add-column → Calls createColumn
  • delete-board-item → Calls removeItem
  • delete-column → Calls removeColumn

Database Schema Example

Copy to clipboard

Schema::create('kanban_columns', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->integer('position')->default(0);
    $table->timestamps();
});

Schema::create('kanban_items', function (Blueprint $table) {
    $table->id();
    $table->foreignId('column_id')->constrained('kanban_columns')->cascadeOnDelete();
    $table->string('title');
    $table->integer('position')->default(0);
    $table->timestamps();
});

Frontend Integration

The frontend dispatches events to Livewire, which are handled by these methods. Ensure your frontend HTML includes proper data-aura-* attributes matching your backend IDs.


This updated documentation provides a comprehensive guide to both frontend usage and backend implementation, including a practical example and database considerations. Let me know if you'd like to expand any section further!