پرش به مطلب اصلی

کنترلر

کنترلر SelectController کلاس پایه برای تامین داده‌های کامپوننت x-select است.
در عمل، شما یک کنترلر فرزند می‌سازید و منطق داده را داخل آن پیاده‌سازی می‌کنید. در کنترلر فرزند ،باید دقیقا یکی از دو متد data() یا query() را پیاده‌سازی کنید:

  • اگر هیچ‌کدام پیاده‌سازی نشوند، خطا برمی‌گردد.
  • اگر هر دو پیاده‌سازی شوند، خطا برمی‌گردد.

این خطا با کد SELECT_INVALID_FILTER_CONFIGURATION برگردانده می‌شود.

انتخاب بین data() و query()

حالت data()

وقتی data() را پیاده‌سازی می‌کنید، می‌توانید خروجی را به یکی از فرم‌های زیر برگردانید:

  • array
  • Collection
  • LengthAwarePaginator

این حالت برای زمانی مناسب است که:

  • از قبل خروجی را در یک سرویس/ریپازیتوری آماده کرده‌اید.
  • نیاز به کنترل کامل روی خروجی نهایی دارید.

حالت query()

وقتی query() را پیاده‌سازی می‌کنید، باید یک Eloquent Builder برگردانید. در این حالت، خود SelectController این کارها را انجام می‌دهد:

  • اعمال ترتیب اولویت روی آیتم‌های انتخاب‌شده.
  • صفحه‌بندی خروجی.
  • تلاش برای تکمیل label آیتم‌های selected از دیتابیس (در صورت نبود label در خروجی selected()).

کانستنت‌های صفحه‌بندی

در SelectController دو کانستنت وجود دارد که می‌توانید در کنترلر فرزند override کنید:

  • PER_PAGE (پیش‌فرض: 15)
  • QUERY_RESULT_MODE (پیش‌فرض: 'paginate')

مقادیر مجاز برای QUERY_RESULT_MODE:

  • 'paginate' → استفاده از paginate() لاراول
  • 'dorapi_paginate' → استفاده از dorapi()->dorapiPaginate()
  • 'get' → دریافت خروجی با get() بدون صفحه‌بندی

مثال override

protected const int PER_PAGE = 30;
protected const string QUERY_RESULT_MODE = 'dorapi_paginate';

فرمت متد selected()

selected() می‌تواند یکی از این دو فرمت را برگرداند:

فقط شناسه‌ها

public function selected(): array
{
return [5, 8, 12];
}

شناسه + label

public function selected(): array
{
return [
['id' => 5, 'label' => 'ملت'],
['id' => 8, 'label' => 'صادرات'],
];
}

اگر label را خودتان بدهید، کنترلر تلاش نمی‌کند همان label را دوباره از داده‌ها استخراج کند.

ادغام با $this->selectedItem و $this->selectedItems

مقادیر انتخاب‌شده‌ای که از درخواست/سشن می‌آیند، قبل از اجرای نهایی با خروجی selected() ادغام می‌شوند.

  • در حالت تک‌انتخابی، مقدار در $this->selectedItem قرار می‌گیرد.
  • در حالت چندانتخابی، مقادیر در $this->selectedItems قرار می‌گیرند.
  • سپس این مقادیر با خروجی selected() merge می‌شوند و لیست نهایی selected ساخته می‌شود.

این یعنی اگر در selected() مقدار اولیه برگردانید، انتخاب فعلی کاربر از request/session هم به آن اضافه می‌شود.

ستون label سفارشی (labelColumn)

ستون label به‌صورت پیش‌فرض name است. اگر منبع دادهٔ شما label را با کلید دیگری (مثلاً title) برمی‌گرداند، پراپرتی labelColumn را در کنترلر فرزند مقداردهی کنید:

protected string $labelColumn = 'title';
تغییر نسبت به نسخه‌های قبل

متد selectedLabelColumn() حذف شده و fallback خودکار از name به title دیگر وجود ندارد. ستون label دقیقاً همان مقدار labelColumn است (پیش‌فرض: name) و وجود آن در همهٔ آیتم‌های خروجی اجباری است؛ در غیر این صورت خطای زیر برمی‌گردد:

The label column 'name' does not exist in the returned data.

ساختار حداقلی داده‌ها

هر آیتمِ خروجی می‌تواند شامل این فیلدها باشد:

  • idاجباری.
  • ستون label — اجباری. کلید آن با labelColumn تعیین می‌شود (پیش‌فرض: name). اگر داده‌ی شما label را با کلید دیگری مثل title برمی‌گرداند، حتماً labelColumn را تنظیم کنید.
  • is_active — بسته به مدل:
    • در حالت query()، اگر is_active جزو fillableهای مدل باشد، این کلید در خروجی اجباری است و مقدار آن مستقیماً از داده خوانده می‌شود.
    • در غیر این صورت (مدل بدون is_active یا حالت data()اختیاری است و اگر ارسال نشود به‌صورت پیش‌فرض فعال (IsActive::YES یعنی مقدار 1) درنظر گرفته می‌شود؛ مقادیرِ ارسال‌شده دست‌نخورده می‌مانند.
  • selected — اختیاری، برای علامت‌زدنِ آیتمِ انتخاب‌شده.
همگام‌سازی خودکار selected

کنترلر پیش از ارسال پاسخ، مقدار selected همهٔ آیتم‌ها را با شناسه‌های انتخاب‌شدهٔ واقعی (request/session و خروجی selected()) بازنویسی (reconcile) می‌کند. یعنی اگر در داده‌ی خام selected => true مانده باشد ولی آن آیتم واقعاً انتخاب نشده باشد، در خروجی نهایی false می‌شود و برعکس.

گرفتن label شناسه‌ها در سمت سرور (resolveLabelsForIds)

متد عمومی resolveLabelsForIds() با استفاده از همان منطق data()/query() کنترلر، label قابل‌نمایش هر شناسه را در سمت سرور برمی‌گرداند:

$labels = app(CitySelectController::class)
->resolveLabelsForIds([5, 8], parentSelected: 21);

// خروجی: نگاشت id => label (اگر label پیدا نشود، null)
// ['5' => 'ساری', '8' => 'بابل']
  • پارامتر دوم (parentSelected) برای سلکت‌های وابسته است و مقدار سلکت والد را مشخص می‌کند.
  • کاربرد اصلی آن جایی است که سمت سرور به label نیاز دارید — مثلاً ست‌کردن فیلترهای پیش‌فرض جدول برای سلکت‌های وابسته/APIمحور که لیست گزینه‌هایشان فقط از طریق همین endpoint در دسترس است.

مقدار والد در سلکت‌های وابسته (parentSelected)

در یک سلکت وابسته، مقدار انتخاب‌شده‌ی والد در parentSelected و کل زنجیره‌ی والدها در parentChain در دسترس است.

اگر کامپوننت والد multi select باشد، همه‌ی مقادیر انتخاب‌شده‌ی آن در یک درخواست ارسال می‌شوند (نه یک درخواست به‌ازای هر مقدار) و parentSelected آرایه‌ای از همه‌ی آن مقادیر خواهد بود:

انتخاب کاربر در والدمقدار parentSelected
هیچ مقداری انتخاب نشدهnull
یک مقدار (والد تکی یا چندتایی)مثلاً '12'
چند مقدار (والد چندتایی)آرایه، مثلاً ['12', '15', '21']
پشتیبانی از هر دو حالت

چون parentSelected بسته به تعداد انتخاب والد، مقدار رشته یا آرایه است، در کوئری کنترلر فرزند از whereIn به‌همراه کست آرایه استفاده کنید تا هر دو حالت پوشش داده شود:

->whereIn('province_id', (array) $this->parentSelected)

با where(...) فقط حالت تک‌مقداری درست کار می‌کند و برای والد چندانتخابی نتیجه‌ی درستی نمی‌دهد.

قابلیت‌های مهم دیگر

  • مقدار search از ریکوئست خوانده می‌شود و می‌توانید در کوئری استفاده کنید.
  • در حالت فیلتر (from_filter)، مقدار selected از session مربوط به table generator خوانده می‌شود.
  • آیتم‌های selected در مرتب‌سازی اولویت دارند و معمولا بالاتر نمایش داده می‌شوند.

مثال‌ها

مثال 1: استفاده با data()

<?php

namespace App\Http\Controllers\Select;

use App\Models\City;
use Dornica\BladeComponents\Forms\Select\Controllers\SelectController;

class CitySelectController extends SelectController
{
public function data(): mixed
{
return City::query()
->when($this->search, function ($query) {
$query->where('name', 'like', "%{$this->search}%");
})
->orderBy('name')
->get();
}

public function selected(): array
{
return [1, 2];
}
}

مثال 2: استفاده با query() (پیشنهادی)

<?php

namespace App\Http\Controllers\Select;

use App\Models\Bank;
use Dornica\BladeComponents\Forms\Select\Controllers\SelectController;
use Illuminate\Database\Eloquent\Builder;

class BankSelectController extends SelectController
{
protected const int PER_PAGE = 20;
protected const string QUERY_RESULT_MODE = 'paginate';

public function query(): ?Builder
{
return Bank::query()
->when($this->search, function ($query) {
$query->where('name', 'like', "%{$this->search}%");
});
}

public function selected(): array
{
return [
['id' => 5, 'label' => 'test 5'],
['id' => 8, 'label' => 'test 8'],
];
}
}

مثال 3: سلکت وابسته با parentSelected

کست (array) باعث می‌شود این کنترلر هم برای والد تکی و هم برای والد چندانتخابی (با چند استانِ انتخاب‌شده) کار کند.

<?php

namespace App\Http\Controllers\Select;

use App\Models\City;
use Dornica\BladeComponents\Forms\Select\Controllers\SelectController;
use Illuminate\Database\Eloquent\Builder;

class ProvinceCitySelectController extends SelectController
{
public function query(): ?Builder
{
return City::query()
->whereIn('province_id', (array) $this->parentSelected)
->when($this->search, function ($query) {
$query->where('name', 'like', "%{$this->search}%");
});
}

public function selected(): array
{
return [3];
}
}

جمع‌بندی

  • برای هر کنترلر، فقط یکی از data() یا query() را پیاده‌سازی کنید.
  • اگر صفحه‌بندی استاندارد و مدیریت ساده‌تر می‌خواهید، query() انتخاب بهتری است.
  • برای infinite scroll حتما با از pagination استفاده کنید
  • در صورت نیاز، PER_PAGE، QUERY_RESULT_MODE و labelColumn را در کنترلر فرزند شخصی‌سازی کنید.
  • اگر ستون label داده‌ی شما name نیست (مثلاً title است)، تنظیم labelColumn اجباری است.