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.