using System; using System.Collections.Generic; using System.Linq; namespace Ooui { [Newtonsoft.Json.JsonConverter (typeof (EventTargetJsonConverter))] public abstract class EventTarget { readonly List stateMessages = new List (); readonly Dictionary> eventListeners = new Dictionary> (); public string Id { get; private set; } = GenerateId (); public string TagName { get; private set; } public event Action MessageSent; public IEnumerable StateMessages { get { lock (stateMessages) { return new List (stateMessages); } } } protected EventTarget (string tagName) { TagName = tagName; Send (new Message { MessageType = MessageType.Create, TargetId = Id, Key = TagName, }); } public virtual EventTarget GetElementById (string id) { if (id == Id) return this; return null; } public void AddEventListener (string eventType, EventHandler handler) { if (eventType == null) return; if (handler == null) return; var sendListen = false; List handlers; if (!eventListeners.TryGetValue (eventType, out handlers)) { handlers = new List (); eventListeners[eventType] = handlers; sendListen = true; } handlers.Add (handler); if (sendListen) Send (new Message { MessageType = MessageType.Listen, TargetId = Id, Key = eventType, }); } public void RemoveEventListener (string eventType, EventHandler handler) { if (eventType == null) return; if (handler == null) return; List handlers; if (eventListeners.TryGetValue (eventType, out handlers)) { handlers.Remove (handler); } } protected bool SetProperty (ref T backingStore, T newValue, string attributeName, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = "") { if (!backingStore.Equals (newValue)) { backingStore = newValue; SendSet (attributeName, newValue); return true; } return false; } public const char IdPrefix = '\u2999'; static long idCounter = 0; static string GenerateId () { var id = System.Threading.Interlocked.Increment (ref idCounter); return $"{IdPrefix}{id}"; } public virtual void Send (Message message) { SaveStateMessageIfNeeded (message); MessageSent?.Invoke (message); } protected void SendCall (string methodName, params object[] args) { Send (new Message { MessageType = MessageType.Call, TargetId = Id, Key = methodName, Value = args, }); } protected void SendSet (string attributeName, object value) { Send (new Message { MessageType = MessageType.Set, TargetId = Id, Key = attributeName, Value = value, }); } public virtual void Receive (Message message) { if (message == null) return; if (message.TargetId != Id) return; SaveStateMessageIfNeeded (message); TriggerEventFromMessage (message); } protected void SaveStateMessage (Message message) { lock (stateMessages) stateMessages.Add (message); } void LockedReplaceStateMessage (Message old, Message message) { if (old != null) { stateMessages.Remove (old); } stateMessages.Add (message); } protected virtual void SaveStateMessageIfNeeded (Message message) { switch (message.MessageType) { case MessageType.Create: SaveStateMessage (message); break; case MessageType.Set: { lock (stateMessages) { var old = stateMessages.FirstOrDefault ( x => x.MessageType == MessageType.Set && x.Key == message.Key); LockedReplaceStateMessage (old, message); } } break; case MessageType.Listen: SaveStateMessage (message); break; } } protected virtual void TriggerEventFromMessage (Message message) { List handlers; if (eventListeners.TryGetValue (message.Key, out handlers)) { var args = EventArgs.Empty; foreach (var h in handlers) { h.Invoke (this, args); } } } } class EventTargetJsonConverter : Newtonsoft.Json.JsonConverter { public override bool CanRead => false; public override void WriteJson (Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) { writer.WriteValue (((EventTarget)value).Id); } public override object ReadJson (Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) { throw new NotImplementedException (); } public override bool CanConvert (Type objectType) { return typeof (EventTarget).IsAssignableFrom (objectType); } } }