Last active
November 16, 2022 13:23
-
-
Save Eunomiac/481615cd7500470997711ffa8d23eb27 to your computer and use it in GitHub Desktop.
A fully modular method of applying seamless Mixins to Javascript classes, intended for Foundry VTT development.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* MIXINS GUIDE | |
A "mixin" is a block of code that can be injected into any class, without regard to the standard chain of inheritance. | |
This allows code to be written once, as a mixin, and then applied to any number of classes regardless of whether they | |
inherit from each other. | |
In the context of Foundry, this is especially helpful for code that is shared between the Actor and Item classes (or | |
subclasses thereof), which would normally have to be duplicated within each. | |
It also provides a new means of sharing code, since integrating a mixin into an existing class is seamless, requiring | |
no modification of the inheritance structure, and affecting no code other than that explicitly contained within the | |
mixin itself. | |
Mixins can do everything a standard subclass can do: override methods, introduce new methods (including new static methods), | |
and tap into the receiving class' properties and methods. The context remains that of the receiving class: 'this' and 'super' | |
written in a mixin will refer to the instance and superclass, respectively, of that class. | |
USAGE: DEFINING MIXINS | |
====================== | |
Define a mixin by wrapping the following code around the methods, properties, etc. you want to inject. Your code should | |
be written as if the mixin were a subclass of any classes you'll be applying it to. | |
const MyCloseButtonMixin = (superclass) => class extends superclass { | |
// @override | |
activateListeners(html) { | |
super.activateListeners(html); | |
// Add close button functionality to id'd elements | |
html.find("#closeButton").on({click: () => this.close()}); | |
} | |
} | |
USAGE: SUBCLASSING MIXINS | |
========================= | |
Mixins can subclass from other mixins just as with a normal class, though again with a special wrapper: | |
export const MySoftCloseSubMixin = (superclass) => class extends MyCloseButtonMixin(superclass) { | |
// @override | |
activateListeners(html) { | |
super.activateListeners(html); | |
// Replace that close button with a minimize button. | |
html.find("#closeButton") | |
.off("click") | |
.on({click: () => this.minimize()}); | |
} | |
}; | |
USAGE: APPLYING MIXINS TO CLASSES | |
================================= | |
The "MIX" class factory, exported from this module, makes applying multiple mixins to a receiving class | |
syntactically simple: | |
import MIX, {MySoftCloseSubMixin} from "mixins.js"; | |
class MyActorSheet extends MIX(ActorSheet).with(MySoftCloseSubMixin) { ... } | |
class MyItemSheet extends MIX(ItemSheet).with(MySoftCloseSubMixin) { ... } | |
NOTES | |
================== | |
When writing mixins that will be applied to Item and Actor subclasses, use "this.doc" instead of "this.actor" or "this.item" | |
to retrieve the corresponding objects. | |
*/ | |
// ===== "MIX" CLASS FACTORY ===== | |
class MixinBuilder { | |
constructor(superclass) { this.superclass = superclass } | |
with(...mixins) { return mixins.reduce((cls, mixin = (x) => x) => mixin(cls), this.superclass) } | |
} | |
const MIX = (superclass) => new MixinBuilder(superclass); | |
export default MIX; | |
// ===== MIXINS ===== | |
export const CloseButton = (superclass) => class extends superclass { | |
activateListeners(html) { | |
super.activateListeners(html); | |
html.find("#closeButton").click(() => this.close()); | |
} | |
}; | |
// (Note you can define mixins anywhere: No imports needed to define a mixin.) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment