<
Neerav Mehta
Founder & CEO
For the last couple of years, developers relied on tools such as Adobe Flex, Adobe Flash and JS to build Rich Internet Applications (RIA). Even though these tools were convenient to use, applications built with them had several drawbacks. Some of them are listed below:
With AngularJS, things are looking a lot brighter.
Google Developers deserve a lot of credit for building a light-weight and scale-able framework on JS and HTML5 that handles all complexities and takes all the pain away from the developer. Developers can now focus more on the functionality they expect while building highly-interactive web-applications.
In Drupal, AngularJS has an able technology ally that’s probably the most revered CMS on earth.
We assume you have a basic knowledge of Drupal installation, configuration, Drush, themes and module development. A similar knowledge of JS, Twitter Bootstrap, MVC & AngularJS will be beneficial.
First, we need to configure Drupal with AngularJS. There are a few prerequisites we need to install. They are as follows:
Drupal
AngularJs
In case you have any issue installing Drupal7, follow this tutorial.
We are using Drush command to install modules in Drupal:
$ drush dl angularjs
$ drush en angularjs
$ drush dl bootstrap
$ drush en bootstrap
$ drush dl libraries
$ drush en libraries
Add other modules you wish to install as well.
After you are done installing Drupal and the desired modules, it’s time to install the AngularJS library in Drupal. For that, you need to create a library folder with the following path: sites/all/libraries as shown in the screen-shot below:
And now you need to copy the angular folder inside it as shown in the screen-shot below:
Now, it’s time to visit AngularJS’s configuration section to configure it. A screenshot of the page is shown below:
Now, you can verify whether AngularJS has been installed correctly on your system. Navigate to the link angular/nodes#/. It will show you a basic AngularJS application to prove that everything is working fine. Check the screen-shot below:
There is a very good FireFox add-on called AngScope to inspect the AngularJS application object. It will display the AngularJS scope and objects as in the screen-shot below:
We have now set up Drupal and AngularJS.
We are going to create a Calendar application by using AngularJS and see how we can write a Drupal module to integrate with it.
We have planned a module which is called ng_node_calender. It has an AngularJS application and module files. You can see the screen-shot below for more details:
Module structure: ng_node_calender/
-ng_node_calender.module
-ng_node_calender.info
-ng_node_calender.install //if required
-themes/* //angular templates file
-js/* //angular app and directives
The JS folder will be home to the AngularJS application. The theme folder will have the Drupal templates files.
Now, we have the directory set up for the module. Let’s start writing our first callback method for the AngularJS application to see how Drupal works with AngularJS.
Module ng_node_calender.info looks like:
name = AngularJS NodeCalender
description = It will provide node calender
core = 7.x
dependencies[] = angularjs
version = "7.x-1.3"
core = "7.x"
project = "ng_node_calender"
datestamp = "1404549418"
Module ng_node_calender.module looks like:
/**
* Implements hook_menu().
*/
function ng_node_calender_menu() {
$items = array();
$items['ng_node/calender_app'] = array(
'title' => t('Angular Node Calender'),
'page callback' => 'ng_node_calender',
'access arguments' => array('access content'),
);
return $items;
}
In the above code, we have registered menu callback to load the AngularJS application inside the Drupal environment.
/**
* Page callback for ng_node_calender().
*/
function ng_node_calender(){
angularjs_init_application('ng_node_calender');
//CSS files for calender
drupal_add_css(drupal_get_path('module', 'ng_node_calender') . '/js/lib/fullcalendar-2.0.2/fullcalendar.css');
drupal_add_css(drupal_get_path('module', 'ng_node_calender') . '/js/lib/fullcalendar-2.0.2/fullcalendar.print.css');
//JS files for calender
drupal_add_js(drupal_get_path('module', 'ng_node_calender') . '/js/lib/fullcalendar-2.0.2/lib/moment.min.js');
drupal_add_js(drupal_get_path('module', 'ng_node_calender') . '/js/lib/fullcalendar-2.0.2/fullcalendar.min.js');
drupal_add_js(drupal_get_path('module', 'ng_node_calender') . '/js/lib/fullcalendar-2.0.2/gcal.js');
drupal_add_js(drupal_get_path('module', 'ng_node_calender') . '/js/directives/calendar.js');
drupal_add_js(drupal_get_path('module', 'ng_node_calender') . '/js/directives/ui-bootstrap.min.js');
drupal_add_js(drupal_get_path('module', 'ng_node_calender') . '/js/directives/ui-bootstrap-tpls.min.js');
drupal_add_js(drupal_get_path('module', 'ng_node_calender') . '/js/app.js');
return theme('ng_node_calender');
}
Here, we have registered all the required JS files, which we will be using in our application.
/**
* Implements hook_theme
*
* @return array
*/
function ng_node_calender_theme() {
$themes = array();
$themes['ng_node_calender'] = array(
'template' => 'theme/ng_node_calender',
'parameters' => array(),
);
return $themes;
}
We have registered template files which actually initialize the application by using ng markups. We have created ng_node_calender.tpl.php inside the theme directory and its code is as follows:
<div id="ng_node_calender" ng-app="ng_node_calender">
<div ng-view></div>
</div>
If you look at the angularjs module, you will see a file called angularjs.api.inc. It provides hooks which are given by the angularjs module in order to register AngularJS application directives , controllers and so on.
Now, let us focus on app.js, which actually loads the whole application.
Here is the code:
var app = angular
.module("ng_node_calender", [
"node",
"nodes",
"ui.calendar",
"ui.bootstrap",
"ui.bootstrap.modal",
])
.config(function ($routeProvider) {
$routeProvider
.when("/", {
controller: CalenderCtrl,
templateUrl:
Drupal.settings.angularjsApp.basePath + "/ng_node/calender/display",
})
.otherwise({ redirectTo: "/" });
});
Drupal.settings.angularjsApp.basePath will contain the base path and CalenderCtrl contains the controller object.
Let us have a look at the CalenderCtrl() function:
function CalenderCtrl($scope, $rootScope, $modal, Nodes, Node) {
var date = new Date();
var d = date.getDate();
var m = date.getMonth();
var y = date.getFullYear();
$scope.alerts = [];
$scope.changeTo = "Hungarian";
/* event source that pulls from google.com */
$scope.eventSource = {
url: "http://www.google.com/calendar/feeds/usa__en%40holiday.calendar.google.com/public/basic",
className: "gcal-event", // an option!
currentTimezone: "America/Chicago", // an option!
};
/* event source that contains custom events on the scope */
$scope.events = [
{ title: "All Day Event", start: new Date(y, m, 1) },
{
title: "Long Event",
start: new Date(y, m, d - 5),
end: new Date(y, m, d - 2),
},
{
id: 999,
title: "Repeating Event",
start: new Date(y, m, d - 3, 16, 0),
allDay: false,
},
{
id: 999,
title: "Repeating Event",
start: new Date(y, m, d + 4, 16, 0),
allDay: false,
},
{
title: "Birthday Party",
start: new Date(y, m, d + 1, 19, 0),
end: new Date(y, m, d + 1, 22, 30),
allDay: false,
},
{
title: "Click for Google",
start: new Date(y, m, 28),
end: new Date(y, m, 29),
url: "http://google.com/",
},
];
/* event source that calls a function on every view switch */
$scope.eventsF = function (start, end, callback) {
var s = new Date(start).getTime() / 1000;
var e = new Date(end).getTime() / 1000;
var m = new Date(start).getMonth();
var events = [
{
title: "Feed Me " + m,
start: s + 50000,
end: s + 100000,
allDay: false,
className: ["customFeed"],
},
];
callback(events);
};
$scope.closeAlert = function (index) {
$scope.alerts.splice(index, 1);
};
$scope.calEventsExt = {
color: "#f00",
textColor: "yellow",
events: [
{
type: "party",
title: "Lunch",
start: new Date(y, m, d, 12, 0),
end: new Date(y, m, d, 14, 0),
allDay: false,
},
{
type: "party",
title: "Lunch 2",
start: new Date(y, m, d, 12, 0),
end: new Date(y, m, d, 14, 0),
allDay: false,
},
{
type: "party",
title: "Click for Google",
start: new Date(y, m, 28),
end: new Date(y, m, 29),
url: "http://google.com/",
},
],
};
/* alert on eventClick */
$scope.alertOnEventClick = function (event, allDay, jsEvent, view) {
$scope.alerts.push({ msg: "Another alert!" });
};
/* alert on Drop */
$scope.alertOnDrop = function (
event,
dayDelta,
minuteDelta,
allDay,
revertFunc,
jsEvent,
ui,
view
) {
$scope.alertMessage = "Event Droped to make dayDelta " + dayDelta;
};
/* alert on Resize */
$scope.alertOnResize = function (
event,
dayDelta,
minuteDelta,
revertFunc,
jsEvent,
ui,
view
) {
$scope.alertMessage = "Event Resized to make dayDelta " + minuteDelta;
};
/* add and removes an event source of choice */
$scope.addRemoveEventSource = function (sources, source) {
var canAdd = 0;
angular.forEach(sources, function (value, key) {
if (sources[key] === source) {
sources.splice(key, 1);
canAdd = 1;
}
});
if (canAdd === 0) {
sources.push(source);
}
};
/* add custom event*/
$scope.addEvent = function () {
$scope.events.push({
title: "Open Sesame",
start: new Date(y, m, 28),
end: new Date(y, m, 29),
className: ["openSesame"],
});
};
/* remove event */
$scope.remove = function (index) {
$scope.events.splice(index, 1);
};
/* Change View */
$scope.changeView = function (view, calendar) {
calendar.fullCalendar("changeView", view);
};
/* Change View */
$scope.renderCalender = function (calendar) {
if (calendar) {
calendar.fullCalendar("render");
}
};
/* config object */
$scope.uiConfig = {
calendar: {
height: 450,
editable: true,
eventStartEditable: false,
header: {
left: "prev,next today",
center: "title",
right: "month,basicWeek,basicDay",
},
eventClick: $scope.alertOnEventClick,
eventDrop: $scope.alertOnDrop,
eventResize: $scope.alertOnResize,
},
};
$scope.changeLang = function () {
if ($scope.changeTo === "Hungarian") {
$scope.uiConfig.calendar.dayNames = [
"Vasárnap",
"Hétfő",
"Kedd",
"Szerda",
"Csütörtök",
"Péntek",
"Szombat",
];
$scope.uiConfig.calendar.dayNamesShort = [
"Vas",
"Hét",
"Kedd",
"Sze",
"Csüt",
"Pén",
"Szo",
];
$scope.changeTo = "English";
} else {
$scope.uiConfig.calendar.dayNames = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
];
$scope.uiConfig.calendar.dayNamesShort = [
"Sun",
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
];
$scope.changeTo = "Hungarian";
}
};
/* event sources array*/
$scope.eventSources = [$scope.events, $scope.eventSource];
}
This controller function is the implementation of angular-ui calendar directive, which can be found here.
So let’s see how it works. We have a menu callback ng_node/calender_app. Hitting it will trigger a callback method called ng_node_calender, which loads and initializes the AngularJS application with all the dependencies.
The question is how will AngularJS controller be called? How will it load Fullcalender AngularJS directives? We know that an AngularJS directive is required to include markup inside template file. How can we achieve this inside Drupal menu callback and with templates files?
Here is the answer to all these questions. We need to create another menu callback which will load the partial view of AngularJS inside Drupal templates. Let’s have a look at it:
function ng_node_calender_menu() {
$items = array();
/**
* @todo: Create menu callback which load angular js calender
*/
$items['ng_node/calender_app'] = array(
'title' => t('Angular Node Calender'),
'page callback' => 'ng_node_calender',
'access arguments' => array('access content'),
);
$items['ng_node/calender/display'] = array(
'title' => t('Display calender'),
'page callback' => 'ng_node_calender_display',
'access arguments' => array('access content'),
);
return $items;
}
By hitting ng_node/calender/display through the AngularJS application, it will trigger ng_node_calender_display, which will load a partial view through the Drupal template system. We have used <div ui-calendar="uiConfig.calendar" ng-model="eventSources"></div>
, which is responsible for loading AngularJS directives for Calendar. Look at the code below to understand better:
function ng_node_calender_display(){
return drupal_get_form('calender_display_form_builder');
}
function calender_display_form_builder($form, &$form_state){
$form['calender_display'] = array(
'#type' => 'item',
'#ng_controller' => 'CalenderCtrl',
'#markup' => '<div ui-calendar="uiConfig.calendar" ng-model="eventSources"></div>'
);
return $form;
}
If you look at the above code, you will notice #ng_controller => CalenderCtrl. This property is provided by the angularjs module so that we can bind controller class through Drupal Form API. In this way, angularjs will know we have attached a controller with this element. As there are other properties also provided by that module (fpr eg., #ng_model), you can also add more AngularJS properties by using the #attributes property of FORM API.
Here is the screen-shot of the final result:
We have covered the basic techniques to show you how to use Drupal Framework with AngularJS Framework with adherence to Drupal Standards. Hope this post helps you with your Drupal-Angularjs integration project!
Neerav Mehta
Neerav Mehta is the Founder & CEO of Red Crackle. With sterling qualities, Neerav’s technological acumen is firing a generation of progressive companies on the digital path. With an undergraduate degree in Electrical Engineering from India's most prestigious institution IIT Bombay and having spent seven years developing and contributing to the launch of AMD's innovative line of computer products, Neerav founded Red Crackle where he is lauded for his dynamic and innovative genius.
Let’s get you started!