Headless Drupal Web App with Angular JS and DrupalGap
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:
- A user runs the app in their browser
- The browser asks the user for access to their current location
- The app passes their location to Drupal
- Drupal will return nearby content (if any) to the app
- 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
- Go to admin/structure/types/manage/location/fields
- In the "Add existing field" section, select "Image: field_image (Image)", or select your own existing image field, or create a new one
- Click the "Save" button
- 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...
- Set the "Geocode from field" select list to "Address"
- Set the "Geocoder" select list to "Google geocoder"
- 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
- Go to admin/structure/types/manage/location/display
- Change the "Format" for "Position" to "Geofield Map"
- Click the "Save" button.
- 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
- In Drupal, go to: admin/structure/views/add, then input these values:
- View name: Nearby Locations
- Machine name: nearby_locations
- Show Content of type Location sorted by Unsorted
- Check the "Create a page" box
- Path: nearby-locations.json
- Display format: JSON data document
- Items to display: 5
- Click "Continue & Edit"
11.2 Create a Contextual Filter for the Geofield's Proximity
- In the "Contextual Filters" section, Click the "Add" button
- Type the word "Position" into the Search box, to locate your field
- In the search results, check the box next to "Content: Position (field_position) - proximity"
- Click the "Apply (All Displays)" button
- In the "When the Filter Value is not in the URL" section, select the 'Display contents of "No Results Found"' radio button
- Expand the "Exceptions" field set, and clear out the "Exception value" text field
- Scroll down to the "Unit of Distance" selection list and choose your preferred option (we'll use Miles for this example)
- 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)
- In the "Sort criteria" section, Click the "Add" button
- Type the word "Position" into the Search box, to locate your field
- In the search results, check the box next to "Content: Position (field_position) - proximity"
- Click the "Apply (All Displays)" button
- Select the "Sort ascending" radio button
- Under the "Source of Origin Point" select list, choose the "Contextual Geofield Proximity Filter" option
- 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:
- In the "Fields" section, click on the "Title" field, and change the label to: title
- Click the "Apply (All Displays)" button
- Click the "Add" button to add a new field
- Type the word "Nid" into the Search box, to locate the "Content: Nid" checkbox
- Click the "Apply (All Displays)" button
- Change the label to: nid
- Click the "Apply (All Displays)" button
- Click the "Add" button to add another field
- Type the word "Position" into the Search box, to locate your field
- In the search results, check the box next to "Content: Position (field_position) - proximity"
- Click the "Apply (All Displays)" button
- Uncheck the "Create a label" checkbox
- Set the "Round" to 2 decimal points
- Select the "Miles" option on the "Unit of Measure" select list
- Under the "Source of Origin Point" select list, choose the "Contextual Geofield Proximity Filter" option
- Click the "Apply (All Displays)" button
11.4a Add a Latitude and Longitude Field to the View
- In the "Fields" section, click the "Add" button
- Type the word "Position" into the Search box, to locate your field
- In the search results, check the box next to "Content: Position"
- Click the "Apply (All Displays)" button
- Change the label to: latitude
- Change the "Formatter" to "Latitude Only"
- Click the "Apply (All Displays)" button
- Repeat steps 1-7 for Longitude
11.5 Preview the Results
- Scroll down to the "Preview with contextual filters" text field
- 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
- Click the "Update preview" button
- 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:
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
Charles Novick (not verified)
Tue, 07/28/2015 - 21:20
Permalink
Hey Tyler,
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
Tue, 07/28/2015 - 21:41
Permalink
The ./drupalgap CLI must be
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!
Charles Novick (not verified)
Wed, 07/29/2015 - 19:04
Permalink
I have wget installed on my
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.
Brian (not verified)
Fri, 09/11/2015 - 23:50
Permalink
I can't find addressfield.js
I can't find addressfield.js and drupalgap.json
Brian
tyler
Wed, 09/16/2015 - 23:22
Permalink
drupalgap.json is something
drupalgap.json is something you generate, see step # 6 here: https://github.com/signalpoint/DrupalGap/blob/7.x-2.x/README.md
addressfield.js is available here: https://github.com/signalpoint/addressfield/tree/7.x-1.x-angular
Jairo Betancur (not verified)
Mon, 11/23/2015 - 00:33
Permalink
Hi.
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.
Nithin Kolekar (not verified)
Wed, 12/16/2015 - 03:54
Permalink
didn't get the diffrence
didn't get the diffrence between
and
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
Wed, 12/16/2015 - 08:50
Permalink
You'll want to use the 7.x-1
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
Wed, 12/16/2015 - 08:52
Permalink
./drupalgap install is just a
./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).