Skip to main content

Pages And Tables

QoreResource includes default index, create, detail and edit pages. Each page is a node response, so you can keep the default behaviour or replace a layout with your own nodes.

Page Switches

Override these when a resource should not expose one of the standard pages:

public function hasCreatePage(): bool
{
return false;
}

public function hasEditPage(): bool
{
return false;
}

Available switches:

  • hasIndexPage(): bool
  • hasCreatePage(): bool
  • hasDetailPage(): bool
  • hasEditPage(): bool
  • hasTableSettings(): bool
  • isExportEnabled(): bool

The controllers and generated URLs respect these switches, but policies still decide access for the current user.

Page Layouts

The default layouts are intentionally small:

  • index renders getResourceTable()->toNode();
  • create renders a card with getCreateForm();
  • detail renders a detail card and relation table tabs;
  • edit renders a card with getEditForm($model).

Override the layout method when the default structure is not enough:

use Illuminate\Database\Eloquent\Model;
use Qore\Next\System\Node\Node;
use Qore\Next\System\Node\SizeType;
use Qore\Next\System\Nodes\CardNode;
use Qore\Next\System\Nodes\FlexNode;
use Qore\Next\System\Nodes\TextNode;

public function detailLayout(Model $model): Node
{
return (new FlexNode)
->setIsVertical()
->setGap(SizeType::MIDDLE)
->addChild(
(new CardNode(__('common.summary')))
->addChild(new TextNode($model->description))
)
->addChild($this->getDefaultDetailLayoutCard($model));
}

Use getDefaultDetailLayoutCard($model) when you want to add content around the standard detail table instead of replacing it completely.

Fields Per Page

Field visibility controls which fields appear on each page. The resource collects those fields through:

  • getFieldsForIndex(): Field[]
  • getFieldsForCreate(): Field[]
  • getFieldsForDetail(Model $model): Field[]
  • getFieldsForEdit(Model $model): Field[]

Use field-level visibility when the decision belongs to the field:

TextField::make('internal_note')
->setIsShownOnIndex(fn () => false)
->setIsShownOnDetail(fn (Product $product) => $product->has_notes);

Override the resource layout when the decision belongs to the page composition.

Index Query

indexQuery() is the base query for the resource index, exports, relation options and other resource-owned lists.

use Illuminate\Database\Eloquent\Builder;

public function indexQuery(): Builder
{
return parent::indexQuery()
->where('tenant_id', tenant()->getKey())
->with('owner');
}

Keep authorization in policies. Use indexQuery() for application scoping, default eager loading and default ordering that belongs to this resource.

Index Tabs

indexTabs() adds tabs above the index table. Tabs are represented by TableTab classes and replace the table query when selected.

namespace App\Resources\Tabs;

use App\Models\Product;
use Illuminate\Database\Eloquent\Builder;
use Qore\Next\System\Node\Table\TableTab;

class ActiveProductsTab extends TableTab
{
public function label(): string
{
return __('common.active');
}

public function query(): Builder
{
return Product::query()->where('is_active', true);
}
}

Register tabs on the resource:

use App\Resources\Tabs\ActiveProductsTab;

public function indexTabs(): array
{
return [
new ActiveProductsTab,
];
}

Use tabs for common, mutually exclusive table scopes. Use normal filters for ad hoc searching and filtering. If the tab should share resource scoping or eager loading, call the same query-building logic from the tab's query() method.

Table Behaviour

The default index table comes from getResourceTable():

public function getResourceTable(): ResourceTable
{
return new ResourceTable(
id: "{$this->getName()}_index",
resource: $this,
exportEndpoint: $this->isExportEnabled() ? "resources/{$this->getName()}/export" : null,
);
}

Override table-related methods for common behaviour:

use Illuminate\Database\Eloquent\Model;
use Qore\Next\System\Node\Table\TableSortOrder;

public function getInitialSortField(): ?string
{
return 'created_at';
}

public function getInitialSortOrder(): TableSortOrder
{
return TableSortOrder::DESC;
}

public function getTableRowNavigateUrl(Model $model): ?string
{
return $this->getDetailUrl($model->getKey());
}

public function getTableRowModalUrl(Model $model): ?string
{
return null;
}

Use getTableRowNavigateUrl() for normal drill-down behaviour. Use getTableRowModalUrl() when a row should open a modal node endpoint instead of navigating.

Export

Exports use the index fields by default:

public function getFieldNamesForExport(): array
{
return array_keys($this->getFieldsForIndex());
}

Override getExportQuery() when export needs a different query than the visible index table. Override getFieldNamesForExport() when some index fields should not be exported or extra export-only fields should be included.