Smart UI Components

The Current Model

At a macro level, designs are typically optimized for page size. For the most part, this makes complete sense, as you’ll rarely want to serve the same page full of content to a smartphone as you would serve to a standard desktop user, and you would rarely want to serve a pared-down page to a desktop user like you might to a smartphone user.

There are some drawbacks to designing this way, from the implementation side of things. One major issue is that, depending on the complexity of the layout, the prioritized content, etc, you may need to bloat your page with additional content, which may or may not be shown, as it may be shown or hidden based on media query breakpoints. Another issue is that any individual page components that may need to be differentiated based on its own available screen real estate has no way to do so. Since media queries are based on aspects of the overall page, they’re unreliable for use at the component level.

A glaring example of this is in the use of data grid-style components. You know the ones. Some monstrosity of a table, no matter how well-styled, possibly with sortable columns, checkboxes to highlight rows, all sorts of fancy interactions, and a ton of data. There’s no good way to display something like that in a mobile form factor, and that’s ok sometimes. Sometimes we can get away with rendering a couple of different variations of these, and use media queries to show and hide them as appropriate, as mentioned up above.

But what happens when some developer wants to take your big, bloated data grid and stick it into a container that was never meant to hold something that size? What if they want to take a tabular list of online users and stick it into a 300-pixel left column? Any number of things: it could blow out that column’s width and shift every other element in the page; it could trigger a horizontal scrollbar within the column itself, forcing the user to scroll through this narrow container’s contents in order to reveal the full grid of data; it might even trigger a horizontal scrollbar on the data grid itself, revealing 1-2 columns of data at a time while the user tries to make sense of it.

A New Proposal

Ideally, when rendering a data grid on different device types, we would render data in both a depth and style appropriate for the device at hand. On a desktop browser, we should just be able to throw as many columns and rows on the screen as we want (within reason). On a tablet, we can probably get away with most of the same thing, but we may want to prioritize the data we’re displaying, so that instead of 10-12 columns worth of items for each record, we maybe pare it down to 5-6 columns of the more important items for each record. On a smartphone, we might take it a step further, not only shrinking back to 3-4 of the most important items per record, but maybe even removing the “grid” aspect altogether, and making a vertically-tiled list of cards that render those data points in a nicely-formatted way for reading on a phone screen.

Wouldn’t it be nice if we could author a single component for use within an application, and have it lay itself out in the most appropriate fashion, no matter what type of device it was on, and no matter where in the page it lived? Ideally, if we took our previous example of dropping a grid full of user data into a 300-pixel left column on the page, it could see that it only has 300 pixels to render into, and automatically use its “mobile” layout, rendering a set of vertical tiles of only the most important data. If that component later cut-and-pasted from the left column into the main body of the page, it may render itself in tablet or desktop modes, depending on how much room it had to spread itself into.

Smart UI components should be able to both determine the size of the screen real estate they’ve personally been given to fill, and render themselves appropriately, based on that container size. Using a library like React means that we can wrap that “smart” aspect up under a single root component, which becomes easily portable throughout the applicable, and a perfect metaphor for the semantics of web design, wherein you’re simply dropping in a UserTable or a SelectableList or some other kind of idiomatic component, without having to think about where it may render on the page, or on what form factor or device it will be viewed by your end users.

One major win for this style of development is that you don’t need to even try to stick to a singular HTML implementation that can be tweaked with CSS properties based on media queries, nor do you need to bloat your page with multiple variations of these components, showing or hiding them with media queries. The component itself can simply choose an appropriate layout for itself, and render only that one. In addition, by giving up the idea of media queries, we don’t run the risk of other containers on the page screwing up our layout.

Imagine having a side pane hidden, with a hamburger icon to display it, but when it transitions into view, rather than overlapping your content, it squeezes in alongside it, adjusting the shape of your other page elements. With traditional media queries, nothing would be changed at all, and you may potentially squeeze your other components to illegibility. By using container sizes to control the rendering of your components, this is absolutely accounted for, and a new layout may be selected if its container is squeezed past a certain point.

Your smart components may own as much or as little of their size delineations as you allow. They may completely internalize those decisions and strictly maintain their own layout governance, allow an external consumer to specify different components to be used at different container sizes, or provide a mixed mode that allows for sane defaults to be applied, while also allowing consumers to override those defaults with props.

Implementation Steps: ResizeObserver Component

If we were to walk through what a setup like this might entail, we’ll need to first define some foundational aspects from which we can later compose these smart components. First and foremost, we’ll need some way to measure our current container, and inform our smart components of exactly how much space they have to use on the page. One method for that, the one we’ll use here, is the ResizeObserver API. Another method, which has been requested for years and looks to finally be coming to fruition, is container queries, which would allow us to specify some of these rendering aspects right in our CSS, in a similar syntax to media queries. While this can solve for certain simple use cases, like tweaking CSS properties to change order and layout styles based on container sizes, it will still suffer from the same need to bloat your page with additional variations of unused markup in more complex scenarios.

Creating a ResizeObserver component in React is a snap. It doesn’t need to render anything itself, it just needs to register a callback to be invoked when its container is resized, and then render any child components specified by its consumer, passing that height and width information down to them. For situations like this, I prefer to use a render callback via the children prop, and simply inject the data points as inputs to the callback. Take a look at the src/components/resize-observer.js file in the demo below to see just how we do this, then check out the src/components/resizer.js file for an example of how we might consume it to make a Resizer component that simply reports its dimensions.

ResizeObserver Demo

This component simply subscribes a ResizeObserver, creates a ref to attach to that container, then passes the height/width of the container, along with the ref, to a child component via a render callback function. Using this, we can now create a ContainerQuery component that does nothing but store measurement references to be used for determining when to render its children. We’ll use a render callback function again, so that no child component instances are created and discarded unnecessarily. They won’t be constructed until a match is determined, and their children function is invoked.

Implementation Steps: ContainerQuery Component

With these two items, we can now easily create a SmartComponent to determine when we’re matching a particular form factor, by attaching the received ref to our own component’s wrapper element. We can then iterate over our child ContainerQuery components and find the matching child for our current container size.

Take a look at the src/components/container-query.js and src/components/smart-component.js files to see how we build on the previous layers of code for our new support components, then check out src/components/smart-resizer.js for an example of how to combine it all.

ContainerQuery Demo

Our SmartComponent type uses a ResizeObserver component itself, and within its render callback, attaches the passed ref to a div it renders as the component’s internal container. It then iterates over all of its children, and finds the first match that passes all of its size checks, to make sure the matched child is within any defined height/width constraints. If no matching children are found, it simply renders null. Now, we can put all of these pieces together. This final demo creates a SmartComponent version of a UserDataGrid, in mobile-, tablet-, and desktop-style form factors.

Implementation Steps: Smart UserDataGrid

Feel free to look at each of those in the src/components/user-data-grid folder. The tablet version is very similar to the desktop version, but pares down the number of displayed columns so that we’re focusing on more important data with our minimized display space. The mobile version scraps the notion of a display grid entirely, but still manages to fit all of the relevant data into a more appropriate interface, hiding address/email/phone information behind some icons, and better organizing it for viewing on a mobile device.

The provided page layout splits the screen into 3 sections: an upper full-width half of the screen, and a lower two-column layout, with a 300-pixel left column, and the remainder of screen space in the right column. The same component is dropped into all 3 of those containers, and in all of those containers, it renders appropriately based on the space it has. The parent SmartComponent holds the state for all 3 variants, so any selections of records are maintained as these variants are swapped in and out based on the overall container sizes as you resize the window.

Smart UserDataGrid Demo

Once the individual variants have been written, composing them into a smart version really isn’t difficult at all. We now have a singular component implementation of a UserDataGrid that can be dropped anywhere in the application, and when it initializes, the SmartComponent aspect will filter the children to find the best match for the container it’s been placed in, and return the appropriate component, proxying the props straight through to it. Any interaction that causes its container to change dimensions will also cause it to reevaluate which variant of the component should be rendered.

Caveats

One thing to reiterate about this method of implementation is that moving between breakpoints is going to cause the unmount of a child component, and remount of another child component. Because of this, any state, reducers, memoized values, etc. should be hoisted to the level of the root SmartComponent, and passed down to all children as props. Otherwise, any stateful value(s) will be lost as one component unmounts and another mounts.

Additionally, to avoid bloating the size of your code bundle that’s being shipped to the client, you may consider lazy-loading the individual variants of your components, so that their code modules are only ever requested when the UI determines that they’re needed. In most cases, a user’s UI form factor is going to stay fairly static throughout the lifetime of their session, so the 90% use case would most likely only ever need a single variant loaded, while allowing for the others to be loaded on demand.

Conclusion

Componentization of web-based applications has made it easy to rapidly prototype things that were previously complicated piles of code. By thinking of components as isolated modules of UI rendering and functionality, we can very quickly create complex interfaces. We should now also start considering that individual components themselves may become sufficiently detailed and complex enough that they might merit a little more upfront planning to create optimized variants based on their own allocated space on the screen. At the same time, we don’t want to make it cumbersome to the development process to have to consider which variants should be placed where at design time. An approach like this allows that complexity to be hidden away, and to present a single interface that’s intelligent enough to determine its own optimal rendering at runtime.

Flexible React Components at Any Scale

I’ve been working with React components for about the last 3 years now, with a variety of different state-management conventions being employed, from internal state to object literals to Backbone models to Redux. One of the things I enjoy the most about React is the simplicity of swapping out templates based on whatever logic you want to use. You can have functions that are conditionally called and output template fragments, you can inline your logic as part of a template itself, or you can have entirely different components to render your output conditionally, based on some logic determined by a parent component.
Continue reading

Do you even learn, bro?

ES5 is fairly mainstream at this point, but a lot of real-world developers still have to deal with the likes of IE8 or other legacy support, and shims and polyfills to add missing and expected functionality. ES6 has been pretty heavily fleshed out, and is starting to gain adoption amongst some of the evergreen vendors, but still has a long way to go. ES7 is still very much in the proposal stages.
Continue reading

Evolving jQuery UI For Web Components

My job centers around writing reusable javascript components. As a result, I spend a lot of time reading, a lot of time writing code, and a lot of time revisiting those things I’ve previously written, and trying to improve upon them. In recent months, I’ve been obsessed with performance improvements, both with rendering state as well as optimizing them for the browser’s paint cycles. There are, by now, plenty of articles out there about window.requestAnimationFrame(), deferred objects and promises, development patterns, up-and-coming Web Components proposals, etc. This article is going to be an attempt to marry a lot of those disparate concepts in the context of the jQuery UI Widget Factory, which is one of the more widely used frameworks out there for reusable UI components.
Continue reading

My descent into AMD…

Asynchronous module definition. It almost sounds like something out of science fiction. What is this relatively newfangled concept, and why shouldn’t you just hike your pants up to your nipples and tell it to stay off your lawn? I, myself, had to be dragged into it kicking and screaming, and now that I’m familiar with it, I can’t imagine how we didn’t come up with the concept even sooner. Continue reading

The Javascript Console

Most web developers are familiar with some of the available developer tools out there in various browsers at this point. However, most people don’t use them anywhere near their full potential. The goal of this post is to explore the javascript console in a bit of detail, describing some of the features and techniques of using it, but without duplicating effort on the part of some other good blog posts. I’m sure I’ll miss some items that people deem appropriate or necessary, so please feel free to fill in the gaps in the comments below. I’ll mostly be speaking from the context of the Chrome developer tools, unless I specify otherwise, as I find them to be one of the more (if not the most) robust offerings currently. Continue reading

Never Stop Learning!

“It’s easy to sit there and say you’d like to have more money. And I guess that’s what I like about it. It’s easy. Just sitting there, rocking back and forth, wanting that money.” — Jack Handey

I love Jack Handey, and you know what else I love? Knowledge. Learning. Experimenting. Successes and failure both. I think it’s easy in this industry, for one to rest on his/her laurels, and not seek out any information on items outside of their immediate scope of work. I interview a lot of candidates that, when asked why they’re looking to leave their current job, or what they’re looking for in their next job, they say they’d like more opportunities to learn. Continue reading

My First jQuery Conference Presentation

So, I did my presentation for the Austin jQuery conference earlier today. Being my first time presenting at anything like this, and being fairly introverted, I think it’s being generous to say I was merely nervous. “Scared shitless” might be a more accurate phrase, which is good, because being shitless, I couldn’t crap my pants right there on stage. My voice was shaky throughout, my breathing was uneven, and I was sweating buckets by the end of it (a combination of the heat-lamp spotlight and my nerves), but I got it done, and got everything out that I wanted to say. Continue reading