// 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()); } public interface IEventListener : IAsyncDisposable { /// /// Listing to a javascript event /// /// The type of the event args for instance MouseEventArgs for mousemove /// Name of the DOM event without "on" /// The value of the id field of the DOM element /// 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 /// The delay between the last time the event occurred and the callback is fired. Set to zero, if no delay is requested /// The method that is invoked, if the DOM element is fired. Object will be of type T /// A unique identifier for the event subscription. Should be used to cancel the subscription Task Subscribe<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string eventName, string elementId, string projectionName, int throotleInterval, Func callback); /// /// Listing to a javascript event on the document itself /// /// The type of the event args for instance MouseEventArgs for mousemove /// Name of the DOM event without "on" /// The delay between the last time the event occurred and the callback is fired. Set to zero, if no delay is requested /// The method that is invoked, if the DOM element is fired. Object will be of type T /// A unique identifier for the event subscription. Should be used to cancel the subscription Task SubscribeGlobal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string eventName, int throotleInterval, Func callback); /// /// Cancel (unsubscribe) the listening to a DOM event, previous connected by Subscribe /// /// The unique event identifier /// true for if the event listener was detached, false if not Task Unsubscribe(Guid key); } public class EventListener : IEventListener, IAsyncDisposable, IDisposable { private readonly IJSRuntime _jsRuntime; private readonly DotNetObjectReference _dotNetRef; private bool _disposed = false; private Dictionary 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 Subscribe<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string eventName, string elementId, string projectionName, int throotleInterval, Func callback) { var (type, properties) = GetTypeInformation(); var key = RegisterCallBack(type, callback); await _jsRuntime.InvokeVoidAsyncWithErrorHandling("mudThrottledEventManager.subscribe", eventName, elementId, projectionName, throotleInterval, key, properties, _dotNetRef); return key; } public async Task SubscribeGlobal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string eventName, int throotleInterval, Func callback) { var (type, properties) = GetTypeInformation(); var key = RegisterCallBack(type, callback); await _jsRuntime.InvokeVoidAsyncWithErrorHandling("mudThrottledEventManager.subscribeGlobal", eventName, throotleInterval, key, properties, _dotNetRef); return key; } public async Task 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 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 } }