2.3 Controllers - Baumgaer/Game GitHub Wiki
- 2.3.1 The Base of a controller
- 2.3.2 The life cycle
- 2.3.3 Add a controller to a component or another controller
- 2.3.4 Event listener
- 2.3.5 Remove a controller from a controller or component
- 2.3.6 Special functionality
- 2.3.7 Fully functionality example
A controller in this framework manages default behaviors of components such like pageing, lazy loading or other things which can be implemented for a good webcomponent.
It should contain all the business logic of a component which does not manage the appearance of a component directly. Logic which does manage the appearance indirectly such like a number of people in a chatroom which controls the color of the name of this room is still welcome.
Furthermore a controller implements the default live cycle of a component with hooks a a method in the corresponding class. It also implements logic for handling event listeners and other controllers which must be removed explicitly when a component is removed from the DOM.
A special behavior is the generation of an unique ID for webcomponents when no ID is explicitly given by an attribute of the DOM-element. When an ID is set, it will be always check for unique appearance.
A controller can only be used in a component or another controller. It is not possible to use a controller as stand alone without another controller or component.
Every controller is derived from the BaseController which is constructed by the BaseControllerFactory.
import {BaseControllerFactory} from "~client/lib/BaseController";
import {baseConstructor} from "~bdo/utils/decorators";
@baseConstructor()
export class MyController extends BaseControllerFactory(EventTarget) {
// ...
}
Example 1: Basic structure of a controller to get basic functionality.
Furthermore a controller is always a baseConstructor
which is necessary to invoke the life cycle, which every controller should have to interact with components and to be able to pass arguments which are defined as public
class fields.
A controller should also extend the EventTarget
to get the functionality to dispatch events or register and remove event listeners.
Every controller will be inherited by a component and derived from BaseController
so there is the life cycle of such a component to be able to interact with components in special situations with a default behavior.
-
constructor(...args: any[])
this is constructor like every constructor but its meaning is different. In this part of the life cycle you should update default values of class fields or set calculated default values which depends on the given arguments. NOTE: Never set an argument to a class field explicitly because they are managed by the
baseConstructor
. -
constructedCallback(): void
This hook is called when the constructor is ready. All default values of class fields are assigned and also given arguments which are external values for class fields are assigned after updating the default values.
In this part of the life cycle you should set bindings of models to the class fields when they are not assignable from external or do other things which depends on a fully constructed instance.
NOTE: The construction is still in progress! "Constructed" means that the usually construction of a class is ready now and you can extend this behavior now.
-
connectedCallback(): void
This life cycle hook is called when the corresponding component is connected to the DOM. Here you should add event listener or busines logic controllers because you have access to the dom node of the component and you can navigate through the DOM.
-
disconnectedCallback(): void
This hook is called when the component which is derived from the BaseController is removed in some way from the DOM without placing it in another part in the document.
-
adoptedCallback(): void
This is called when the corresponding component is moved to another document. This always happens when the component is moved to the shadowRoot of another component or into an iFrames document.
-
remove(): void
Remove is called when the component is explicitly removed from the DOM or when the controller itself is removed from the component by
removeController
-method. All controllers and event listeners will be removed from the component / controller.
You can add a controller by calling addController<T extends Constructor<BaseController>>(name: string, controller: T, arguments: ConstParams<T>)
. When you add a controller, this controller will be added to the controllers: IndexStructure<BaseController>
of the component or controller. The ConstParams<T>
will be extended by an argument owner: typeof this
which is the current instance of the component or controller.
The owner is useful to get access to the component or controller which initializes the controller. When the current instance is a component instead of a controller, owner
will always be undefined.
In a normal case, a controller should be extended by EventTarget
to be able to add event listener (see Example 1).
When your controller is derived from BaseController and EventTarget you can use dispatchEvent(event: Event): void
to trigger event listeners, added by addEventListener(type: string, listener: (event: Event) => void): void
, as like HTMLElements do it.
When you want to remove an event listener just use removeEventListener(name: string, listener: (event: Event) => void): void
exactly like HTMLElements.
When a controller will be removed from a component or the component will be removed from the DOM, all event listeners of all controllers will be removed recursively.
To remove a controller just call removeController(name: string): void
.
When the controller with the name name
is available in controllers: IndexStructure<BaseController>
, all controllers which are initialized by this controller will be removed recursively. Furthermore all event listeners will be removed, too. Only the event listeners which are registered by this controller on a component (owner
) won't be removed. This must be done manually.
The BaseController offers a special functionality for all controllers and components, which are the following:
-
Methods
-
toJSON(): IndexStructure<this[keyof this]>
This will return a plain object with all properties of the current instance. Useful for saving the state in localStorage or other databases.
-
getNamespacedStorage(key: string, nsProp?: string, forceNS?: string): any
This is used by several decorators (see chapter 2.2) to get persistent data from localStorage but can also be used in normal program logic.
-
setUpdateNamespacedStorage(key: string, newVal: any, nsProp?: string): any
This is used by the
baseConstructor
decorator for example to persist or update information in localStorage. Furthermore this can be used manually. -
deleteFromNamespacedStorage(key: string, nsProp?: string): void
This removes information from the localStorage the same way like they are added with
setUpdateNamespacedStorage
.
-
-
Properties
-
id: string
An unique identifier generated with the class name and the number of appearance.
-
owner: this extends HTMLElement ? undefined : IOwnerType
usually this is the constructor class of the current controller which initializes this controller. If this is a component, it will be undefined.
-
properties: Map<string, Property<this>>
This returns all class fields which are decorated with the
property
decorator. -
attributes: Map<string, Attribute<this>>
This returns all class fields which are decorated with the
attribute
decorator.NOTE: This will especially overwrite the attributes property of a webcomponent!
-
bindings: Map<string, Binding<this, DefNonFuncPropNames<this>>>
This will return all properties which are bound to a models property.
-
This example is a controller which takes a model of BDOModel
type with no default value. After usual construction, the protected
watched
property
changeableProperty
, which does not have a default value during the usual construction, gets its default value as a binding from the given model. When this property is initialized the first time, the controller writes "changeableProperty initialized" into the reference node initHint
of the owner
of this controller , which is a component in this case (could be another controller, too).
Every time when the changeableProperty
is changed after the first initialization, this controller will write into the reference node changeHint
of the owner
of this controller, that the value of the changeableProperty
is changed by the new value, that is already assigned to this field.
Furthermore a CustomEvent
will be dispatched, that is eventually listened by the component.
import {BaseControllerFactory} from "~client/lib/BaseController";
import {baseConstructor, property, watched} from "~bdo/utils/decorators";
import {BDOModel} from "~bdo/lib/BDOModel";
@baseConstructor()
export class MyController extends BaseControllerFactory(EventTarget) {
@property() public model!: BDOModel;
@watched() @property() protected changeableProperty!: string;
constructor(params: ConstParams<MyController>) {}
constructedCallback() {
this.changeableProperty = this.model.bind("aProperty");
}
onChangeablePropertyInit(value: string) {
this.owner.refs.initHint.innerText = "changeableProperty initialized";
}
onChangeablePropertyChange(_oldValue: string) {
this.owner.refs.changeHint.innerText =
"changeableProperty was changed by value: " + this.changeableProperty;
this.dispatchEvent(
new CustomEvent("changeablePropertyChange", {
detail: this.changeableProperty,
})
);
}
}
Example 2: Fully functionally example of a very basic controller.