npm package: @schwingbat/component
Description: My personal, lightweight component-based app organization method.
Author: Tony McCoy
Latest version: 0.0.2

# Component

> ***WARNING***: Component is extremely early in development and may not work in real life exactly as this document indicates. This is currently more of a plan for what will be than a description of what is. I don't recommend using Component for anything serious just yet, and you will more than likely have to go read the source code to figure out how to use it. That being said, if you choose to use it anyway, have fun! :) If you have ideas for how to improve it, open an issue. Thanks!

Component is my take on a lightweight component framework. It requires a bit more manual setup to make data reactive, as there is no virtual DOM and the `updaters` model is a little bit different than most frameworks. Basically, instead of the framework automatically resolving the changes that need to be made, you define a function for each property in your component's state object where you write the DOM manipulation code yourself. The framework ensures that the function runs only when the data is different than the last time it ran, keeping DOM activity to a minimum.

Unlike React, the `render` function here runs _only once_ on the component's instantiation. Future state changes and resulting manipulations to the DOM are done by the handlers you write in `events` and `updaters`. This model allows for much less behind-the-scenes processing in order to update things. Where React is like a sledgehammer, Component is like a scalpel.

I make no promises that this way of doing things will work for anyone else, but if you like it, feel free to adopt it!

## Components

Components are basically a capsule for DOM structure and behaviors relating to a particular part of your app. Here's an example of one:

```javascript
const myComponent = new Component({
    // Supply any selector as 'anchor' and your component
    // will automatically append itself once initialized.
    anchor: "#app",
    state: {
        // Optional: initial state values
        clicks: 0
    },
    render(el, state) {
        return el("div", null, [
            el("h1", null, "Click Counter!"),
            el("p", null, "Click the button to increment the counter."),
            el("p", { id: "click-counter" }, `${state.clicks} clicks`),
            el("button", { id: "the-button" }, "Click me!")
        ]);

        /*
            DOM-wise, this returns:

            <div>
                <h1>Click Counter!</h1>
                <p>Click the button to increment the counter.</p>
                <p>0 Clicks</p>
                <button id="the-button">Click me!</button>
            </div>

            The 'el' function takes three parameters:
                - The tag name (div, li, span, canvas, etc.) Any valid HTML tag
                  can be used.
                - A properties object. These should be named exactly the same
                  way as their HTML counterparts, with the exception of "class",
                  which can be used either in quotes or using the className
                  property as in React. Both map to the element's 'class' property.
                - Children, which can be an array of multiple children or a single
                  child. Any strings will be treated as text nodes.

            -> el(tag, props, children)
        */
    },
    postrender() {
        // postrender() runs immediately after running 'render'
        // which makes this an excellent place to do anything you need to do
        // which requires accessing the component's DOM nodes.
        console.log("myComponent has rendered!");
    },
    events: {
        "click #the-button": function(e, el) {
            // Update the state of myComponent by incrementing 'clicks'
            // when an element with the ID of "#the-button" is clicked.
            this.update({ clicks: this.state.clicks + 1 });
        }
    },
    updaters: {
        clicks: function(value) {
            // Whenever the value of 'clicks' changes, this function runs.
            // If 'clicks' is set to the same value, this function doesn't run.

            // Any elements with an ID are stored in this.nodes
            // by their ID - converted to camelCase:
            // #click-counter ends up as this.nodes.clickCounter
            this.nodes.clickCounter.textContent = `${value} clicks`;
        }
    }
});
```

## Using Components Recursively

This is something that some frameworks do well, others do poorly and a few don't really allow at all. With Component, you can use the `clone` function of any component to create a carbon copy, even from inside its own render function. Similar to factory components, the `clone` function takes an object with which to update its state before rendering.

This could be useful for something like a file tree view, where items in the tree can either contain children (if they are a folder), or no children (if they are a file). If we want to use one component for all items in the tree view, we could do something like this:

```javascript
const treeItem = new Component({
    ...
    render(el, state) {
        const children = [
            el("span", { "class": "tree-item__title" }, state.name)
        ];

        if (state.children) {
            // Add a folder icon to the beginning of 'children'
            children.unshift( el("i", { "class": "icon folder-icon" }) );

            // Add a copy of this component for each child.
            children.push(
                el("ul", { "class": "tree-item__children" }, state.children.map(c => this.clone(c)));
            )
        }

        return el("li", { "class": "tree-item" }, children);
    },
    ...
})
```

In addition to using `this.clone()` inside the component, you can also clone it from outside. If you want to make infinitely many copies, but not necessarily recursively (say, a list of components), you can create a single component as your archetype, then `clone` it as many times as you want with custom state objects.