A couple of weeks ago, I start writing a custom component that allows rendering a list of other components dynamically. One of its features shows the list up or down, according to the space available in the viewport (or the screen).
A solution to this type of problem may require the use of the web APIs available through the Window interface.
The Web APIs
API comes from Application Programming Interface, and according to MDN:
Application Programming Interfaces (APIs) are constructs made available in programming languages to allow developers to create complex functionality more easily. They abstract more complex code away from you, providing some easier syntax to use in its place.
In the same way, the Web currently comes with a large number of Web APIs we can use through JavaScript(or TypeScript). You can find more detailed information about them here.
The Window Interface
The Window interface represents a window containing a DOM document. In the practical world, we usually use the global window
variable to get access to it.
For example, open the browser's console, and type the following command there:
window.document
Once you run the previous command, you should get a reference to the current document, which is contained by the window object. In Google Chrome, you'll see a selection effect applied over the whole page. Give it a try!
Let's try with another example. Type the next command in your browser's console:
window.history
The output will be something like this:
As you can see, the result is a History
object. This is a read-only reference, and can be used to change the current browser history as the next example shows:
const history = window.history;
history.back();
All the above examples were about access to properties. However, the window
object also provides useful methods. For example:
window.open();
window.find('pattern');
The first method call will open a new window browser, and the second one will search the given string within the current view.
Using the APIs
First, let's define the HTML template of the list to be rendered dynamically:
<div class="list">
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<li>Item 6</li>
<li>Item 7</li>
</ul>
</div>
Now, let's apply some styles so that it stands out from the container.
.list {
display: none;
border: 1px solid #00458b;
min-width: 200px;
position: absolute;
background: #eee;
}
As you may note, the list won't be rendered by default(display: none
), and we're going to set its position from the code.
Listening to a Mouse Click Event
Let's create a TypeScript file (index.ts
) so that we can start using the Web APIs, and the window
object.
As the next step, we'll need to "catch" the mouse click event globally. Then, the window
reference can be helpful for that:
// index.ts
window.addEventListener('click', (event: MouseEvent) => {
console.log('click', event.x, event.y);
});
Once you run the previous code snippet, the browser's console will print the click event coordinates.
Getting the List from the DOM
The HTML List we created can be accessed using the following JavaScript code:
const listElement = window.document.querySelector('.list') as HTMLDivElement;
- The
window.document
will return a reference to the Document object contained in the window. - The
querySelector
method will return the first element that matches with the selector(.list
). - Since TypeScript is being used, we can use the
as
syntax to perform a type assertion.
Rendering the List Dynamically
Before rendering the list dynamically, we'll need to perform some calculations based on the list size, and the container.
Let's get the list size using the getBoundingClientRect()
method:
const boundingRect = listElement.getBoundingClientRect() as DOMRect;
This method returns a DOMRect
object, which contains the coordinates and the size of the element with other properties.
const viewportHeight = window.innerHeight;
The window.innerHeight
property returns the interior height of the window, in pixels.
Let's read the mouse click event coordinates too:
let { x, y } = event;
In this case, we'll do a calculation for the y-axis coordinate:
if (event.y + boundingRect.height >= viewportHeight) {
console.log('render upwards');
y = y - boundingRect.height;
} else {
console.log('render downwards');
}
The above code snippet will make sure the list can fit below. Otherwise, it will render it upwards the click position.
As a final step, we'll need to update the visibility of the list, and update the position of if after every click event:
listElement.style.display = 'block';
listElement.style.left = `${x}px`;
listElement.style.top = `${y}px`;
Live Demo
Want to play around with this code? Just open the embedded Stackblitz editor:
Conclusion
In this article, I described a useful technique to rely on existing Web APIs to solve a problem. Keep in mind this example is a proof of concept only and you may need to perform additional calculations or accessing different window
properties.
Either way, feel free to reach out on Twitter if you have any questions. Follow me on GitHub to see more about my work.