StringTune/Docs

Custom Modules

Element vs Global Modules

Choose whether your custom module should attach to objects or run as a global runtime participant.

Element vs Global Modules

Before writing code, decide whether your module is object-driven or runtime-driven.

Element modules

Element modules connect to StringObject instances discovered in the DOM.

Use an element module when:

  • behavior is declared in markup
  • you need per-element attributes
  • you write output to one element and its mirrors
  • you want onObjectConnected(...), objects, and object.events

Typical shape:

TypeScript
export class StringMyModule extends StringModule {
  constructor(context: StringContext) {
    super(context);
    this.htmlKey = 'my-module';
  }
}
HTML
<div string="my-module" string-my-value="24"></div>

Global modules

Global modules participate in the runtime without requiring string="..." on elements.

Use a global module when:

  • behavior is app-wide
  • no per-object connection step is needed
  • you mainly react to scroll, pointer, viewport, or events
  • the module manages its own DOM querying or global classes

Typical shape:

TypeScript
export class StringMyGlobalModule extends StringModule {
  override onSubscribe(): void {
    this.events.on('scroll:start', this.onScrollStartEvent);
  }

  override onUnsubscribe(): void {
    this.events.off('scroll:start', this.onScrollStartEvent);
  }
}
TypeScript
stringTune.use(StringMyGlobalModule);

How connection actually works

For element modules, the runtime flow is:

  1. ObjectManager creates a StringObject from a DOM element.
  2. The module is considered connectable when canConnect(object) returns true.
  3. By default, canConnect(...) checks whether object.keys contains htmlKey.
  4. The runtime calls initializeObject(...).
  5. The runtime calls calculatePositions(...).
  6. The runtime calls connectObject(...), which triggers onObjectConnected(...) on first connection.
  7. The module keeps that object in objectsOnPage and objects collections.

Global modules skip the object connection path unless they override canConnect(...) or manually manage objects.

Core module vs UI module

StringModule also has an internal type bucket:

  • type = 1 means core module
  • type = 2 means UI module

Built-ins like StringMagnetic switch to type = 2.

For most custom modules, keep the default unless you have a concrete reason to separate the module into the UI queue. The runtime still dispatches the same lifecycle names to both buckets.

Practical rule

Start with an element module unless you are sure the feature is global.

That keeps the contract clearer:

  • string="..."
  • mapped attributes
  • object-local output

Once that shape stops fitting the feature, move to a global module intentionally rather than by accident.