Create a JavaScript library. Add callbacks
Tags: javascript, typescript, webdev, tutorial
Published: 2020-07-07 10:42:03

And here is a new part of creating a library of modal windows in JavaScript. This time we are implementing two small improvements. First, we'll add the ability to use callbacks to the configuration. And secondly, we'll improve the keyboard control. By tradition, I'm sharing a video version with you, for those who want to see how I wrote it 🎬

{% youtube biGhmm0ly5w %}

## Callback

As per [MDN](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function): "A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action". A small use case:

```js
const addition = (a, b) => a + b;
const multiplication = (a, b) => a * b;
const count = (a, b, callback) => callback(a, b);

console.log(count(10, 20, addition)); // 30
console.log(count(10, 20, multiplication)); // 200
```

In our example, the first two functions `addition` and` multiplication` simply perform a mathematical action with two parameters and return the result of the calculation. But the count method takes three parameters, the first two are numbers, and the third is the action that you need to do with numbers. This is the callback. In this context, such an example may seem redundant. All the convenience of callbacks is revealed when we need to wait for any action or result

And this perfectly shows the situation that can occur when using a library with `hasAnimation`. If we need to perform some kind of functionality not immediately after clicking on the button that will open the modal window, but only after it is fully opened, callbacks will help us.

Let's add this code:

```js
constructor({
    ...
    onOpen = () => {},
    onClose = () => {},
    beforeOpen = () => true,
    beforeClose = () => true,
}: ConfigType) {
    this.$modal = document.querySelector(selector);

    this.onOpen = onOpen;
    this.onClose = onClose;
    this.beforeOpen = beforeOpen;
    this.beforeClose = beforeClose;
    ...
}

close(event?: Event) {
    const isContinue = this.beforeClose(event);
    if (!isContinue) return;
    ...
    this.preparationClosingModal(event);
}

preparationClosingModal(event?: Event) {
    if (this.hasAnimation) {
        const handler = () => {
            ...
            this.onClose(event);
            this.$modal?.removeEventListener('animationend', handler);
        };
        this.$modal?.addEventListener('animationend', handler);
    } else {
        ...
        this.onClose(event);
    }
}
```

For the open method, we'll need to do the same with `this.onOpen` and` this.beforeClose`.

The `this.onOpen` and` this.onClose` methods play the role of events that report the corresponding action of the modal window. Such methods will be called as soon as the animation ends at the modal window (or immediately if the animation is disabled). Such methods are conveniently used, for example, to send analytics in order to track interactive user actions.

The `this.beforeOpen` and` this.beforeClose` methods, as you may have noticed, have a slight difference, they should return a boolean value. This is done intentionally to add flexibility in window configuration. For example, it is convenient to use such methods to block a modal window until the animation is completed (if opening animation takes considerable time, this may be necessary), or to block the state of the window until a certain action is taken by the user (such as filling out a feedback form).

As you can see, we added just a few methods, but significantly expanded the configuration options.

## Keyboard control

The main idea of ​​implementation is to prepare the library for the final parts, which will implement support for accessibility and convenient keyboard control.

This time we'll add one small action, which for me personally is very convenient. This closes the modal window by clicking on `Esc`.

And if you try to look for solutions to track `Esc`, you'll most likely see this code:

```js
document.addEventListener('keyup', function (event) {
    if (event.keyCode === 27) console.log('Esc button was pressed');
});
```

And then one interesting embarrassment happened. If you watched my video, you could see that to determine which key was pressed, I used [keyCode](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode)

```js
onKeydown(event: KeyboardEvent) {
    if (event.keyCode === KEY_CODE.ESC) this.close();
}
```

But if you look at the code now, you'll see another solution.

It happened because `keyCode` has been the standard way to determine the type of key pressed for many years. This has great support for browsers. But the fact is that now this is deprecated and it is no longer recommended to use it.

`keyCode` was deprecated because in practice it was "inconsistent across platforms and even the same implementation on different operating systems or using different localizations." The new recommendation is to use [key](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key) or [code](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code).

However, there are also minor difficulties, the fact is that `KeyboardEvent.key` is implemented differently in different browsers. For example, in IE11 `KeyboardEvent.key` uses `Esc` rather than `Escape` for the corresponding keyboard button, because it was implemented before the specification was completed. More detailed browser support can be found [here](https://caniuse.com/#search=KeyboardEvent.key).

This will look like an implementation with compatibility support for older browsers

```js
export const KEY = {
    ESC: 'Esc',
    ESCAPE: 'Escape',
    CODE: 27,
}

addEventListeners() {
    document.addEventListener('keyup', this.onKeyup);
}

removeEventListeners() {
    document.removeEventListener('keyup', this.onKeyup);
}

/**
 * Keyboard press handler
 *
 * @param {KeyboardEvent} event - Event data
 */
onKeyup(event: KeyboardEvent) {
    const key = event.key || event.keyCode;
    if (key === KEY.ESCAPE || key === KEY.ESC || key === KEY.CODE) this.close(event);
}
```

However, we can leave a more compact form, since we don't need support on so many old browsers

```js
/**
 * Keyboard press handler
 *
 * @param {KeyboardEvent} event - Event data
 */
onKeyup(event: KeyboardEvent) {
    if (event.key === KEY.ESCAPE || event.key === KEY.ESC) this.close(event);
}
```

Now, with the modal window open, we have a handler for clicking the Esc key on the keyboard. This handler calls the `close` method and after closing the modal window we remove the click handler. You can see the complete solution in the repository.

{% github Alexandrshy/keukenhof %}

___

Next time we will consider a very extensive topic of accessibility when working with modal windows. Subscribe, it'll be interesting! See you soon 👋