Editable Notable Projects

Posted by Tyler

If you’ve created a Profile on Limbo, you might have noticed a bit of UX that was lacking. Once you added Notable Projects, you couldn’t edit them. The only way to make changes was to delete the project and add it again.

(If you haven’t created a Profile you should go do that now!)

This was something we knew about and got a handful of reports from users about. I can’t remember why we didn’t make projects editable in the first place. I think it was just fatigue. We were pushing hard to launch, it was one of the things we cut that we’d come back to later.

Later was yesterday. As of about 11:00am EST Nov. 16 you can edit Notable Projects on the Profile form.

An animated gif showing Notable Projects editing

After you add a project you can either tap the edit button or double click the project’s text to enter edit mode.

This was one of those things that wasn’t the end of the world, but wasn’t courteous design. It’s great to have it fixed and out in the wild.

I paused other ongoing work and gave myself part of a day to work on this. It took me about 3.5 hours, plus a bit of extra time for review and cleanup.

In the rest of the post I’ll highlight some of my process of refactoring the code behind the update.

Rethinking, refactoring

The bulk of the code for the Projects UI is in a module named MultiLineInput. My first approach for this update was to shoe-horn editing into the module. It took about 15 minutes of trying different approaches to realize that wasn’t gonna work. We wrote the module for a specific use case. That use case didn’t include editing projects once they existed. I stepped away from the computer to rethink the component from the ground up.

It’s a somewhat complex UI with a bunch of scenarios and tasks users should be able to do. I listed these in my head to make sure I had a good idea of everything I needed to do before getting back in the code.

I didn’t write down the scenarios and tasks at the time. I just repeated them in my head over and over while I was in the shower and while walking the dog. I write my best code not at a computer. Here’s what that stream of consciousness looks like in writing:

  • the foundation is a single textarea
  • when we process the form post on the backend, we look for line breaks in the textarea value to get individual projects
  • this will work without JavaScript
  • each project is a li that contains both the view elements and editable elements, shown or hidden depending on mode
  • users must be able to toggle each project between view and edit modes
  • users must be able to delete projects
  • if there are no projects or fewer than the max allowed we should show an empty li in edit mode
  • if there are max number of items (3) there should not be an empty li in edit mode

Because this is a separate module, I was able to abstract it to an isolated CodePen Demo. A few additions aside, this is a copy/pasted version of the module. The styles are from the main Limbo stylesheet. It’s referenced in the Pen’s settings.

You can also play with a working example in our Pattern Library.

See the Pen Limbo: MultiLineInput Component by Tyler Gaw (@tylergaw) on CodePen.

Enhancing, Progressively

The Profile form doesn’t 100% work without JS, but we’re close. Before this update, the Notable Projects section required JS to function. I decided to fix that.

The foundation of this UI is a textarea where users add one project per line. When we process the form on the back end, we look for \n and \r characters to separate each project. This isn’t the slickest UI, but it works.

Screenshot of the Notable Projects UI without JS
Without JavaScript, the Notable Projects UI still functions. This screenshot shows three projects, each one on a new line. The screenshot also shows the instruction; “Put each project on a new line”. When JS is available, we remove that.

This ensures everyone can add notable projects even if JS isn’t present–for whatever reason.

JavaScript, Take the Wheel

When JS kicks in it hides the textarea and the non-js instructions. We keep the textarea in the DOM because we’ll use that as the place to store the value going forward.

A cool thing about this setup is that when the server renders the page, any projects are already in the textarea. So, instead of making a request for JSON or similar, our JS gets initial values from that textarea:

const items = textarea.val().split(/[\n\r]/).filter(p => p);

That gives us an array of projects we can build the UI with.

We use the textarea in the other direction too. Every new project or edit updates the items array, then joins the array with a newline character, and finally writes it back to the textarea.

const nextVal = items.join("\n");
...
textarea.val(nextVal);

This lets us submit the form values the same way with or without JavaScript. Instead of JS talking to the server, we use the DOM as our datastore.

Determining When to Prompt for Input

Most of MultiLineInput handles events to maintain UI state. A lot of hiding/showing different elements depending on what the user is doing. The most complex part of the module is knowing when to add a new li in edit mode.

Here are the rules:

  • If there are zero projects, add a new li in edit mode
  • If the user added a project and they’re still under the max number allowed, add a new li in edit mode
  • If the user was at the max number of projects allowed and just deleted a project, add a new li in edit mode

To make the code easier to follow, I stored them as variables with ample comments. This happens in a function named inputValueChanged.

// If the next length is greater than the previous we know we just added
// another item. and we're still under the max number of items allowed,
// add another blank item in edit mode.
const addedItemUnderMax = nextLen > prevLen && nextLen < maxItems;

// If the next length is less than the previous, we know we just deleted
// an item and the prevLen was the max, we know we have an empty slot,
// so add another blank item in edit mode.
const deletedItemDroppedBelowMax = nextLen < prevLen && prevLen === maxItems;

If either of those are true, we add the item:

if (addedItemUnderMax || deletedItemDroppedBelowMax) {
  list.append(listItem("", "edit"));
}

The Template

In the code above, there’s a call to a listItem function. That function accepts two arguments. The first is the text of a project, if any. The second argument is an optional mode, either “view” or “edit”. The default is “view”.

The function doesn’t do much. It returns a string of HTML with elements shown or hidden depending on the mode.

const listItem = (itemContent, mode = "view") => `<li>[a whole bunch of HTML]</li>`;

If you tinker with the CodePen you’ll see there’s a more stuff going one, but these were a few of the more interesting pieces.

More Forward Progress

This was a good isolated improvement to work on. It doesn’t change the world, but it does make this small corner of Limbo much easier to work with.

Give it a try, let us know what you think. You can talk to us on Twitter @limboio or email us at support@limbo.io.

Thanks for reading.

Tyler Gaw

Tyler is the Co-Founder of Limbo. He works on the Product Design and designs and builds the front-end.

See all Tyler’s posts