Error message

Deprecated function: implode(): Passing glue string after array is deprecated. Swap the parameters in drupal_get_feeds() (line 394 of /home1/tylerfra/public_html/includes/common.inc).

Headless Drupal Web App with Angular JS and DrupalGap

Category: 

In this tutorial (for NYC CAMP 2015) we'll explore how to use Drupal 7 and DrupalGap 2 (powered by Angular JS) to build a decoupled ("headless") web application for Drupal. In a nutshell, here is what the app will do:

  1. A user runs the app in their browser
  2. The browser asks the user for access to their current location
  3. The app passes their location to Drupal
  4. Drupal will return nearby content (if any) to the app
  5. The app will display the locations on a map and let the user click on them to see more details

Ready? Let's rock and roll, no time to fiddle...

1. Download and Install Drupal 7

Since we're building a headless/decoupled Drupal site, we don't want Drupal to live at our document root. Instead we'll set it aside in its own directory:

http://example.com/drupal

That way if we visit the above URL, we can access the Drupal site when needed.

2. Download and Enable the DrupalGap 2 Module

drush dl services libraries views_datasource angular_drupal
drush dl drupalgap-7.x-2.x
drush en -y drupalgap

3. Download and Enable the Address Field Module

drush dl addressfield
drush en -y addressfield

4. Download and Enable the Geofield module, and its sub module Geofield Map

drush dl geofield
drush en -y geofield geofield_map

5. Download and Enable the Geocoder Module

drush dl geocoder
drush en -y geocoder

6. Create a Content Type called "Location"

Let's create a content type called "Location" at admin/structure/types/add.

6a. Add an Existing Image Field to the Content Type

  1. Go to admin/structure/types/manage/location/fields
  2. In the "Add existing field" section, select "Image: field_image (Image)", or select your own existing image field, or create a new one
  3. Click the "Save" button
  4. Keep all the default settings (optional), then click the "Save settings" button

7. Add an Address Field to the Content Type

In Drupal, go to admin/structure/types/manage/location/fields and add a new field with these values:

  • Label: Address
  • Machine Name: field_address
  • Field Type: Postal address
  • Widget: Dynamic address form

Click the "Save" button, then click the "Save field settings" button, and finally click the "Save settings" button at the bottom of the form. We'll just use all the default values provided for this tutorial, but feel free to adjust them.

8. Add a Geofield to the Content Type

In Drupal, go to admin/structure/types/manage/location/fields and add a new field with these values:

  • Label: Position
  • Machine Name: field_position
  • Field Type: Geofield
  • Widget: Geocode from another field

Click the "Save" button, then...

  1. Set the "Geocode from field" select list to "Address"
  2. Set the "Geocoder" select list to "Google geocoder"
  3. Click the "Save settings" button at the bottom of the form

We'll just use all the default values provided for this tutorial, but feel free to adjust them.

9. Adjust the "Manage Display" Settings for the Content Type for each Display Mode

  1. Go to admin/structure/types/manage/location/display
  2. Change the "Format" for "Position" to "Geofield Map"
  3. Click the "Save" button.
  4. Optional, repeat this for other fields and display modes (i.e. DrupalGap display mode)

10 Create some Locations with Addresses and Images

If you haven't already, go ahead and create some location nodes with addresses and images:

node/add/location

The geocoder will automatically fetch the latitude and longitude coordinates and store them within field_position. I'd recommend creating location nodes with addresses nearby your current position.

11. Create a Views JSON Page Display to Retrieve Nearby Results

Now that we've got some location nodes with address and coordinate data, we're ready to build a View. For this example our center location will be:

United Nations Headquarters
New York, NY 10017

Which has latitude and longitude coordinates of 40.748851, -73.968015. The View  will return to us JSON data about the location nodes that are within a given coordinates' radius.

11.1 Create the View

  1. In Drupal, go to: admin/structure/views/add, then input these values:
  2. View name: Nearby Locations
  3. Machine name: nearby_locations
  4. Show Content of type Location sorted by Unsorted
  5. Check the "Create a page" box
  6. Path: nearby-locations.json
  7. Display format: JSON data document
  8. Items to display: 5
  9. Click "Continue & Edit"

11.2 Create a Contextual Filter for the Geofield's Proximity

  1. In the "Contextual Filters" section, Click the "Add" button
  2. Type the word "Position" into the Search box, to locate your field
  3. In the search results, check the box next to "Content: Position (field_position) - proximity"
  4. Click the "Apply (All Displays)" button
  5. In the "When the Filter Value is not in the URL" section, select the 'Display contents of "No Results Found"' radio button
  6. Expand the "Exceptions" field set, and clear out the "Exception value" text field
  7. Scroll down to the "Unit of Distance" selection list and choose your preferred option (we'll use Miles for this example)
  8. Click the "Apply (All Displays)" button

Why miles? Well you see, here in America we don't like units of 10, and instead prefer to make our lives, and the rest of the world's, difficult.

11.3 Sort by Closest Location (Proximity)

  1. In the "Sort criteria" section, Click the "Add" button
  2. Type the word "Position" into the Search box, to locate your field
  3. In the search results, check the box next to "Content: Position (field_position) - proximity"
  4. Click the "Apply (All Displays)" button
  5. Select the "Sort ascending" radio button
  6. Under the "Source of Origin Point" select list, choose the "Contextual Geofield Proximity Filter" option
  7. Click the "Apply (All Displays)" button

11.4 Change the Label on the Title Field, and Add 4 Fields: nid, proximity, latitude and longitude

We'll adjust the label on the title field, and add 3 more fields to our View:

  1. In the "Fields" section, click on the "Title" field, and change the label to: title
  2. Click the "Apply (All Displays)" button
  3. Click the "Add" button to add a new field
  4. Type the word "Nid" into the Search box, to locate the "Content: Nid" checkbox
  5. Click the "Apply (All Displays)" button
  6. Change the label to: nid
  7. Click the "Apply (All Displays)" button
  8. Click the "Add" button to add another field
  9. Type the word "Position" into the Search box, to locate your field
  10. In the search results, check the box next to "Content: Position (field_position) - proximity"
  11. Click the "Apply (All Displays)" button
  12. Uncheck the "Create a label" checkbox
  13. Set the "Round" to 2 decimal points
  14. Select the "Miles" option on the "Unit of Measure" select list
  15. Under the "Source of Origin Point" select list, choose the "Contextual Geofield Proximity Filter" option
  16. Click the "Apply (All Displays)" button

11.4a Add a Latitude and Longitude Field to the View

  1. In the "Fields" section, click the "Add" button
  2. Type the word "Position" into the Search box, to locate your field
  3. In the search results, check the box next to "Content: Position"
  4. Click the "Apply (All Displays)" button
  5. Change the label to: latitude
  6. Change the "Formatter" to "Latitude Only"
  7. Click the "Apply (All Displays)" button
  8. Repeat steps 1-7 for Longitude

11.5 Preview the Results

  1. Scroll down to the "Preview with contextual filters" text field
  2. Enter your latitude and longitude separated by comma (with no spaces), then add an underscore followed by the radius to search within. For example, 40.748851,-73.968015_10
  3. Click the "Update preview" button
  4. Observe the results

What this means is, given a latitude and longitude center point, find content within a 4 mile radius. Adjust the coordinates and radius to simulate a position near you and your content. Here's what the result set will look similar to:

{
  "nodes" : [
    
  ]
}

12. Download and Install the DrupalGap 2 SDK

Download and extract the contents of the DrupalGap 2 zip to your website's doc root:

/var/www

This means the index.html file (and many others) would be located here:

http://example.com/index.html

After you've extracted the contents of the SDK, then you can install the DrupalGap using one of two ways. Once installed, DrupalGap will look something like the screen shot to the right, when it is running in the browser.

Using the DrupalGap CLI (command line interface)

cd /var/www
chmod +x drupalgap
./drupalgap install

Using npm (and Node.js)

cd /var/www
npm install
bower install

You may have to use sudo on the install command(s).

13. Enable the DrupalGap Geofield Module in the App

cd /var/www
./drupalgap dl geofield

Then enable it inside the index.html file's dg_ng_dependencies() function:

// Contrib
geofield: {},

14. Create a Custom DrupalGap Module JavaScript and CSS Files

cd /var/www
./drupalgap module create my_module

That will create the module's directory and javascript file for you here:

/var/www/app/modules/custom/my_module/my_module.js

Then follow the additional instructions it outputs:

1. Add "my_module: {}," in dg_ng_depencencies() function
2. Add <script src="app/modules/custom/my_module/my_module.js"></script> in the <head>...</head>

If you'd like, try visiting the test page included with your new module [via the Angular JS $routeProvider, which is roughly equivalent to Drupal's hook_menu()]:

http://example.com/#/my_module/hello

Then create an empty css file for your module:

/var/www/app/modules/custom/my_module/my_module.css

Then add this to it:

.angular-google-map-container { margin-top: 4em; height: 400px; }

15. Prepare to Display Google Map

Download lodash.min.js and angular-google-maps.min.js into the /var/www directory, then include these scripts in your index.html file:

<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js"></script>
<script type="text/javascript" src="lodash.min.js"></script>
<script type="text/javascript" src="angular-google-maps.min.js"></script>

16. Build the App

The app primarily consists of the DrupalGap 2 SDK and these 3 files:

  • index.html
  • app.js
  • my_module.js

The source code for this files is available here.

17. Run the App

Since this is a headless Drupal web application, we can run the app in any modern browser by visiting our app's site at:

http://example.com

Once loaded, the app will try to lookup our current position and display it on the map, then send the coordinates to Drupal to request nearby locations, and then display each location on the map, and a simple list of location results below the map:

18. Click on a Result List Item to View a Location Node

Provided we adjusted the DrupalGap Display Mode for our content type, we can now click on a list item result to view the actual node automatically!

This is because DrupalGap has built in support for many common Drupal paths, such as:

node/123
node/123/edit
user/456
user/456/edit
user/login
user/register
etc...

19. Conclusion

Thank you to NYC CAMP 2015 and Forest Mars for the opportunity to present this tutorial!

I hope you enjoyed the tutorial. This would not be possible without Drupal, Angular JS and the countless other open source projects involved. Cheers to open source software!

Comments

Hey Tyler,

Thanks in advance!

Trying to duplicate the demo you did in NYC at DrupalCamp

I'm on OS X 10.10.4 - Yosemite

Sorry to trouble you with this but I'm running into an error with 

13. Enable the DrupalGap Geofield Module in the App

Output from terminal below:

 

Charless-MacBook-Pro:DrupalGap-7.x-2.x cnovick$ ./drupalgap dl geofield

./drupalgap: line 149: cd: app/modules: No such file or directory--2015-07-28 21:07:28--  https://github.com/signalpoint/geofield/archive/7.x-1.x-angular.zipResol... github.com... 192.30.252.128Connecting to github.com|192.30.252.128|:443... connected.HTTP request sent, awaiting response... 302 FoundLocation: https://codeload.github.com/signalpoint/geofield/zip/7.x-1.x-angular [following]--2015-07-28 21:07:28--  https://codeload.github.com/signalpoint/geofield/zip/7.x-1.x-angularReso... codeload.github.com... 192.30.252.144Connecting to codeload.github.com|192.30.252.144|:443... connected.HTTP request sent, awaiting response... 404 Not Found2015-07-28 21:07:29 ERROR 404: Not Found. unzip:  cannot find or open 7.x-1.x-angular, 7.x-1.x-angular.zip or 7.x-1.x-angular.ZIP.rm: 7.x-1.x-angular: No such file or directorymv: rename geofield-7.x-1.x-angular to geofield: No such file or directoryDownloaded geofield to app/modules/geofieldCharless-MacBook-Pro:DrupalGap-7.x-2.x cnovick$

tyler's picture

The ./drupalgap CLI must be executed from the www directory of your app. What directory are you running it in? From the error we can see that it is trying to cd into app/modules, but it doesn't see it. Also, I've only developed the CLI for use on Linux with wget, so it may not work on Macs yet, because AFAIK Macs like curl instead of wget by default. Please advise by creating an issue on GitHub to follow up, thanks!

I have wget installed on my Mac - but I am running the command from a directory I've set in MAMP as a Document root as oppsed to /var/www. I installed your "Headless Drupal with Angular JS and Bootstrap - Hello World" on an Acquia DevDesktop instance (Mac) using a similar process and it worked fine. I'll move my testing to Ubuntu or Debian on Virtual Box. I will also create an issue on GitHub in case you want to address it for Mac users. I'm happy to test your code.

I can't find addressfield.js and drupalgap.json

 

Brian

tyler's picture

Hi.

I have been trying to do this tutorial but haven't got it right. Now I'm getting this:

Error: [$injector:modulerr] http://errors.angularjs.org/1.4.2/$injector/modulerr?p0=dgApp&p1=[$injector:modulerr] http://Ferrors.angularjs.org/1.4.2/$injector/modulerr?p0=geofield&p1

And many other related errors.

Can any one help me please?

This is my web application link: http://dev-drupalgap2-test.pantheon.io/application/

Thanks in advance for any help.

didn't get the diffrence between 

Download and extract the contents of the DrupalGap 2 zip to your website's doc root

and 

After you've extracted the contents of the SDK, then you can install the DrupalGap using one of two ways

could you please explain what will happen/ which files will be created (in addtion to the SDK files) after runing commnad ./drupalgap install?

I am trying to develop native mobile app using drupalgap but before that I am testing tutorials present in this site and also druapalgap.org docs. It seems that there are currently two branch SDKs (1.x and 2.x ) are present and couldn't decide which SDK should I select for the development (condidering d8 in mind for easy migration).

 

tyler's picture

You'll want to use the 7.x-1.x branch for now, it is production ready and well documented (powered by jQuery Mobile and PhoneGap).

It is likely I will be deprecating the 7.x-2.x and 8.x-2.x branches in favor of my fresh new 8.x-1.x branch. This 8.x-1.x branch will be "headless" itself, letting the developer pick what (if any) JS framework they want to use on top of it. I will then likely backport this 8.x-1.x branch to the 7.x-2.x branch so D7 can enjoy the new technology.

tyler's picture

./drupalgap install is just a bash script that does the equivalent to bower install, and that is download all the dependencies, place them in their folders, etc. It's their for environments that don't necessarily of npm (e.g. shared hosts).