Skip to content

ESM Control Registration ​

For larger applications, it is recommended to define controls in separate files using ES modules and register them from a central location. This approach provides better code organization, tree-shaking support, and easier maintenance.

Project Structure ​

Controls are organized in a dedicated directory with each control in its own file:

resources/js/
├── controls.js              # Central registration file
└── controls/
    ├── popup-control.js
    ├── phone-input-control.js
    ├── address-input-control.js
    └── ...

Defining a Control Class ​

Each control is defined in its own file and exported as the default export. The class extends ControlBase imported from larajax.

js
// controls/popup-control.js
import { ControlBase } from "larajax";

export default class PopupControl extends ControlBase
{
    init() {
        this.$content = this.element.querySelector('.popup-content');
    }

    connect() {
        this.listen('click', '.popup-trigger', this.onTriggerClick);
    }

    onTriggerClick() {
        this.$content.classList.toggle('is-visible');
    }
}

Central Registration ​

Import all controls and register them in a single entry point file. This provides a clear overview of all available controls and their identifiers.

js
// controls.js
import { registerControl } from "larajax"

import PopupControl from './controls/popup-control';
import PhoneInputControl from './controls/phone-input-control';
import AddressInputControl from './controls/address-input-control';

// General
registerControl('popup', PopupControl);

// Form Components
registerControl('phone-input', PhoneInputControl);
registerControl('address-input', AddressInputControl);

TIP

Group related controls with comments to improve readability and make it easier to find specific registrations.

Lazy Registration ​

Controls that are large or appear on few pages can be imported on demand. Pass a function that returns the import instead of the class. The control file, and everything it imports, loads when the first element with that control appears on the page.

js
// controls.js
registerControl('code-editor', () => import('./controls/code-editor-control'));

Elements added while the control loads are connected once it is ready, and until then fetchControl() returns null for them. If the import fails, the error is reported and the elements stay unconnected. When bundling, enable code splitting so the import becomes a separate file (esbuild needs format: 'esm' and splitting: true).

Inside an element with the data-lazy-controls attribute, controls connect only when that element scrolls into view, so the import waits until then.

html
<div data-lazy-controls>
    <div data-control="code-editor"></div>
</div>

Benefits ​

This approach offers several advantages over inline registration:

  • Separation of concerns - Each control lives in its own file with focused responsibility
  • Tree-shaking - Bundlers can eliminate unused controls from production builds
  • Testability - Controls can be imported and tested in isolation
  • IDE support - Better autocomplete, navigation, and refactoring support
  • Reusability - Controls can be shared across projects via npm packages

Naming Conventions ​

Follow these conventions for consistency:

ItemConventionExample
File namekebab-case with -control suffixphone-input-control.js
Class namePascalCase with Control suffixPhoneInputControl
Control identifierkebab-case matching the componentphone-input

Including in Your Build ​

Ensure the central registration file is included in your JavaScript bundle entry point:

js
// app.js
import './controls';

This will execute the registration code and make all controls available to the page observer.