Skip to main content

NodeField

NodeField renders a node where a field would normally render a value.

That makes it useful for small pieces of backend-driven UI that belong in a resource layout: a status badge, a progress bar, a row action button, a read-only preview or a compact custom component.

It is display-first. NodeField is not mutatable by default, and it does not need a database column unless the node itself submits to another endpoint.

What The Callback Receives

setNode() receives NodeFieldRequest and must return a Node:

setNode(Closure(NodeFieldRequest): Node $callback): static

NodeFieldRequest can expose:

  • getModel() on index and detail pages, and on edit forms with a model;
  • getForm() when the field is rendered inside a FormNode;
  • getNodeManager() when the node is being handled as part of a node response.

Use that context to build a node for the current row, current model or current form state.

Status Badge

This example displays a tag in the normal field position. On an index page, every row receives its own model before the tag is built.

use App\Models\Project;
use Qore\Next\System\Field\Node\NodeFieldRequest;
use Qore\Next\System\Fields\NodeField;
use Qore\Next\System\Node\ColorType;
use Qore\Next\System\Node\Node;
use Qore\Next\System\Nodes\TagNode;

NodeField::make('status_badge', __('common.status'))
->setNode(function (NodeFieldRequest $request): Node {
/** @var Project|null $project */
$project = $request->getModel();

if (! $project) {
return new TagNode(__('common.unknown'), ColorType::DEFAULT);
}

return new TagNode(
title: $project->status_label,
color: $project->is_blocked ? ColorType::RED : ColorType::GREEN,
);
});

Use this when the display value needs more shape than plain text, but should still live with the other resource fields.

Progress Node

ProgressNode is a good fit for percentages that are calculated on the model but should be shown visually.

use App\Models\Project;
use Qore\Next\System\Field\Node\NodeFieldRequest;
use Qore\Next\System\Fields\NodeField;
use Qore\Next\System\Node\ColorType;
use Qore\Next\System\Node\Node;
use Qore\Next\System\Nodes\ProgressNode;

NodeField::make('completion', __('common.completion'))
->setIsShownOnCreate(fn (): bool => false)
->setNode(function (NodeFieldRequest $request): Node {
/** @var Project|null $project */
$project = $request->getModel();

return (new ProgressNode($project?->completion_percentage ?? 0))
->setStrokeColor(ColorType::GREEN);
});

The field stays read-only; the model or service remains responsible for calculating the percentage.

Row Action Button

Buttons inside a NodeField still participate in the node lifecycle. Give interactive nodes a stable ID, especially on index tables, so the frontend state has a predictable key.

use App\Jobs\SendProjectReminder;
use App\Models\Project;
use Qore\Next\System\Field\Node\NodeFieldRequest;
use Qore\Next\System\Fields\NodeField;
use Qore\Next\System\Node\Button\ClickEvent;
use Qore\Next\System\Node\Node;
use Qore\Next\System\Nodes\ButtonNode;

NodeField::make('send_reminder', __('common.reminder'))
->setIsShownOnCreate(fn (): bool => false)
->setIsShownOnEdit(fn (Project $project): bool => false)
->setNode(function (NodeFieldRequest $request): Node {
/** @var Project|null $project */
$project = $request->getModel();

if (! $project) {
return new ButtonNode(__('common.send_reminder'), id: 'send-reminder-disabled')
->setIsDisabled(true);
}

return (new ButtonNode(
title: __('common.send_reminder'),
id: "send-project-reminder-{$project->getKey()}",
))
->setIcon('send')
->onClick(function (ClickEvent $event) use ($project): void {
SendProjectReminder::dispatch($project);

$event->getNodeManager()->reloadGlobals();
});
});

The ID includes the model key so every row has its own state key. Without that, multiple row buttons can become hard to reason about.

Form Preview

Inside forms, NodeField can read the current Form state. That is useful for previews that change as other fields update.

use Qore\Next\System\Field\Node\NodeFieldRequest;
use Qore\Next\System\Fields\NodeField;
use Qore\Next\System\Node\ColorType;
use Qore\Next\System\Node\Node;
use Qore\Next\System\Nodes\TagNode;

NodeField::make('preview', __('common.preview'))
->setNode(function (NodeFieldRequest $request): Node {
$form = $request->getForm();
$title = $form?->getFieldValue('title');

return new TagNode(
title: $title ?: __('common.no_title'),
color: $title ? ColorType::BLUE : ColorType::DEFAULT,
);
});

If the preview should update when another field changes, put the update callback on the changing field and use the form state to trigger the recalculation.

When To Create A Custom Field Instead

Use NodeField for display nodes and small actions inside a field slot.

Create a custom field when you need a reusable input component, custom validation shape, custom filtering or model mutation behaviour.