npm package: @gregoire.ciles/modular
Description: A simple and lightweight module system for JavaScript
Author: Grégoire Ciles
Latest version: 0.3.0

<h1 align="center">Modular</h1>
<p align="center">A simple and lightweight module system for JavaScript.</p>

## Fast Track ⏱️

Discover the library in **less than 5 minutes**.

Install [Node.js](https://nodejs.org/en/download/) and install SplitText using your favorite package
manager.

```bash
npm install @gregoire.ciles/modular
```

```bash
yarn add @gregoire.ciles/modular
```

```bash
pnpm add @gregoire.ciles/modular
```

Then, open your favorite code editor and create new files `index.html` and `index.ts`.

```html
<header data-module="header"></header>
```

```ts
import { Modular, Module } from '@gregoire.ciles/modular';

class Header extends Module<HTMLElement> {
  init(): void {
    console.log(this);
  }
}

const modular = new Modular({
  modules: {
    header: Header,
  },
});

modular.init();
```

Finally, open `index.html` in your browser and open the console to see the result.

## TypeScript Support

Modular is written in TypeScript and provides type definitions.

> **Note:** TypeScript is not required to use Modular.

> **💡 TIP:** Type annotations are very useful and help your IDE understand the type provided by
> Modular. <br /> The IDEs (VSCode, WebStorm, etc.) will be able to provide you with autocompletion
> and type hints.

## API

### Modular

#### `constructor(options: ModularOptions)`

Creates a Modular instance and registers provided modules.

##### `options`

| Name      | Type                                | Default | Description       |
| --------- | ----------------------------------- | ------- | ----------------- |
| `modules` | `Record<string, ModuleConstructor>` | `{}`    | A map of modules. |

#### `init()`

Initializes all registered modules. <br /> Calls the init and bind methods on each modules instance.

#### `destroy()`

Destroys all initialized modules. <br /> Calls the destroy method on each module instance.

#### `has(moduleName: string): boolean`

Find if the collection has a module registered with the given name.

##### `moduleName`

The name of the module. <br /> This is the value of the `data-module` attribute. <br /> For example,
if you have `<div data-module="header"></div>`, the module name is `header`.

### Module

#### `constructor(element: HTMLElement)`

Create a new instance of Module.

##### `ID`

Unique identifier for the module instance. <br /> This is the value of the `data-module-id`
attribute.

##### `name`

Name of the module, used for referencing and querying.

##### `element`

The DOM element associated with the module. <br /> This is the element with the `data-module`
attribute.

#### `init()`

Initializes the module. <br /> This method is called by Modular.

#### `destroy()`

Cleans up and destroys the module. <br /> This method is called by Modular.

#### `bind()`

Bind all methods to the module instance. <br /> Automatically called by the Modular class before the
init method.

```ts
import { Module } from '@gregoire.ciles/modular';

class Header extends Module {
  init(): void {
    this.element.addEventListener('click', this.handleClick);
  }

  bind(): void {
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick(): void {
    console.log(this);
  }
}
```

#### `call(moduleName: string, methodName: string, ...args: any[]): void`

Calls a method on all instances of a specified module by its name.

```html
<div>
  <button data-module="one">One</button>
  <button data-module="two">Two</button>
</div>
```

```ts
import { Modular, Module } from '@gregoire.ciles/modular';

class One extends Module<HTMLButtonElement> {
  init(): void {
    this.element.addEventListener('click', this.handleClick.bind(this));
  }

  handleClick(): void {
    this.call('two', 'method');
  }
}

class Two extends Module<HTMLButtonElement> {
  method(): void {
    console.log(this.name, 'method');
  }
}

const modular = new Modular({
  modules: {
    one: One,
    two: Two,
  },
});

modular.init();
```

#### `callById(id: number, methodName: string, ...args: any[]): void`

Calls a method on a module instance identified by its ID. <br /> The ID is generated by the compiler
and is unique for each module instance. <br /> This method is useful when you want to call a method
of a specific instance of a module. <br /> This is the element with the `data-module-id` attribute.
<br /> For example, if you have two instances of the same module on the same page, you can call a
method of a specific instance. <br />

```html
<div data-module="header" data-module-id="1"></div>
<div data-module="header" data-module-id="2"></div>
```

```ts
import { Module } from '@gregoire.ciles/modular';

class Header extends Module<HTMLDivElement> {
  init(): void {
    this.callById(1, 'method');
  }

  method(): void {
    console.log('method');
  }
}
```

#### `q<TReturnValue extends HTMLElement | HTMLElement[]>(selectors?: string, context?: HTMLElement): TReturnValue | null`

Queries the DOM for elements matching a specific selector within the module's context.

```html
<div data-module="header">
  <div class="logo" data-header="logo"></div>
</div>
```

```ts
import { Module } from '@gregoire.ciles/modular';

class Header extends Module<HTMLDivElement> {
  init(): void {
    const logo = this.q<HTMLElement>('logo');
  }
}
```

You can also add native selectors like `.`, `#`, etc.

```html
<div data-module="header">
  <button class="profile" data-header="button"></button>
  <button class="logout" data-header="button"></button>
</div>
```

```ts
import { Module } from '@gregoire.ciles/modular';

class Header extends Module<HTMLDivElement> {
  init(): void {
    const profileButton = this.q<HTMLButtonElement>('button.profile');
  }
}
```

#### `parent<T extends HTMLElement>(query: string, target: T): T | undefined`

Finds the first parent element of a specified target that matches a given query.

```html
<div data-module="header">
  <div data-header="wrapper">
    <button data-header="item">Item</button>
  </div>
</div>
```

```ts
import { Module } from '@gregoire.ciles/modular';

class Header extends Module<HTMLDivElement> {
  init(): void {
    const item = this.q<HTMLButtonElement>('item');
    const parentItem = this.parent('wrapper', item);
  }
}
```

#### `getData<T extends HTMLElement>(qualifiedName: string, target?: T): string | null`

Retrieves the value of a data attribute from the specified element.

```html
<div data-module="header" data-is-open="false"></div>
```

```ts
import { Module } from '@gregoire.ciles/modular';

class Header extends Module<HTMLDivElement> {
  init(): void {
    console.log(this.getData('is-open')); // false
  }
}
```

#### `setData<T extends HTMLElement>(qualifiedName: string, value: string, target?: T): void`

Sets the value of a data attribute on the specified element.

```html
<div data-module="header" data-is-open="false"></div>
```

```ts
import { Module } from '@gregoire.ciles/modular';

class Header extends Module<HTMLDivElement> {
  init(): void {
    this.setData('is-open', 'true');
    console.log(this.getData('is-open')); // true
  }
}
```

## Credits

[© Grégoire Ciles](https://github.com/GregoireCiles)