# als-component

`als-component` is a lightweight JavaScript library for creating reactive, server-rendered, and browser-based components. It allows you to build dynamic components that work both on the server (Node.js) and in the browser, providing seamless reactivity, lifecycle hooks, and event handling.

## Installation

Install via NPM:

```bash
npm install als-component
```


## Quick Start

### Example for Node.js (Server)

```js
const Component = require('als-component');

class HelloComponent extends Component {
   constructor(props) { super(props) }

   render({ name }) {
      return `<div>Hello, ${name}!</div>`;
   }
}

const instance = new HelloComponent({ name: 'World' });
console.log(instance.call()); // Outputs: <div component="HelloComponent">Hello, World!</div>
```

### Example for Browser

```html
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>als-component Example</title>
   <script src="/node_modules/als-component/component.js"></script>
   <!-- Or minified version -->
   <script src="/node_modules/als-component/component.min.js"></script>
</head>
<body>
   <div component="App"></div>
   <script>
      class App extends Component {
         constructor(props) { super(props) }
         render({ count = 0 }) {
            return `
               <div>
                  <button click="${this.action('click', () => this.update({ count: count - 1 }))}">-</button>
                  <span>${count}</span>
                  <button click="${this.action('click', () => this.update({ count: count + 1 }))}">+</button>
               </div>
            `;
         }
      }

      const app = new App({ count: 0 });
      app.update();
   </script>
</body>
</html>
```

## API Reference

### `constructor(props = {}, inner)`

Initializes a new component.

- `props` *(Object)*: The initial properties of the component.
- `inner` *(String)*: The inner content of the component.

### `call()`

Renders the component and returns the HTML string.

**Example:**

```js
const html = instance.call();
console.log(html); // <div component="ComponentName">Rendered content</div>
```

### `update(props, inner)`

Updates the component with new properties and re-renders it.

**Example:**

```js
instance.update({ count: 5 });
```

### `action(event, fn)`

Creates an event action and binds it to the element.

- `event` *(String)*: The event type (e.g., 'click').
- `fn` *(Function)*: The event handler function.
- **Returns**: *(String)*: A unique action identifier.

**Example:**

```js
const clickId = instance.action('click', () => alert('Button clicked'));
```

### `on(event, fn)`

Registers a lifecycle hook.

- `event` *(String)*: The lifecycle event ('mount' or 'unmount').
- `fn` *(Function)*: The callback function.

**Example:**

```js
instance.on('mount', () => console.log('Component mounted'));
instance.on('unmount', () => console.log('Component unmounted'));
```

### `runActions()`

Binds all stored actions to the corresponding elements and triggers `mount` hooks.

## Asynchronous Rendering

The `render` method can be asynchronous. If it is an `async` function, the component handles it automatically.

**Example:**

```js
class AsyncComponent extends Component {
   async render() {
      const data = await fetchData();
      return `<div>${data}</div>`;
   }
}

const asyncInstance = new AsyncComponent();
asyncInstance.update();
```

### `async buildAsync(updateElement = true)` and `buildSync(updateElement = true)`

You can call for `buildAsync` (return promise) or `buildSync` with `updateElement` flag to update or not to update the element.

### `set elementOuter`

You can manualy set outerHTML for component's element. If element is not exists, it simply will do nothing. 


## Lifecycle Hooks

### `mount`

Called when the component is added to the DOM.

### `unmount`

Called when the component is removed from the DOM.

**Example:**

```js
instance.on('mount', () => console.log('Mounted'));
instance.on('unmount', () => console.log('Unmounted'));
```

## Event Handling

Use `action()` to bind events to elements within the component.

**Example:**

```js
class ClickComponent extends Component {
   render() {
      return `<button click="${this.action('click', () => alert('Button clicked'))}">Click Me</button>`;
   }
}
```

## Nested Components

You can nest components within each other. The parent component can include a child component using `call()`.

**Example:**

```js
class ChildComponent extends Component {
   render() {
      return `<span>Child Component</span>`;
   }
}

class ParentComponent extends Component {
   render() {
      return `<div>Parent: ${new ChildComponent().call()}</div>`;
   }
}

const parentInstance = new ParentComponent();
parentInstance.update();
```

## Memory Management

The `als-component` library uses `WeakMap` to store component instances, ensuring that they are garbage collected when removed from the DOM.

**Example:**

```js
const instance = new Component();
document.body.appendChild(instance.element);
instance.update();
instance.element.remove();
instance.runActions();
```

The component is removed from `WeakMap` automatically when it is no longer referenced.

## Optimization Tips

- Use `update()` sparingly to avoid excessive DOM manipulations.
- Prefer using asynchronous `render` for components that require data fetching.
- Use unique keys for components to avoid collisions.

## Error Handling

When using asynchronous methods or complex nested components, ensure you handle potential errors gracefully.

**Example:**

```js
try {
   await instance.update();
} catch (error) {
   console.error('Error updating component:', error);
}
```


## Example

```html
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <script src="/component.js"></script>
   <title>Users</title>
</head>
<body>
   <div component="Users"></div>
</body>
<script>
   class User extends Component {
      constructor(props) { super(props) }

      remove() {
         const {user,usersComponent} = this.props
         // const usersComponent = this.Component.components.Users // Another way to get component
         usersComponent.update({users:usersComponent.props.users.filter(({id}) => id !== user.id)})
      }

      render({user}) {
         const {id,email,age,gender,username,firstName} = user
         return /*html*/`<div>
            ${firstName}
            <button click="${this.action('click',() => this.remove())}">delete</button>
         </div>`
      }
   }

   class Users extends Component {
      constructor(props={},inner) {
         super(props,inner)
      }

      async render({users}) {
         if(users === undefined) {
            fetch('https://dummyjson.com/users')
            .then(res => res.json())
            .then(data => {
               this.update({users:data.users})
            });
            return /*html*/`<div>Fething users</div>`
         }
         return /*html*/`<div>
            ${users.map(user => new User({user,key:user.id,usersComponent:this}).call()).join('')}
         </div>`
      }
   }

   const usersComponent = new Users()
   usersComponent.update()

</script>
</html>
```

