Facades
The Facade
is the central concept in the Troika framework. It serves as the "component" unit for all objects, with the following responsibilities:
- Holding the object's current state, as a simple set of flat properties
- Synchronizing that state to a more complex model or imperative code
- Enforcing a simple, consistent object lifecycle
In your app, you will define Facades to represent different types of objects in your scene. You will also make use of built-in Facade types that are specialized for specific purposes.
Each Facade type is defined as a JavaScript class
, extending the base Facade
class. It has a few base methods, but otherwise each Facade class is free to define its own shape in the form of its public instance properties. Those properties will receive values from a scene descriptor or manual updates later on.
See the base Facade
class source for some additional class and method JSDoc.
Why "Facade" instead of "Component"?
The main reason for this naming choice was that "Component" is used by many other frameworks (React, Web Components, etc.) Since Troika will most likely be used as a subcomponent within another web framework, it felt confusing to have the same term referring to multiple things within the same app.
Also, "Facade" is descriptive of its purpose: to create a simple public false-front that is backed by more complex code.
Lifecycle¶
The facade lifecycle is intentionally very simple:
Instantiation¶
The Facade class's constructor
is called, and is always passed a single argument which is the parent
facade instance. Troika facades are never reparented, so that parent
will remain the same for the lifetime of the instance. The constructor
is a good place to perform any initialization for things that will remain for the facade's lifetime, such as creating backing objects, setting up event listeners, etc.
Updates¶
This is where the facade instance receives its state. Since "state" is defined as the facade object's properties, updating that state simply consists of assigning a set of property values. This is usually done by copying a scene descriptor's values directly onto the facade instance during a scene update pass. It can also be triggered manually via the facade's update
method.
This part of the lifecycle is also usually when the facade synchronizes its new state properties to its more complex backing object model.
For properties that are "standalone", meaning they don't rely on any other properties, it is common for the facade class to define property accessors where the set()
implementation updates the backing model right away.
After all properties are updated, the special afterUpdate()
lifecycle method will always be called. This method is where you can put any implementation code that uses multiple properties together, since you can rely on all those properties being up-to-date at this time.
Destruction¶
When a facade instance is removed from the scene tree, its destructor()
method is called. This is where you can perform teardown logic, dispose of backing objects, remove event listeners, etc.
Events¶
Facades implement the EventTarget interface, so you can addEventListener
just like you would a DOM element. Events dispatched this way can bubble, be cancelled, etc. as you'd expect.
There is also a parallel messaging notification system that is used internally for sending large numbers of simple messages up the parent hierarchy in a highly optimized way. You likely won't need this, but see the
notifyWorld()
andonNotifyWorld()
methods for more.
See the section on Interactivity and Events for more.
Example¶
Here's a very simple example of creating a Facade class that synchronizes some properties to a backing SuperComplicatedObject:
import { Facade } from 'troika-core'
export class MyThingFacade extends Facade {
// Instantiation:
constructor (parent) {
super(parent)
// Init backing object:
this._impl = new SuperComplicatedObject()
// Define state properties with initial values:
this.width = 1
this.height = 1
this.depth = 1
this.color = '#123456'
}
// Getter/setter for directly syncing a standalone property:
set color (value) {
this._impl.setColor(value)
}
get color () {
return this._impl.getColor()
}
// Handler for syncing interdependent properties:
afterUpdate () {
this._impl.setDimensions(this.width, this.height, this.depth)
super.afterUpdate() //don't forget the super call!
}
// Cleanup:
destructor () {
this._impl.teardown()
delete this._impl
super.destructor()
}
}
This facade would then be created and updated using a scene descriptor like so:
{
key: 'thing1',
facade: MyThingFacade,
width: 100,
height: 45,
depth: 23,
color: '#336699'
}
You'll learn more about scene descriptors in the next section.
Special Facade Classes¶
The base Facade
class is a superclass of all facades, but you will seldom extend it directly. You'll instead most often use a more specialized facade type. Here are some you may want to be aware of:
ParentFacade¶
[Source] - This extends Facade
with the ability to manage not only itself but also a set of child facades. At the end of its update phase it will recursively synchronize a set of child facade instances, based on an array of descriptor objects returned by its describeChildren()
method (which by default returns the value of its .children
property.)
Most facade classes you will end up working with extend from ParentFacade
.
ListFacade¶
[Source] - Inspired by D3, this is an optimized way to update many of the same type of object that skips creating intermediate descriptor objects for each item. For details see Data Lists.
Object3DFacade, Object2DFacade¶
These are base facades for the troika-3d
and troika-2d
packages. If you are creating a 3D/2D graphical scene, you'll likely be extending these for most of your objects. See the docs for those packages for details.
Notable Facade Methods¶
update({...values})¶
This convenience method allows you to set one or more of a facade's property values, automatically invoking the afterUpdate lifecycle method and requesting a render frame. Calling this from within an event handler, for example, allows facade components to update their own state.
myFacade.update({
prop1: 'newValue1',
prop2: 'newValue2'
})
requestRender()¶
This method notifies the top-level world manager that this object has changed in some way that affects its visible rendering, so a rendering frame will be scheduled.
getChildByKey(key)¶
This method looks for a direct child facade that was created with a given key
. This is not often needed.
forEachChild(func)¶
This method lets you iterate a ParentFacade instance's direct child facades, invoking func
for each child. This is not often needed, since updating children is usually better served by update
.
traverse(func)¶
This method lets you recursively traverse a facade instance and its entire subtree, depth-first, invoking func
for each facade. This is not often needed, since updating a subtree is usually better served by update
.