Features
This tutorial is based on the open-source version of the DayPilot AngularJS event calendar plugin.
AngularJS event calendar
Day and week view, bound to a shared calendar data source
Modal dialog for event editing
Date davigator
Full source code (open-source)
PHP backend with source code
ASP.NET MVC 5 backend with source code
License
The source code of the sample project is licensed under the Apache License 2.0.
The DayPilot Lite for JavaScript library is available under the Apache License 2.0.
See Also: Angular Calendar/Scheduler Component
The open-source DayPilot Lite calendar/scheduling library now supports Angular as well. See the new Angular version of this tutorial:
AngularJS Event Calendar Plugin Setup
Include DayPilot library:
<script src="js/daypilot/daypilot-all.min.js" type="text/javascript"></script>
Note that AngularJS library must be loaded first:
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.15/angular.min.js"></script>
<script src="js/daypilot/daypilot-all.min.js" type="text/javascript"></script>
AngularJS Event Calendar Directive
The AngularJS plugin provides <daypilot-calendar> directive that you can use to initialize the calendar:
<daypilot-calendar id="week"></daypilot-calendar>
The directive will display an empty calendar with the default configuration.
The value of "id" attribute will be used for the scope variable that will store the event calendar object:
var dp = $scope.week;
Event Calendar Configuration
Use the "daypilot-config" attribute to specify the object that stores the calendar properties:
<daypilot-calendar id="week" daypilot-config="weekConfig"></daypilot-calendar>
Controller
<script>
var app = angular.module('main', ['daypilot']).controller('DemoCtrl', function($scope, $timeout, $http) {
$scope.weekConfig = {
viewType: "Week",
};
});
</script>
The configuration object can specify the calendar event handlers and properties.
Supported events:
onEventClick
onEventClicked
onEventMove
onEventMoved
onEventResize
onEventResized
onTimeRangeSelect
onTimeRangeSelected
Supported properties:
angularAutoApply
businessBeginsHour
businessEndsHour
cellHeight
columnMarginRight
days
durationBarVisible
headerHeight
height
heightSpec
hourWidth
initScrollPos
locale
startDate
theme
timeFormat
visible
Event Calendar Data
Use the "daypilot-events" attribute to specify the object that stores the calendar event data.
Setup
<daypilot-calendar id="week" daypilot-config="weekConfig" daypilot-events="events" ></daypilot-calendar>
AngularJS Controller
<script>
var app = angular.module('main', ['daypilot']).controller('DemoCtrl', function($scope, $timeout, $http) {
$scope.events = [];
$scope.weekConfig = {
viewType: "Week",
};
});
</script>
Loading Calendar Event Data
We will load the calendar data using an AJAX request to "backend_events.php" script on the server side.
function loadEvents() {
// using $timeout to make sure all changes are applied before reading visibleStart() and visibleEnd()
$timeout(function() {
var params = {
start: $scope.week.visibleStart().toString(),
end: $scope.week.visibleEnd().toString()
}
$http.post("backend_events.php", params).success(function(data) {
$scope.events = data;
});
});
}
Sample JSON response:
[
{
"id":"12",
"text":"Test",
"start":"2015-03-15T12:00:00",
"end":"2015-03-15T16:30:00"
}
]
It is an array of event data objects. Supported properties of the event object
Required properties:
id
end
start
text
Optional properties:
backColor
barColor
barBackColor
barVisible
cssClass
html
PHP (backend_events.php):
<?php
require_once '_db.php';
$json = file_get_contents('php://input');
$params = json_decode($json);
$stmt = $db->prepare("SELECT * FROM events WHERE NOT ((end <= :start) OR (start >= :end))");
$stmt->bindParam(':start', $params->start);
$stmt->bindParam(':end', $params->end);
$stmt->execute();
$result = $stmt->fetchAll();
class Event {}
$events = array();
date_default_timezone_set("UTC");
$now = new DateTime("now");
$today = $now->setTime(0, 0, 0);
foreach($result as $row) {
$e = new Event();
$e->id = $row['id'];
$e->text = $row['text'];
$e->start = $row['start'];
$e->end = $row['end'];
$events[] = $e;
}
header('Content-Type: application/json');
echo json_encode($events);
?>
Note that AngularJS ($http method) sends the data to the server side as a JSON string (unlike jQuery which uses the "application/x-www-form-urlencoded" format by default).
ASP.NET MVC (BackendController.cs)
public class BackendController : Controller
{
CalendarDataContext db = new CalendarDataContext();
public class JsonEvent
{
public string id { get; set; }
public string text { get; set; }
public string start { get; set; }
public string end { get; set; }
}
public ActionResult Events(DateTime? start, DateTime? end)
{
// SQL: SELECT * FROM [event] WHERE NOT (([end] <= @start) OR ([start] >= @end))
var events = from ev in db.Events.AsEnumerable() where !(ev.End <= start || ev.Start >= end) select ev;
var result = events
.Select(e => new JsonEvent()
{
start = e.Start.ToString("s"),
end = e.End.ToString("s"),
text = e.Text,
id = e.Id.ToString()
})
.ToList();
return new JsonResult { Data = result };
}
}
Drag and Drop Event Moving
The drag and drop event moving support is enabled by default. The users can move event by dragging them using a mouse.
As soon as the event is dropped at the new location the following actions are performed:
onEventMove event handler is fired. You can cancel the default action by calling args.preventDefault() here.
The default action is performed (specified using .eventMoveHandling property). The default value is "Update" which means the event will be updated and rendered at the new location.
onEventMoved event handler is fired.
We will add our logic to onEventMove. Because the default action is set to "Update" we will just have to notify the server about the change using an AJAX call. No client-side update is needed.
The source of the event data will be updated automaticaly ($scope.events).
AngularJS Controller
<script>
var app = angular.module('main', ['daypilot']).controller('DemoCtrl', function($scope, $timeout, $http) {
$scope.events = [];
$scope.weekConfig = {
viewType: "Week",
// ...
onEventMove: function(args) {
var params = {
id: args.e.id(),
newStart: args.newStart.toString(),
newEnd: args.newEnd.toString()
};
$http.post("backend_move.php", params);
},
};
});
</script>
PHP (backend_move.php):
<?php
require_once '_db.php';
class Result {}
$json = file_get_contents('php://input');
$params = json_decode($json);
$stmt = $db->prepare("UPDATE events SET start = :start, end = :end WHERE id = :id");
$stmt->bindParam(':id', $params->id);
$stmt->bindParam(':start', $params->newStart);
$stmt->bindParam(':end', $params->newEnd);
$stmt->execute();
$response = new Result();
$response->result = 'OK';
$response->message = 'Update successful';
header('Content-Type: application/json');
echo json_encode($response);
?>
ASP.NET MVC (BackendController.cs)
public class BackendController : Controller
{
CalendarDataContext db = new CalendarDataContext();
// ...
public ActionResult Move(int id, string newStart, string newEnd)
{
var toBeMoved = (from ev in db.Events where ev.Id == id select ev).First();
toBeMoved.Start = Convert.ToDateTime(newStart);
toBeMoved.End = Convert.ToDateTime(newEnd);
db.SubmitChanges();
return new JsonResult { Data = new Dictionary<string, object> { { "id", toBeMoved.Id } } };
}
}
After dropping the event at the new location the calendar is updated:
Drag and Drop Event Resizing
The event resize handling works the same way: Just add onEventResize event handler and notify the server-side script using an AJAX call.
<script>
var app = angular.module('main', ['daypilot']).controller('DemoCtrl', function($scope, $timeout, $http) {
$scope.events = [];
$scope.weekConfig = {
viewType: "Week",
// ...
onEventResize: function(args) {
var params = {
id: args.e.id(),
newStart: args.newStart.toString(),
newEnd: args.newEnd.toString()
};
$http.post("backend_move.php", params);
},
};
});
</script>
Event Editing using a Modal Dialog
We will add a popup modal dialog for editing event details using onEventClick event handler:
$scope.dayConfig = {
// ....
onEventClick: function(args) {
var modal = new DayPilot.Modal({
onClosed: function(args) {
if (args.result) { // args.result is empty when modal is closed without submitting
loadEvents();
}
}
});
modal.showUrl("edit.php?id=" + args.e.id());
}
};
PHP (edit.php)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Edit Event</title>
<link type="text/css" rel="stylesheet" href="media/layout.css" />
<script src="js/jquery/jquery-1.9.1.min.js" type="text/javascript"></script>
<script src="js/daypilot/daypilot-all.min.js" type="text/javascript"></script>
</head>
<body>
<?php
// check the input
is_numeric($_GET['id']) or die("invalid URL");
require_once '_db.php';
$stmt = $db->prepare('SELECT * FROM [events] WHERE id = :id');
$stmt->bindParam(':id', $_GET['id']);
$stmt->execute();
$event = $stmt->fetch();
?>
<form id="f" action="backend_update.php" style="padding:20px;">
<input type="hidden" name="id" id="id" value="<?php print $_GET['id'] ?>" />
<h1>Edit Event</h1>
<div class="space">
<a href="#" id="delete">Delete Event</a>
</div>
<div>Name: </div>
<div><input type="text" id="name" name="name" value="<?php print $event['text'] ?>" /></div>
<div class="space"><input type="submit" value="Save" /> <a href="#" id="cancel">Cancel</a></div>
</form>
<script type="text/javascript">
$("#f").submit(function () {
var f = $("#f");
$.post(f.attr("action"), f.serialize(), function (result) {
DayPilot.Modal.close(result);
});
return false;
});
$("#delete").click(function() {
$.post("backend_delete.php", { id: $("#id").val()}, function(result) {
DayPilot.Modal.close(result);
});
return false;
});
$("#cancel").click(function() {
DayPilot.Modal.close();
return false;
});
$(document).ready(function () {
$("#name").focus();
});
</script>
</body>
</html>
The modal dialog intercepts the form submission and updates the data using a special AJAX call to backend_update.php. As soon as it receives the response, it closes the modal dialog using DayPilot.Modal.close() call.
PHP (backend_update.php)
<?php
require_once '_db.php';
class Result {}
$stmt = $db->prepare("UPDATE events SET text = :text WHERE id = :id");
$stmt->bindParam(':id', $_POST["id"]);
$stmt->bindParam(':text', $_POST["name"]);
$stmt->execute();
$response = new Result();
$response->result = 'OK';
$response->message = 'Update successful';
header('Content-Type: application/json');
echo json_encode($response);
?>
Switching Day/Week View
In order to support a day view in addition to the week view we will add one more instance of the event calendar:
<daypilot-calendar id="day" ></daypilot-calendar>
It will have its own configuration object (dayConfig):
<daypilot-calendar id="day" daypilot-config="dayConfig" ></daypilot-calendar>
<script>
var app = angular.module('main', ['daypilot']).controller('DemoCtrl', function($scope, $timeout, $http) {
$scope.dayConfig = {
viewType: "Day",
// ...
};
});
</script>
And it will use the same data object (events):
<daypilot-calendar id="day" daypilot-config="dayConfig" daypilot-events="events" ></daypilot-calendar>
The users will be able to switch the views using a set of buttons above the calendars:
<button ng-click="showDay()">Day</button>
<button ng-click="showWeek()">Week</button>
Event handlers
$scope.showDay = function() {
$scope.dayConfig.visible = true;
$scope.weekConfig.visible = false;
};
$scope.showWeek = function() {
$scope.dayConfig.visible = false;
$scope.weekConfig.visible = true;
};
Navigator for Switching the Date
The last step will be adding a Navigator control that will let the users switch the current date.
Directive:
<daypilot-navigator id="navi" daypilot-config="navigatorConfig" ></daypilot-navigator>
AngularJS Controller
<script>
var app = angular.module('main', ['daypilot']).controller('DemoCtrl', function($scope, $timeout, $http) {
// ...
$scope.navigatorConfig = {
selectMode: "day",
showMonths: 3,
skipMonths: 3,
onTimeRangeSelected: function(args) {
$scope.weekConfig.startDate = args.day;
$scope.dayConfig.startDate = args.day;
loadEvents();
}
};
});
</script>
The onTimeRangeSelected event handlers updates the current date for both event calendar instances using their properties objects (weekConfig, dayConfig) and reloads the events.
Supported events:
onTimeRangeSelect
onTimeRangeSelected
Supported properties:
angularAutoApply
cellHeight
cellWidth
dayHeaderHeight
locale
selectionDay
selectMode
showMonths
skipMonths
titleHeight
theme
weekStarts