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

 Javascript |  copy code |? 
1
this.sideBar.show(new AccountView({
2
    advicePage: true
3
}));


goalSetupView.js

 Javascript |  copy code |? 
1
this.sideBar.show(new AccountView({
2
    goalSetupPage: true
3
}));


accountView.html

 HTML |  copy code |? 
01
<div class="goal-name">
02
    <span class="name"></span>
03
    <span class="blue-pen"></span>
04
 
05
    <% if (goalSetupPage) { %>
06
        <span class="blue-x"></span>
07
    <% else if (advicePage) { %>
08
        <span class="status <%= account.status() %>"></span>
09
    <% } %>
10
</div>
11
 
12
<div class="goal-contents"><!--DVFMTSC-->
13
    <div class="row">
14
        <span>Type</span>
15
        <span></span>
16
 
17
        <button class="silver change-goal">Change</button>
18
    </div>
19
 
20
    <% if (advicePage) { %>
21
        <div class="row">
22
            <span>Target</span>
23
            <span class="goal-amount">
24
                <span></span>
25
                <span class="blue-pen"></span>
26
            </span>
27
        </div>
28
    <% } %>
29
</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

 Javascript |  copy code |? 
1
this.sideBar.show(new AccountView({
2
    showDeleteButton: false,
3
    showTargetBalance: true
4
}));

goalSetupView.js

 Javascript |  copy code |? 
1
this.sideBar.show(new AccountView({
2
    showDeleteButton: true,
3
    showTargetBalance: false
4
}));

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

rolloverSetupView.js

 Javascript |  copy code |? 
1
this.sideBar.show(new AccountView({
2
    showDeleteButton: true,
3
    showTargetBalance: false,
4
    showChangeButton: false
5
}));

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

new accountView.html

 HTML |  copy code |? 
01
<div class="goal-name">
02
    <span class="name"><%= name() %></span>
03
    <span class="blue-pen"></span>
04
 
05
    <% if (showDeleteButton) { %>
06
        <span class="blue-x"></span>
07
    <% } else { %>
08
        <span class="status <%= account.status() %>"></span>
09
    <% } %>
10
</div>
11
 
12
<div class="goal-contents">
13
    <div class="row">
14
        <span>Type</span>
15
        <span><%= account.get('goalType') %></span>
16
 
17
        <% if (showChangeButton) { %>
18
            <button class="silver change-goal">Change</button>
19
        <% } %>
20
    </div>
21
 
22
    <% if (showTargetBalance) { %>
23
        <div class="row">
24
            <span>Target</span>
25
            <span class="goal-amount">
26
                <span><%= goalAmount() %></span>
27
                <span class="blue-pen"></span>
28
            </span>
29
        </div>
30
    <% } %>
31
</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.