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. 

DayPilot Pro

See also DayPilot Pro for JavaScript - a commercial version of the DayPilot library with advanced features. Includes AngularJS scheduler control (timeline for multiple resources).

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

angularjs-event-calendar-directive.png

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

angularjs-event-calendar-week.png

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

angularjs-event-calendar-week-data.png

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

angularjs-event-calendar-drag-and-drop-moving.png

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:

  1. onEventMove event handler is fired. You can cancel the default action by calling args.preventDefault() here.
  2. 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.
  3. 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:

angularjs-event-calendar-moving-dropped.png

Drag and Drop Event Resizing

angularjs-event-calendar-drag-and-drop-resizing.png

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

angularjs-event-calendar-edit-modal-dialog.png

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

angularjs-event-calendar-day-week-switch.png

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

angularjs-event-calendar-navigator.png

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