Create a JavaScript library. Build MVP
Tags: javascript, beginners, webdev, sideprojects
Published: 2020-06-13 08:42:42

It's time to use the template that we developed in [the previous article](https://dev.to/alexandrshy/creating-a-template-repository-in-github-1d05) 🚀

I want to develop my own small side project. In a nutshell, this will be a JavaScript library for working with modal windows. I want to cover all the steps from creating the library itself (in our case, from creating a template for the library 😄), to publishing documentation and presenting the resulting project

And again, I recorded my entire process on video 👋

{% youtube 56NxcIXxgsQ %}

## Let's start preparing

Creating a repository using a template with a configured build for the project. Then we clone the repository 

```
git clone git@github.com:Alexandrshy/keukenhof.git
```

and don't forget to install all the dependencies

```
cd keukenhof
yarn
```

Since we use the GitHub Actions to build and publish the package, we need to create tokens for `GitHub` and `npm` and add them to the secrets

You also need to make the following changes to the `package.json` file (since it is a copy for the template, it has some irrelevant fields). If you are creating a clean project just add your description

```json
  "name": "keukenhof",
  "description": "Lightweight modal library 🌷",
  "repository": {
    "type": "git",
    "url": "https://github.com/Alexandrshy/keukenhof"
  },
  "keywords": [
    "javascript",
    "modal",
    "dialog",
    "popup"
  ],
  "bugs": {
    "url": "https://github.com/Alexandrshy/keukenhof/issues"
  },
  "homepage": "https://github.com/Alexandrshy/keukenhof",
```

This is where we finished the preparation, we move on to writing code

## MVP Development

Reserve the name of our library in window

```js
export const Keukenhof = ((): KeukenhofType => {})();

window.Keukenhof = Keukenhof;
```

To describe the `Keukenhof` type, we need to understand what interface we'll have in MVP. I'll only define the `init` function which on the basis of markup is to initialize the handler to open the modal

```js
export type ConfigType = {
    selector?: string;
    triggers?: HTMLElement[];
    openAttribute?: string;
    closeAttribute?: string;
    openClass?: string;
};

export type KeukenhofType = {
    init: (config?: ConfigType) => void;
};
```

The configuration object will have the following fields:

* `openClass`: class name that will be added to the modal window when it opens;
* `selector`: modal window selector with which to interact;
* `triggers`: list of nodes click on which will open a modal window;
* `openAttribute`: `data attribute` of the element's connection (usually a button) with the modal window;
* `closeAttribute`: `data attribute` for the element that will register the click and close the current modal window

Write the init function:

```js
/**
 * Initialize modal windows according to markup
 *
 * @param {ConfigType} config - modal window configur
 */
const init = (config?: ConfigType) => {
    const options = {openAttribute: ATTRIBUTES.OPEN, ...config};
    const nodeList = document.querySelectorAll<HTMLElement>(`[${options.openAttribute}]`);
    const registeredMap = createRegisterMap(Array.from(nodeList), options.openAttribute);
};

return {init};
```

The `init` function finds a list of elements containing an attribute to open (if this attribute was not overridden in the configuration object, we use the default `ATTRIBUTES.OPEN`, we have it moved to a separate file with constants). Since one modal window can be opened by clicking on several elements, we need to map all modal windows to all elements that have openAttribute. To do this, we write the function `createRegisterMap`:

```js
    const createRegisterMap = (nodeList: HTMLElement[], attribute: string) => {
        // Accumulating an object where the key is the modal window selector, and the value is the element that will open the corresponding modal window
        return nodeList.reduce((acc: {[key: string]: HTMLElement[]}, element: HTMLElement): {
            [key: string]: HTMLElement[];
        } => {
            // Get the value from the attribute
            const attributeValue = element.getAttribute(attribute);
            // If there is no value, just skip the item
            if (!attributeValue) return acc;
            // If the element is encountered for the first time, add it to the accumulator and write an empty array
            if (!acc[attributeValue]) acc[attributeValue] = [];
            acc[attributeValue].push(element);
            return acc;
        }, {});
    };
```

After we received the map of modal windows that need to be initialized, we iterate each element of the map and create instances of Modal:

```js
for (const selector in registeredMap) {
    const value = registeredMap[selector];
    options.selector = selector;
    options.triggers = [...value];
    modal = new Modal(options);
}
```

Let's start describing the Modal class itself:

```js
/**
 * Modal window
 */
class Modal {

    /**
     * Modal constructor
     *
     * @param {ConfigType} param - config
     */
    constructor({
        selector = '',
        triggers = [],
        openAttribute = ATTRIBUTES.OPEN,
        closeAttribute = ATTRIBUTES.CLOSE,
        openClass = 'isOpen',
    }: ConfigType) {
        this.$modal = document.querySelector(selector);
        this.openAttribute = openAttribute;
        this.closeAttribute = closeAttribute;
        this.openClass = openClass;

        this.registerNodes(triggers);
    }

    /**
     * Add handlers for clicking on elements to open related modal windows
     *
     * @param {Array} nodeList  - list of elements for opening modal windows
     */
    registerNodes(nodeList: HTMLElement[]) {
        nodeList
            .filter(Boolean)
            .forEach((element) => element.addEventListener('click', () => this.open()));
    }
}

export const ATTRIBUTES = {
    OPEN: 'data-keukenhof-open',
    CLOSE: 'data-keukenhof-close',
}; 
```

The `registerNodes` method adds click handlers for buttons with the `data-keukenhof-open` attribute. I advise you to use constants for string elements to avoid errors and make future refactoring easier. The `open` method now we can describe in just one line

```js
/**
 * Open moda window
 */
open() {
    this.$modal?.classList.add(this.openClass);
}
```

Now, we can "open" our modal window 🎉 I think you understand what the `close` method will look like

```js
/**
 * Close modal window
 */
close() {
    this.$modal?.classList.remove(this.openClass);
}
```

And to call this method, you need to add click handlers for elements with the attribute `data-keukenhof-close`. We'll do it when opening a new modal window, so as not to keep handlers for modal windows that are closed


```js
/**
 * Click handler
 *
 * @param {object} event - Event data
 */
onClick(event: Event) {
    if ((event.target as Element).closest(`[${this.closeAttribute}]`)) this.close();
}
```

We need to bind the `this` value in constructor

```js
this.onClick = this.onClick.bind(this);
```

Implementing separate methods for removing and adding click handlers

```js
/**
 * Add event listeners for an open modal
 */
addEventListeners() {
    this.$modal?.addEventListener('touchstart', this.onClick);
    this.$modal?.addEventListener('click', this.onClick);
}

/**
 * Remove event listener for an open modal
 */
removeEventListeners() {
    this.$modal?.removeEventListener('touchstart', this.onClick);
    this.$modal?.removeEventListener('click', this.onClick);
}
```

We'll add click handlers when opening a modal window, and delete when closing

```js
open() {
    this.$modal?.classList.add(this.openClass);
    this.addEventListeners();
}

close() {
    this.$modal?.classList.remove(this.openClass);
    this.removeEventListeners();
}
```

Well, that's it, the minimum functionality is ready 🚀 It might seem that our solution is redundant, for a library that simply adds and removes a class. And at the moment this is true, but it gives us the opportunity to expand our functionality in the future, which I plan to do 🙂

Library Usage Example
{% codepen https://codepen.io/alexandrshy/pen/YzwWXbz %}

Link to [the repository on GitHub](https://github.com/Alexandrshy/keukenhof)

Link to upcoming improvements in [the Roadmap (https://github.com/Alexandrshy/keukenhof#roadmap)

## Conclusion

I hope my article was useful to you. Follow me on [dev.to](https://dev.to/alexandrshy), on [YouTube](https://www.youtube.com/channel/UCs4L_M9a97FGaU3EHKSjSag), on [GitHub](https://github.com/Alexandrshy). Soon I'll continue this series of articles and I'll definitely share my results with you 👋