Skip to content

Drawing HTML Elements upwards/downwards dynamically in the Screen

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

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:

A window.history output example

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.

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

You might also like

Testing a Fastify app with the NodeJS test runner cover image

Testing a Fastify app with the NodeJS test runner

Introduction Node.js has shipped a built-in test runner for a couple of major versions. Since its release I havenā€™t heard much about it so I decided to try it out on a simple Fastify API server application that I was working on. It turns out, itā€™s pretty good! Itā€™s also really nice to start testing a node application without dealing with the hassle of installing some additional dependencies and managing more configurations. Since itā€™s got my stamp of approval, why not write a post about it? In this post, we will hit the highlights of the testing API and write some basic but real-life tests for an API server. This server will be built with Fastify, a plugin-centric API framework. They have some good documentation on testing that should make this pretty easy. Weā€™ll also add a SQL driver for the plugin we will test. Setup Let's set up our simple API server by creating a new project, adding our dependencies, and creating some files. Ensure youā€™re running node v20 or greater (Test runner is a stable API as of the 20 major releases) Overview * index.js - node entry that initializes our Fastify app and listens for incoming http requests on port 3001 * app.js - this file exports a function that creates and returns our Fastify application instance * sql-plugin.js - a Fastify plugin that sets up and connects to a SQL driver and makes it available on our app instance Application Code A simple first test For our first test we will just test our servers index route. If you recall from the app.js code above, our index route returns a 501 response for ā€œnot implementedā€. In this test, we're using the createApp function to create a new instance of our Fastify app, and then using the inject method from the Fastify API to make a request to the / route. We import our test utilities directly from the node. Notice we can pass async functions to our test to use async/await. Nodeā€™s assert API has been around for a long time, this is what we are using to make our test assertions. To run this test, we can use the following command: By default the Node.js test runner uses the TAP reporter. You can configure it using other reporters or even create your own custom reporters for it to use. Testing our SQL plugin Next, let's take a look at how to test our Fastify Postgres plugin. This one is a bit more involved and gives us an opportunity to use more of the test runner features. In this example, we are using a feature called Subtests. This simply means when nested tests inside of a top-level test. In our top-level test call, we get a test parameter t that we call methods on in our nested test structure. In this example, we use t.beforeEach to create a new Fastify app instance for each test, and call the test method to register our nested tests. Along with beforeEach the other methods you might expect are also available: afterEach, before, after. Since we donā€™t want to connect to our Postgres database in our tests, we are using the available Mocking API to mock out the client. This was the API that I was most excited to see included in the Node Test Runner. After the basics, you almost always need to mock some functions, methods, or libraries in your tests. After trying this feature, it works easily and as expected, I was confident that I could get pretty far testing with the new Node.js core APIā€™s. Since my plugin only uses the end method of the Postgres driver, itā€™s the only method I provide a mock function for. Our second test confirms that it gets called when our Fastify server is shutting down. Additional features A lot of other features that are common in other popular testing frameworks are also available. Test styles and methods Along with our basic test based tests we used for our Fastify plugins - test also includes skip, todo, and only methods. They are for what you would expect based on the names, skipping or only running certain tests, and work-in-progress tests. If you prefer, you also have the option of using the describe ā†’ it test syntax. They both come with the same methods as test and I think it really comes down to a matter of personal preference. Test coverage This might be the deal breaker for some since this feature is still experimental. As popular as test coverage reporting is, I expect this API to be finalized and become stable in an upcoming version. Since this isnā€™t something thatā€™s being shipped for the end user though, I say go for it. Whatā€™s the worst that could happen really? Other CLI flags ā€”watch - https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--watch ā€”test-name-pattern - https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--test-name-pattern TypeScript support You can use a loader like you would for a regular node application to execute TypeScript files. Some popular examples are tsx and ts-node. In practice, I found that this currently doesnā€™t work well since the test runner only looks for JS file types. After digging in I found that they added support to locate your test files via a glob string but it wonā€™t be available until the next major version release. Conclusion The built-in test runner is a lot more comprehensive than I expected it to be. I was able to easily write some real-world tests for my application. If you donā€™t mind some of the features like coverage reporting being experimental, you can get pretty far without installing any additional dependencies. The biggest deal breaker on many projects at this point, in my opinion, is the lack of straightforward TypeScript support. This is the test command that I ended up with in my application: Iā€™ll be honest, I stole this from a GitHub issue thread and I donā€™t know exactly how it works (but it does). If TypeScript is a requirement, maybe stick with Jest or Vitest for now šŸ™‚...

I Broke My Hand So You Don't Have To (First-Hand Accessibility Insights) cover image

I Broke My Hand So You Don't Have To (First-Hand Accessibility Insights)

We take accessibility quite seriously here at This Dot because we know it's important. Still, throughout my career, I've seen many projects where accessibility was brushed aside for reasons like "our users don't really use keyboard shortcuts" or "we need to ship fast; we can add accessibility later." The truth is, that "later" often means "never." And it turns out, anyone could break their hand, like I did. I broke my dominant hand and spent four weeks in a cast, effectively rendering it useless and forcing me to work left-handed. I must thus apologize for the misleading title; this post should more accurately be dubbed "second-hand" accessibility insights. The Perspective of a Developer Firstly, it's not the end of the world. I adapted quickly to my temporary disability, which was, for the most part, a minor inconvenience. I had to type with one hand, obviously slower than my usual pace, but isn't a significant part of a software engineer's work focused on thinking? Here's what I did and learned: - I moved my mouse to the left and started using it with my left hand. I adapted quickly, but the experience wasn't as smooth as using my right hand. I could perform most tasks, but I needed to be more careful and precise. - Many actions require holding a key while pressing a mouse button (e.g., visiting links from the IDE), which is hard to do with one hand. - This led me to explore trackpad options. Apart from the Apple Magic Trackpad, choices were limited. As a Windows user (I know, sorry), that wasn't an option for me. I settled for a cheap trackpad from Amazon. A lot of tasks became easier; however, the trackpad eventually malfunctioned, sending me back to the mouse. - I don't know a lot of IDE shortcuts. I realized how much I've been relying on a mouse for my work, subconsciously refusing to learn new keyboard shortcuts (I'll be returning my senior engineer license shortly). So I learned a few new ones, which is good, I guess. - Some keyboard shortcuts are hard to press with one hand. If you find yourself in a similar situation, you may need to remap some of them. - Copilot became my best friend, saving me from a lot of slow typing, although I did have to correct and rewrite many of its suggestions. The Perspective of a User As a developer, I was able to get by and figure things out to be able to work effectively. As a user, however, I got to experience the other side of the coin and really feel the accessibility (or lack thereof) on the web. Here are a few insights I gained: - A lot of websites apparently _tried_ to implement keyboard navigation, but failed miserably. For example, a big e-commerce website I tried to use to shop for the aforementioned trackpad seemed to work fine with keyboard navigation at first, but once I focused on the search field, I found myself unable to tab out from it. When you make the effort to implement keyboard navigation, please make sure it works properly and it doesn't get broken with new changes. I wholeheartedly recommend having e2e tests (e.g. with Playwright) that verify the keyboard navigation works as expected. - A few websites and web apps I tried to use were completely unusable with the keyboard and were designed to be used with a mouse only. - Some sites had elaborate keyboard navigation, with custom keyboard shortcuts for different functionality. That took some time to figure out, and I reckon it's not as intuitive as the designers thought it would be. Once a user learns the shortcuts, however, it could make their life easier, I suppose. - A lot of interactive elements are much smaller than they should be, making it hard to accurately click on them with your weaker hand. Designers, I beg you, please make your buttons bigger. I once worked on an application that had a "gloves mode" for environments where the operators would be using gloves, and I feel like maybe the size we went with for the "gloves mode" should be the standard everywhere, especially as screens get bigger and bigger. - Misclicking is easy, especially using your weaker hand. Be it a mouse click or just hitting an Enter key on accident. Kudos to all the developers who thought about this and implemented a confirmation dialog or other safety measures to prevent users from accidentally deleting or posting something. I've however encountered a few apps that didn't have any of these, and those made me a bit anxious, to be honest. If this is something you haven't thought about when developing an app, please start doing so, you might save someone a lot of trouble. Some Second-Hand Insights I was only a little bit impaired by being temporarily one-handed and it was honestly a big pain. In this post, I've focused on my anecdotal experience as a developer and a user, covering mostly keyboard navigation and mouse usage. I can only imagine how frustrating it must be for visually impaired users, or users with other disabilities, to use the web. I must confess I haven't always been treating accessibility as a priority, but I've certainly learned my lesson. I will try to make sure all the apps I work on are accessible and inclusive, and I will try to test not only the keyboard navigation, ARIA attributes, and other accessibility features, but also the overall experience of using the app with a screen reader. I hope this post will at least plant a little seed in your head that makes you think about what it feels like to be disabled and what would the experience of a disabled person be like using the app you're working on. Conclusion: The Humbling Realities of Accessibility The past few weeks have been an eye-opening journey for me into the world of accessibility, exposing its importance not just in theory but in palpable, daily experiences. My short-term impairment allowed me to peek into a life where simple tasks aren't so simple, and convenient shortcuts are a maze of complications. It has been a humbling experience, but also an illuminating one. As developers and designers, we often get caught in the rush to innovate and to ship, leaving behind essential elements that make technology inclusive and humane. While my temporary disability was an inconvenience, it's permanent for many others. A broken hand made me realize how broken our approach towards accessibility often is. The key takeaway here isn't just a list of accessibility tips; it's an earnest appeal to empathize with your end-users. "Designing for all" is not a checkbox to tick off before a product launch; it's an ongoing commitment to the understanding that everyone interacts with technology differently. When being empathetic and sincerely thinking about accessibility, you never know whose life you could be making easier. After all, disability isn't a special condition; it's a part of the human condition. And if you still think "Our users don't really use keyboard shortcuts" or "We can add accessibility later," remember that you're not just failing a compliance checklist, you're failing real people....

How to Build a Slideshow App Using Swiper and Angular cover image

How to Build a Slideshow App Using Swiper and Angular

Google Slides and Microsoft PowerPoint are the most popular options to build presentations nowadays. However, have you ever considered having a custom tool where you can use your web development knowledge to create beautiful slides? In this post, we will build a slideshow app with the ability to render one slide at a time using Angular, Swiper, and a little bit of CSS. Project Setup Prerequisites You'll need to have installed the following tools in your local environment: - Node.js. Preferably the latest LTS version. - A package manager. You can use either NPM or Yarn. This tutorial will use NPM. Creating the Angular Project Let's start creating a project from scratch using the Angular CLI tool. ` This command will initialize a base project using some configuration options: - --routing. It will create a routing module. - --prefix corp. It defines a prefix to be applied to the selectors for created components(corp in this case). The default value is app. - --style scss. The file extension for the styling files. - --skip-tests. It avoids the generations of the .spec.ts files, which are used for testing. Creating the Slides Component We can create a brand new component to handle the content of the application's slides. We can do that using the command mg generate as follows: ` The output of the previous command will show the auto-generated files. Update the Routing Configuration Remember we used the flag --routing while creating the project? That parameter has created the main routing configuration file for the application: app-routing.module.ts. Let's update it to be able to render the slides component by default. ` Update the App Component template Remove all code except the router-outlet placeholder: ` This will allow you to render the slides component by default once the routing configuration is running. Using Swiper What is Swiper? Swiper is a popular JavaScript library that lets you create transitions that can work on websites, mobile web apps, and mobile native/hybrid apps. It's available for all modern frameworks and it's powered with top-notch features you can find on the official website. Installing Swiper The plugin is available via NPM and you can use the following command to install it in the project. ` Updating the Application Module Next, we'll need to update the application module before starting to use Swiper. ` Using swiper element in the Template It's time to work in the slide component, and make use of the plugin along with the available options. ` For a better understanding, let's describe what's happening in the above template: * The element will create a Swiper instance to be rendered in the component. * slidesPerView will set the number of slides visible at the same time. * navigation will render the navigation buttons to slide to the left and right. * pagination will set the pagination configuration through an object. * keyboard will enable navigation through the keyboard. * virtual will enable the virtual slides feature. This is important in case you have several slides, and you want to limit the amount of them to be rendered in the DOM. * class will set a class to customize the styling. * swiperSlide is an Angular directive that helps to render a slide instance. One important note here is the custom container created under the swiperSlide directive allows us to customize the way we can render every slide. In this case, it's used to set a layout for every slide, and make sure to render it centered, and using the whole height of the viewport. Set the Swiper Configuration As you may note, the template will require additional configurations, and we'll need to create a couple of slides for the component. ` In the above code snippet, the component imports the SwiperCore class along with the required modules. Next, the component needs to install those using a SwiperCore.use([]) call. Later, a BehaviorSubject is created to emit all slides content once the component gets initialized. Using Swiper Styles Since the current application is configured to use SCSS styles, we'll need to import the following styles into the slides.component.scss file: ` Of course, these imports will work with the latest version of Swiper(version 8 at the time of writing this article). However, some users have found some issues while importing Swiper styles (mainly after doing an upgrade from Swiper v6 and v7). For example: ` or an error like this: ` If you got any of those issues, you can give it a try with the following imports instead: ` For the purpose of this demo, we'll attach additional styling to make it work: ` The .my-swiper selector will set the appropriate height for every slide. On other hand, the .swiper-slide-container selector will provide a layout as a slide container. And this is how it will look on your web browser. Live Demo and Source Code Want to play around with the final application? Just open the following link in your browser: https://luixaviles.github.io/slideshow-angular-swiper. Find the complete angular project in this GitHub repository: slideshow-angular-swiper. Do not forget to give it a star ā­ļø and play around with the code. Feel free to reach out on Twitter if you have any questions. Follow me on GitHub to see more about my work....

Svelte 5 is Here! cover image

Svelte 5 is Here!

Svelte 5 was finally released after a long time in development. Fortunately, we've been able to test it for some time, and now it has a stable release. Let's dig into its features and why this is such a significant change, even though Svelte 4 code is almost 100% compatible. Svelte syntax everywhere This is one of my favorite additions. Previously, Svelte syntax was limited to a component element. Refactoring code from a component and moving it to a JavaScript file worked differently. Now you can use the .svelte.js or .svelte.ts extension, which allows you to use the same syntax everywhere. It's important to note that it's a way to express that this is not just JS, and what you write may be compiled to something else, just like .svelte files are not just html even though they look very similar. Runes The introduction of runes is one of the most significant changes in Svelte. Many users felt attracted to variables being instantly reactive in previous versions. ` There was, however, a lot of magic underneath and a considerable amount of work from the compiler to make it behave reactively. $state In Svelte 5, a reactive variable has to be explicitly declared using the $state rune, which brings a clear distinction between what is reactive and what isnā€™t. ` In the previous code, $state is not an actual function being called; it's a hint for the compiler to do something special with this declaration. Rune names always start with a dollar sign ($) and do not need to be imported. However, thereā€™s much more to the changes than just the way we declare reactive variables. Runes bring a lot of new features that make Svelte 5 a great improvement. In Svelte 5, objects or arrays use Proxies to allow for granular reactivity, meaning that individual properties in an object are reactive, even if they are nested objects or arrays. If you modify a property, it will only update that property and will not trigger an update on the whole object. It also supports triggering updates when methods like array.push are called. In previous versions, an assignment was required to trigger an update: ` To have a similar behavior of svelte 4 (no deep reactivity), use the $state.raw() rune. This syntax of .* is common for related features of a rune: $state.raw() will require an assignment to trigger reactivity. ` Because proxies are used, you may need to extract the underlying state instead of the proxy itself. In that case, $state.snapshot() should be used: ` $derived We will use $derived and $derived.by to declare a derived state. The two runes are essentially the same, except one allows us to use a function instead of an expression, allowing for more complex operations to calculate the derived values. ` $effect Effects are useful for running something triggered by a change in state. One of the things you'll note from the $effect rune is that dependencies don't need to be explicit. Those will be picked from reactive values read synchronously in the body of the effect function. ` Something to bear in mind is that these dependencies are picked each time the effect runs. The values read during the last run become the dependencies of the effect. ` Depending on the result of the random method, foo or bar will stop being a dependency of the effect. You should place them outside the condition so they can trigger reruns of the effect. *Variants* of the effect rune are $effect.pre, which runs before a DOM update, and $effect.tracking(), which checks for the context of the effect (true for an effect inside an effect or in the template). $props This rune replaces the export let keywords used in previous versions to define a component's props. To use the $props syntax, we can consider it to return an object with all the properties a component receives. We can use JavaScript syntax to destructure it or use the rest property if we want to retrieve every property not explicitly destructured.. ` $bindable If you want to mutate a prop so that the change flows back to the parent, you can use the $bindable prop to make it work in both directions. A parent component can pass a value to a child component, and the child component is able to modify it, returning the data back to the parent component. ` $inspect The $inspect rune only works in dev mode and will track dependencies deeply. By default, it will call console.log with the provided argument whenever it detects a change. To change the underlying function, use $inspect.with().with): ` $host The host rune gives access to the host element when compiling as a custom element: ` ` Other changes Another important change is that component events are just props in Svelte 5, so you can destructure them using the $props() rune: ` A special children property can be used to project content into a component instead of using slots. Inside the components, use the [@render[(https://svelte.dev/docs/svelte/@render) tag to place them. ` Optional chaining (?.) prevents from attempting to render it if no children are passed in. If you need to render different components in different places (named slots before Svelte 5, you can pass them as any other prop, and use @render with them. Snippets Snippets allow us to declare markup slices and render them conveniently using the render tag (@render). They can take any number of arguments. ` Snippets can also be passed as props to other components. ` ` Conclusion Some exciting changes to the Svelte syntax were introduced while, at the same time, maximum compatibility efforts were made. This was a massive rewrite with numerous improvements in terms of performance, bundle size, and DX. Besides that, a new CLI has been released, making the whole experience of starting a project or adding features delightful. If you haven't tried Svelte before, it's a great time to try it now....

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co