Objects
Understand how the ManagedObject class provides a common foundation for all parts of a Desk application.
Object orientation
Desk apps are object-oriented, and rely on composition to define a tree structure at runtime, i.e. a strict hierarchy of objects.
Note: This documentation assumes you’re familiar with the basics of object-oriented programming in JavaScript. If you’re not, you may want to read up on the topic before proceeding.
- Classes and objects (instances)
- Properties and methods
- Inheritance using modern JavaScript syntax (extends, super)
To communicate between objects, you can use standard JavaScript properties and methods — but the framework also provides features to attach component objects to ‘parent’ or containing objects. For example, a view object is attached to an activity. This enables the following special features:
- Property bindings automatically observe and copy property data from a containing parent object to a contained object (one-way only, e.g. to update views when the activity is updated).
- Objects emit events that can be handled by containing objects (the other way around from bindings, e.g. to handle user input).
- Objects can be unlinked from the hierarchy when they’re no longer needed, clearing event handlers and bindings automatically, as well as unlinking other contained objects.
Managed objects
To enable this functionality, most of the classes provided by the Desk framework extend the ManagedObject
class. This class serves as the primary building block for the Desk application hierarchy: it manages attach objects, binds data between them, and emits events.
- class ManagedObjectThe base class of all managed objects, which can be placed into a tree structure to enable event handling and data binding.
Note: The
ManagedObject
primarily ‘manages’ references between objects (hence the class name), ensuring that properties and events can be observed without memory leaks. By sticking with a strict hierarchy of attached managed objects, Desk introduces a concept of ownership — avoiding many of the pitfalls of traditional JavaScript programming surrounding closures and event handlers.
Most of the time you won’t be aware of the ManagedObject
class itself. For example, to define an activity, you’ll need to extend the Activity class (itself a subclass of ManagedObject
), which comes with a view
property that attaches view objects automatically.
class MyActivity extends Activity {
// ...
protected ready() {
this.view = new MyView(); // MyView instance is attached here
app.showPage(this.view);
}
}
With just this code, property bindings and event handlers are added and cleaned up automatically, as the activity and view are attached and/or unlinked.
Similarly, views defined using JSX syntax or ui
methods construct (and attach) an entire object hierarchy in one go when they’re instantiated:
// Here, MyView is a class that extends UICell
const MyView = ui.cell(
{ padding: 16 },
// for each object, a UILabel object is attached automatically:
ui.label("Hello World"),
);
Even the app object itself is a managed object. This object is created immediately when the app starts, and can be used from anywhere in your code. Several other managed objects are attached during runtime.
// add an activity (this attaches an object)
app.addActivity(new MyActivity(), true);
// show a dialog
let result = await app.showConfirmationDialogAsync("Are you sure?");
Attaching objects
On your own managed objects, you can also attach your other managed objects to build out the application hierarchy beyond activities and views — for example, to incorporate relational data or complex view models.
- Objects can be attached ad-hoc using the attach() method, allowing you to assign relationships between objects dynamically. (Naturally, objects can only be attached to a single parent object at a time)
- Objects can also be attached by referencing them from specific properties. In this case, a property to be watched is set up using the autoAttach() method. Any object assigned to such a property is automatically attached to the parent object. When the referenced object is unlinked, the property is set to undefined.
- For both methods, an observer or callback function can be provided to listen for events or changes on attached objects.
- When an object is no longer needed, it can be unlinked manually using the unlink() method. This method unlinks the object from its parent, and also unlinks all of its own attached objects.
After an object is unlinked (see below) it can no longer be attached to another parent object. Unlinked objects also can’t emit any events, be observed, or have their properties bound to other objects.
- attach(target, observer?) protectedAttaches the provided managed object to this object.
- autoAttach(propertyName, observer?) protectedObserves a property, so that any object assigned to it is attached immediately.
- unlink()Unlinks this managed object.
- isUnlinked()Returns true if the object has been unlinked.
To get a reference to the containing (attached parent) object, or the closest containing object of a specific type, you can use the static whence() method that’s available on the ManagedObject
class and all subclasses.
- ManagedObject.whence(this, object?) staticReturns the containing (attached) managed object of this type, for the provided object.
class MyObject extends ManagedObject {
// ...
readonly other = this.attach(new OtherObject());
}
let someObject = new MyObject();
MyObject.whence(someObject.other); // => someObject
// attached objects are unlinked automatically:
someObject.unlink();
someObject.other.isUnlinked(); // => true
Why should I need to “unlink” a managed object?
Unlinking managed objects is not always necessary. JavaScript is a garbage-collected language, so objects that are no longer referenced by any other object or active closure are automatically removed from memory.
However, unlinking an object is still a good idea if the object had any event listeners or bindings added to it during its lifetime. Unlinking the object explicitly removes such references, and breaks up any circular references that may otherwise prevent the object from being garbage-collected.
Handling unlinked objects
When writing a managed object class, you may want to perform some cleanup when the object is unlinked. The ManagedObject class allows you to override the beforeUnlink() method for an opportunity to perform such cleanup.
class MyObject extends ManagedObject {
// Called just before the object is unlinked:
protected beforeUnlink() {
// ... cleanup code goes here
}
}
On the other hand, after attaching another object, you may want to run some code when the attached object is unlinked. Both the attach() and autoAttach() methods accept an optional callback function (or Observer class) that’s invoked when the attached object emits an event and when the object is unlinked.
class ParentObject extends ManagedObject {
readonly target = this.attach(new MyObject(), (target, event) => {
if (!target) {
// ...handle the attached object being unlinked
} else if (event && event.name === "Change") {
// ...handle change event from the attached object
}
});
}
Both callbacks are invoked when the attached object is unlinked explicitly, using the unlink() method on the object itself or one of its ‘parent’ containing objects. Note that the callbacks are not invoked when the object is garbage-collected by the JavaScript runtime engine.
Attaching objects using managed lists
If you need to keep track of multiple managed objects in a list, you could of course use a regular array or JavaScript Set
. However, Desk provides a special ManagedList class that’s designed to work with managed objects in a more efficient way.
- A
ManagedList
contains an ordered set of managed objects — each object can only be added to the list once. - A
ManagedList
can be restricted to only contain objects of a specific type. - A
ManagedList
that’s attached to a parent object, automatically attaches all of the objects contained in the list as well. - When an attached
ManagedList
is unlinked, all of the attached objects are unlinked immediately. - When an object is unlinked, it’s automatically removed from a containing (parent)
ManagedList
. - A
ManagedList
automatically propagates events from attached objects, making it easier to listen for events on all objects at the same time.
class CustomerOrders extends ManagedObject {
readonly orders = this.attach(
new ManagedList().restrict(Order),
(target, event) => {
// ... handle changes on this list AND its objects
},
);
}
Further reading
Learn more about event handling, property bindings, and data structures in the following articles:
- Event handlingLearn about events, event handling, and observers to watch for property changes.
- BindingsLearn how to use bindings, to communicate property changes between attached objects.
- Data structuresUse managed lists and records to model hierarchical data in your application.
To see how managed objects are used in practice, refer to the following articles:
- App contextAn overview of the functionality provided by the global application context.
- ActivitiesLearn how to use activities to define the behavior and state of each part of your application.
- ViewsThis article is not yet available.