Building a performant real-time web app with Ember Fastboot and Phoenix (Part 3)

Users and Authentication (UI)

Mike North
PEEP Stack
9 min readApr 24, 2016

--

This article is part 3 in a series. You should first read the previous parts leading up to this, or it may not make much sense!

Part 1: The meeting of two well-aligned, opinionated frameworks
Part 2: Users and Authentication (API)
Part 2.1: Some small adjustments to your API
Part 3: Users and Authentication (UI)
Part 4: Logging in to our API & ember-simple-auth
Part 5: Building a CRUD resource
Part 6: Animating your UI with liquid-fire
Part 7: Room UI & Messages

Ok, keep your API running, and open up your Ember UI project. We’ll want to install a couple of dependencies, which will require stopping and then restarting ember-cli

ember install ember-cp-validations
ember install ember-materialize-shim
ember install ember-route-action-helper

A little about these addons

Some cleanup

Go ahead and remove everything in app/templates/application.hbs except for

{{outlet}}

and delete app/styles/app.css if it still exists. We’ll add a little more styling to your app, via the root SCSS file (app/styles/app.scss).

Component: Text Field

We want to build a text field component, based off of the one you can find in the MaterializeCSS documentation. The structure of the HTML should look like this

<div class=”input-field”>
<input type=”text”>
<label>First Name</label>
</div>

out of the box, we get some nice support for “hovering” labels — largely based off of some clever CSS. Additionally, we can stylize an “invalid” or “valid” state, and accompany with an error message if we like

<div class=”input-field”>
<input class=”validate invalid” type=”text”>
<label data-error=”Wrong first name”>
First Name
</label>
</div>

Let’s start simple. Ember components are defined by a top-level element, and then contents within that element. In our case, the “root” element of this component will be the DIV with the input-field class on it.

Begin by using ember-cli to build a component with an appropriate name. This name must have a dash in it, for reasons that are beyond the scope of this article. Let’s use x-input. Run this in your terminal:

ember g component x-input

ember-cli will generate

  • ./app/components/x-input.js — a component javascript module
  • ./app/templates/components/x-input.hbs — a handlebars template for your component
  • ./tests/integration/components/x-input-test.js a passing component integration test.

Now is a good time to stop and think about our component’s contract with the outside world. Treat this like a Public API — the internals of your component may change, but as long as the contract with the outside world remains the same, things should be smooth and easy. We need three things:

  • The value that the component is bound to, which is updated based on the user’s changes
  • The label of the field
  • The type of the field (think: HTML input type)
  • Any error messages to display — we’ll add some nice client-side validation later, but that’s not a concern of this component

The following should do for now:


{{x-input
type=”password”
label=”Password”
value=myPassword
errors=myErrors }}

Open up the component’s JavaScript module, and the handlebars template side by side. We’ll be working in these two files for a little while.

First, we’ll want to add the input-field class to the root element of the component. To do this, add the following property:

classNames: ['input-field']

Also, we’ll want to default to a “text” input by default, so add this property too

type: “text”

Start your template off with something like this

<input
value={{value}}
type={{type}}
onkeyup={{action (mut value) value='target.value'}} />
<label>{{label}}</label>

What we’re doing here is using the “keyup” DOM event as a trigger to update the “value” property of our component, using the “target.value” property of the DOM event that’s fired.

Next let’s handle the error messages. We’ll want a comma-separated string of all error messages to show up as the label’s data-error attribute, but our component contract should be an array. To apply this transformation, a computed property will be useful. Go back to the component javascript module and add

_errorMessages: computed(‘errors.[]’, function() {
return (this.get(‘errors’) || []).join(‘, ‘);
})

and in the template, bind this new _errorMessages property to the label’s data-error attribute

<label data-error={{_errorMessages}}>{{label}}</label>

Now, this error message will only show up in the event that the right classes are on the input. We’ll want to set up some new computed properties to implement the following behavior

  • If the component’s value property is non-empty, we’ll want the input to have the “validate” class. This will prevent the component from showing up with validation errors when the user first sees it and it’s blank.
  • If the component is validating (due to the value being non-empty), and the errors array property has one or more objects in it, the input should additionally have the “invalid” class, otherwise it should have the “valid” class.

This can be done completely using Ember’s built-in handlebars helpers

<input
value={{value}}
type={{type}}
class=”{{if value
(concat
“validate ”
(if errors.length “invalid” “valid”)
)
}}”
onkeyup={{action (mut value) value=’target.value’}}>

Your component should now be implemented like this

x-input component (javascript)
x-input component (handlebars)

Finally, we need to override Materialize’s built-in validation stuff. We will replace it with something much more sophisticated and easy to use.

Go to your terminal, and make a new initializer with ember-cli

ember g initializer materialize-setup

You want to override a function validate_field, if it exists on the window object. The initializer should end up looking like this

Disable materialize’s built-in validation stuff, via an initializer

Component: Card

Next, let’s create a card component. For our purposes, cards will basically have three things

  • A title
  • A body
  • A footer with a button in it, and that button’s label should be customizable

Head on over to your terminal, and use ember-cli to kick start this

ember g component x-card

Now let’s think about the contract we want between this component and the rest of our app. Something like this should work nicely:

{{#x-card
title="Login to Peepchat"
onsubmit=myAction
buttonText="Login"}}
<p> This is the body of the card </p>{{/x-card}}

Open up the x-card.js and x-card.hbs side-by-side. The javascript side is extremely simple here

x-card component (javascript)

And the handlebars is pretty straight forward as well

x-card component (handlebars)

You’ll see here that I allow for the possibility that either the onsubmit action or title property don’t exist. It’s a good practice to make your component behave as reasonably as possible in the event that it has been passed “empty” state.

Building out the login and registration pages

Now it’s time to use our compose our two new components to build out our login and registration pages. For reasons that will become clear in a moment, we’ll want each of these cards to be their own specialized component

Create two new routes

ember g route auth/login
ember g route auth/register

and two new components

ember g component login-card
ember g component register-card

For now, login-card and register-card will be components that each contain the card, which contain the form for the login and registration pages, respectively. For now, don’t worry about the javascript modules for the components — we’ll just use the handlebars templates to encapsulate a card for each page

app/tempaltes/components/login-card.hbs
app/templates/components/register-card.hbs

Let’s start with the login page. Go to http://localhost:4200/auth/login and open up your ./app/templates/auth/login.hbs. You’ll want to wrap the Materialize grid around your cards, and use your x-input components within the body of the card. Something like this

You should now see something like this in your browser

If you click on the “login” button, you should see an error message in your web console, indicating that no doLogin action has been found on your route. Open up ./app/routes/auth/login.js and add a placeholder action so you can see that everything is wired up

Head on over to ./app/templates/auth/register.hbs and do basically the same thing as for login, but add an extra field for password confirmation

And add a similar placeholder action to the register route (./app/routes/auth/register.js)

If you go to http://localhost:4200/auth/register, you should see something like this

Registration page

Awesome! Now let’s run our tests by going to http://localhost:4000/tests, you should see that a couple are now failing. You’ll need to update the component integration tests for login-card and register-card as follows

tests/integration/components/login-card-test.js
tests/integration/components/register-card-test.js

Your tests should now pass. This is a good time to make a git commit, open up a pull request, and merge it into master. Thanks to the work we did in part 1, your UI will auto-deploy when your tests pass in CI 🙌.

If you want to compare your code to mine at this point, here’s what my project looks like

Users in the UI

Next, let’s create a model for users

ember g model user email:string password:string password_confirmation:string

Now we need to return this model from our ./app/routes/auth/register.js route’s model hook

app/routes/auth/register.js

We’ll do something similar to the ./app/routes/auth/login.js route, but return a plain javascript object with the same properties instead

I’m doing it this way for a few reasons

  • I want to demonstrate that using ember-data records and plain javascript are both acceptable, and can both work with client-side validations
  • Registering a new user represents a “Create” operation, and I’ll be hijacking some of ember-data’s record creation stuff to implement it
  • Per the OAuth2 standard, the response we expect from logging in is a token — NOT a user object. This is enough of a departure from typical CRUD operations that it makes sense to use lower-level tools like fetch.

Now, in the register page’s template (./app/templates/auth/register.hbs) we’ll want to pass the models into the respective card components

app/templates/auth/register.hbs
app/templates/auth/login.hbs

Because our validation criteria will be different depending on whether a user is registering or logging in (password_confirmation doesn’t matter when logging in), we’ll put the client-side validation logic onto the components instead of the model.

It’s nice to have things like validations defined in a single place, so that changes can be made across the whole app quickly and easily. Let’s create a “util” for this (a generic javascript module). Run this in your terminal

ember g util user-validations

This will create a new file ./app/utils/user-validations.js and an accompanying unit test. We’ll start with some basic validations that will cover our register and login use cases.

app/utils/user-validations.js

You’ll need to update your tests as well

app/utils/user-validations-test.js

Obviously these aren’t meaningful tests. We’ll come back in a future part of this series to catch up on proper test writing. Now we need to hook these validations up in your login-card and register-card components. We will do so by simply importing the respective exports from the user-validations module we just created, and passing them into the appropriate components, as follows:

app/components/login-card.js
app/templates/components/login-card.hbs
app/components/register-card.js
app/templates/components/register-card.hbs

Check out your login (http://localhost:4200/auth/login) and register (http://localhost:4200/auth/register) pages now, and try typing in a few values for usernames and passwords. You should see client-side validation messages coming through when appropriate

Login page, with client-side validations

This is another great opportunity to make a commit, open a pull request, and merge these changes into your project. If you want to compare your changes to mine, they are here:

Pointing to the right API

Time to hook registration up to our API. First, let’s make sure ember-data is pointing to the correct place in our dev and production environments. The best way to do this is via environment configuration.

Open up ./app/config/environment.js and at the following to the ENV variable declaration

DS: {
host: 'http://localhost:4000',
namespace: 'api'
}

Then, look down toward the bottom of the file to find something like this

if (environment === 'production') {}

In the case that we’re in the production environment, we’ll want to point ember-data to our production API.

REMEMBER TO REPLACE THE HOST WITH YOUR OWN HEROKU API HOSTNAME

if (environment === 'production') {
ENV.DS.host = 'https://frozen-coast-87972.herokuapp.com';
}

Now, use ember-cli to generate an adapter called ‘application’. This will be the default adapter used throughout your app

ember g adapter application

You’ll want to pull in the environment configuration, and set up your adapter as follows:

app/adapters/application.js

You’ll notice that we’re customizing the URL for creating “user” records. By default this would be

POST http://localhost:4000/api/users

But of course we want it to be

POST http://localhost:4000/api/register

Registration

Now go to your register route, and update it so that the doRegister action actually saves the record, and then sends the user to the login screen

app/routes/auth/register.js

Try registering a user, it should now work! This is a great time to save your work, make a PR and merge it into your master branch. If you want to compare your work to mine, here it is

The next part of this series will deal with actually logging in! Stay tuned!

--

--