Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 34 additions & 12 deletions docs/content/features/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ public class ProductList : HydroComponent
}
```

In this case using a Hydro action might be an overkill, since it will cause an unnecessary additional request and rendering of the component. To avoid that, you can dispatch actions straight from your client code by using `Model.Client.Dispatch`:
In this case using a Hydro action might be an overkill, since it will cause an unnecessary additional request and
rendering of the component. To avoid that, you can dispatch actions straight from your client code by using
`Model.Client.Dispatch`:

```razor
<button
Expand All @@ -111,7 +113,25 @@ public class ProductList : HydroComponent
}
```

> **_NOTE:_** When using `[SkipOutput]` any changes to the state won't be persisted.
You can also use SkipOutput method. It's useful when you want to conditionally skip the output:

```csharp
// ProductList.cshtml.cs

public class ProductList : HydroComponent
{
public void Save()
{
if (this.Validate())
{
SkipOutput();
Dispatch(new ProductSaved());
}
}
}
```

> **_NOTE:_** When skipping the output, any changes to the state won't be persisted.

## Synchronous vs asynchronous

Expand All @@ -127,7 +147,8 @@ public void Add()
}
```

`Add` triggers the event synchronously, so the button that triggers this action will be disabled until both the action and the event executions are done.
`Add` triggers the event synchronously, so the button that triggers this action will be disabled until both the action
and the event executions are done.

Now, let's compare it with the asynchronous way:

Expand All @@ -139,8 +160,8 @@ public void Add()
}
```

`Add` triggers the event asynchronously, so the button that triggers this action will be disabled until the action is done. The event execution won't be connected with the action's pipeline and will be run on its own.

`Add` triggers the event asynchronously, so the button that triggers this action will be disabled until the action is
done. The event execution won't be connected with the action's pipeline and will be run on its own.

## Event scope

Expand Down Expand Up @@ -204,11 +225,12 @@ There might be a situation where you want to filter the events you receive in yo
your component subscribes to an event, but handles it only when it contains a certain flag. That flag can be any string
and is called a `subject`.

You can imagine a page with multiple lists of todos. Each list is a Hydro component that listens to events like `TodoAdded`,
`TodoRemoved` or `TodoEdited`. When a todo is removed on one list, you don't want all the other lists to receive and react to that event, but only
the list that contained that todo item. This is solved in Hydro by using `subject` parameter, which in this case will be the list's id.
When `TodoAdded`, `TodoRemoved` or `TodoEdited` are dispatched, `subject` is set to their list's id. The list component subscribes to those
events with `subject` set to the their list's id.
You can imagine a page with multiple lists of todos. Each list is a Hydro component that listens to events like
`TodoAdded`, `TodoRemoved` or `TodoEdited`. When a todo is removed on one list, you don't want all the other lists to
receive and react to that event, but only the list that contained that todo item. This is solved in Hydro by using
`subject` parameter, which in this case will be the list's id. When `TodoAdded`, `TodoRemoved` or `TodoEdited` are
dispatched, `subject` is set to their list's id. The list component subscribes to those events with `subject` set to the
their list's id.

Example:

Expand Down Expand Up @@ -250,9 +272,9 @@ public class TodoList : HydroComponent
}
```

In `Subscribe` method call `subject` parameter is a `Func<string>` instead of `string`,
In `Subscribe` method call `subject` parameter is a `Func<string>` instead of `string`,
because its value could be taken from component's properties that are not set yet, since it's
a constructor.

> NOTE: If you subscribe for an event without specifying the subject, it will catch all the events
of that type, no matter if they were dispatched with subject or not.
> of that type, no matter if they were dispatched with subject or not.
20 changes: 15 additions & 5 deletions src/HydroComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ public void Redirect(string url) =>
/// <param name="payload">Payload for the destination components</param>
public void Location(string url, object payload = null) =>
HttpContext.Response.HydroLocation(url, payload);

/// <summary>
/// Cache value
/// </summary>
Expand Down Expand Up @@ -511,6 +511,17 @@ protected Cache<T> Cache<T>(Func<T> func, CacheLifetime lifetime = CacheLifetime
return cacheValue;
}

/// <summary>
/// Skips generating the HTML output after executing the decorated Hydro action.
/// Any changes to the state won't be persisted.
/// Useful when action is performing only side effects that do not cause changes to the current component's HTML content.
/// </summary>
protected void SkipOutput()
{
_skipOutput = true;
HttpContext.Response.Headers.TryAdd(HydroConsts.ResponseHeaders.SkipOutput, "True");
}

private async Task<string> RenderOnlineComponent(IPersistentState persistentState) =>
DetermineRootComponent()
? await RenderOnlineRootComponent(persistentState)
Expand All @@ -533,7 +544,7 @@ private async Task<string> RenderOnlineRootComponent(IPersistentState persistent
_componentId = componentId;

PopulateBaseModel(persistentState);

if (!await AuthorizeAsync())
{
return string.Empty;
Expand Down Expand Up @@ -658,7 +669,7 @@ private async Task<string> GenerateComponentHtml(string componentId, IPersistent
{
AddClientScript($"Hydro.dispatchEvents('{GetSerializedEventDispatchers()}', this);");
}

foreach (var script in _clientScripts)
{
rootElement.AppendChild(GetClientScript(componentHtmlDocument, script));
Expand Down Expand Up @@ -899,8 +910,7 @@ private async Task TriggerMethod()

if (methodAttributes.Any(a => a.GetType() == typeof(SkipOutputAttribute)))
{
_skipOutput = true;
HttpContext.Response.Headers.TryAdd(HydroConsts.ResponseHeaders.SkipOutput, "True");
SkipOutput();
}

var operationId = HttpContext.Request.Headers.TryGetValue(HydroConsts.RequestHeaders.OperationId, out var incomingOperationId)
Expand Down