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.
207 lines
8.0 KiB
207 lines
8.0 KiB
// 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
|
|
}
|
|
}
|