The OpenD Programming Language

Event

Represents an event that is currently being processed.

More...

Constructors

this
this(string eventName, Widget emittedBy)

Creates an event without populating any members and without sending it. See dispatch

Members

Aliases

srcElement
alias srcElement = source
target
alias target = source

This is the widget that emitted the event.

Functions

adjustClientCoordinates
void adjustClientCoordinates(int deltaX, int deltaY)

This is an internal implementation detail you should not use. It would be private if the language allowed it and it may be removed without notice.

adjustScrolling
void adjustScrolling()

This is an internal implementation detail you should not use. It would be private if the language allowed it and it may be removed without notice.

cancelable
bool cancelable()

hints as to whether preventDefault will actually do anything. not entirely reliable.

dispatch
void dispatch()

this dispatches the element using the capture -> target -> bubble process

getPropertiesList
void getPropertiesList(void delegate(string name) sink)
getPropertyAsString
void getPropertyAsString(string name, void delegate(string name, scope const(char)[] value, bool valueIsJson) sink)

Implementations for the ReflectableProperties interface/

preventDefault
void preventDefault()

Prevents the default event handler (if there is one) from being called

propagates
bool propagates()

Events should generally follow the propagation model, but there's some exceptions to that rule. If so, they should override this to return false. In that case, only bubbling event handlers on the target itself and capturing event handlers on the containing window will be called. (That is, dispatch will call sendDirectly instead of doing the normal capture -> target -> bubble process.)

sendDirectly
void sendDirectly()

this sends it only to the target. If you want propagation, use dispatch() instead.

setPropertyFromString
SetPropertyResult setPropertyFromString(string name, const(char)[] str, bool strIsJson)

Implementations for the ReflectableProperties interface/

stopPropagation
void stopPropagation()

Stops the event propagation immediately.

Mixin templates

Register
mixintemplate Register()

You can mix this into child class to register some boilerplate. It includes the EventString member, a constructor, and implementations of the dynamic get data interfaces.

Properties

intValue
int intValue [@property getter]
stringValue
string stringValue [@property getter]

Variables

relatedTarget
Widget relatedTarget;

Note: likely to be deprecated at some point.

source
Widget source;

This is the widget that emitted the event.

Inherited Members

From ReflectableProperties

getPropertiesList
void getPropertiesList(void delegate(string name) sink)

Iterates the event's properties as strings. Note that keys may be repeated and a get property request may call your sink with null. It it does, it means the key either doesn't request or cannot be represented by json in the current implementation.

getPropertyAsString
void getPropertyAsString(string name, void delegate(string name, scope const(char)[] value, bool valueIsJson) sink)

Requests a property to be delivered to you as a string, through your sink delegate.

setPropertyFromString
SetPropertyResult setPropertyFromString(string name, const(char)[] str, bool strIsJson)

Sets the given property, if it exists, to the given value, if possible. If strIsJson is true, it will json decode (if the implementation wants to) then apply the value, otherwise it will treat it as a plain string.

SetPropertyResult
enum SetPropertyResult

setPropertyFromString possible return values

RegisterSetters
mixintemplate RegisterSetters()

You can mix this in to get an implementation in child classes. This does setPropertyFromString.

RegisterGetters
mixintemplate RegisterGetters()

You can mix this in to get an implementation in child classes. This does getPropertyAsString and getPropertiesList.

Detailed Description

Minigui's event model is based on the web browser. An event has a name, a target, and an associated data object. It starts from the window and works its way down through the target through all intermediate Widgets, triggering capture phase handlers as it goes, then goes back up again all the way back to the window, triggering bubble phase handlers. At the end, if Event.preventDefault has not been called, it calls the target widget's default handlers for the event (please note that default handlers will be called even if Event.stopPropagation was called; that just stops it from calling other handlers in the widget tree, but the default happens whenever propagation is done, not only if it gets to the end of the chain).

This model has several nice points:

  • It is easy to delegate dynamic handlers to a parent. You can have a parent container with event handlers set, then add/remove children as much as you want without needing to manage the event handlers on them - the parent alone can manage everything.
  • It is easy to create new custom events in your application.
  • It is familiar to many web developers.

There's a few downsides though:

  • There's not a lot of type safety.
  • You don't get a static list of what events a widget can emit.
  • Tracing where an event got cancelled along the chain can get difficult; the downside of the central delegation benefit is it can be lead to debugging of action at a distance.

In May 2021, I started to adjust this model to minigui takes better advantage of D over Javascript while keeping the benefits - and most compatibility with - the existing model. The main idea is to simply use a D object type which provides a static interface as well as a built-in event name. Then, a new static interface allows you to see what an event can emit and attach handlers to it similarly to C#, which just forwards to the JS style api. They're fully compatible so you can still delegate to a parent and use custom events as well as using the runtime dynamic access, in addition to having a little more help from the D compiler and documentation generator.

Your code would change like this:

// old
widget.addEventListener("keydown", (Event ev) { ... }, /* optional arg */ useCapture );

// new
widget.addEventListener((KeyDownEvent ev) { ... }, /* optional arg */ useCapture );

The old-style code will still work, but using certain members of the Event class will generate deprecation warnings. Changing handlers to the new style will silence all those warnings at once without requiring any other changes to your code.

All you have to do is replace the string with a specific Event subclass. It will figure out the event string from the class.

Alternatively, you can cast the Event yourself to the appropriate subclass, but it is easier to let the library do it for you!

Thus the family of functions are:

Widget.addEventListener is the fully-flexible base method. It has two main overload families: one with the string and one without. The one with the string takes the Event object, the one without determines the string from the type you pass. The string "*" matches ALL events that pass through.

Widget.addDirectEventListener is addEventListener, but only calls the handler if target == this. Useful for something you can't afford to delegate.

Widget.setDefaultEventHandler is what is called if no preventDefault was called. This should be called in the widget's constructor to set default behaivor. Default event handlers are only called on the event target.

Let's implement a custom widget that can emit a ChangeEvent describing its checked property:

class MyCheckbox : Widget {
	/// This gives a chance to document it and generates a convenience function to send it and attach handlers.
	/// It is NOT actually required but should be used whenever possible.
	mixin Emits!(ChangeEvent!bool);

	this(Widget parent) {
		super(parent);
		setDefaultEventHandler((ClickEvent) { checked = !checked; });
	}

	private bool _checked;
	@property bool checked() { return _checked; }
	@property void checked(bool set) {
		_checked = set;
		emit!(ChangeEvent!bool)(&checked);
	}
}

Creating Your Own Events

To avoid clashing in the string namespace, your events should use your module and class name as the event string. The simple code mixin Register; in your Event subclass will do this for you.

class MyEvent : Event {
	this(Widget target) { super(EventString, target); }
	mixin Register; // adds EventString and other reflection information
}

Then declare that it is sent with the Emits mixin, so you can use Widget.emit to dispatch it.

Meta

History

Prior to May 2021, Event had a set of pre-made members with no extensibility (outside of diy casts) and no static checks on field presence.

After that, those old pre-made members are deprecated accessors and the fields are moved to child classes. To transition, change string events to typed events or do a dynamic cast (don't forget the null check!) in your handler.