Plantt - AngularJS Scheduler Widget

Demo & Documentation

Actions

  • Interface
    • Click & drag the grid header to move the view left or right
  • Add events
    • Double-click on the grid to add an event on a single day
    • Click & drag on the grid to add an event on the corresponding period
  • Change events
    • Click & drag an event to move it on the timeline and set its dates
    • Click & drag event's handles to extend or shrink an event and set its dates
  • Other event-related actions
    • Double-click an event to (make something you want)

Plantt module, how to use: example

Here is the code for the example displayed above:

HTML (default style depends on Bootstrap)

<!DOCTYPE html>
<html ng-app="planttExample">
<head>
  <link href="css/vendor/bootstrap.min.css" rel="stylesheet" type="text/css" />
  <link href="plantt.css" rel="stylesheet" type="text/css" />
  <style type="text/css">
     /* Defining some custom CSS for event's classes (event.type) */
     scheduler event.normal { background-color: #3AAFAF; }
     scheduler event.urgent { background-color: #D88447; }
  </style>

  <script src="js/vendor/angular-1.5.8.min.js"></script>
  <script src="js/vendor/angular-locale_fr-fr.js"></script>
  <script src="plantt.js"></script>
  <script src="js/example.js"></script>
</head>

<body class="container-fluid">

  <section class="row">
     <div class="col-md-12">

        <scheduler ng-controller="planttCtrl"></scheduler>

     </div>
  </section>

</body>
</html>
				

Javascript (depends on AngularJS only)

var planttExample = angular.module("planttExample", ["plantt.module"]);

planttExample.controller("planttCtrl", function($scope, $timeout){

   // Basic settings (optional)
   $scope.eventHeight    = 50;               // Height of events elements in pixels
   $scope.eventMargin    = 10;               // Margin above events elements for spacing
   $scope.nbLines        = 6;                // Maximum number of lines we can draw in timeline
   $scope.autoLock       = true;             // To enable the automatic lock of past events
   $scope.lockMarginDays = 15;               // Number of days before today for the automatic lock to take effect
   $scope.formatDayLong  = 'EEEE dd MMMM';   // The JS date format for the long display of dates
   $scope.formatDayShort = 'dd/MM/yyyy';     // The JS date format for the short display of dates
   $scope.formatMonth    = 'MMMM yyyy';      // The JS date format for the month display in header
   $scope.nbLines        = 6;                // Maximum number of lines we can draw in timeline
   $scope.lockMarginDays = 2;                // Number of days between today and the start date of events for the automatic lock to take effect
   $scope.viewStart      = addDaysToDate(new Date(), -1);  // First day to display in view.
   $scope.viewEnd        = addDaysToDate(new Date(), 2);   // Last day to display in view.
   // Crucial settings for the use of hours in timeline
   $scope.useHours       = true;             // To specify the use of hours (to display hourly grid and don't force events hours to 12:00)
   $scope.dayStartHour   = 8;                // The hour number at which the day begins (default 08:00)
   $scope.dayEndHour     = 20;               // The hour number at which the day ends   (default 20:00)

   // Create the events list
   $scope.events = [
      { id: 0, title: 'Yesterday afternoon', type: 'urgent',
        startDate: new Date(2016, 9-1,  9, 13, 0), endDate: new Date(2016, 9-1,  9, 20, 0) },
      { id: 1, title: 'Today morning', type: 'normal',
        startDate: new Date(2016, 9-1, 10,  8, 0), endDate: new Date(2016, 9-1, 10, 12, 0) },
      { id: 7, title: 'Today Afternoon', type: 'normal',
        startDate: new Date(2016, 9-1, 10, 14, 0), endDate: new Date(2016, 9-1, 10, 19, 0) },
      { id: 2, title: 'Yesterday - Tomorrow', type: 'urgent',
        startDate: new Date(2016, 9-1,  9, 14, 0), endDate: new Date(2016, 9-1, 11, 15, 0) },
      { id: 3, title: 'One night long', type: 'normal',
        startDate: new Date(2016, 9-1, 10, 19, 0), endDate: new Date(2016, 9-1, 11,  9, 0) },
      { id: 4, title: 'One complete day', type: 'normal',
        startDate: new Date(2016, 9-1, 12,  8, 0), endDate: new Date(2016, 9-1, 12, 21, 0) },
      { id: 5, title: 'Not so far in future', type: 'normal',
        startDate: new Date(2016, 9-1, 12, 14, 0), endDate: new Date(2016, 9-1, 13, 19, 0) },
      { id: 6, title: 'Tomorrow afternoon', type: 'urgent',
        startDate: new Date(2016, 9-1, 11, 14, 0), endDate: new Date(2016, 9-1, 11, 20, 0) }
   ];

   // Listen to the "planttError" DOM event, to do something when an error occurs
   $scope.$on('planttError', function(e, err){
	console.log('Plantt '+err.levelName+' ('+err.level+'):', err.message);
   });

   // Listen to the "daySelect" DOM event, to add a new event
   $scope.$on('daySelect', function(e, date){
	var name = prompt('New event title:');
	if (!name) return;
	if (name === "") return;
	var end  = new Date(date.getTime() + 1000*60*60);
	var id   = $scope.events.length + 1;
	var newEvent = {
	   id: id,
	   title: name,
	   type: 'normal',
	   startDate: date,
	   endDate: end
	};
	$scope.events.push(newEvent);
	$timeout(function(){
		$scope.renderView();
	}, 0);
   });

   // Listen to the "periodSelect" DOM event, to add a new event
   $scope.$on('periodSelect', function(e, dates){
	var name = prompt('New event title:');
	if (!name) return;
	if (name === "") return;
	var id   = $scope.events.length + 1;
	var newEvent = {
	   id: id,
	   title: name,
	   type: 'normal',
	   startDate: dates.start,
	   endDate: dates.end
	};
	$scope.events.push(newEvent);
	$timeout(function(){
		$scope.renderView();
	}, 0);
   });

   // Listen to the "eventMove" DOM event, to store the new position of the event in time
   $scope.$on('eventMove', function(e, event, newStartDate, newEndDate, newStartHour, newEndHour){
	newStartDate.setHours(newStartHour);
	newEndDate.setHours(newEndHour);
	event.startDate = newStartDate;
	event.endDate   = newEndDate;
	$timeout(function(){
	   $scope.renderView();
	}, 0);
   });

   // Listen to the "eventScale" DOM event, to store the new positions of the event limits in time
   $scope.$on('eventScale', function(e, event, side, newDate, newHour){
	newDate.setHours(newHour);
	if (side === 'left')
	   event.startDate = newDate;
	else if (side === 'right')
	   event.endDate = newDate;
	$timeout(function(){
	   $scope.renderView();
	}, 0);
   });

   // Listen to the "eventOpen" DOM event
   $scope.$on('eventOpen', function(e, event){
	console.log(event);
	alert('Opening event "' + event.title +'"');
   });


});

Documentation

CSS classes

Since all parts of this module are DOM elements, you can use CSS on all aspects of the interface.
Here is a list of the the most useful classes and selectors for the elements you may want to customize.

If you need full list of selectors used to build the interface, please refer to plantt.css file.

Not customizable CSS

There are some CSS parts that are critical; you should not modify them, otherwise it can break the interface:

Selector Critical styles for UI
scheduler position: relative; display: block; width: 100%;
scheduler header display: block; width: 100%; padding: 5px 15px;
scheduler header .prevActions float: left;
scheduler header .nextActions float: right;
scheduler header .centerActions text-align: center;
scheduler table width: 100%; table-layout: fixed;
scheduler table tr th,
scheduler table tr td
height: 45px; border-right: 1px solid #DDD; vertical-align: middle; cursor: pointer; text-align: center;
scheduler table tbody tr td vertical-align: bottom;
scheduler table td.today background-color: #FFDBCE;
scheduler .timelineContainer width: 100%; z-index: 100;
scheduler .eventsContainer position: absolute; width: 100%; z-index: 900;
scheduler event,
scheduler eventhelper
position: absolute; box-sizing: border-box; z-index: 200;
scheduler event handle display: block; position: absolute; height: 100%; width: 10%; background-color: rgba(0,0,0,0.1); min-width: 5px; z-index: 300;
scheduler event handle.leftHandle left: 0; cursor: w-resize;
scheduler event handle.rightHandle right: 0; cursor: e-resize;
scheduler event label display: block; padding: 17px 10%; text-align: center; cursor: move; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;

Customizable CSS

The Plantt module adds several CSS classes dynamically to the grid and drawed events. You can overwrite them in your own stylesheet.
Here are these classes and how & when they're added:

Class name Apply to Added Default style Default style description
.today Grid
table td.today
When the column represents today's date background-color: #FFDBCE; Highlights the today's date on the grid timeline
.sunday Grid
table td.sunday
When the column represents a sunday (last day of week) border-right: 1px solid #FFB8B8; Puts a line to separate weeks
.month Grid
table td.month
When the column represents the last day of month border-right: 1px solid #F55; Puts a line to separate weeks
.overLeft Event
event.overLeft
When the event's start date is before the view's start day (left boundary) border-radius: 50px 0 0 50px; Rounds the left side of the event's box
.overRight Event
event.overRight
When the event's end date is after the view's end day (right boundary) border-radius: 0 50px 50px 0; Rounds the right side of the event's box
.past Event
event.past
When the event's end date is before the today's date background-color: #888; Makes the color of the box neutral (mid-grey)
.current Event
event.current
When the event's start date is before the today's date
AND the event's end date is after the today's date
background: linear-gradient(
    45deg, transparent, transparent 40px,
    rgba(255,0,0,0.15) 40px,
    rgba(255,0,0,0.15) 80px,
    transparent 80px, transparent
);
background-size: 120px;
background-position: 0 0;
animation: animStripes 5s linear infinite;
Makes a reddish stripped background in the event's box, animated from left to right
(cf. plantt.css to see animation code)
.locked Event
event.locked
event.locked label
When the event is set to be locked element border: 2px dashed red; box-shadow: none;
> label cursor: not-allowed; padding: 15px 10%;
Creates a red dashed border and change cursor

Module Scope

The Plantt module's directives scope is shared with the controller's $scope.
You can access (read/write) everything you need from all of thoses:

Variables

Variable name Variable type Importance Default Value Description
$scope.events Array object Mandatory [] The list of all events objects. Each object must contain:
  • id: (integer) ID of the event, must be unique
  • title: (string) Title of the event
  • startDate: (Date object) The start date of the event
  • endDate: (Date object) The end date of the event
  • type: (string) Optional, one or more CSS classes to be added to the event's element
  • lock: (boolean) Optionnal, set to true to lock the event on timeline (make it ungrabbable)
$scope.useHours Boolean Optional False Set to True in order to use hours and hourly grid
$scope.dayStartHour Integer Optional 8 First hour to display into each days columns (useful only if useHours is True)
$scope.dayEndHour Integer Optional 20 Last hour to display into each days columns (useful only if useHours is True)
$scope.eventHeight Integer Optional 50 Height of events elements in pixels
$scope.eventMargin Integer Optional 10 Margin above events elements for spacing
$scope.nbLines Integer Optional 5 Maximum number of lines we can draw in timeline
$scope.autoLock Boolean Optional true To enable or disable the automatic lock of current & past events
$scope.lockMarginDays Integer Optional 0 Number of days between today and the start date of events for the automatic lock to take effect
$scope.formatDayLong String Optional 'EEEE MMMM dd' The JS date format for the long display of dates (see Date filter in module ng)
$scope.formatDayShort String Optional 'yyyy-MM-dd' The JS date format for the short display of dates (see Date filter in module ng)
$scope.formatMonth String Optional 'MMMM yyyy' The JS date format for the month display in header (see Date filter in module ng)

Methods

Method name Method parameters Method returns Description
$scope.renderView() none nothing Redraw the view.
Note that it must be called inside a $timeout() function (with time to 0) in order to render the events elements after they change.
$scope.prevDay() none nothing Redraw the view with an offset of one day to the left.
$scope.nextDay() none nothing Redraw the view with an offset of one day to the right.
$scope.centerView() daysBefore: (integer) The number of days to show before today (default 7)
daysAfter: (integer) The number of days to show after today (default 14)
nothing Redraw the view around today's column, with a specified zoom.
$scope.prevCustom() days: (integer) The number of day to offset the view nothing Redraw the view with an offset of days to the left.
$scope.nextCustom() days: (integer) The number of day to offset the view nothing Redraw the view with an offset of days to the right.
$scope.setStartView() year: (integer) The year of the date to set
month: (integer) The month of the date to set (1 based)
day: (integer) The day of the date to set
nothing Redraw the view with a specified date for the start.
$scope.setEndView() year: (integer) The year of the date to set
month: (integer) The month of the date to set (1 based)
day: (integer) The day of the date to set
nothing Redraw the view with a specified date for the end.
$scope.zoomIn() step: (integer) The number of days to remove from view nothing Redraw the view with an offset of -step to the left and to the right.
Max zoom is 2 days.
$scope.zoomOut() step: (integer) The number of days to add to view nothing Redraw the view with an offset of +step to the left and to the right.
Min zoom is 365 days.

Utility functions

The Plantt module comes with 3 global utility functions (accessible everywhere):

Function name Description Function parameters Function returns
daysInMonth() To get the number of days in the date's month date (Date object) The date from which we want the number of days of its month (Integer) The number of days in the given date's month (28, 29, 30, or 31)
daysInPeriod() To get the number of days in a period between two dates date1 (Date object) The date for the period start
date2 (Date object) The date for the period end
wantDiff (Boolean) Set to True to get it signed (default false)
(Integer) The number of days in the period between date1 and date2.
If wantDiff is true, the result can be < 0 (useful to test if the end is before the start)
addDaysToDate() To add or remove some days to a given date (used for offsets) date (Date object) The original date
days (Integer, signed) The number of days to offset in future (+) or past (-) to the date.
(Date object) The resulting date, with hours normalized to 12:00:00.000 (noon).
addHoursToDate() To add or remove some hours to a given date (used for offsets) date (Date object) The original date
hours (Integer, signed) The number of hours to offset in future (+) or past (-) to the date.
(Date object) The resulting date, with hours shifted accordingly.

Events

The Plantt module emits some custom DOM events in rootScope.
You can use it with $scope.$on('eventName', yourCallBack). Here they are:

Event name Fired when Available variables Note
daySelect Double-click on the grid body date (Date object) The selected day's date
periodSelect Click & drag on the grid body dates (Date objects) The selected period's dates
  • dates.start
  • dates.end
when in Daily mode, dates are normalized to 12:00:00.000 (noon).
eventMove Click, drag & release an event's element event ($scope.event item object) The grabbed event from the $scope.events list
newStartDate (Date object) The date for the event start date to be set
newEndDate (Date object) The date for the event end date to be set
newStartHour (integer) The hour in day for the start date to be set
newEndHour (integer) The hour in day for the end date to be set
The variables newStartDate and newEndDate are Date objects
with hours normalized to 12:00:00.000 (noon), for use in Daily mode.
When using Hourly mode, you'll need to take care of newStartHour and newEndHour.
eventScale Click, drag & release an event's handle element event ($scope.event item object) The grabbed event from the $scope.events list
side (string) The side of the handle that was used ('left' or 'right')
newDate (Date object) The new date for the handle
newHour (integer) The hour in day for the handle's date to be set
The variable newDate is a Date object
with hours normalized to 12:00:00.000 (noon).
When using Hourly mode, you'll need to take care of newHour.
eventOpen Double-click on an event's element event ($scope.event item object) The double-clicked event item from the $scope.events list
eventCtxMenu Right-click on an event's element event ($scope.event item object) The right-clicked event item from the $scope.events list
planttError When an error, warning, notice or info occurs error (object) The error details, which contains:
  • error.level (integer) The error level code
  • error.levelName (string) The error level name
  • error.message (string) The error message
Error levels names are:
  • 0: Fatal error
  • 1: Warning
  • 2: Notice
  • 3: Info
© Polosson 2016 - MIT Licence. You can use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, at the condition you include this copyright notice and a copy of the permission notice (see LICENCE file).