Sky Event Model =============== ```dart import 'dart:collection'; import 'dart:async'; class ExceptionAndStackTrace { const ExceptionAndStackTrace(this.exception, this.stackTrace); final T exception; final StackTrace stackTrace; } class ExceptionListException extends IterableMixin> implements Exception { List> _exceptions; void add(T exception, [StackTrace stackTrace = null]) { if (_exceptions == null) _exceptions = new List>(); _exceptions.add(new ExceptionAndStackTrace(exception, stackTrace)); } int get length => _exceptions == null ? 0 : _exceptions.length; Iterator> get iterator => _exceptions.iterator; } typedef bool Filter(T t); typedef void Handler(T t); class DispatcherController { DispatcherController() : dispatcher = new Dispatcher(); final Dispatcher dispatcher; void add(T data) => dispatcher._add(data); } class Dispatcher { List> _listeners; void listen(Handler handler) { // you should not throw out of this handler if (_listeners == null) _listeners = new List>(); _listeners.add(new Pair(handler, Zone.current.bindUnaryCallback(handler))); } bool unlisten(Handler handler) { if (_listeners == null) return false; var target = _listeners.lastWhere((v) => v.a == handler, orElse: () => null); if (target == null) return false; _listeners.removeAt(_listeners.lastIndexOf(target)); return true; } void _add(T data) { if (_listeners == null) return; ExceptionListException exceptions = new ExceptionListException(); // we make a copy of the list here so that the listeners can // mutate our list without worry _listeners.toList().forEach((Pair item) { try { item.b(data); } catch (exception, stackTrace) { exceptions.add(exception, stackTrace); } }); if (exceptions.length > 0) throw exceptions; } Dispatcher where(Filter filter) => new WhereDispatcher(this, filter); Dispatcher until(Filter filter) { var subdispatcher = new Dispatcher(); Handler handler; handler = (T data) { if (filter(data)) unlisten(handler); else subdispatcher._add(data); }; listen(handler); return subdispatcher; } Future firstWhere(Filter filter) { Completer completer = new Completer(); Handler handler; handler = (T data) { if (filter(data)) { completer.complete(data); unlisten(handler); } }; listen(handler); return completer.future; } } class WhereDispatcher extends Dispatcher { WhereDispatcher(this.parent, this.filter) : super(); Dispatcher parent; Filter filter; void listen(Handler handler) { if (_listeners == null || _listeners.length == 0) parent.listen(_handler); super.listen(handler); } bool unlisten(Handler handler) { var result = super.unlisten(handler); if (result && _listeners.length == 0) parent.unlisten(_handler); return result; } void _handler(T data) { if (filter(data)) _add(data); } } abstract class Event { Event() { init(); } void init() { } bool get bubbles; EventTarget _target; EventTarget get target => _target; EventTarget _currentTarget; EventTarget get currentTarget => _currentTarget; bool handled; // precise semantics depend on the event type, but in general, set this when you set result ReturnType result; bool resultIsCompatible(dynamic candidate) => candidate is ReturnType; // TODO(ianh): abstract API for doing things at shadow tree boundaries // TODO(ianh): do events get blocked at scope boundaries, e.g. focus events when both sides are in the scope? // TODO(ianh): do events get retargetted, e.g. focus when leaving a custom element? // e.g. sent from inside a shadow tree, when exiting the shadow tree, focus event should: // - disappear if we're moving from one to another element // - be targetted if it's going to another node in a different scope } class EventTarget { EventTarget() : _eventsController = new DispatcherController(); Dispatcher get events => _eventsController.dispatcher; EventTarget get parentNode; List getEventDispatchChain() { if (this.parentNode == null) { return [this]; } else { var result = this.parentNode.getEventDispatchChain(); result.insert(0, this); return result; } } final DispatcherController _eventsController; dynamic dispatchEvent(Event event, { dynamic defaultResult: null }) { // O(N*M) where N is the length of the chain and M is the average number of listeners per link in the chain // note: this will throw an ExceptionListException if any of the listeners threw assert(event != null); // event must be non-null event.handled = false; assert(event.resultIsCompatible(defaultResult)); event.result = defaultResult; event._target = this; var chain; if (event.bubbles) chain = this.getEventDispatchChain(); else chain = [this]; var exceptions = new ExceptionListException(); for (var link in chain) { try { link._dispatchEventLocally(event); } on ExceptionListException catch (e) { exceptions.add(e); } } if (exceptions.length > 0) throw exceptions; return event.result; } void _dispatchEventLocally(Event event) { event._currentTarget = this; _eventsController.add(event); } } ```