Skip to main content

Decorators

Decorators are ways to inject behavior into elements that already defined. Example use cases might be we want to track some analytics to see how often certain UIs are being used, or we want certain actions to trigger a route state change, or we need a way to add tooltips to elements without refactoring the elements to be tooltip aware.

A decorator can accept parameters and can extrude values but defines no body of its own.

decorator TrackClicks {

state int clickCount;

mouse:click => clickCount++;

}

A decorator can declare the following member types:

  • Companion
  • Parameters
  • State
  • Attributes
  • Instance Styles
  • Style List
  • Lifecycle handlers
  • Input event handlers
  • Spawn list

Unlike the other top level declarations, there is no functional style syntax alternative for decorators. Decorators cannot be applied to decorator or function.

There is a special extension of decorator when you only want it to apply to a typography element. In this case the decorator must be declared with decorator typography. When doing so, $text becomes available to the decorator and a compile error will thrown if you attempt to decorate a non typography element with this decorator.

A decorator can be applied either to a top-level template or typography declaration or to invocation, the effect is the same. When multiple decorators are applied to an element, they apply in the order they are used.

@SomeDecorator
template SomeTemplate { ... }
template DecoratorExample : DecoratorExample render {

// lets turn this button into something that is able to switch between menu screens.
// We could add a click handler here that does this logic, but it would be better if we
// could hook into a menu transition system that we previously created.
Button("Take me there!");

// using a decorator (which can be user defined) we add functionality that intercepts the button click
// and invokes our route transition instead. We didn't even alter the button to do this
@RouterLink("/game/main_menu")
Button("Take me there!");

// Decorators can also accept arbitrary bindings. In this case I extended the Button to also track how many times it was clicked,
// And setup an analytics category with an identifier. Button itself didn't change at all
@TrackClicks(category = "Transitions", identifier = "Go To Main Menu")
Button("Take me there!");

// We can combine as many decorators as we like. Here is the button with both analytics and routing attached
@RouterLink("/game/main_menu")
@TrackClicks(category = "Transitions", identifier = "Go To Main Menu")
Button("Take me there!");

// decorators can also extrude their state, this is only valid if the decorator is marked as `persistent`
@TrackClicks(category = "Transitions", identifier = "Go To Main Menu") [totalClicks]
Button("Clicked: " + totalClicks + " times");

// decorators can also extrude their `this` value but doing so requires an `as` alias.
@TrackClicks(category = "Transitions", identifier = "Go To Main Menu") [$this as clickTracker]
Button("Clicked: " + clickTracker.totalClicks + " times");
}

A decorator with no body is valid, has zero runtime impact, and is useful for reflection based used cases like testing, code generation, or authoring environments.