Free for 90 days: Sign up now and get 90 days managed free after your first deposit. See offer details

<title>Dismiss</title>

Scalable Design to Delight Customers

For web designers, this problem-and-solution goes behind the scenes of Betterment's user-friendly design.

Articles by Adam Langsner

By Adam Langsner
  |  Published: January 27, 2014

We write our web code in small pieces, or modules, so it's easy to move things around.

In this blog post, I describe how I made a component more re-usable by moving the page-dependent code out of the component and into the pages that actually use it.

advice_page_scalable_designOne of the hallmarks of using Betterment is our sophisticated web user interface. Customers can complete tasks in an intuitive and friendly way—we believe that it’s a part of our value to delight customers. (Hey, it’s one reason we’ve been called the “Apple of finance.”)

Yet as anyone who works in web design probably knows, intuitive user experience is something that goes much deeper than what customers see on the surface. Good design isn’t always about what you show and how you show it – sometimes it’s about what you don’t show. As we add more sophisticated features to our platform, we often find ourselves reusing pieces of functionality. But new contexts call for customizations—the ability to easily toggle on and off attributes of what is fundamentally the same component.

The need to do this quickly and efficiently is served by a fundamental principle: break down our code into smaller pieces, or modules, which makes it easier for us to move things around quickly. That way we can change one area without affecting others and focus more on what matters: making our customers happy.

One component, multiple locations

I recently needed to make a small adjustment to the component I had created a year ago—and I realized that I had originally designed it in a way that went against those principles. 

We used this component on our advice tab, as well as where our customers go to set up a new goal. Now, we wanted to also use it on the page where our customers go to set up their IRA rollovers. Problem was, the way I first built it, the component had too much knowledge about the page it was living on. If I wanted to use it in a new place, I had to modify it to work on the new page rather than simply dropping it on the page and being done with it. 

Consider the following simplified example, which demonstrates the problem state:

adviceView.js

this.sideBar.show(new AccountView({
    advicePage: true
}));

goalSetupView.js

this.sideBar.show(new AccountView({
    goalSetupPage: true
}));

accountView.html

<div class="goal-name">
    <span class="name"></span>
    <span class="blue-pen"></span>

    <% if (goalSetupPage) { %>
        <span class="blue-x"></span>
    <% else if (advicePage) { %>
        <span class="status <%= account.status() %>"></span>
    <% } %>
</div>

<div class="goal-contents">
    <div class="row">
        <span>Type</span>
        <span></span>

        <button class="silver change-goal">Change</button>
    </div>

    <% if (advicePage) { %>
        <div class="row">
            <span>Target</span>
            <span class="goal-amount">
                <span></span>
                <span class="blue-pen"></span>
            </span>
        </div>
    <% } %>
</div>

In the first two files we are displaying the AccountView component in the sidebar of the advice and the goal setup page. In both of these files we simply pass an option to the AccountView with the name of the page we’re on: “advicePage: true” or “goalSetupPage: true”. The AccountView handles the rest.

The third file is the HTML that actually draws the AccountView component. On the advice page in the top right we show the goal’s status (on track or off track), but on the goal setup page it is replaced with a remove icon to allow the user to delete the goal. You’ll also notice that on the advice page we have an extra row on the bottom showing the goal’s target balance, whereas on the goal setup page we do not show that.

We wanted to use the AccountView on the rollover setup page with one slight difference from the goal setup page: the Change button to the right of the goal type should be hidden.

To do this, the component would need to support a rolloverPage option and then have knowledge of how it should appear on that page. The more pages we want to use this component in, the more options we have to handle in its HTML. This is simply not efficient code! It’s not maintainable and makes it difficult to modularize our codebase.

Better way to solve this problem: An analogy

Then the right analogy came to me to help me solve the problem. Consider a restaurant to be the reusable component and its customers to be the pages where the component is used.

If our current implementation was used in this scenario it would look like this: You would call the restaurant, tell them your full name and they’d just know exactly what you wanted. The restaurant would have to maintain a book with all of their customers and what they wanted to order.

But in reality, this just doesn’t work very well. The restaurant has to know about their customers before they can order. A person can’t just walk in and get some food if his or her name isn’t in the book yet.

When we’re hungry, we take the sensible approach and speak the restaurant’s language — they provide a menu and we order from it. This way the restaurant need only concern itself with the food they make, not the people who order it. That’s what I needed to do with the component—have the page tell it what to do, rather than vice versa.

Refactoring the code

I refactored the component so that the pages that used it spoke its language. Now the AccountView is concerned not with the pages that use it, but only with the elements it needs to draw.

adviceView.js

this.sideBar.show(new AccountView({
    showDeleteButton: false,
    showTargetBalance: true
}));

goalSetupView.js

this.sideBar.show(new AccountView({
    showDeleteButton: true,
    showTargetBalance: false
}));

Now, to use it on the rollover setup page we can simply add this code to show the AccountView:

rolloverSetupView.js

this.sideBar.show(new AccountView({
    showDeleteButton: true,
    showTargetBalance: false,
    showChangeButton: false
}));

Here is how we handle those options in the component with the new HTML:

new accountView.html

<div class="goal-name">
    <span class="name"><%= name() %></span>
    <span class="blue-pen"></span>

    <% if (showDeleteButton) { %>
        <span class="blue-x"></span>
    <% } else { %>
        <span class="status <%= account.status() %>"></span>
    <% } %>
</div>

<div class="goal-contents">
    <div class="row">
        <span>Type</span>
        <span><%= account.get('goalType') %></span>

        <% if (showChangeButton) { %>
            <button class="silver change-goal">Change</button>
        <% } %>
    </div>

    <% if (showTargetBalance) { %>
        <div class="row">
            <span>Target</span>
            <span class="goal-amount">
                <span><%= goalAmount() %></span>
                <span class="blue-pen"></span>
            </span>
        </div>
    <% } %>
</div>

While not substantially different, this new code is cleaner, and lets the AccountView deal only with what it needs to deal with. It should never need know what page it’s living on.

The Result

With this refactoring of the web code, I was able to re-design a component in a way that made it completely scalable—and also created a model to build new components. This is just one more way we incorporate efficiency into our platform—making it easier to put customers first.

Interested in engineering at Betterment? Betterment is an engineering-driven company that has developed the most-trusted online financial advisor based on the principles of optimization and efficiency. You can learn more about engineering jobs and our culture here.

Determination of most trusted online financial advisor reflects Betterment LLC’s distinction of having the most customers in the industry, made in reliance on customer counts, self-reported pursuant to SEC rules, across all online-only registered investment advisors.

Recommended Content

View All Resources

WebValve – The Magic You Need for HTTP Integration

Struggling with HTTP integrations locally? Use WebValve to define HTTP service fakes and toggle between real and fake services in non-production environments.

Guidelines for Testing Rails Applications

Discusses the different responsibilities of model, request, and system specs, and other high level guidelines for writing specs using RSpec & Capybara.

Betterment’s Model for Financial Advice: An Overview

Achieving your financial goals is only possible if you plan effectively. Saving enough, choosing the right accounts, deciding when you can buy a house or when to retire—all of these are essential decisions even before you build an optimal portfolio.

Explore your first goal

Cash Reserve

Our high-yield account built to help you earn more on every dollar you save.

Safety Net

This is a great place to start—an emergency fund for life's unplanned hiccups. A safety net is a conservative portfolio.

Retirement

Whether it's a long way off or just around the corner, we'll help you save for the retirement you deserve.

General Investing

If you want to invest and build wealth over time, then this is the goal for you. This is an excellent goal type for unknown future needs or money you plan to pass to future generations.

See details and disclosure for Betterment's articles and FAQs.