📜 ⬆️ ⬇️

Handling keystrokes aka shortcuts and debugging

Hello! It will be about hotkeys in WEBAPI + JavaScript, we will consider their ways of organizing and the problems that arise primarily in large applications.


Consider how to handle keys on a specific task.


"Task"


Imagine that in an existing project you need to implement keyboard input processing. At the same time, the project interface and its controllers, for purely historical reasons, such as they are. And they are the following:


ParentController in which there are two components with their own states and state. Controller1 and the element that uses CTRL+SHIFT+F to search the site, and Controller2 with its DOM element, which is a local area, in the presence of which the search is performed inside it. At the same time they can be on the screen at the same time. Below are several ways to solve this problem.


1. “ KeyboardEvent and its manual processing”


KeyboardEvent objects describe the keyboard user experience. Each event describes a key; The event type (keydown, keypress, or keyup) determines the type of action produced.


Sounds great doesn't it? Let's take a closer look.
Consider intercepting keystrokes CTRL+SHIFT+F , usually corresponding to a global search call.


 element.addEventListener('keypress', (event) => { const keyName = event.key; // Приведение к нижнему регистру имени клавиши обязательно // т.к. при нажатии вместе с SHIFT оно будет в верхнем регистре if (event.ctrlKey && event.shiftKey && event.key.toLowerCase() === 't') { alert('CTRL+SHIFT+T pressed'); } }); 

Now, we can apply to our problem in two ways (for example)


Perform key interception in controllers 1 and 2 separately


This will lead to the fact that depending on the order in the DOM, you may need to useCapture to ensure the order of processing Controller2 and then Controller1. So you get isolated logic, but if the application is complex and there are many such controllers, this solution is not good. some may be on the screen at the same time and they may have their own strict processing order, which does not depend on their position in the DOM tree. (see bubbling and capturing )


Perform key interception in CommonController


An alternative solution may be to handle clicks in a common parent controller, which knows exactly when to show its children, controlled by the first and second controllers. This, while increasing the child controllers, will not cause difficulties in catching events and making decisions on which controller to handle the keys. However, there will be another problem - a thick if appears in the parent controller, which handles all possible cases. For large applications, this solution is not suitable, because at some point, another Controller may appear that is not a child of the ParentController then you have to ParentController handler to a higher level, to their common parent, and so on ... Sooner or later one of the controllers will not know too much about the elements inside it.



In fact, only 80% of browsers can work with KeboardEvent.key , in all others you will need to operate KeboardEvent.keyCode : Number key codes. Which greatly complicates life. Here it is worth going to the description of the disadvantages of this approach.


Minuses:



But natively, there are no unnecessary dependencies and libraries.


Then we will discuss two libraries, one of which was designed to solve their own similar problems.


2. “Using the HotKeys library”


Three thousand stars on a githaba, modest size and no dependencies. The Chinese manufacturer promises us a solution that will suit everyone. However, we will not hurry. Let's try to solve our problem with its help.


 // обработчик клавиш hotkeys('ctrl+shift+f', function(event, handler){ alert('CTRL+SHIFT+T pressed'); }); 

The syntax is already much shorter, and the main chip for solving the problem will be the direct display of the components of controllers 1 and 2 on the screen. A little digging through the library code makes it easy to see that handlers form a stack that fills or clears as they are registered on the screen (Assume an element with a handler that appeared later than an existing one will have priority in the hot key processing queue).


Often it happens so that the element that should intercept processing appears later. In this case, we can safely spread the logic of handling handles to each of the controllers. And other type of chips, help us to separate one stream of clicks from another. But in the case when the порядок появления на экране ≠ приоритету обработки нажатий - the same problems arise as with the native eventListener's. We'll have to make everything in a common parent controller.


In addition, it often happens that you need to block the default behavior, but the event is not considered processed (in other words, there is no unambiguous understanding of whether the event is processed or not, if we received it) or must be processed by two controllers at the same time. One of which will cause a reaction to the behavior, and the other will just take into account what the event was.


Total advantages:



Minuses:



Suitable for solving typical tasks, but with the writing of a trading terminal or a large admin panel there will be problems with debugging for sure.


3. “Using stack-shortcuts library”


As a result of many rakes and attempts to use other people's decisions, I had to make my own bicycle A library that helps to debug first of all will retain all the best properties of the popular ones and contribute something new.


What tasks were solved during the creation?



 // подписка this.shortcuts = shortcuts({ 'CMD+SHIFT+F': function (event, next) { alert('CMD+SHIFT+F pressed'); } }); // утилизация this.shortcuts.destroy(); 

Applicable to our problem, the solution completely coincides with the previous library. There is still no complete separation of processing logic without undue knowledge of each other, but much has become simpler and clearer. Thanks to the following:



Debugging Handlers are arranged in such a way that a callstack of them is formed before the call. It allows you to see in the console the entire chain of events passing from the first handler to the next.


next () - The function call means that the event has not been processed and will be passed to the next handler. Quite a familiar contract, which is used in intermediate handlers or middleware in express . So you will always know whether the event is processed or simply mutated or "taken into account".



This is how the call stack looks like if you put a breakpoint in one of them.


Well, about the cons:




Source: https://habr.com/ru/post/438770/