This is the first in a series of articles that will document lessons learned while exploring using Ember as a decoupled client with Drupal.

You will need to have Ember CLI installed and a local Drupal 8 (local development assumed). This initial series of articles is based on Ember 2.14 and Drupal 8.3.5 but my initial development was over 6 months ago with earlier versions of both Ember so this should work if you have an earlier ember 2.11 or so installed.

You should read this excellent series of articles written by Preston So of Acquia on using Ember with Drupal that provides a great background and introduction to Ember and Drupal.

The Ember community has excellent documentation and guides including how to install Ember and the Ember Cli, The Ember Cli is like drush or drupal console and will be used to create the initial ember app stub code and install ember addons.

We will be using the JSON API format which is an emerging specification for REST APIs in JSON. The JSONAPI was initially developed by Yehuda Katz, who is the one of the creators of emberjs and thus has tight integration in Ember and Ember Data. There is an open issue and plan to include the JSONAPI module as a Drupal 8 core experimental module in Drupal 8.4 but for now you will need to install this module. Also for reference and background - the D.O documentation page for the JSON API module.

In this first article, we will create and setup a basic ember app to retrieve a listing of nodes (articles) with a default Drupal 8.3.5 install. The default node permissions allow view access for anonymous but our next article will explore adding authentication and pagination, exploring layout control using bootstrap.

Dependencies to walk along with this introduction.

  • Drupal 8.3.5 install - we used a default install (no edits to settings.yml files) to start so we can explore initial issues and how to resolve.
  • Ember 2.14 is being used
  • Ember Inspector added to our browser (chrome in our case)
  • Was using Acquia DevDesktop and my local site URL: http://d8site6.dd:8083

Install and enable the following Drupal modules

  • devel
  • devel_generate
  • jsonapi

drush dl devel jsonapi

drush en -y devel, devel_generate, jsonapi

Generate 20 initial nodes with Devel Generate

drush genc 20

Creating our Ember App

We will be using the Ember Cli to create the ember app stub code and built-in browser for local development. From the root directory of your Drupal installation:

Creates the initial ember application - call it what ever you like.

  • ember new emberapp
  • This will take a few minutes as it's doing a fair amount of work and logs it's progress to your console.

We can now change directories into the ember app and start the built-in ember server. It's best to do this in a new command window (shell) so that you still have access to the ember cli. The ember app will be automatically rebuilt as we add/edit the app source code.

  • cd emberapp 
  • ember serve
  • The application will be compiled and any errors displayed. Once built, you will see Build successful (7737ms) – Serving on http://localhost:4200

Now bring up the app in your browser and you should get the initial ember app welcome page.

Let's edit the app landing page so we can see how live editing works. You will see the application code is located under emberapp/app and has a very structured layout. Ember has formal conventions or best practices that make it easier to ramp up and understand other developers code.

  • edit the app/templates/applications.hbs. Remove the {{welcome-page}} component and add our basic title for now.
  • If you still have the ember server running, you will see the application rebuild and browser refresh automatically with the affected change.
{{! app/templates/application.hbs }}
<h1>My Sample Ember App using Drupal</h1>

{{outlet}}

Next, let's work on retrieving a list of articles using the ember app. Ember is URL-driven so it always starts at the URL. We will create the route stub code using the ember cli and it will automatically update the main app/router.js and create a route handler with a default template that we can customize. You should be in the created <emberapp> directory now so the generated stub code is created as part of the new ember app.

  • ember g route articles  (note need to be in the created emberapp directory)
Blaines-MacBook-Pro:app blaine$ ember g route articles
installing route
  create app/routes/articles.js
  create app/templates/articles.hbs
updating router
  add route articles
installing route-test
  create tests/unit/routes/articles-test.js

We need to define the model to represent the persistent data store. When data changes, or new data is added, the model is saved.  We will create the model using the full entity name so that it matches the expected returning JSON object type from Drupal. If you navigate to {siteurl}/jsonapi/node/article?_format=api_json, you can see the format of the REST API response and the data format, note the type attribute is set to node--article.

  • ember g model node--article
  • This creates a model stub app/models/node--article.js
  • The updates to the generated models/node-article.js file below now describe the attributes or fields we are interested in - reference: https://guides.emberjs.com/v2.14.0/models/defining-models/
// app/models/node--article.js
import DS from 'ember-data';

export default DS.Model.extend({
  nid: DS.attr(),
  uuid: DS.attr(),
  title: DS.attr(),
  created: DS.attr(),
  body: DS.attr()
});

Now we have the model defined and need to update the generated route stub code we created for articles. Our changes will fetch the data to populate the model when the route is used. The Ember route API has quite a few methods but we will just need the model method to have it return all articles - update your app/routes/articles.js as below.

import Ember from 'ember';

export default Ember.Route.extend({
  model() {
    return this.get('store').findAll('node--article');    
  }
});

When the model hook fires, it will make the API request to Drupal. We need to create and modify the Ember adapters for connecting to Drupal. In Ember Data, the Adapter determines how data is persisted to a backend data store, such as the URL format and headers for a REST API.  First we will create the adapter stub code and then modify the default implementation.

  • ember g adapter application
// app/adapters/application.js
import DS from 'ember-data';

export default DS.JSONAPIAdapter.extend({
  host: 'http://d8site6.dd:8083',
  namespace: 'jsonapi',

  pathForType(type) {
      let entityPath;
      switch(type) {
        case 'node--article':
          entityPath = 'node/article';
          break;
      }
      return entityPath;
    },

    buildURL() {
      return this._super(...arguments) + '?_format=api_json';
    }

});
  • Modify the adapters/application.js as noted in the above code.
  • We need to define the host URL and namespace that will be used to build the url in the buildURL method. Note the call to this._super method(). You will see this type of implementation frequently in ember code. This is how you call the parent implementation for the method (i.e. the object you are extending), so you can override methods but still access the implementation of your parent class.
  • Replace the host URL with your site specific URL 
  • There are several Ember Data Adapters but we will be using the JSONAPI Adapter and we extend from that base class. The adapter class has a handful of hooks that are commonly used to customize it and we need to alter the URL path used to make the REST API request. By default, it will use the data model name 'type' that we created. Although the Drupal entity type and data model is node--article, the API URL that we need is node/article so we need to override the pathForType method - reference: https://guides.emberjs.com/v2.14.0/models/customizing-adapters

We are getting close, we now need to update our templates to display the articles. When we created the route articles, it created templates/articles.hbs. We will modify to iterate over the model records and it will render automatically into the application.html {{outlet}}.

<h2>List of articles</h2>
<ul>
  {{#each model as |article|}}
    <li>{{article.title}}</li>
    <p>{{article.body.value}}</p>
  {{/each}}
</ul>

 

Let's navigate in our browser to the articles route http://localhost:4200/articles

Oh no, you likely see a blank white page which is a good indication of an error. Check the browser console using the browser devtools.

Screenshot of devtools showing CORS issue

We have a CORS issue because we are making the API request from a different URL (domain and port) and this is not allowed by browsers for security reasons by default. There is nothing we can really do from the browser or client side and have to grant permission. This is more a local development issue because you would deploy the compiled production Ember App to the server running Drupal most likely but if not, then we need to address this issue the same way. 

Fortunately for us, Drupal as of Drupal 8.2, there is native support for CORS - https://www.drupal.org/node/2715637

  • copy the sites/default/default.services.yml to services.yml and enable the CORS service. The minimum settings to work for now are:
  • # Configure Cross-Site HTTP requests (CORS).
       # Read https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
       # for more information about the topic in general.
       # Note: By default the configuration is disabled.
      cors.config:
        enabled: true
        # Specify allowed headers, like 'x-allowed-header'.
        allowedHeaders: ['Content-Type', 'Access-Control-Allow-Headers']
        # Specify allowed request methods, specify ['*'] to allow all possible ones.
        allowedMethods: []
        # Configure requests allowed from specific origins.
        allowedOrigins: ['*']
        # Sets the Access-Control-Expose-Headers header.
        exposedHeaders: false
        # Sets the Access-Control-Max-Age header.
        maxAge: false
        # Sets the Access-Control-Allow-Credentials header.
        supportsCredentials: false
  • Once the services.yml is in place, clear your drupal site cache and refresh the browser.

You should now be able to see the article listing.

Screenshot of ember app showing article listing

 

 

Next in Part 2, let's explore adding authentication and CORS will once again resurface.