A technical design pattern for handling complex views in web apps

Pix Engineering
The Pixel
Published in
8 min readOct 27, 2016
Photo by Alex wong on Unsplash

When you first start building an app, you often start with simple views. But in the real world of enterprise apps, nothing ever stays simple. Users or product managers ask for new features, new bits of information, and new ways to get to the specific set of data they need to do their job. Soon you need to start relating data, computing values or generating display strings, and filtering one collection of data based on selections from another. It gets complicated!

When you look for examples of how to write code for your web app, you’ll invariably find the simple TODO examples, or other relatively straightforward examples with simple views. These are okay to start with, but few help you understand how to structure your application code once you have complex views with complex dependencies — where changes to one item on the page need to reach out in multiple directions to impact several other items on that same page, and vice versa.

So what should you do when you realize your simple views have evolved into a project that’s complicated and difficult to manage? There are a few different approaches that might work, and each has its pros and cons. Here is an overview of the one I’d recommend you start with — the “top down” approach.

You may start with something like this:

Over time, as applications grow, extra features will be asked for and added until you end up with something like this:

Note the complex interaction requirements outlined on the sticky note by our Product Management team. Wow, this already feels like it’s going to be messy to manage!

But it doesn’t have to be.

The following assumes you are using data binding, and understand the concepts of controllers, html templates, etc.

Before you dive too far into how to make complex and interactive views and components work, you need to identify the components in your application that are involved, and describe the ways they might need to communicate, behave, or even interact. To do this well you need to agree on a handful of words to use to identify them and the concept(s) they represent. Along the way, you should consider whether there are different types or variations of each concept…but we’ll get to that later.

Encapsulation
When I consider encapsulation, I like to look at visual elements or code and ask, “Can I draw a line around this and have captured a key idea that I could describe?”

I imagine the boxes inside of boxes that have a specific purpose and can be described, in increasing granularity. It’s not uncommon to find me sitting with printouts of screenshots, wireframes, or even printed pages of code for a specific area of the application, and a bunch of colored pens, drawing boxes around the ideas that have a specific describable purpose, and those that should be encapsulated. I’ll do this with the wireframe for our complex view. At a rough high-level pass it looks something like this:

Some of the items we’ve outlined and identified are presentation only, and will be sections of html that will make up a template or template partial, but others need to have more logic involved, and have data and behaviors that are encapsulated within them, which can be called out as something more complex than just a template. These need to become what we’ll discuss next — components and sub-components.

Components
When I look at an application UI from the perspective of trying to identify the components and sub-components I want to break it out into, I generally start by taking a pass at drawing the boxes I mentioned above around the various segments in a screenshot or wireframe for the application. Imagining the boxes inside of boxes as you look at the pieces of your application is a great way to begin identifying components and sub-components.

For our purpose let’s talk about two flavors: smart components and dumb components.

Smart components generally have a connection to the services or data store needed to access your model collections and take actions against them. They will often have logic specific to the part they play in the application, and will often have code which directly manages and changes data-models passed to sub-components they contain. Smart components are often the type of view encapsulation you would use for what we’d consider a “view” or “sub-view.”

Dumb components simply take data and present it for you, and perhaps let you interact with it, but they don’t care about any specific context and generally won’t have access to the services or data store needed to access your application data. They simply take data in, and inform you of any actions you’ve programmed them to allow the user to take. Dumb components are great reusable building blocks to leverage throughout your application. Consider them the plug-and-play pieces that can be used in any application, provided the developer can understand the way the component needs its data, and how it communicates any interaction the user might have with it.

That brings us to one rule about all components that you absolutely must follow to maintain sanity as a software engineer: Modify in. Inform out.

Your view will be made up of components and nested sub-components of varying complexities and behaviors. �As you outline how these are brought together, you should be able to draw a tree that maps which components or views contain which other components. Components and the data that drives them should be managed and controlled in a top-down manner. A component should never make a change that will directly impact the state of any other component (or instance of a component) that it does not contain.

So how do you make a filter in one component impact the data shown in another, without letting it directly change the data both have access to? It’s all in how — and where — we manage the models.

Models
We need to think about two aspects of the life of models in our application: data modeling and data flow modeling.

Data modeling: �Webopedia defines this concept as, “The analysis of data objects and their relationships to other data objects. Creates a conceptual model of how data items are both structured and relate to each other.”

Data flow modeling: Webopedia defines this concept as, “The process of identifying, modeling and documenting how data moves around an information system. Data flow modeling examines processes (activities that transform data from one form to another), data stores (the holding areas for data), external entities (what sends data into a system or receives data from a system, and data flows (routes by which data can flow).”

With this in mind it’s valuable to identify some labels for model types. I generally talk about three different types of models with my team:

DTO Model (Data Transfer Model) — what is sent back and forth over the internet or network between the backend and the client/browser.

Example:

Business Model — What we want to work with in memory. We’ll usually want at least a couple of classes to manage each of our business models (i.e. “orders”, “customer”, “order”, “order_line_item”, etc.). These include:

Data Store Resource, which encapsulates all the logic we need to request and manage a local collection of business model instances, as well as expose convenience helper methods for creating new ones. �
Business Model Factory, which is responsible for creating instances usually derived from a collection of DTO data returned from the server (though not always), that can be decorated with computed properties, helper functions, and relationships to other business models we might be working with. All this comes together to make working with the collections the individual model instances easier.

Note: �We leverage the excellent library JS-Data for this at Pixability. �It’s allows you to dynamically create and manage items like the examples below with very simple configuration!

Examples:

View/Component Model — A model class that helps us bring together the data needed for a view or component. It will generally import various other models and data management services to do its job. This type of model is what our view controller should be pointing to to call the functions that will transform data to update the current component, or one of its children.

Application data store

I recommend applications have a single centralized application data store for managing collections of business models. This is where all your Business Model Resource classes should live, and should essentially hide the DTO models traveling to and from the server from the rest of the application code. �

The app level data store should address the majority of your needs for holding and managing all your business models. It is what your views should be referencing when asking for data that needs to come from or go to the server. It should be smart enough for developers to be able to ask for filtered and sorted sets of data to feed into their view models, without having to figure out whether the application already has the data at hand, or needs to ask the server for it. �

The JS-Data library mentioned above can help with this considerably. It’s worth reading through, and is a good starting place if you’re not familiar with creating your own centralized app data store.

Once you have a centralized data store in place, and you can handle fetching collections of data from it for your views and components, you can begin to streamline and structure the data you need for your View Models, and determine how to structure this top-down model management concept.

Bringing it all together

Now a quick example of how to bring it all together in a top-level View Model class. Imagine a top-level model that prepares all data that needs to work together for its child views, then passes them down through binding into the various components.

The following is pseudo code to give you a rough idea of how things might be passed around.

A sub-component for this view could get data and event handlers as follows…

…with a very thin view controller (we won’t outline that here) which simply hands off data and calls event handler functions through to the view model to handle. This might look like…

…and it’s done!

Let’s summarize this…

When simple apps become complicated (and we know they will), it’s time to pause and outline the hierarchy of your application composition.

Think about encapsulation.�Identify the components, and identify your models and manage them well.

– Business models to represent specific types of data in your application.
— View Models to manage the state and collections of data needed for your components.
— Business Model Resources to manage your integration with a backend service, in memory collection management. �

Consider using an application data store to marshal and manage these.

Manage changes to the data-sets needed to drive your views and components from a View Model, at the highest logical level. And remember the rule about components:

– Design your data management such that you “change down, but inform up.”
— Where one component might need to interact with the other, elevate event handling and data manipulation to a parent component/view, then simply let your views and components respond to the changes in the data they are bound to.

This may still be somewhat simplified for certain scenarios, but it demonstrates a practical approach to manage the data for views that contain multiple sibling components that are driven by changes in the other components.

--

--