You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Connected.Components/Services/EventManager/EventManager.cs

207 lines
8.0 KiB

2 years ago
// Copyright (c) MudBlazor 2021
// MudBlazor licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
namespace Connected
{
public interface IEventListenerFactory
{
IEventListener Create();
}
public class EventListenerFactory : IEventListenerFactory
{
private readonly IServiceProvider _provider;
public EventListenerFactory(IServiceProvider provider)
{
_provider = provider;
}
public IEventListener Create() =>
new EventListener(_provider.GetRequiredService<IJSRuntime>());
}
public interface IEventListener : IAsyncDisposable
{
/// <summary>
/// Listing to a javascript event
/// </summary>
/// <typeparam name="T">The type of the event args for instance MouseEventArgs for mousemove</typeparam>
/// <param name="eventName">Name of the DOM event without "on"</param>
/// <param name="elementId">The value of the id field of the DOM element</param>
/// <param name="projectionName">The name of a JS function (relative to window) that used to project the event before it is send back to .NET. Can be null, if no projection is needed </param>
/// <param name="throotleInterval">The delay between the last time the event occurred and the callback is fired. Set to zero, if no delay is requested</param>
/// <param name="callback">The method that is invoked, if the DOM element is fired. Object will be of type T</param>
/// <returns>A unique identifier for the event subscription. Should be used to cancel the subscription</returns>
Task<Guid> Subscribe<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string eventName, string elementId, string projectionName, int throotleInterval, Func<object, Task> callback);
/// <summary>
/// Listing to a javascript event on the document itself
/// </summary>
/// <typeparam name="T">The type of the event args for instance MouseEventArgs for mousemove</typeparam>
/// <param name="eventName">Name of the DOM event without "on"</param>
/// <param name="throotleInterval">The delay between the last time the event occurred and the callback is fired. Set to zero, if no delay is requested</param>
/// <param name="callback">The method that is invoked, if the DOM element is fired. Object will be of type T</param>
/// <returns>A unique identifier for the event subscription. Should be used to cancel the subscription</returns>
Task<Guid> SubscribeGlobal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string eventName, int throotleInterval, Func<object, Task> callback);
/// <summary>
/// Cancel (unsubscribe) the listening to a DOM event, previous connected by Subscribe
/// </summary>
/// <param name="key">The unique event identifier</param>
/// <returns>true for if the event listener was detached, false if not</returns>
Task<bool> Unsubscribe(Guid key);
}
public class EventListener : IEventListener, IAsyncDisposable, IDisposable
{
private readonly IJSRuntime _jsRuntime;
private readonly DotNetObjectReference<EventListener> _dotNetRef;
private bool _disposed = false;
private Dictionary<Guid, (Type eventType, Func<object, Task> callback)> _callbackResolver = new();
[DynamicDependency(nameof(OnEventOccur))]
public EventListener(IJSRuntime runtime)
{
_jsRuntime = runtime;
_dotNetRef = DotNetObjectReference.Create(this);
}
[JSInvokable]
public async Task OnEventOccur(Guid key, string @eventData)
{
if (_callbackResolver.ContainsKey(key) == false) { return; }
var element = _callbackResolver[key];
var @event = JsonSerializer.Deserialize(eventData, element.eventType, new WebEventJsonContext(new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
}));
if (element.callback != null)
{
await element.callback.Invoke(@event);
}
}
public async Task<Guid> Subscribe<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string eventName, string elementId, string projectionName, int throotleInterval, Func<object, Task> callback)
{
var (type, properties) = GetTypeInformation<T>();
var key = RegisterCallBack(type, callback);
await _jsRuntime.InvokeVoidAsyncWithErrorHandling("mudThrottledEventManager.subscribe", eventName, elementId, projectionName, throotleInterval, key, properties, _dotNetRef);
return key;
}
public async Task<Guid> SubscribeGlobal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string eventName, int throotleInterval, Func<object, Task> callback)
{
var (type, properties) = GetTypeInformation<T>();
var key = RegisterCallBack(type, callback);
await _jsRuntime.InvokeVoidAsyncWithErrorHandling("mudThrottledEventManager.subscribeGlobal", eventName, throotleInterval, key, properties, _dotNetRef);
return key;
}
public async Task<bool> Unsubscribe(Guid key)
{
if (_callbackResolver.ContainsKey(key) == false) { return false; }
try
{
await _jsRuntime.InvokeVoidAsyncWithErrorHandling("mudThrottledEventManager.unsubscribe", key);
return true;
}
catch (Exception)
{
return false;
}
}
private (Type Type, string[] Properties) GetTypeInformation<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>()
{
var type = typeof(T);
var properties = type.GetProperties().Select(x => char.ToLower(x.Name[0]) + x.Name.Substring(1)).ToArray();
return (type, properties);
}
private Guid RegisterCallBack(Type type, Func<object, Task> callback)
{
var key = Guid.NewGuid();
_callbackResolver.Add(key, (type, callback));
return key;
}
#region disposing
public async ValueTask DisposeAsync()
{
if (_disposed == true) { return; }
foreach (var item in _callbackResolver)
{
try
{
await _jsRuntime.InvokeVoidAsyncWithErrorHandling("mudThrottledEventManager.unsubscribe", item.Key);
}
catch (Exception)
{
//ignore
}
}
_dotNetRef.Dispose();
_disposed = true;
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_dotNetRef.Dispose();
foreach (var item in _callbackResolver)
{
try
{
_jsRuntime.InvokeVoidAsync("mudThrottledEventManager.unsubscribe", item.Key);
}
catch (Exception)
{
//ignore
}
}
}
_disposed = true;
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
#endregion
}
}