JS FAQs
Why do we use a single payload object instead of individual parameters?
Section titled “Why do we use a single payload object instead of individual parameters?”Using a single payload object gives us flexibility. When a class needs a new parameter, we just add a property to the payload — we don’t need to change every place the class is instantiated or worry about parameter order.
// With payload - flexible and order-independentnew Header({ nav: document.querySelector(".js--nav"), dropdowns: document.querySelectorAll(".js--dropdown"), burger: document.querySelector(".js--burger"), overlay: document.querySelector(".js--overlay"), Manager: this.Manager,});What is this.DOM and why must the main element be stored in this.DOM.element?
Section titled “What is this.DOM and why must the main element be stored in this.DOM.element?”this.DOM is a dedicated object to store all DOM element references used by the class. The primary element — the one the class is being applied to — must always be stored as this.DOM.element for consistency across all Terra classes.
class InfiniteMarquee { constructor(payload) { var { element, speed, controlsOnHover, reversed, Manager } = payload; this.DOM = { element }; // ... }}The main element should always use the js-- prefix in its class name to indicate it’s targeted by JavaScript:
<ul class="c--marquee-a js--marquee"> <!-- ... --></ul>Why is the destroy() method so important?
Section titled “Why is the destroy() method so important?”Since we use Swup for page transitions, content is replaced dynamically without a full page reload. Without proper cleanup, old event listeners remain attached to removed elements, memory usage increases over time, and multiple handlers can fire for the same action.
The destroy() method must clean up everything:
What’s the difference between a “library” and an “instance” in the Manager?
Section titled “What’s the difference between a “library” and an “instance” in the Manager?”This is a fundamental distinction:
- A library is the imported code/class itself. It’s stored once in the Manager and reused across page transitions.
- An instance is a concrete object created from that library, tied to a specific DOM element. There can be many instances of the same library.
// Library: imported once and storedManager.addLibrary({ name: "Marquee", lib: marqueeModule });
// Instances: one per DOM elementManager.addInstance({ name: "Marquee", instance: new Marquee({...}), element: domElement1 });Manager.addInstance({ name: "Marquee", instance: new Marquee({...}), element: domElement2 });On page transitions, instances are destroyed and recreated for the new page, but libraries stay in the Manager so they don’t need to be re-imported.
What are MitterContentReplaced and MitterWillReplaceContent?
Section titled “What are MitterContentReplaced and MitterWillReplaceContent?”These are the two lifecycle events emitted by Main.js that map to Swup’s page transition hooks:
MitterContentReplaced→ Fires when a new page loads (content has been replaced). This is where you create instances.MitterWillReplaceContent→ Fires when leaving a page (before content is replaced). This is where you destroy instances.
Every handler listens for these two events:
events() { this.emitter.on("MitterContentReplaced", async () => { this.DOM = this.updateTheDOM; super.assignInstances({ elementGroups: [ { elements: this.DOM.marqueeElements, config: this.config, boostify: { distance: 100 }, }, ], }); });
this.emitter.on("MitterWillReplaceContent", () => { if (this.DOM.marqueeElements.length) { super.destroyInstances(); } });}What is Boostify and why do we use it?
Section titled “What is Boostify and why do we use it?”Boostify is a performance optimization library that defers loading of components until they are actually needed. Instead of importing and instantiating all libraries on page load, Boostify triggers them based on user interactions:
- Scroll: Load when user scrolls a certain distance (default: 30px)
- Click: Load when user clicks an element (used for modals)
The CoreHandler decides automatically: if an element is in the viewport, it loads immediately. If it’s not in the viewport, it defers to Boostify’s scroll event. You can customize the trigger distance:
super.assignInstances({ elementGroups: [ { elements: this.DOM.marqueeElements, config: this.config, boostify: { distance: 100 }, // Triggers after 100px scroll }, ],});For click-triggered libraries like Modal:
super.assignInstances({ elementGroups: [ { elements: this.DOM.modalButton, config: this.config, boostify: { method: 'click' }, }, ],});ADVANCED
Section titled “ADVANCED”Why is the handler config a callback function instead of a plain object?
Section titled “Why is the handler config a callback function instead of a plain object?”The config is a callback ({element}) => ({...}) because it needs access to the specific HTML element it’s being applied to. The CoreHandler calls this callback with the concrete element as an argument, so you can read attributes from each individual element:
this.config = ({element}) => ({ Manager: this.Manager, element: element, speed: element.getAttribute("data-speed") || 1, controlsOnHover: element.getAttribute("data-controls-on-hover") || "false", reversed: element.getAttribute("data-reversed") || "false",})This way, if you have two marquees with different speeds on the same page, each gets its own configuration based on its HTML attributes:
<ul class="c--marquee-a js--marquee" data-speed="1"><!-- ... --></ul><ul class="c--marquee-b js--marquee" data-speed="12" data-reversed="true"><!-- ... --></ul>What is forceLoad and when do I need it?
Section titled “What is forceLoad and when do I need it?”forceLoad: true tells the CoreHandler to import and instantiate a library immediately, bypassing the viewport and Boostify checks. You need it in two scenarios:
- Library-in-library: When loading a library inside another library (e.g., a Slider inside a Modal), since the inner library needs to load right when requested, regardless of viewport status.
- Anchor links: When an anchor at the top of the page needs height-modifying libraries loaded before scrolling.
this.emitter.on("Slider:load", (payload) => { this.DOM = this.updateTheDOM; super.assignInstances({ elementGroups: [ { elements: this.DOM.sliderElement, config: this.config, boostify: { distance: 30 }, }, ], forceLoad: true, // Skip viewport check, load now });});What is the EventSystem and when do I use it?
Section titled “What is the EventSystem and when do I use it?”The EventSystem is a wrapper around the emitter that provides standardized custom events for two main scenarios:
- Library-in-library communication: When one library needs to trigger another (e.g., Modal opening a Slider)
- Anchor navigation: When anchor links need to trigger height-modifying libraries
It provides two methods:
// To load a library from within another libraryeventSystem.loadEvent({ library: "Slider", where: "Modal" });
// To destroy a library from within another libraryeventSystem.destroyEvent({ library: "Slider", where: "Modal" });These emit events following the naming convention LibraryName:load and LibraryName:destroy, which you’ll need to make your handlers listen for when you’re interested in loading a concrete library at a precise moment.
How does the condition option work in resources.js?
Section titled “How does the condition option work in resources.js?”The condition callback lets you skip loading a library based on runtime conditions. If it returns false, the library won’t be imported at all:
{ name: "Collapsify", resource: async () => { const { default: Collapsify } = await import("@terrahq/collapsify"); return Collapsify; }, options: { modifyHeight: true, condition: async () => { const { u_is_touch } = await import('@andresclua/jsutil'); return !u_is_touch(); }, },},In this example, Collapsify will only load on non-touch devices. This is useful for libraries that don’t make sense on certain devices or screen sizes.
When do I add a handler to an animation?
Section titled “When do I add a handler to an animation?”You’ll need to add a handler if your animation needs to instantiate a class, instead of being just an addition to gsap’s TL.
You can have an animation file that instances a class through an event, which would trigger its Handler.
// In the animation file that we execute through AutoAnimations// When the animation ends, we load a class using an Event// This example is from our website, terra-front repositoryvar tl = this.gsap.timeline({ defaults: { ease: "power2.out", duration: 0.6, }, onComplete: () => { // Dispatch event when animation completes if it's not a touch device if (!u_is_touch()) { this.eventSystem.loadEvent({library: 'InteractiveGrid', where: 'Hero Home'}); } } });Knowledge Check
Test your understanding of this section
Loading questions...