Skip to content

Component JavaScript

This guide explains how to create interactive JavaScript components in LamaPress.

Table of Contents


Overview

JavaScript components in LamaPress are automatically loaded when an element has a data-component attribute. Components export default classes with lifecycle methods for initialization, event binding, animations, and cleanup.

Component Structure

Basic Component

Create components/{type}/{name}/index.js:

javascript
import { app } from '@src/js/core/app'

export default class MyComponent {
  constructor(element) {
    this.element = element
    this.init()
  }

  init = () => {
    // Initialization logic
  }

  bindEvents = () => {
    // Event listeners
  }

  enter = () => {
    // Reveal animations
  }

  destroy = () => {
    // Cleanup
  }
}

Component Lifecycle

Components follow this lifecycle:

  1. constructor() - Called when component is created
  2. init() - Setup logic (called from constructor)
  3. bindEvents() - Attach event listeners (call in init)
  4. enter() - Reveal animations (called automatically)
  5. destroy() - Cleanup (called on page transitions)

Automatic Loading

data-component Attribute

Add data-component to HTML element:

php
<div class="ll-block--accordion" data-component="blocks/accordion">
  <!-- Component content -->
</div>

Format: {type}/{name}

Examples:

  • data-component="blocks/accordion"
  • data-component="sections/hero_basic"
  • data-component="parts/button"

Component Registration

Components are automatically:

  1. Detected via data-component attribute
  2. Loaded from components/{type}/{name}/index.js
  3. Instantiated with element as parameter
  4. Registered in component instance map

Component Methods

constructor()

Called when component is created:

javascript
constructor(element) {
  this.element = element
  this.init()
}

Parameters:

  • element - DOM element with data-component attribute

init()

Setup logic for component:

javascript
init = () => {
  this.setupElements()
  this.bindEvents()
}

Common tasks:

  • Cache DOM elements
  • Initialize variables
  • Call bindEvents()

bindEvents()

Attach event listeners:

javascript
bindEvents = () => {
  this.button?.addEventListener('click', this.handleClick)
  app.instances.get('eventManager').addScopeToEvent('scroll', 'page')
}

Best practice: Always call from init().

enter()

Reveal animations (called automatically):

javascript
enter = () => {
  gsap.from(this.element, {
    opacity: 0,
    y: 20,
    duration: 1,
  })
}

Note: Called after component is rendered.

destroy()

Cleanup on page transitions:

javascript
destroy = () => {
  this.timeline?.kill()
  this.button?.removeEventListener('click', this.handleClick)
}

Cleanup tasks:

  • Kill GSAP timelines
  • Remove event listeners
  • Clear intervals/timeouts

Accessing Core Instances

Access core instances via app.instances:

javascript
import { app } from '@src/js/core/app'

export default class MyComponent {
  init = () => {
    const scroller = app.instances.get('scroller')
    const device = app.instances.get('device')
    const router = app.instances.get('router')
  }
}

Available instances:

  • scroller - Scroll management
  • device - Device detection
  • router - Page routing
  • eventManager - Event system
  • page - Current page instance

Component Examples

Simple Interactive Component

javascript
import { app } from '@src/js/core/app'

export default class Accordion {
  constructor(element) {
    this.element = element
    this.allowMultiple = this.element.getAttribute('data-multiple') === 'true'
    this.items = [...this.element.querySelectorAll('[data-component="blocks/accordion_item"]')]
  }

  closeItems = (itemsToExclude = []) => {
    const itemsToClose = this.items.filter((item) => !itemsToExclude.includes(item))
    itemsToClose.map((item) => {
      app.instances.get('page').instances.get(item).close()
    })
  }
}

Component with Animation

javascript
import gsap from 'gsap'
import { ScrollTrigger } from 'gsap/all'

export default class AnimatedSection {
  constructor(element) {
    this.element = element
    this.init()
  }

  init = () => {
    this.title = this.element.querySelector('.js-title')
    this.content = this.element.querySelector('.js-content')
    
    gsap.set([this.title, this.content], {
      opacity: 0,
      y: 20,
    })
  }

  enter = () => {
    this.timeline = gsap.timeline({
      scrollTrigger: {
        trigger: this.element,
        start: 'top 80%',
      },
    })

    this.timeline.to(this.title, {
      opacity: 1,
      y: 0,
      duration: 0.6,
    })

    this.timeline.to(this.content, {
      opacity: 1,
      y: 0,
      duration: 0.6,
    }, '-=0.3')
  }

  destroy = () => {
    this.timeline?.kill()
    ScrollTrigger.getAll().forEach(trigger => {
      if (trigger.vars.trigger === this.element) {
        trigger.kill()
      }
    })
  }
}

Component with Event Listeners

javascript
import { app } from '@src/js/core/app'

export default class VideoPlayer {
  constructor(element) {
    this.element = element
    this.init()
  }

  init = () => {
    this.video = this.element.querySelector('video')
    this.playButton = this.element.querySelector('.js-play')
    this.pauseButton = this.element.querySelector('.js-pause')
    this.bindEvents()
  }

  bindEvents = () => {
    this.playButton?.addEventListener('click', this.play)
    this.pauseButton?.addEventListener('click', this.pause)
    this.video?.addEventListener('ended', this.onEnded)
  }

  play = () => {
    this.video?.play()
  }

  pause = () => {
    this.video?.pause()
  }

  onEnded = () => {
    // Handle video end
  }

  destroy = () => {
    this.playButton?.removeEventListener('click', this.play)
    this.pauseButton?.removeEventListener('click', this.pause)
    this.video?.removeEventListener('ended', this.onEnded)
  }
}

Best Practices

1. Use Arrow Functions

Use arrow functions for methods to preserve this:

javascript
// Good
handleClick = () => {
  // this.element is accessible
}

// Avoid
handleClick() {
  // this might be lost
}

2. Clean Up in destroy()

Always clean up in destroy():

javascript
destroy = () => {
  this.timeline?.kill()
  this.button?.removeEventListener('click', this.handleClick)
  clearInterval(this.interval)
}

3. Access Other Components

Access other component instances:

javascript
const otherComponent = app.instances.get('page').instances.get(element)

Next Steps:

Released under the MIT License.