Classes Structure
Introduction
Section titled “Introduction”Classes in the NETWRIX project follow Terra’s strict structural conventions, ensuring consistency, maintainability, and compatibility with the SWUP transition system. Every class adheres to specific patterns for DOM management, initialization, event handling, and cleanup.
Understanding these conventions is crucial for creating components that integrate seamlessly with the framework’s Handler system and lifecycle management.
Core Structure
Section titled “Core Structure”Every Terra class follows this foundation:
class ComponentName { constructor({ element, param1, param2 }) { this.DOM = { element: element, }; this.param1 = param1; this.param2 = param2;
this.init(); this.events(); }
init() { // Initialization logic }
events() { // Event listener setup }
destroy() { // Cleanup logic }}The Four Pillars
Section titled “The Four Pillars”- Constructor - Parameter handling and DOM storage
- init() - Initialization and setup logic
- events() - Event listener registration
- destroy() - Cleanup and memory management
Constructor Section
Section titled “Constructor Section”DOM Object
Section titled “DOM Object”All classes must include a this.DOM = {} object to store DOM element references.
Rules:
- The primary element must be stored in
this.DOM.element - Use the
js--prefix for all JavaScript-targeted elements - Only use
querySelectoror ID for the primary element - Never use
querySelectorAllfor the primary element
constructor({ element, config }) { this.DOM = { element: element, // Primary element (required) buttons: element.querySelectorAll('.js--button'), // Child elements container: element.querySelector('.js--container'), };}Why js— Prefix?
Section titled “Why js— Prefix?”The js-- prefix distinguishes elements used by JavaScript from those used only for styling:
<!-- ✅ JavaScript target --><div class="js--slider c--slider-a"> <button class="js--slider-next c--slider-a__next"></button></div>
<!-- ❌ Confusing - which is for JS? --><div class="slider"> <button class="slider-next"></button></div>Benefits:
- Clear separation of concerns (JS vs CSS)
- Prevents accidental class removal during styling
- Easy to identify interactive elements in HTML
Parameter Handling
Section titled “Parameter Handling”All class parameters should be passed as a single object:
// ✅ Correct - Object parametersnew Slider({ element: document.querySelector('.js--slider'), autoplay: true, speed: 400,});
// ❌ Wrong - Positional parametersnew Slider(element, true, 400);Store Parameters as Instance Properties:
constructor({ element, autoplay, speed }) { this.DOM = { element: element, };
// Store for use in other methods this.autoplay = autoplay; this.speed = speed;
this.init(); this.events();}The init() Method
Section titled “The init() Method”Purpose: Handles initialization logic, setting initial states, and preparing the class for interaction.
Use Cases:
- Setting up initial animations
- Creating internal state
- Initializing third-party libraries (sliders, carousels)
- Setting default values
- Creating timelines
Do NOT include event listeners in init() - that’s what events() is for.
Example: Slider Initialization
Section titled “Example: Slider Initialization”init() { // Import and initialize tiny-slider this.slider = tns({ container: this.DOM.element, items: 1, slideBy: 'page', autoplay: this.autoplay, speed: this.speed, nav: this.nav, });
console.log('Slider initialized with speed:', this.speed);}Example: Animation Setup
Section titled “Example: Animation Setup”init() { // Set initial state for animation gsap.set(this.DOM.items, { opacity: 0, y: 20, });
// Create animation timeline this.timeline = gsap.timeline({ paused: true }); this.timeline.to(this.DOM.items, { opacity: 1, y: 0, stagger: 0.1, duration: 0.6, });}When to Skip init()
Section titled “When to Skip init()”If a class only handles events with no initialization needed:
class ClickCounter { constructor({ element }) { this.DOM = { element }; this.count = 0; // No init() needed - only tracking clicks this.events(); }
events() { this.clickHandler = () => this.handleClick(); this.DOM.element.addEventListener('click', this.clickHandler); }
handleClick() { this.count++; }}The events() Method
Section titled “The events() Method”Purpose: Exclusively manages event listener setup. Keep all addEventListener calls here.
Rules:
- Add event listeners only (no initialization logic)
- Store handler references for cleanup
- Use arrow functions or bind handlers to preserve
this
Storing Handler References
Section titled “Storing Handler References”Critical for proper cleanup in destroy():
events() { // ✅ Store handler reference this.clickHandler = (event) => this.handleClick(event); this.scrollHandler = () => this.handleScroll();
this.DOM.element.addEventListener('click', this.clickHandler); window.addEventListener('scroll', this.scrollHandler);}Method Organization
Section titled “Method Organization”Keep handlers in separate methods for clarity:
events() { this.clickHandler = (event) => this.handleClick(event); this.mouseEnterHandler = () => this.pause(); this.mouseLeaveHandler = () => this.play();
this.DOM.element.addEventListener('click', this.clickHandler); this.DOM.element.addEventListener('mouseenter', this.mouseEnterHandler); this.DOM.element.addEventListener('mouseleave', this.mouseLeaveHandler);}
handleClick(event) { // Click handling logic console.log('Button clicked!');}
pause() { // Pause logic}
play() { // Play logic}Conditional Event Listeners
Section titled “Conditional Event Listeners”Based on configuration:
events() { // Always add this listener this.clickHandler = () => this.handleClick(); this.DOM.element.addEventListener('click', this.clickHandler);
// Only add if hover controls are enabled if (this.controlsOnHover) { this.mouseEnterHandler = () => this.pause(); this.mouseLeaveHandler = () => this.play();
this.DOM.element.addEventListener('mouseenter', this.mouseEnterHandler); this.DOM.element.addEventListener('mouseleave', this.mouseLeaveHandler); }}The destroy() Method
Section titled “The destroy() Method”Critical for SWUP: Every class must include a destroy() method to prevent memory leaks during page transitions.
What to Clean Up
Section titled “What to Clean Up”- Remove Event Listeners - Prevent memory leaks
- Destroy Third-Party Instances - Slider libraries, etc.
- Clear Timelines - GSAP animations
- Null References - Handler functions and DOM elements
Complete Example
Section titled “Complete Example”destroy() { // 1. Remove event listeners this.DOM.element.removeEventListener('click', this.clickHandler);
if (this.controlsOnHover) { this.DOM.element.removeEventListener('mouseenter', this.mouseEnterHandler); this.DOM.element.removeEventListener('mouseleave', this.mouseLeaveHandler); }
// 2. Clear handler references this.clickHandler = null; this.mouseEnterHandler = null; this.mouseLeaveHandler = null;
// 3. Destroy third-party instances if (this.slider && this.slider.destroy) { this.slider.destroy(); this.slider = null; }
// 4. Clear GSAP timelines if (this.timeline) { this.timeline.kill(); this.timeline = null; }
// 5. Clear DOM references this.DOM = null;
// 6. Clear configuration this.autoplay = null; this.speed = null;}Why destroy() is Critical
Section titled “Why destroy() is Critical”Without proper cleanup:
// Page 1: User visits, slider initializesnew Slider({ element: document.querySelector('.js--slider') });
// User clicks link, SWUP transitions to Page 2// ❌ Without destroy(): Event listeners still active on removed DOM// ❌ Memory leak: Old slider instance remains in memory// ❌ Performance: Multiple instances accumulate
// With destroy() called by Handler:// ✅ Event listeners removed// ✅ Memory freed// ✅ Clean slate for new pageDocumentation Requirements
Section titled “Documentation Requirements”Every class must include detailed documentation explaining:
- Purpose - What the class does
- Parameters - What arguments it accepts
- Example Usage - How to instantiate it
Documentation Template
Section titled “Documentation Template”/**** * ClassName * * [Brief description of what this class does and its purpose] * * Parameters: * - element (DOM Element): [Description of the primary element] * - param1 (type): [Description and default value if any] * - param2 (type): [Description and default value if any] * * Example Usage: * ```javascript * new ClassName({ * element: document.querySelector('.js--component'), * param1: value1, * param2: value2, * }); * ``` ****/
class ClassName { // Class implementation}Real Example: Slider
Section titled “Real Example: Slider”/**** * Slider Class * * Creates an interactive content slider with navigation controls, * autoplay functionality, and touch support. Built on top of tiny-slider. * * Parameters: * - element (DOM Element): The container element for the slider * - autoplay (boolean): Whether to automatically advance slides (default: false) * - speed (number): Transition speed in milliseconds (default: 400) * - nav (boolean): Show navigation dots (default: true) * - controls (boolean): Show prev/next arrows (default: true) * * Example Usage: * ```javascript * new Slider({ * element: document.querySelector('.js--slider'), * autoplay: true, * speed: 600, * nav: true, * controls: true, * }); * ``` ****/
class Slider { constructor({ element, autoplay = false, speed = 400, nav = true, controls = true }) { this.DOM = { element: element, }; this.autoplay = autoplay; this.speed = speed; this.nav = nav; this.controls = controls;
this.init(); this.events(); }
// ... rest of implementation}Handling Multiple Instances
Section titled “Handling Multiple Instances”When you need multiple instances of a class on a page:
HTML Structure
Section titled “HTML Structure”<div class="js--slider" data-autoplay="true" data-speed="600"> <!-- Slider content --></div>
<div class="js--slider" data-autoplay="false" data-speed="400"> <!-- Different slider content --></div>Instantiation Pattern
Section titled “Instantiation Pattern”Use querySelectorAll and forEach:
const sliderElements = document.querySelectorAll('.js--slider');
sliderElements.forEach((element) => { new Slider({ element: element, autoplay: element.dataset.autoplay === 'true', speed: parseInt(element.dataset.speed) || 400, });});❌ Don’t use querySelectorAll on the primary element parameter:
// ❌ Wrong - creates one instance with ALL slidersnew Slider({ element: document.querySelectorAll('.js--slider'), // Don't do this});Handler Integration
Section titled “Handler Integration”In NETWRIX, Handlers manage multiple instances automatically:
class SliderHandler extends CoreHandler { events() { this.emitter.on("MitterContentReplaced", async () => { this.DOM = this.updateTheDOM;
// Handler creates one instance per element super.assignInstances({ elementGroups: [{ elements: this.DOM.sliderElements, // NodeList config: this.config, }], }); }); }}Real-World Example: InfiniteMarquee
Section titled “Real-World Example: InfiniteMarquee”import { horizontalLoop } from '@andresclua/infinite-marquee-gsap';import gsap from 'gsap';import { u_stringToBoolean } from '@andresclua/jsutil';
/**** * InfiniteMarquee Class * * Creates an infinite horizontal marquee that loops its child elements. * Supports speed control, direction reversal, and pause on hover. * * Parameters: * - element (DOM Element): The DOM element containing marquee items * - speed (number): Animation speed multiplier (default: 1) * - reversed (boolean): Whether to move in reverse (default: false) * - controlsOnHover (boolean): Pause on hover (default: false) * * Example Usage: * ```javascript * new InfiniteMarquee({ * element: document.querySelector('.js--marquee'), * speed: 2, * reversed: true, * controlsOnHover: true * }); * ``` ****/
class InfiniteMarquee { constructor(payload) { this.DOM = { element: payload.element, };
// Parse boolean from string if needed const reversed = u_stringToBoolean(payload.reversed); this.reversed = payload.reversed === undefined || payload.reversed === null ? false : reversed;
this.speed = payload.speed === undefined ? 1 : payload.speed; this.controlsOnHover = payload.controlsOnHover === undefined ? false : payload.controlsOnHover;
this.init(); this.events(); }
init() { // Initialize the horizontal loop animation this.loop = horizontalLoop(this.DOM.element.children, { paused: false, repeat: -1, reversed: this.reversed, speed: this.speed, }); }
events() { // Only add hover controls if enabled if (this.controlsOnHover) { this.mouseEnterHandler = () => this.pause(); this.mouseLeaveHandler = () => this.play();
this.DOM.element.addEventListener("mouseenter", this.mouseEnterHandler); this.DOM.element.addEventListener("mouseleave", this.mouseLeaveHandler); } }
pause() { gsap.to(this.loop, { timeScale: 0, overwrite: true }); }
play() { gsap.to(this.loop, { timeScale: 1, overwrite: true }); }
destroy() { // Remove event listeners if (this.controlsOnHover) { this.DOM.element.removeEventListener("mouseenter", this.mouseEnterHandler); this.DOM.element.removeEventListener("mouseleave", this.mouseLeaveHandler);
this.mouseEnterHandler = null; this.mouseLeaveHandler = null; }
// Clear GSAP loop if (this.loop) { this.loop.kill(); this.loop = null; }
// Clear all properties this.speed = null; this.reversed = null; this.controlsOnHover = null; this.DOM = null; }}
export default InfiniteMarquee;Best Practices
Section titled “Best Practices”✅ Do’s
Section titled “✅ Do’s”-
Always include this.DOM - Even for simple classes
this.DOM = { element: element }; -
Use js— prefix - For all JavaScript-targeted elements
<div class="js--slider c--slider-a"></div> -
Store handler references - Essential for cleanup
this.clickHandler = (e) => this.handleClick(e); -
Include destroy() - Required for SWUP compatibility
destroy() {this.DOM.element.removeEventListener('click', this.clickHandler);this.clickHandler = null;this.DOM = null;} -
Document thoroughly - Help future developers
/***** ComponentName* [Full documentation with examples]****/ -
Use default parameters - Make classes flexible
constructor({ element, speed = 400, autoplay = false }) { } -
Separate concerns - init() for setup, events() for listeners
-
Export as default - Consistent import pattern
export default ClassName;
❌ Don’ts
Section titled “❌ Don’ts”- Don’t skip documentation
- Don’t use querySelectorAll for primary element
- Don’t forget to null references in destroy()
- Don’t add listeners directly in constructor
- Don’t use anonymous functions in addEventListener
- Don’t mix initialization and event logic
Common Patterns
Section titled “Common Patterns”Pattern 1: GSAP Animation Class
Section titled “Pattern 1: GSAP Animation Class”class FadeInAnimation { constructor({ element, duration = 0.6, delay = 0 }) { this.DOM = { element }; this.duration = duration; this.delay = delay; this.init(); }
init() { this.timeline = gsap.timeline({ paused: true }); this.timeline.from(this.DOM.element, { opacity: 0, y: 20, duration: this.duration, delay: this.delay, }); }
play() { this.timeline.play(); }
destroy() { if (this.timeline) { this.timeline.kill(); this.timeline = null; } this.DOM = null; }}Pattern 2: Form Validation Class
Section titled “Pattern 2: Form Validation Class”class FormValidator { constructor({ element, onSuccess, onError }) { this.DOM = { element: element, inputs: element.querySelectorAll('input, textarea'), submit: element.querySelector('button[type="submit"]'), }; this.onSuccess = onSuccess; this.onError = onError; this.events(); }
events() { this.submitHandler = (e) => this.handleSubmit(e); this.DOM.element.addEventListener('submit', this.submitHandler); }
handleSubmit(event) { event.preventDefault(); const isValid = this.validate();
if (isValid) { this.onSuccess && this.onSuccess(); } else { this.onError && this.onError(); } }
validate() { // Validation logic return true; }
destroy() { this.DOM.element.removeEventListener('submit', this.submitHandler); this.submitHandler = null; this.DOM = null; this.onSuccess = null; this.onError = null; }}Pattern 3: Data Attribute Configuration
Section titled “Pattern 3: Data Attribute Configuration”class LazyVideo { constructor({ element }) { this.DOM = { element };
// Read configuration from data attributes this.videoSrc = element.dataset.videoSrc; this.autoplay = element.dataset.autoplay === 'true'; this.loop = element.dataset.loop === 'true';
this.init(); this.events(); }
init() { // Create video element this.video = document.createElement('video'); this.video.src = this.videoSrc; this.video.autoplay = this.autoplay; this.video.loop = this.loop; this.DOM.element.appendChild(this.video); }
events() { this.playHandler = () => this.play(); this.DOM.element.addEventListener('click', this.playHandler); }
play() { this.video.play(); }
destroy() { this.DOM.element.removeEventListener('click', this.playHandler); this.playHandler = null;
if (this.video) { this.video.pause(); this.video.remove(); this.video = null; }
this.DOM = null; }}Summary
Section titled “Summary”Terra’s class structure ensures:
- Consistency - All classes follow the same pattern
- Maintainability - Clear separation between setup, events, and cleanup
- SWUP Compatibility - destroy() prevents memory leaks
- Readability - Well-documented, self-explanatory code
- Team Alignment - Everyone writes classes the same way
The Four Essential Elements:
- this.DOM - Store all element references
- init() - Setup and initialization
- events() - Event listener registration
- destroy() - Complete cleanup
By adhering to these conventions, your classes will integrate seamlessly with NETWRIX’s Handler system and work reliably across page transitions.
Related Topics
Section titled “Related Topics”- 🔄 Handlers System - Component lifecycle management
- 🎬 Animations - GSAP animation patterns
- 📦 Libraries - Available utilities and tools
- 📚 Framework Overview - Architecture context