DOM Event Streams

1,066 views
Skip to first unread message

Pete Blois

unread,
Jan 11, 2013, 2:15:44 PM1/11/13
to mi...@dartlang.org
One of our goals for exposing DOM events in Dart is to have them follow the 'best practices' for events- particularly events which are exposed in places such as your application data model.

The current plan is that DOM events would be exposed as:
class Element {
  static const HtmlStreamProvider<MouseEvent> clickEvent = 
    const HtmlStreamProvider<MouseEvent>('click');

  Stream<MouseEvent> get onClick => clickEvent.forTarget(this);
}

The HtmlStreamProvider class is:
class HtmlStreamProvider<T extends Event> {
  const HtmlStreamProvider(String eventType);
  Stream<T> forTarget(EventTarget e, {bool useCapture: false})
}

There's basically two parts- 
  1. The static declaration of the event as an HtmlStreamProvider which provides a Stream for a DOM event on a specific target. This is only really used for bubbling DOM events.
  2. A Stream<> getter on the object instance to allow easy access to the event stream.
NOTE: we will not remove the old APIs until the new APIs have been in & baked for a while.

Examples of old vs new syntax:

Listening to an event
Old:
element.on.click.add((e) {
});
New:
element.onClick.listen((e) {
});

Capturing an event
Old:
element.on.click.add((e) {  
}, true);
New:
Element.clickEvent.forTarget(element, useCapture:true).listen((e) {
});

Listening to a bubbled event
Old (not really supported):
document.body.$dom_addEventListener('canPlay', (e) {}, false);
New:
MediaElement.canPlayEvent.forTarget(document.body).listen((e) {
});


QUESTIONS:
  1. Are you planning on exposing all model events as Streams? By this I mean something like backbone.js-style events. Our goal for DOM events is to be consistent with the dominant data event model.
  2. Any opinions on naming? Currently the events are exposed as onEventName, but it's often common to use this naming style for virtual methods. Using just eventName has a number of conflicts with members. We aren't entirely thrilled with onEventName.
If you're interested in taking a peek, the CL with the changes is at:

W. Brian Gourlie

unread,
Jan 11, 2013, 3:10:26 PM1/11/13
to mi...@dartlang.org
I really like this change.  I also have no problem with the onEventName naming scheme.

Sean Eagan

unread,
Jan 11, 2013, 4:15:24 PM1/11/13
to General Dart Discussion

Awesome stuff!

On Fri, Jan 11, 2013 at 1:15 PM, Pete Blois <bl...@google.com> wrote:
  1. Any opinions on naming? Currently the events are exposed as onEventName, but it's often common to use this naming style for virtual methods. Using just eventName has a number of conflicts with members. We aren't entirely thrilled with onEventName.
I'm ok with onEventName, but another suggestions would be to make the HtmlStreamProvider naming convention slightly more verbose:

Element.clickEventProvider.provideEvent(document.body);

... and then steal the "Event" suffix naming convention for the Streams themselves:

el.clickEvent.listen(f);

Cheers!
Sean

John Evans

unread,
Jan 11, 2013, 5:50:59 PM1/11/13
to mi...@dartlang.org
fromEvent?

Bernhard Pichler

unread,
Jan 12, 2013, 3:29:10 PM1/12/13
to mi...@dartlang.org
How do i remove an event listener? Does the "listen" method return some kind of handle?

The way it works today is really bad. Most people would assume that ...

element.on.click.add(
onClickHandler);
element.on.click.remove(onClickHandler);

onClickHandler(e) { }

... just works, but it does not (which is really a shame ... www.dartbug.com/144). 

So things can only get better :)

Florian Loitsch

unread,
Jan 12, 2013, 3:36:03 PM1/12/13
to General Dart Discussion
On Sat, Jan 12, 2013 at 9:29 PM, Bernhard Pichler <bernhard.ro...@gmail.com> wrote:
How do i remove an event listener? Does the "listen" method return some kind of handle?
Yes. 'listen' returns a Subscription object. 

The way it works today is really bad. Most people would assume that ...

element.on.click.add(
onClickHandler);
element.on.click.remove(onClickHandler);

onClickHandler(e) { }

... just works, but it does not (which is really a shame ... www.dartbug.com/144). 
This problem would be gone, but in return you need to keep the subscription object around to be able to cancel the subscription. 
--
Consider asking HOWTO questions at Stack Overflow: http://stackoverflow.com/tags/dart
 
 



--
Give a man a fire and he's warm for the whole day,
but set fire to him and he's warm for the rest of his life. - Terry Pratchett

Bernhard Pichler

unread,
Jan 12, 2013, 3:45:01 PM1/12/13
to mi...@dartlang.org
A subscription object is much better then the API we have today.
It should be clear what's going on and does not lead to false assumptions.

Thanks!

Joao Pedrosa

unread,
Jan 13, 2013, 2:57:54 PM1/13/13
to mi...@dartlang.org
Hi,

Examples of old vs new syntax:

Listening to an event
Old:
element.on.click.add((e) {
});
New:
element.onClick.listen((e) {
});

Just adding some example of what it could be if it was not more wasteful, here's how I do it in my own library:

  on({dr_click(ev), dr_mouseup(ev)}) {
    storeEventListeners([
      'dr_click', dr_click,
      'dr_mouseup', dr_mouseup,
    ]);
  }

And that's after adopting some Dart features (named parameters and function parameter declaration. And a more complex one:

  on({focus(ev), blur(ev), dr_enterkey(), dr_edit(), keyup(ev),
      keydown(ev), keypress(ev), change(ev), mouseup(ev), mousedown(ev),
      paste(ev), input(ev), dr_input()}) { 
    configureEvents(el, [
      "focus", focus,
      "blur", blur,
      "keyup", keyup,
      "keydown", keydown,
      "keypress", keypress,
      "change", change,
      "mouseup", mouseup,
      "mousedown", mousedown,
      "paste", paste,
      "input", input,
    ]);
    if (dr_enterkey != null) {
      doConfigureEvent(el, 'keydown', (ev) {
        if (ev.keyCode == 13) {
          dr_enterkey();
        }
      });
    }
    if (dr_edit != null) {
      doConfigureEvent(el, 'keyup', (ev) {
        if (ev.keyCode == 13) {
          dr_edit();
        }
      });
    }
    if (dr_input != null) {
      doConfigureEvent(el, 'keyup', (ev) {
        var kc = ev.keyCode;
        if (!DR.highControlKey(kc) && (kc == 8 || kc == 32 || kc >= 46)) {
          // 8 - backspace; 32 - spacebar; 46 - delete key.
          // 47 and up numbers, characters.
          dr_input();
        }
      });
      doConfigureEvent(el, 'paste', (ev) {
        DR.timer(dr_input);
      });
      doConfigureEvent(el, 'input', (ev) => dr_input());
      if (DR.isIE) {
        doConfigureEvent(el, 'propertychange', dr_input);
      }
    }
  }

I have some conventions going. Events prefixed by "dr_" are custom events in contrast to the events expected from the DOM.

I fire custom events by using their string names, like so: callEvent("dr_foo").

I quite like the on(...) API. It can do more work during the registering of events, but then it all works from a single method and custom widgets can also enjoy custom events. A single call to on() can register a bunch of events in one go too. Like so:

    id.on(focus: (ev) {
        _focus = true;
        _blurEventDelayer.stop();
      }, blur: (ev) {
        _focus = false;
        _blurEventDelayer.delay();
        hideSelectionFrame();
    });

You guys have much more responsibility in creating a generic and general API. But events are so important, and the more you streamline them, the better we'll all be.

Thanks a bunch.

Cheers,
Joao


Alex Tatumizer

unread,
Jan 13, 2013, 5:00:42 PM1/13/13
to mi...@dartlang.org

I think the design would be simpler if dart defined standard interface "EventEmitter" (in the way similar to nodejs and others)
EventEmitter basically has 2 functions
var subscriber=someEventEmitter.subscribe('click',myFunction); // event type is a string!
someEventEmitter.unsubscribe(subscriber)

or something similar (e.g. shortcut "on" for "subscribe).

A couple of obvious observations:
1)  stream IS an EventEmitter (so instead of turning the "stream" of  events into dart Stream, we simply treat Stream as variant of EventEmitter to begin with)
2) it becomes possible to implement generic functions over ANY event emitter (I already encountered this problem while trying to translate circuit concept from javascript to dart).
3) explicit definition of "EventEmitter" simplifies thinking about event processing (and programming - by implication)

Current design is basically OK, it's just lacking implementation of abstract EventEmitter in every class which is **informally** an event emitter already.
How about that?



 

George Moschovitis

unread,
Jan 14, 2013, 4:23:29 AM1/14/13
to mi...@dartlang.org

Element.clickEventProvider.provideEvent(document.body);

... and then steal the "Event" suffix naming convention for the Streams themselves:

el.clickEvent.listen(f);


I like both onEvent *and* your suggestion.
I think I would prefer subscribe() over listen though.

-g.

PS: In general this is a change to the right direction :)

 

Sean Eagan

unread,
Jan 14, 2013, 9:40:39 AM1/14/13
to General Dart Discussion
Another idea, since HtmlStreamProvider (I think it was renamed to
EventStreamProvider) only contains one method, maybe just expose that
method:

Element.clickEventFor(document.body);

then add a typedef:

typedef Stream<T> EventStreamProvider<T>(EventTarget<T> e, {bool
useCapture: false});

which could be used in conjunction with closurizing the methods (for
example Element.clickEventFor above) if/when more generic handling is
needed.

Cheers,
Sean Eagan

Pete Blois

unread,
Jan 14, 2013, 12:26:37 PM1/14/13
to mi...@dartlang.org
We definitely should look into simplifying EventStreamProvider down to a method, though I'm a bit cautious as it may limit future functionality. If it helps inlining and optimizing the dart2js generated code then that's a definite win.

In general I think that using the EventStreamProvider is a bit of an edge case, but it could be useful if we consolidate a number of the duplicated bubbling events between Element, Window and Document to just Element.

Alex Tatumizer

unread,
Jan 14, 2013, 3:49:31 PM1/14/13
to mi...@dartlang.org
Any opinions on naming? Currently the events are exposed as onEventName, but it's often common to use this naming style for virtual methods. Using just eventName has a number of conflicts with members. We aren't entirely thrilled with onEventName.

I think "Emitter" is the missing word. It can be
element.clickEmitter.subscribe((e) {
});

But maybe generic version of emitter won't be bad either? Element implementing EventEmitter directly:
element.subscribe('click', (e) {
});
Sure, you lose type safety here, but code is more succinct (which might be important for frequently used construct).

Library can support both forms actually.

Mathieu Breton

unread,
Jan 15, 2013, 12:48:03 PM1/15/13
to mi...@dartlang.org
Hello,
I see no reason to upgrade to this new API. "Old" API is elegant, simple and is a light way to listen an event on a DOM element. The new way doesn't respect the OO philosophy by using a "dirty" global static method to listen all DOM events, it's more verbose and not intuitive.
For example, in your Dart editor just type "on" on an element instance and you see all events "subcrable" on this element, then type "add" and your handler and it's done. The new way, asks to call a external global class to listen an event that your not sure that your element supports, unlike to the "old" API. Moverer the "old" API is similar to the event API of C# that I find pretty good.

I will be very interrested by a link or a doc to explain this 'best practices' about event? 

Pete Blois

unread,
Jan 15, 2013, 1:04:18 PM1/15/13
to mi...@dartlang.org
The static methods are there for listening to bubbling events and capturing events. 99% of the time you should be able to use the direct stream and not the static stream provider.

Maintaining the autocomplete list from typing '.on' is one of the primary reasons the events are being prefixed with 'on'.

One of my primary complaints about the separate class for events (the .on.eventName syntax) is that the majority of the time when you're exposing events from custom non-UI classes, you would not use the separate class to contain events. We did not want DOM events to be exposed in an inconsistent way with these other events.

Ladislav Thon

unread,
Jan 15, 2013, 1:40:40 PM1/15/13
to mi...@dartlang.org
"Old" API is elegant, simple and is a light way to listen an event on a DOM element.

Unless you want to unsubscribe later...
 
The new way doesn't respect the OO philosophy by using a "dirty" global static method to listen all DOM events, it's more verbose and not intuitive.

How exactly element.onClick.listen(...) doesn't respect the OO philosophy, is more verbose and not intuitive?

I think that the special cases that require the static stream provider can easily be changed to a more succint syntax. (BTW, what is a StreamProvider? That sounds like a Java name a lot! :-) )

LT
Message has been deleted

Pete Blois

unread,
Jan 15, 2013, 4:46:41 PM1/15/13
to mi...@dartlang.org
Agreed, naming is not ideal. I'd like to make this callable to mimic the syntax of the typedef mentioned above, but avoid potential limitations in the future.

We're working on allowing subclassing of Element, so ideally custom events are exposed the same way as the rest of the DOM events.

For listening to your custom event further up the tree, it falls into the 'bubbling events' case I mentioned above-
Listening to a bubbled event
Old (not really supported):
document.body.$dom_addEventListener('canPlay', (e) {}, false);
New:
MediaElement.canPlayEvent.forTarget(document.body).listen((e) {
});

Note that this can also be used for declaring events anywhere-
class Something {
  static const EventStreamProvider<SomeEvent> someEvent = const EventStreamProvider<SomeEvent>('somethinghappened');
}

Then to listen to that:
Something.someEvent.forTarget(element).listen(...)

Though we could possibly provide a Stream getter on EventTarget such as:
Stream<Event> getEventStream(String eventType, {bool useCapture});

The benefit of the static providers is that it gives more clarity as to the event type and avoids potential typos in strings.

On Tue, Jan 15, 2013 at 1:16 PM, Ross Smith <ross.da...@gmail.com> wrote:
s


Message has been deleted

Tomas Polovincak

unread,
Jan 22, 2013, 9:10:14 PM1/22/13
to mi...@dartlang.org
How I can emit custom events when old API with on[eventName].dispatch will be removed?

Kevin Kellogg

unread,
Jan 22, 2013, 10:02:39 PM1/22/13
to mi...@dartlang.org
On Tuesday, January 22, 2013 9:10:14 PM UTC-5, Tomas Polovincak wrote:
How I can emit custom events when old API with on[eventName].dispatch will be removed?

Dispatch is replaced with "add"ing your event to a StreamController (StreamController implements StreamSink). Expose StreamController#stream to listen for events.

Kevin

Tomas Polovincak

unread,
Jan 22, 2013, 10:10:52 PM1/22/13
to mi...@dartlang.org
thanks for your help.

Greg Lusk

unread,
Jan 23, 2013, 1:40:14 PM1/23/13
to mi...@dartlang.org
Could someone provide a quick code snippet on how to register a custom event using the new API?

Thanks in advance,

Greg

Pete Blois

unread,
Jan 23, 2013, 1:46:50 PM1/23/13
to mi...@dartlang.org
Take a look at my comment further up this thread and let me know if there are still some questions.

Greg Lusk

unread,
Jan 23, 2013, 2:43:08 PM1/23/13
to mi...@dartlang.org
Pete,

Using the old event syntax, I can do something like this to dispatch and raise a custom event to a DOM object dynamically:

 query('#btn').on['clickMe'].dispatch(new CustomEvent('clickMe', true, true, new ClickMeEventArgs("value1", "value2")));

So I'm looking for the equivalent syntax using the new API. I need to be able to dynamically attach and raise events to DOM (and custom) objects and let the DOM handle the handler invocation so that I don't have to code a separate event handling mechanism. Correspondingly, I need to allow other elements to listen for these dynamically-added events.  We have been able to achieve this goal using the old API, but haven't yet been able to do so using the new one.  Having to bake in "hard-coded" events at design time is not always an option in our application model.

Pete Blois

unread,
Jan 23, 2013, 2:51:30 PM1/23/13
to mi...@dartlang.org
We haven't yet added the dispatch method for dispatching events, the old syntax will continue to work and will work correctly with the new streams.

Right now the leading API proposal for dispatching events is to move closer to the standard DOM api-
query('#btn').dispatchEvent(new CustomEvent('clickMe', true, true, new ClickMeEventArgs("value1", "value2")));

Also note that we're updating event constructors to use named parameters for most of the arguments, so you're code here would look more like:
query('#btn').dispatchEvent(new CustomEvent('clickMe', detail: new ClickMeEventArgs("value1", "value2")));

Greg Lusk

unread,
Jan 23, 2013, 2:56:06 PM1/23/13
to mi...@dartlang.org
Excellent! That's what I needed to know!

On a related note, is there any thought being given to being able to strongly type the "detail" property of the CustomEvent class when you receive it in the handler? I understand that it's being serialized and then deserialized into a LinkedHashMap for marshaling of events between Isolates, but it does make it difficult on the handler to not be able to obtain the "detail" property in its original object form.

Thanks!

Pete Blois

unread,
Jan 23, 2013, 3:00:41 PM1/23/13
to mi...@dartlang.org
We'd like to allow subclassing of Event classes which would allow arbitrary properties. We have some work to do, but the goal is to get this at the same time as subclassing of Element.


--

Greg Lusk

unread,
Jan 23, 2013, 3:01:54 PM1/23/13
to mi...@dartlang.org
That would be a great feature. +1 for adding it.

Tomas Polovincak

unread,
Jan 23, 2013, 7:31:48 PM1/23/13
to mi...@dartlang.org
as Kevin Kellogg write StreamController works fine:

StreamController<CustomEvent> sc = new StreamController.multiSubscription();
sc
.listen((CustomEvent event) {
     
print("listener #1");
});
sc
.listen((CustomEvent event) {
     
print("listener #2");
});
sc
.add(new CustomEvent('clickMe', true, true, new ClickMeEventArgs("value1","value2")));

Greg Lusk

unread,
Jan 23, 2013, 7:36:14 PM1/23/13
to mi...@dartlang.org
Thanks, Tomas.
--

Pete Blois

unread,
Jan 23, 2013, 7:53:09 PM1/23/13
to mi...@dartlang.org
Not that going through a StreamController is a bit misleading for DOM events, as it's not actually going through the DOM (and things like bubbling will not take effect).

If you do use a StreamController, I'd recommend something other than CustomEvent (or any other DOM event) for the data, for clarity.


--

Kevin Kellogg

unread,
Jan 23, 2013, 11:30:41 PM1/23/13
to mi...@dartlang.org
Good point. I should have been more mindful of the context of this thread.

Tomas: "sc.listen" should be "sc.stream.listen". StreamController soon will not extend Stream.

Thiện Mẫn Hoàng

unread,
Sep 10, 2013, 5:24:33 AM9/10/13
to mi...@dartlang.org
This is just too complicated
Element.clickEvent.forTarget(element, useCapture: true).listen((e) {
});

What about element.onClick.capture((e) {...});?

Pete Blois

unread,
Sep 10, 2013, 11:56:19 AM9/10/13
to mi...@dartlang.org


--
For other discussions, see https://groups.google.com/a/dartlang.org/
 
For HOWTO questions, visit http://stackoverflow.com/tags/dart
 
To file a bug report or feature request, go to http://www.dartbug.com/new

DoHyung Kim

unread,
Jun 22, 2015, 6:41:23 AM6/22/15
to mi...@dartlang.org
Sorry for coming quite late on this issue (seems already too late, but I began to look into Dart since the end of the last year).

But, it is quite weird to have both listen and capture on Stream. Stream.capture can't benefit from various filtering and transformative methods available on Stream (can't also from async/await). For instance, once map or where is called, the returned Stream won't be a ElementStream anymore. It's my impression that Stream is not designed to have additional subscription method like Stream.capture.

I know that I can get separate Streams for events in bubbling and capturing phases via separate APIs, but it would have been better to have separate event streams and accompanying getters for bubbling and capturing events.

Am I missing some context?

Thanks.

2013년 9월 11일 수요일 오전 12시 56분 19초 UTC+9, Pete Blois 님의 말:

Pete Blois

unread,
Jun 22, 2015, 1:25:05 PM6/22/15
to General Dart Discussion
Streams are indeed not really sub-classable but from the API complexity standpoint having separate members for capture is too much. IMHO desiring API extensions such as 'matches' is perfectly reasonable- an ideal scenario for extension methods!

The mere presence of those members has a non-trivial effect on dart2js compilation size, I'd be hesitant to double them.

Justin Fagnani

unread,
Jun 22, 2015, 5:24:01 PM6/22/15
to General Dart Discussion
On Mon, Jun 22, 2015 at 10:25 AM, 'Pete Blois' via Dart Misc <mi...@dartlang.org> wrote:
Streams are indeed not really sub-classable but from the API complexity standpoint having separate members for capture is too much. IMHO desiring API extensions such as 'matches' is perfectly reasonable- an ideal scenario for extension methods!

The mere presence of those members has a non-trivial effect on dart2js compilation size, I'd be hesitant to double them.

You don't have to double, you can produce a new Stream:

class ElementStream<T> {
  Stream<T> captureStream;
}

similar to matches()

To unsubscribe from this group and stop receiving emails from it, send an email to misc+uns...@dartlang.org.

DoHyung Kim

unread,
Jun 22, 2015, 7:32:43 PM6/22/15
to mi...@dartlang.org
Thank you for the brief of underlying rationale. Practically the current API works w/o problems.

BTW, you mentioned dart2js generated code size. Isn't tree shaking supposed to remove unused fields? Just out of curiosity.

Reply all
Reply to author
Forward
0 new messages