Welcome to Part 15 of this series of guides on how to integrate Gmail with Angular and Spring Boot.

Now you'll do some decorating.

That is, you'll make the inbox look pretty. At least prettier than it was.

As you may recall from the previous guide, I left you with an inbox that looked fairly awful. Now it's time to fix that.

And in this guide, I'll show you how to do it.

When you're done, you'll have something that looks like this:

Minus the redactions, of course.

But it gets even better than that. This is what the inbox looks like on a smartphone:

Pretty cool, huh? 

If that's what you're looking for, keep reading.

Or you can go straight to the source on GitHub.

Thus Far...

Thus far in the series, you have:

  • Created an OAuth2 client ID and secret on Google Cloud Console
  • Enabled the Gmail API via Google Cloud Console
  • Set the necessary properties in your Spring Boot application.properties file 
  • Set up a DataStore implementation with its associated factory
  • Set up a refresh listener
  • Created a utility class that handles the Google authorization code flow
  • Set up proper Gmail consent with the Google Cloud Platform
  • Created a controller that handles a request to get the Google authorization code flow URL
  • Used Postman to get the Google authorization code URL
  • Used that URL to authorize your application to access your Gmail inbox
  • Updated the controller so it handles a token request
  • Updated the utility class so it creates the credential from a token
  • Persisted that credential
  • Used Postman to retrieve the token
  • Learned why you didn't actually need to retrieve the token
  • Learned about the structure of the Message object
  • Learned about MIME
  • Created an Email type that gets sent back to the client
  • Wrote code that converts a Message object to an Email object
  • Instantiated the Gmail service
  • Used the Gmail service to retrieve just the messages you want to retrieve
  • Used the Gmail service to extracted full Message objects from very lightweight objects
  • Added a new endpoint to listen for inbox requests
  • Used that endpoint to send back the user's most recent emails
  • Learned how to disallow your application from accessing your inbox
  • Created a new component on the Angular side that shows emails from the user's inbox
  • Created a new service on the Angular side that retrieves emails from the Spring Boot microservice
  • Optimized the payload response from the microservice

And if you haven't done those things, Part 1 is waiting for you.

...And Now

In this guide, you will:

  • Display HTML reference characters as they should be displayed
  • Get rid of whitespace that isn't whitespace
  • Design a responsive inbox that's user-friendly

And then you can go back to camel racing.

I Lied

I won't really cover all three of those bullet points above in this guide. That's because I've already covered the first two bullet points in previous guides.

You can read all about the first bullet point in my guide on how to display HTML reference characters.

And you can read all about the second bullet point in my guide on trimming zero-width non-joining characters.

So if you want to learn about those things, just click the links to the appropriate guides.

But I will cover that third bullet point right here.

The Ellipsis Project

Let me introduce you to The Ellipsis Project.

That's what I call this phase of creating a user-friendly inbox. Remember: we're following the model of Google's Gmail. That's a pretty nice UI.

So here's what the Gmail inbox does: it shows the subject of the email, followed by a dash, followed by as much of the synopsis as will fit on the screen.

You can see what I'm talking about if you go back to the images towards the beginning of this guide.

The idea here is to transform the subject and the synopsis into a single string separated by a dash. Then display as much of that string as possible.

If the whole thing can't fit within the element, the UI will display an ellipsis (...) at the end.

That's The Ellipsis Project.

Now you might think that you have to do a lot of fancy calculatin' and pixel-density measurin' to make all that happen. But you don't.

Nope. CSS will handle it for you. 

You just have to tell CSS what to do.

So start by updating inbox.component.css:

.checkbox-margin {
  margin: 0 12px;
}

That ensures there's plenty of space you see around those checkboxes on the left-hand side of the inbox. Leave that out and they'll crunch together with the "From" cell.

th.mat-header-cell:first-of-type, td.mat-cell:first-of-type, td.mat-footer-cell:first-of-type {
  padding-left: 0px !important;
}

That one overrides Angular Material's default left padding. You've got your own design going on here.

td.action-cell {
  width: 5%;
}

td.from-cell {
  width: 15%;
  padding-right: 20px;
}

td.date-cell {
  width: 10%;
}

Those three classes define the "Action," "From," and "Date" cells, respectively.

The "Action" cell is only a checkbox at this point. So it doesn't need any more than 5% of the screen width.

The "From" cell could have a long name in it. So it takes up 15% of the width. Also it adds some right padding to prevent it from running up against the subject-synopsis cell.

The "Date" cell also doesn't take up much space, just 10%.

td.subject-cell {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  width: 70%;
  max-width: 0;
  padding-right: 20px;
}

Here's where CSS does all the hard work for you in The Ellipsis Project.

That white-space property set to nowrap tells the UI not to wrap really long strings. If that's all you did, the table would expand to show the entire length of the string on one line.

But there's more.

The overflow property set to hidden tells the UI to crop off text that doesn't fit.

The text-overflow property set to ellipsis does exactly what you think it does.

Now take a look at the next two properties. The width is 70% but the max-width is set to 0.

What's that all about?

Well, simply put: it's a hack.

But it's a good hack. And I like hacks.

Leave out that max-width property and you'll see that The Ellipsis Project doesn't work. The table expands to fit the entire synopsis.

You don't want that.

So go with the hack.

Caring With the Component

Next, update inbox.component.ts. Add this method:

  getSubjectSnippetDisplay(email: Email): string {
    let subject: string = email.subject;
    let snippet: string = email.snippet;

    let display: string = `${subject} - ${snippet}`;
    return display;
  }

That's the method that concatenates the subject and the synopsis together and separates them with a dash. Nothing spectacular happening there.

And now that you're displaying a date column, be sure to update the displayedColumns array:

  displayedColumns: string[] = ['action', 'from', 'subject', 'date'];

Toying With the Template

Now modify inbox.component.html. Make the following changes:

        <ng-container matColumnDef="action">
          <tr><th mat-header-cell *matHeaderCellDef></th></tr>
          <tr>
            <td mat-cell *matCellDef="let row" class="action-cell">
              <mat-checkbox class="checkbox-margin"></mat-checkbox>
            </td>
          <tr>
        </ng-container>

        <ng-container matColumnDef="from">
          <tr><th mat-header-cell *matHeaderCellDef mat-sort-header> From </th></tr>
          <tr><td mat-cell *matCellDef="let row" class="from-cell"> {{emailService.getDisplayableFrom(row.from)}} </td><tr>
        </ng-container>

        <ng-container matColumnDef="subject">
          <tr><th mat-header-cell *matHeaderCellDef mat-sort-header> Subject </th></tr>
          <tr><td mat-cell *matCellDef="let row" class="subject-cell" [innerHTML]="getSubjectSnippetDisplay(row)"></td></tr>
        </ng-container>

        <ng-container matColumnDef="date">
          <tr><th mat-header-cell *matHeaderCellDef mat-sort-header> Date </th></tr>
          <tr><td mat-cell *matCellDef="let row" class="date-cell"> {{dateService.getCustomDateDisplay(row.date, 'MMM d')}} </td></tr>
        </ng-container>

Note that each <ng-container> includes a <td> element that uses the classes you defined in the CSS.

Hone in on that third <ng-container> for a moment.  That's where the UI displays the concatenated subject and synopsis. Note that it's using the [innerHTML] binding property so that HTML character references get displayed correctly.

And pay attention to the value set in that [innerHTML] property. It's the method you created in the previous section: getSubjectSnippetDisplay().

That's going to give you the concatenated string. That string will get truncated if it's longer than the width of the table cell.

And, of course, you'll get the ellipsis as well.

Wrapping It Up

No need to show you the finished product since you've already seen it in the intro. 

Now you can apply what you've learned here to your own projects. Make your UIs more user-friendly by shrinking strings with the width of the elements.

Have fun!

Photo by Anthony from Pexels