This is part 3 of our series on developing a Decoupled Drupal Client Application with Ember. If you haven't yet read the previous articles, it would be best to review Part1 first. In this article, we are going to clean up the code to remove the hard coded URL for the host, move the login form to a separate page and add a basic header and styling.
We currently have defined the host URL in both the adapter (app/adapters/application.js) for the Ember Data REST calls as well as the AJAX Service that we use for the authentication (app/services/ajax.js). This is clearly not a good idea but helped us focus on the initial goal and our simple working app.
Ember CLI ships with support for managing your application's environment. Ember CLI will setup a default environment config file at config/environment
. Here, you can define an ENV
object for each environment, which are currently limited to three: development, test, and production.
The ENV object contains several important keys but we will focus on:
APP
can be used to pass flags/options to your application instance.environment
contains the name of the current enviroment (development
,production
ortest
).
You can access these environment variables in your application code by importing from your-application-name/config/environment
.
Edit emberapp/config/environment.js file and under the APP key, add a property for API_HOST
APP: {
// Here you can pass flags/options to your application instance
// when it is created
API_HOST: '<enter the url for your site>',
}
You will notice in this config file, you can set properties that are used in the three different environments. For now we are only using the development environment and leave you to explore further in the online Ember Guide section.
Edit our adapter and import in the config file to use the APP.API_HOST property instead of the hard coded host URL.
// app/adapters/application.js
import DS from 'ember-data';
import config from '../config/environment';
export default DS.JSONAPIAdapter.extend({
host: config.APP.API_HOST,
namespace: 'jsonapi',
You should still be able to retrieve the article listing from your site at localhost:4200/articles.
Do the same now for the services/ajax.js and verify that you can still login.
// app/services/ajax.js
import Ember from 'ember';
import AjaxService from 'ember-ajax/services/ajax';
import config from '../config/environment';
export default AjaxService.extend({
host: config.APP.API_HOST,
Add a basic menu and styling
Add the following css to app/styles/app.css
/* Add a black background color to the top navigation */
.topnav {
background-color: #333;
overflow: hidden;
}
/* Style the links inside the navigation bar */
.topnav a {
float: left;
display: block;
color: #f2f2f2;
text-align: center;
padding: 14px 16px;
text-decoration: none;
font-size: 17px;
}
/* Change the color of links on hover */
.topnav a:hover {
background-color: #ddd;
color: black;
}
/* Add a color to the active/current link */
.topnav a.active {
background-color: #4CAF50;
color: white;
}
Now, edit the templates/application.hbs and add our basic menu with links to a Login and Logout that will switch depending on if we are logged in or not. This won't work yet as we are missing some pieces.
{{! app/templates/application.hbs }}
<h1>BasicEmber App using Drupal</h1>
<div class="topnav" id="myTopnav">
{{#link-to 'index' class="button"}}Home{{/link-to}}
{{#if session.isAuthenticated}}
{{#link-to 'user' class="button"}}Logout{{/link-to}}
{{else}}
{{#link-to 'user' class="button"}}Login{{/link-to}}
{{/if}}
</div>
<div style="margin-top:20px;padding:20px;">
{{#if session.isAuthenticated}}
<button {{action "logout"}}>Logout</button>
{{else}}
<form {{action 'authenticate' on='submit'}}>
{{input value=name placeholder='Login'}}
<div style="margin-top:5px;">{{input value=pass placeholder='Password' type='password'}}</div>
<div style="padding-top:10px;"><button type="submit">Login</button></div>
</form>
{{/if}}
</div>
{{outlet}}
We need to create the /user route and we are also going to refactor our login form into a component. Let's create the default stub code using Ember CLI.
- ember g route user
- ember g component user-login
Components are a vital part of an Ember application. They allow you to define your own, application-specific HTML tags and implement their behavior using JavaScript. An Ember component consists of a handlebars template file and a javascript file under app/components. We will now move the login form HTML to the new user-login component template file.
{{! app/templates/compponents/user-login.hbs }}
<div style="margin-top:20px;padding:20px;">
{{#if session.isAuthenticated}}
<button {{action "logout"}}>Logout</button>
{{else}}
<form {{action 'authenticate' on='submit'}}>
{{input value=name placeholder='Login'}}
<div style="margin-top:5px;">{{input value=pass placeholder='Password' type='password'}}</div>
<div style="padding-top:10px;"><button type="submit">Login</button></div>
</form>
{{/if}}
</div>
Edit the application.hbs file and we will remove the login form HTML and related template code that was moved to the user-login component template or else we will soon be seeing it on the page twice. We want to only display the login form when we navigate to the /user route. We've added the route using Ember CLI but have not yet added anything to the templates/user.hbs so your not currently seeing anything on the page.
Edit templates/user.hbs to render the user-login component by simply adding {{user-login}}
{{! app/templates/user.hbs }}
{{user-login}}
The login form should now be appearing but won't yet work as we have to move the code for the authenticate and logout actions to the components/user-login.js and we will have a self contained component.
// app/components/user-login.js
import Ember from 'ember';
export default Ember.Component.extend({
ajax: Ember.inject.service(),
session: Ember.inject.service('session'),
actions: {
authenticate() {
// Use the ember-simple-auth addon authenticate action in authenticator/drupal.js
let { name, pass } = this.getProperties('name', 'pass');
console.log('login-page.js - call the authenticator');
this.get('session').authenticate('authenticator:drupal', name, pass).then(function () {
console.log('login-page.js - authentication completed successfully')
}).catch((reason) => {
this.set('authenticate errorMessage', reason.error || reason);
});
},
logout() {
// Use the ember-simple-auth addon invalidate action in authenticator/drupal.js
let isAuthenticated = this.get('session.isAuthenticated');
console.log('Logout Action - isAuthenticated:', isAuthenticated);
this.get('session').invalidate().then(function (response) {
console.log('login-page component - logout promise successful response ', response);
}
).catch(function (error) {
console.log('login-page component - logout promise Error ', error);
}
);
return;
},
}
});
Be sure to clean up and remove the code from the application controller now - let's strip it back to the default empty controller class and verify the login/logout functionality is working.
// app/controllers/application.js
import Ember from 'ember';
export default Ember.Controller.extend({
});
Why is the login menu item not changing - it should change to logout.
- We need to inject the SESSION service into the application controller so the template has access to session.isAuthenticated variable.
Updated controllers/application.js
import Ember from 'ember';
export default Ember.Controller.extend({
session: Ember.inject.service('session'),
});
Last step, remove the div wrapper and the login form from application.hbs since we are now using the user-login component.
{{! app/templates/application.hbs }}
<h1>BasicEmber App using Drupal</h1>
<div class="topnav" id="myTopnav">
{{#link-to 'index' class="button"}}Home{{/link-to}}
{{#if session.isAuthenticated}}
{{#link-to 'user' class="button"}}Logout{{/link-to}}
{{else}}
{{#link-to 'user' class="button"}}Login{{/link-to}}
{{/if}}
</div>
{{outlet}}
Now the menu should toggle between Login and Logout. Next in Part 4 of this series we will add the ability to create a new article.