Mobile-first design for the Notes application

When we added CSS and JavaScript for Bootstrap et al., that was only the start. To implement a responsive mobile-friendly design, we need to modify every template to use Bootstrap components. Bootstrap’s features, in version 4.x, are grouped into four areas:

  • Layout: Declarations to control the layout of HTML elements, supporting different layouts based on device size
  • Content: For regularizing the look of HTML elements, typography, images, tables, and more
  • Components: A comprehensive set of UI elements including navigation bars, buttons, menus, popups, forms, carousels, and more to make it easy to implement applications
  • Utilities: Additional tools to aid in tweaking the presentation and layout of HTML elements

The Bootstrap documentation is full of what we might call recipes, to implement the structure of HTML elements for certain Bootstrap components or effects. A key to the implementation is that Bootstrap effects are triggered by adding the correct HTML class declaration to each HTML component.

Let’s start with page layout using Bootstrap.

1. Laying the Bootstrap grid foundation

Bootstrap uses a 12-column grid system to control layout, giving applications a responsive mobile-first foundation on which to build. When correctly set up, a layout using Bootstrap components can automatically rearrange components for different sized screens between extra small up to large desktop computers. The method relies on <div> elements with classes to describe the role each <div> plays in the layout.

The basic layout pattern in Bootstrap is as follows:

<div class=”container-fluid”> <!– or just .container –>

<div class=”row”>

<div class=”col-sm-3″>Column 1 content</div> <!– 25% –>

<div class=”col-sm-9″>Column 2 content</div> <!– 75% –>

</div>

<div class=”row”>

<div class=”col-sm-3″>Column 1 content</div> <!– 25% –>

<div class=”col-sm-6″>Column 2 content</div> <!– 50% –>

<div class=”col-sm-3″>Column 3 content</div> <!– 25% –>

</div>

</div>

This is a generic Bootstrap layout example, not anything we’re putting into the Notes app. Notice how each layer of the layout relies on different class declarations. This fits Bootstrap’s pattern of declaring behavior by using classes.

In this case, we’re showing a typical page layout of a container, containing two rows, with two columns on the first row and three columns on the second. The outermost layer uses the .container or .container-fluid elements. Containers provide a means to center or horizontally pad the content. Containers marked as .container- fluid act as if they have width: 100%, meaning they expand to fill the horizontal space.

A .row is what it sounds like, a “row” of a structure that’s somewhat like a table. Technically, a row is a wrapper for columns. Containers are wrappers for rows, and rows are wrappers for columns, and columns contain the content displayed to our users.

Columns are marked with variations of the .col class. With the basic column class, .col, the columns are divided equally into the available space. You can specify a numerical column count to assign different widths to each column. Bootstrap supports up to 12 numbered columns, hence each row in the example adds up to 12 columns.

You can also specify a breakpoint to which the column applies:

  • Using col-xs targets extra-small devices (smartphones, <576px).
  • Using col-sm targets small devices (>= 576px).
  • Using col-md targets medium devices (>= 768px).
  • Using col-lg targets large devices (>= 992px).
  • Using col-xl targets extra-large devices (>= 1200px).

Specifying a breakpoint, for example, col-sm, means that the declaration applies to devices matching that breakpoint or larger. Hence, in the example shown earlier, the column definitions were applied to col-sm, col-md, col-lg, and col-xl devices, but not to col-xs devices.

The column count is appended to the class name. That means using col-# when not targeting a breakpoint, for example, col-4, or col-{breakpoint}-# when targeting a breakpoint, for example, col-md-4, to target a space four columns wide on medium devices. If the columns add up to more than 12, the columns beyond the twelfth column wrap around to become a new row. The word auto can be used instead of a numerical column count to size the column to the natural width of its contents.

It’s possible to mix and match to target multiple breakpoints:

<div class=”container-fluid”>

<div class=”row”>

<div class=”col-xs-9 col-md-3 col-lg-6″>Column 1 content</div>

<div class=”col-xs-3 col-md-9 col-lg-6″>Column 2 content</div>

</div>

</div>

This declares three different layouts, one for extra-small devices, another for medium devices, and the last for large devices.

This introduction gives us enough knowledge to start modifying the Notes application. Our next task is to better understand the structure of the application pages.

2. Responsive page structure for the Notes application

We could go through a whole user experience analysis of Notes, or get designers involved, and get the perfect page design for each screen of the Notes application. But the current Notes application design is the result of a developer coding up page designs that are functional and not ugly. Let’s start by discussing the logic behind the structure of the page designs we have. Consider the following structure:

<!DOCTYPE html>

<html>

<head> .. headerStuff </head>

<body>

.. pageHeader

.. main content

.. bottomOfPageStuff

</body>

</html>

This is the general structure of the pages in Notes. The page content has two visible rows: the header and the main content. At the bottom of the page are invisible things such as the JavaScript files for Bootstrap and jQuery.

As it currently stands, the header contains a title for each page as well as navigation links so the user can browse the application. The content area is what changes from page to page, and is either about viewing content or editing content. The point is that for every page we have two sections for which to handle layout.

The question is whether views/layout.hbs should have any visible page layout. This template is used for the layout of every page in the application. The content of those pages is different enough that it seems layout.hbs cannot have any visible elements.

That’s the decision we’ll stick with for now. The next thing to set up is an icon library we can use for graphical buttons.

3. Using icon libraries and improving visual appeal

The world around us isn’t constructed of words, but instead things. Hence, pictorial elements and styles, such as icons, can help computer software to be more comprehensible. Creating a good user experience should make our users reward us with more likes in the app store.

There are several icon libraries that can be used on a website. The Bootstrap team has a curated list at https://getbootstrap.com/docs/4.5/extend/icons/. For this project, we’ll use Feather Icons (https://feathericons.com/). It is a conveniently available npm package at https://www.npmjs.com/package/feather-icons.

To install the package, run this command:

$ npm install feather-icons@4.25.x –save 

You can then inspect the downloaded package and see that ./node_modules/feather-icons/dist/feather.js contains browser-side code, making it easy to use the icons.

We make that directory available by mounting it in app.mjs, just as we did for the Bootstrap and jQuery libraries. Add this code to app.mjs:

app.use(‘/assets/vendor/feather-icons’, express.static(

path.join( dirname, ‘node_modules’, ‘feather-icons’, ‘dist’)));

Going by the documentation, we must put this at the bottom of views/layout.hbs to enable feather-icons support:

<script src=”/assets/vendor/feather-icons/feather.js”></script>

<script>

feather.replace();

</script>

This loads the browser-side library and then invokes that library to cause the icons to be used.

To use one of the icons, use a data-feather attribute specifying one of the icon names, like so:

<i data-feather=”circle”></i>

As suggested by the icon name, this will display a circle. The Feather Icons library looks for elements with the data-feather attribute, which the Feather Icons library uses to identify the SVG file to use. The Feather Icons library completely replaces the element where it finds the data-feather attribute. Therefore, if you want the icon to be a clickable link, it’s necessary to wrap the icon definition with an <a> tag, rather than adding data-feather to the <a> tag.

Let’s now redesign the page header to be a navigation bar, and use one of the Feather icons.

4. Responsive page header navigation bar

The header section we designed before contains a page title and a little navigation bar. Bootstrap has several ways to spiff this up, and even give us a responsive navigation bar that neatly collapses to a menu on small devices.

In views/header.hbs, make this change:

<header class=”page-header”>

<h1>{{ title }}</h1>

<nav class=”navbar navbar-expand-md navbar-dark bg-dark”>

<a class=”navbar-brand” href=’/’><i data-feather=”home”></i></a>

<button class=”navbar-toggler” type=”button”

data-toggle=”collapse” data-target=”#navbarSupportedContent”

aria-controls=”navbarSupportedContent”

aria-expanded=”false” aria-label=”Toggle navigation”>

<span class=”navbar-toggler-icon”></span>

</button>

<div class=”collapse navbar-collapse” id=”navbarSupportedContent”>

<div class=”navbar-nav col”>

{{#if breadcrumb}}

<a class=”nav-item nav-link” href='{{breadcrumb.url}}’>

{{breadcrumb.title}}</a>

{{/if}}

</div>

<a class=”nav-item nav-link btn btn-light col-auto” href=’/notes/add’>ADD Note</a>

</div>

</nav>

</header>

Adding class=“page-header” informs Bootstrap that this is, well, the page header. Within that, we have the <h1> header as before, providing the page title, and then a responsive Bootstrap navbar.

By default, the navbar is expanded—meaning the components inside the navbar are visible—because of the navbar-expand-md class. This navbar uses a navbar- toggler button that governs the responsiveness of the navbar. By default, this button is hidden and the body of the navbar is visible. If the screen is small enough, the navbar-toggler is switched so it’s visible and the body of the navbar becomes invisible, and when clicking on the now-visible navbar-toggler, a menu drops down containing the body of the navbar:

We chose the Feather Icons’ home icon because that link goes to the home page. It’s intended that the middle portion of the navbar will contain a breadcrumb trail as we navigate around the Notes application.

The ADD Note button is glued to the right-hand side with a little Flexbox magic. The container is a Flexbox, meaning we can use the Bootstrap classes to control the space consumed by each item. The breadcrumb area is the blank spot between the home icon and the ADD Note button. It is empty in this case, but the <div> element that would contain it is there and declared with class=”col”, meaning that it takes up a column unit. The ADD Note button is, on the other hand, declared with class=”col-auto”, meaning it takes up only the room required for itself. Therefore, the empty breadcrumb area that will expand to fill the available space, while the ADD Note button fills only its own space, and is therefore pushed over to the side.

Because it’s the same application, the functionality all works; we’re simply working on the presentation. We’ve added a few notes but the presentation of the list on the front page leaves a lot to be desired. The small size of the title is not very touch- friendly since it doesn’t present a large target area for a fingertip. And can you explain why the notekey value has to be displayed on the home page? With that in mind, let’s move on to fixing up the front page.

5. Improving the Notes list on the front page

The current home page has some simple text list that’s not terribly touch-friendly, and showing the key at the front of the line might be inexplicable to the user. Let’s fix this.

Edit views/index.hbs as follows, with the changed lines shown in bold:

<div class=”container-fluid”>

<div class=”row”>

<div class=”col-12 btn-group-vertical” role=”group”>

{{#each notelist}}

<a class=”btn btn-lg btn-block btn-outline-dark”

    href=”/notes/view?key={{ key }}”>{{ title }}</a>

{{/each}}

</div>

</div>

</div> 

The first change is to switch away from using a list and to use a vertical button group. The button group is a Bootstrap component that’s what it sounds like, a group of buttons. By making the text links look and behave like buttons, we’re improving the UI, especially its touch-friendliness. We chose the btn-outline-dark button style because it looks good in the UI. We use large buttons (btn-lg) that fill the width of the container (btn-block).

We eliminated showing the notekey value to the user. This information doesn’t add anything to the user experience. Running the application, we get the following:

This is beginning to take shape, with a decent-looking home page that handles resizing very nicely and is touch-friendly. The buttons have been enlarged nicely to be large enough for big fingers to easily tap.

There’s still something more to do with this since the header area is taking up a fair amount of space. We should always feel free to rethink a plan as we look at intermediate results. Earlier, we created a design for the header area, but on reflection, that design looks to be too large. The intention had been to insert a breadcrumb trail just to the right of the home icon, and to leave the <h1> title at the top of the header area. But this takes up too much vertical space, so we can tighten up the header and possibly improve the appearance.

Edit partials/header.hbs with the following line in bold:

<header class=”page-header”>

<nav class=”navbar navbar-expand-md navbar-dark bg-dark”>

<a class=”navbar-brand” href=’/’><i data-feather=”home”></i></a>

<button class=”navbar-toggler” type=”button”

data-toggle=”collapse” data-target=”#navbarSupportedContent”

aria-controls=”navbarSupportedContent”

aria-expanded=”false”

aria-label=”Toggle navigation”>

<span class=”navbar-toggler-icon”></span>

</button>

<div class=”collapse navbar-collapse” id=”navbarSupportedContent”>

<span class=”navbar-text col”>{{ title }}</span>

<a class=”nav-item nav-link btn btn-light col-auto” href=’/notes/add’>ADD Note</a>

</div>

</nav>

</header>

This removes the <h1> tag at the top of the header area, immediately tightening up the presentation.

Within the navbar-collapse area, we’ve replaced what had been intended as the breadcrumb with a simple navbar-text component containing the page title. To keep the ADD Note button glued to the right, we’re maintaining the class=”col” and class=”col-auto” settings:

Which header area design is better? That’s a good question. Since beauty is in the eye of the beholder, both designs are probably equally good. What we have demonstrated is the ease with which we can update the design by editing the template files.

Let’s now take care of the page for viewing notes.

6. Cleaning up the note viewing experience

Viewing a note isn’t bad, but the user experience can be improved. For example, the user does not need to see the notekey, meaning we might remove that from the display. Additionally, Bootstrap has nicer-looking buttons we can use.

In views/noteview.hbs, make these changes:

<div class=”container-fluid”>

<div class=”row”><div class=”col-xs-12″>

{{#if note}}<h3>{{ note.title }}</h3>{{/if}}

{{#if note}}<p>{{ note.body }}</p>{{/if}}

<p>Key: {{ notekey }}</p>

</div></div>

{{#if notekey }}

<div class=”row”><div class=”col-xs-12″>

<div class=”btn-group”>

<a class=”btn btn-outline-dark”

href=”/notes/destroy?key={{notekey}}”

role=”button”>Delete</a>

<a class=”btn btn-outline-dark”

href=”/notes/edit?key={{notekey}}”

role=”button”>Edit</a>

</div>

</div></div>

{{/if}}

</div> 

We have declared two rows, one for the note, and another for buttons for actions related to the note. Both are declared to consume all 12 columns, and therefore take up the full available width. The buttons are again contained within a button group, but this time a horizontal group rather than vertical.

Running the application, we get the following:

Do we really need to show the notekey to the user? We’ll leave it there, but that’s an open question for the user experience team. Otherwise, we’ve improved the note- reading experience.

Next on our list is the page for adding and editing notes.

7. Cleaning up the add/edit note form

The next major glaring problem is the form for adding and editing notes. As we said earlier, it’s easy to get the text input area to overflow a small screen. Fortunately, Bootstrap has extensive support for making nice-looking forms that work well on mobile devices.

Change the form in views/noteedit.hbs to this:

<form method=’POST’ action=’/notes/save’>

<div class=”container-fluid”>

{{#if docreate}}

<input type=’hidden’ name=’docreate’ value=”create”>

{{else}}

<input type=’hidden’ name=’docreate’ value=”update”>

{{/if}}

<div class=”form-group row align-items-center”>

<label for=”notekey” class=”col-1 col-form-label”>Key</label>

{{#if docreate }}

<div class=”col”>

<input type=’text’ class=”form-control” placeholder=”note key” name=’notekey’ value=”/>

</div>

{{else}}

{{#if note }}

<span class=”input-group-text”>{{notekey}}</span>

{{/if}}

<input type=’hidden’ name=’notekey’ value='{{#if note }}{{notekey}}{{/if}} ‘/>

{{/if}}

</div>

<div class=”form-group row”>

<label for=”title” class=”col-1 col-form-label”>Title</label>

<div class=”col”>

<input type=”text” class=”form-control” id=’title’ name=’title’ placeholder=”note title” value='{{#if note }}{{note.title}}{{/if}}’>

</div>

</div>

<div class=”form-group row”>

<textarea class=”form-control” name=’body’ rows=”5″>{{#if note }}{{note.body}}{{/if}}</textarea>

</div>

<button type=”submit” class=”btn btn-default”>Submit</button>

</div>

</form>

There’s a lot going on here. What we’ve done is reorganize the form so Bootstrap can do the right things with it. The first thing to note is that we have several instances of this:

<div class=”form-group row”> .. </div>

The entire form is contained within a container-fluid, meaning that it will automatically stretch to fit the screen. The form has three of these rows with the form-group class.

Bootstrap uses form-group elements to add structure to forms and to encourage proper use of <label> elements, along with other form elements. It’s good practice to use a <label> element with every <input> element to improve assistive behavior in the browser, rather than simply leaving some dangling text.

For horizontal layout, notice that for each row there is a <label> with a col-1 class, and the <input> element is contained within a <div> that has a col class. The effect is that the <label> has a controlled width and that the labels all have the same width, while the <input> elements take up the rest of the horizontal space.

Every form element has class=”form-control”. Bootstrap uses this to identify the controls so it can add styling and behavior.

The placeholder=’key’ attribute puts sample text in an otherwise empty text input element. It disappears as soon as the user types something and is an excellent way to prompt the user with what’s expected.

Finally, we changed the Submit button to be a Bootstrap button. These look nice, and Bootstrap makes sure that they work great:

The result looks good and works well on the iPhone. It automatically sizes itself to whatever screen it’s on. Everything behaves nicely. In the preceding screenshot, we’ve resized the window small enough to cause the navbar to collapse. Clicking on the so- called hamburger icon on the right (the three horizontal lines) causes the navbar contents to pop up as a menu.

We have learned how to improve forms using Bootstrap. We have a similar task in the form to confirm deleting notes.

8. Cleaning up the delete-note window

The window used to verify the user’s choice to delete a note doesn’t look bad, but it can be improved.

Edit views/notedestroy.hbs to contain the following:

<form method=’POST’ action=’/notes/destroy/confirm’>

<div class=”container-fluid”>

<input type=’hidden’ name=’notekey’ value=’

{{#if note}}{{notekey}}{{/if}}’>

<p class=”form-text”>Delete {{note.title}}?</p>

<div class=”btn-group”>

<button type=”submit” value=’DELETE’

class=”btn btn-outline-dark”>DELETE</button>

<a class=”btn btn-outline-dark”

href=”/notes/view?key={{#if note}}{{notekey}}{{/if}}”

role=”button”>

Cancel</a>

</div>

</div>

</form>

We’ve reworked it to use similar Bootstrap form markup. The question about deleting the note is wrapped with class=”form-text” so that Bootstrap can display it properly.

The buttons are wrapped with class=”btn-group” as before. The buttons have exactly the same styling as on other screens, giving a consistent look across the application:

There is an issue in that the title text in the navbar does not use the word Delete. In routes/notes.mjs, we can make this change:

// Ask to Delete note (destroy) router.get(‘/destroy’, async (req, res, next) => {

var note = await notes.read(req.query.key); res.render(‘notedestroy’, {

title: note ? ‘Delete ${note.title}’ : “”,

notekey: req.query.key, note: note

});

});

What we’ve done is to change the title parameter passed to the template. We’d done this in the /notes/edit route handler and seemingly missed doing so in this handler.

That handles rewriting the Notes application to use Bootstrap. Having a complete Bootstrap-based UI, let’s look at what it takes to customize the Bootstrap look and feel.

Source: Herron David (2020), Node.js Web Development: Server-side web development made easy with Node 14 using practical examples, Packt Publishing.

Leave a Reply

Your email address will not be published. Required fields are marked *