// 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.Diagnostics.CodeAnalysis; using Microsoft.JSInterop; namespace Connected.Services { public interface IJsEvent : IDisposable { Task Connect(string elementId, JsEventOptions options); Task Disconnect(); event Action CaretPositionChanged; event Action Paste; event Action Select; } /// /// Subscribe JS events of any element by html id /// public class JsEvent : IJsEvent { private bool _isDisposed = false; private readonly DotNetObjectReference _dotNetRef; private readonly IJSRuntime _jsRuntime; private string _elementId; private bool _isObserving; internal HashSet _subscribedEvents = new HashSet(); [DynamicDependency(nameof(OnCaretPositionChanged))] [DynamicDependency(nameof(OnPaste))] [DynamicDependency(nameof(OnSelect))] [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(JsEventOptions))] public JsEvent(IJSRuntime jsRuntime) { _dotNetRef = DotNetObjectReference.Create(this); _jsRuntime = jsRuntime; } /// /// Connect to the ancestor element of the element(s) that should be observed /// /// Ancestor html element id /// Define here the descendant(s) by setting TargetClass and the keystrokes to be monitored public async Task Connect(string elementId, JsEventOptions options) { if (_isObserving || _isDisposed) return; _elementId = elementId; _isObserving = await _jsRuntime.InvokeVoidAsyncWithErrorHandling("mudJsEvent.connect", _dotNetRef, elementId, options); ; } /// /// Disconnect from the previously connected ancestor and its descendants /// public async Task Disconnect() { if (_elementId == null) return; await UnsubscribeAll(); try { await _jsRuntime.InvokeVoidAsync($"mudJsEvent.disconnect", _elementId); } catch (Exception) { /*ignore*/ } _isObserving = false; } internal void Subscribe(string eventName) { if (_elementId == null) throw new InvalidOperationException("Call Connect(...) before attaching events!"); if (_subscribedEvents.Contains(eventName) || _isDisposed) return; try { _jsRuntime.InvokeVoidAsync("mudJsEvent.subscribe", _elementId, eventName).AndForget(); _subscribedEvents.Add(eventName); } catch (JSDisconnectedException) { } catch (TaskCanceledException) { } } internal async Task Unsubscribe(string eventName) { if (_elementId == null) return; try { await _jsRuntime.InvokeVoidAsync($"mudJsEvent.unsubscribe", _elementId, eventName); } catch (Exception) { /*ignore*/ } _subscribedEvents.Remove(eventName); } internal async Task UnsubscribeAll() { if (_elementId == null) return; try { foreach (var eventName in _subscribedEvents) await _jsRuntime.InvokeVoidAsync($"mudJsEvent.unsubscribe", _elementId, eventName); } catch (Exception) { /*ignore*/ } _subscribedEvents.Clear(); } List> _caretPositionChangedHandlers = new List>(); /// /// Subscribe this event to get notified about caret changes in an input on click and on keyup /// public event Action CaretPositionChanged { add { if (_caretPositionChangedHandlers.Count == 0) { Subscribe("click"); Subscribe("keyup"); } _caretPositionChangedHandlers.Add(value); } remove { if (_caretPositionChangedHandlers.Count == 0) return; if (_caretPositionChangedHandlers.Count == 1) { Unsubscribe("click").AndForget(); Unsubscribe("keyup").AndForget(); } _caretPositionChangedHandlers.Remove(value); } } /// /// To be invoked only by JS /// [JSInvokable] public void OnCaretPositionChanged(int caretPosition) { foreach (var handler in _caretPositionChangedHandlers) { handler.Invoke(caretPosition); } } List> _pasteHandlers = new List>(); /// /// Subscribe this event to get notified about paste actions /// public event Action Paste { add { if (_pasteHandlers.Count == 0) Subscribe("paste"); _pasteHandlers.Add(value); } remove { if (_pasteHandlers.Count == 0) return; if (_pasteHandlers.Count == 1) Unsubscribe("paste").AndForget(); _pasteHandlers.Remove(value); } } /// /// To be invoked only by JS /// [JSInvokable] public void OnPaste(string text) { foreach (var handler in _pasteHandlers) { handler.Invoke(text); } } List> _selectHandlers = new List>(); /// /// Subscribe this event to get notified about paste actions /// public event Action Select { add { if (_selectHandlers.Count == 0) Subscribe("select"); _selectHandlers.Add(value); } remove { if (_selectHandlers.Count == 0) return; if (_selectHandlers.Count == 1) Unsubscribe("select").AndForget(); _selectHandlers.Remove(value); } } /// /// To be invoked only by JS /// [JSInvokable] public void OnSelect(int start, int end) { foreach (var handler in _selectHandlers) { handler.Invoke(start, end); } } protected virtual void Dispose(bool disposing) { if (!disposing || _isDisposed) return; _isDisposed = true; Disconnect().AndForget(); _dotNetRef.Dispose(); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } }