NE.Standard

Interactions and Effects

Two mechanisms make a component react to something besides its own bound values: an interaction, entirely client-side, and a client effect, which a command (or an interaction) hands the client to run.

Interactions

An interaction watches one component's event and writes a property on another — or on itself — with no round trip. Every visual component exposes them through VisualComponentBase<TComponent>.

The id always names the source, never the target. The component the call is made on is the one whose property changes; the id argument is the component whose event is watched:

new StackPanelComponent()
    .SetVisibility(UIVisibility.Hidden)
    .InteractOnHoverStart(CardId, IVisualComponent.VisibilityProperty, UIVisibility.Visible)
    .InteractOnHoverEnd(CardId, IVisualComponent.VisibilityProperty, UIVisibility.Hidden)

Written the other way round it compiles and does nothing — the source would be a component that is hidden and can never raise the event. The single-argument overloads default the source to the component itself, which is why InteractBeforeClick(LoadingProperty, true) reads naturally.

Interact — comparing a value

public TComponent Interact(UIProperty property, UIComparisonOperator @operator = UIComparisonOperator.Equal,
    object? value = null, object? whenTrue = null, object? whenFalse = null)

public TComponent Interact(UIProperty source, UIProperty target, UIComparisonOperator @operator = UIComparisonOperator.Equal,
    object? value = null, object? whenTrue = null, object? whenFalse = null)

public TComponent Interact(string sourceComponentId, UIProperty source, UIProperty target,
    UIComparisonOperator @operator = UIComparisonOperator.Equal, object? value = null, object? whenTrue = null, object? whenFalse = null)

Watches source continuously and writes target to whenTrue or whenFalse depending on whether source @operator value holds. UIComparisonOperator has eleven members: Required, Equal, NotEqual, Greater, GreaterOrEqual, Less, LessOrEqual, Like, In, Regex, LikeIgnoreCase.

InteractOn* — reacting to an event

public TComponent InteractBeforeClick(UIProperty target, object? value)
public TComponent InteractAfterClick(UIProperty target, object? value)
public TComponent InteractOnHoverStart(UIProperty target, object? value)
public TComponent InteractOnHoverEnd(UIProperty target, object? value)
public TComponent InteractOn(string sourceEvent, UIProperty target, object? whenTriggered)

Any other event name goes through the last, general-purpose overload — InteractOn("focus", ...).

Each has a sourceComponentId-taking overload for watching another component. InteractBeforeClick fires after validation and just before the command dispatches; InteractAfterClick fires after the command's result — changes and effects — has been applied. Together they are the "busy while this round trip runs" idiom, and ButtonComponent.OnClickShowingLoading(command) is exactly this pair wired to Loading:

public T OnClickShowingLoading(string command)
    => OnClick(command)
        .InteractBeforeClick(IVisualComponent.LoadingProperty, true)
        .InteractAfterClick(IVisualComponent.LoadingProperty, false);

This is one of two independent ways to say a control is busy — entirely client-side, nothing bound, nothing for a controller to clear. The other is binding Loading to a controller flag the command itself sets; that one starts and stops on the server's own timing, for when being busy outlives the single command. Using InteractBeforeClick without a matching InteractAfterClick (or a binding that clears it) leaves a spinner that never stops.

An interaction may run a client effect instead of writing a property:

public TComponent InteractOn(string sourceEvent, ClientEffect effect)
public TComponent InteractOn(string sourceComponentId, string sourceEvent, ClientEffect effect)
public TComponent Interact(UIProperty source, ClientEffect effect, UIComparisonOperator @operator = UIComparisonOperator.Required, object? value = null)
public TComponent Interact(string sourceComponentId, UIProperty source, ClientEffect effect, UIComparisonOperator @operator = UIComparisonOperator.Required, object? value = null)

Only an effect whose CanRunInInteraction is true may be used this way — true only for an effect that completes entirely on the client, with no round trip behind it (see the table below).

Client effects

A ClientEffect is something a command (or a qualifying interaction) hands the client to run after the result — changes first, then effects, so an effect that focuses or scrolls sees what the command just changed. ClientEffectKinds lists the built-in kinds; a package can add its own by returning a new Kind string.

Effect Constructor Does In an interaction
NavigateEffect NavigateEffect(UINavigationRequest request) Navigates the client to another route. No
FocusEffect FocusEffect(string targetComponentId, params object?[]? dynamicParameters) Focuses a component. Yes
ScrollToEffect ScrollToEffect(string targetComponentId, ScrollToBehavior behavior, ScrollToBlock block, params object?[]? dynamicParameters) Scrolls a component into view. Yes
ScrollEffect ScrollEffect(string targetComponentId, ScrollPosition position, params object?[]? dynamicParameters) Scrolls a container (start/end/offset/page), as opposed to bringing a component into view. Yes
ShowEffect ShowEffect(string targetComponentId, params object?[]? dynamicParameters) Shows a component. No
HideEffect HideEffect(string targetComponentId, params object?[]? dynamicParameters) Hides a component; it keeps the room it holds. No
CollapseEffect CollapseEffect(string targetComponentId, params object?[]? dynamicParameters) Takes a component out of the layout; the room it held closes up. No
OpenDialogEffect OpenDialogEffect(string dialogKey) Opens a dialog declared by the view. No
CloseDialogEffect CloseDialogEffect(string dialogKey) Closes a dialog. No
ShowNotificationEffect ShowNotificationEffect(string message, UIColorStyle severity = UIColorStyle.Info) Shows a toast. No
DownloadFileEffect DownloadFileEffect(string requestPath, string fileName) Fetches a file staged through Context.Downloads; the path is single-use. No
SetThemeEffect SetThemeEffect(UIThemeMode? mode = null) Puts the client into a theme and persists it as the session's — the one effect with a server side. Yes
RenameTabEffect RenameTabEffect(string targetComponentId, string key, params object?[]? dynamicParameters) Opens the inline rename field on one tab, the way a double-click does. Yes
RenameNodeEffect RenameNodeEffect(string targetComponentId, string key, params object?[]? dynamicParameters) Opens the inline rename field on one tree node, the way F2 does. Yes
CopyToClipboardEffect CopyToClipboardEffect.Literal(string text) / .ValueOf(string targetComponentId, ...) Copies text, or a component's current value, to the clipboard. Yes

Returning effects from a command

UICommandResult.Ok(effects) and UICommandResult.Fail(error, effects) both take an optional IReadOnlyList<ClientEffect>?:

[UICommand]
public UICommandResult AskBeforeDelete()
{
    if (!ConfirmGroup.HasBuilds)
        return UICommandResult.Ok([new ShowNotificationEffect("Nothing left to delete.", UIColorStyle.Info)]);

    ConfirmGroup.Ask();

    return UICommandResult.Ok([new OpenDialogEffect(ConfirmKey)]);
}

[UICommand]
public UICommandResult DeleteBuild()
{
    ConfirmGroup.Delete();

    return UICommandResult.Ok([new CloseDialogEffect(ConfirmKey), new ShowNotificationEffect("The build is gone.", UIColorStyle.Success)]);
}

ConfirmKey here is the Key of a UIDialog the view declared in CreateDialogs() (see views, dialogs).

How a failed command is reported

The client never inspects success/error directly — both delivery channels only ever apply changes and effects. A failed UICommandResult with no effects of its own gets a ShowNotificationEffect attached automatically; returning your own effects from a failure is how a command takes over the reporting. UIErrorHandlingOptions (configured with application.ConfigureErrorHandling(configure)) controls it:

Option Meaning
NotFoundRoute Route used when a requested route was not registered.
ErrorRoute Route used when an unhandled exception occurs resolving a view — not a failed command, which is always reported to the user instead of navigating anywhere.
NotifyOnCommandFailure Whether a failed command with no effects of its own gets the automatic notification (default true).
IncludeExceptionDetail Whether the real exception message reaches the browser — leave off in production; an exception routinely carries a connection string or a file path.
CommandRefusedMessage Shown when a command is refused by an authorization check.
CommandFailedMessage Shown when a command fails for any other reason.

The message a viewer sees has three levels: what UICommandResult.Fail(error) wrote is shown as it stands; an UnauthorizedAccessException gets CommandRefusedMessage; anything else gets CommandFailedMessage unless IncludeExceptionDetail is on. All three go through the translator.

NavigateEffect takes a UINavigationRequest:

public sealed class UINavigationRequest
{
    public required string Route { get; init; }
    public IReadOnlyDictionary<string, object?>? Parameters { get; init; }
}
return UICommandResult.Ok([new NavigateEffect(new UINavigationRequest { Route = "/dashboard" })]);

Parameters become the route's navigation parameters — the same shape a query string produces on a direct request, readable from OnInitializeAsync (see controllers).

Where to go next

  • Controllers for how a command is declared and what its return type may be.
  • Views for the dialogs OpenDialogEffect/CloseDialogEffect target.
  • Binding for the properties an interaction writes.