Version 1
Version 1

Calendar Grouping

In the Calendar Grouping, you can display data in a calendar view. You can customize the style of the calendar by changing the style or the layout of the group items using a row template. In the following example, the calendar grouping strategy is used with the Grid Layout to present a data set in a form of a calendar. This calendar is editable.

Use the following steps to implement the Calendar Grouping.

Sample Code

These steps assume that you have already defined the custom styles and data. See Calendar Grouping for additional information.

  1. Add the following references after the Dataview reference.
<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/normalize.css@8.0.1/normalize.css" />
    <link href="css/bootstrap-snippet.min.css" rel="stylesheet" />
    <link href="css/gc.dataviews.core.min.css" rel="stylesheet" />
    <link href="css/gc.dataviews.calendar.min.css" rel="stylesheet" />
    <link href="css/gc.dataviews.grid.min.css" rel="stylesheet" />

    <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js" type="text/javascript"></script>
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js" type="text/javascript"></script>
    <script src="scripts/gc.dataviews.common.min.js"></script>
    <script src="scripts/gc.dataviews.core.min.js"></script>
    <script src="scripts/gc.dataviews.calendar.min.js"></script>
    <script src="scripts/gc.dataviews.grid.min.js"></script>
  </head>
</html>
  1. Add a div tag within the body tag to include the DOM element as the container in the page.
<body class="theme-default">
  <template id="editDialogTemplate" style="display: none">
    <div class="editEventDialog">
      <div class="editEventBackground">
        <div class="editEventHeaderInner">
          <div class="header">Edit Event</div>
          <div class="gc-editing-close gc-float-right" onclick="removeDialog(event)">
            <span class="gc-icon close-icon"></span>
          </div>
        </div>
      </div>
      <div class="editEventContent">
        <div class="contentItem">
          <div class="title fixed"><label>Title</label></div>
          <div class="input-container">
            <input id="eventTitleContent" />
          </div>
        </div>
        <div class="contentItem">
          <div class="title fixed"><label>When</label></div>
          <div class="input-container">
            <div class="input-group date">
              <input type="time" id="datetimepicker1" />
            </div>
          </div>
          <div class="title"><label>to</label></div>
          <div class="input-container">
            <div class="input-group date">
              <input type="time" id="datetimepicker2" />
            </div>
          </div>
        </div>
      </div>
      <div class="editEventBackground">
        <div class="btn-group pull-right editEventBtnInner" rol="group">
          <button type="button" class="btn btn-default editBtn" onclick="deleteEvent(event)">Delete Event</button>
          <button type="button" class="btn btn-default editBtn" onclick="saveEvent(event)">Save</button>
          <button type="button" class="btn btn-default editBtn" onclick="cancelEditEvent(event)">Cancel</button>
        </div>
      </div>
    </div>
  </template>
  <div class="main-container">
    <div class="button-container">
      <div class="flex0">
        <div id="commandPanel" class="btn-group" role="group">
          <button id="dayView" type="button" class="btn btn-default">Day</button>
          <button id="weekView" type="button" class="btn btn-default">Week</button>
          <button id="monthView" type="button" class="btn btn-default active">Month</button>
        </div>
      </div>
      <div class="flex1">
        <div id="title">June 2018</div>
      </div>
      <div class="flex0">
        <div class="btn-group" role="group">
          <div class="btn btn-default app-prev">
            <i class="demo-icon icon-left-big"></i>
          </div>
          <div class="btn btn-default app-next">
            <i class="demo-icon icon-right-big"></i>
          </div>
        </div>
      </div>
    </div>
    <div id="grid" class="grid"></div>
  </div>
</body>
  1. Add the template definition and calendar definition followed by the column definition.
var editData;
var cols = [
  {
    id: 'topic',
    dataField: 'topic',
  },
  {
    id: 'start',
    dataField: 'start',
    dataType: 'date',
    format: 'HH:mm',
  },
  {
    id: 'end',
    dataField: 'end',
    dataType: 'date',
    format: 'HH:mm',
  },
  {
    id: 'speaker',
    dataField: 'speaker',
  },
];
var monthEventTemplate =
  '\n<div class="event-container">\n  <div class="event-title">\n    <span class="time">\{{=it.start}}</span> <span class="topic">\{{=it.topic}}</span>\n  </div>\n<div>';
var dayEventTemplate = '<div class="event-content">\{{=it.topic}}</div>';
var now = new Date(Date.now());
var startTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 8, 0, 0);
var endTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 0, 0);
var calendar = new GC.DataViews.CalendarGrouping({
  viewMode: 'Month',
  daysEventStartField: 'start',
  daysEventEndField: 'end',
  startDate: startTime,
  daysStartTime: startTime,
  daysEndTime: endTime,
});
calendar.eventClick.addHandler(function(args) {
  removeDialog();
  var dialog = createEditEventDialog();

  if (dialog) {
    setTimeout(function() {
      var popoverDialog = $('.popover-dialog');

      if (popoverDialog.length > 0) {
        popoverDialog.remove();
      }

      dataView.container.parentElement.appendChild(dialog);
      var left = parseInt(($(window).width() - $(dialog).width()) / 2 + $(window).scrollLeft(), 10);
      var top = parseInt(($(window).height() - $(dialog).height()) / 2 + $(window).scrollTop(), 10);
      $(dialog).css({
        left: left,
        top: top,
      });

      if (!args.data) {
        var start = new Date();
        var end = new Date(
          start.getFullYear(),
          start.getMonth(),
          start.getDate(),
          start.getHours() + 2,
          start.getMinutes(),
          start.getSeconds()
        );
        args.data = {
          topic: '',
          start: start,
          end: end,
        };
      }

      editData = args.data;
      var excelFormatter = new GC.DataViews.GeneralFormatter('hh:mm:ss');
      $('#editDialog #eventTitleContent')[0].value = editData.topic;
      $('#editDialog #datetimepicker1')[0].value = excelFormatter.format(editData.start);
      $('#editDialog #datetimepicker2')[0].value = excelFormatter.format(editData.end);
    }, 100);
  }
});
var dataView = new GC.DataViews.DataView(
  document.getElementById('grid'),
  data,
  cols,
  new GC.DataViews.GridLayout({
    grouping: {
      field: 'start',
      converter: function converter(field) {
        return field.toDateString();
      },
    },
    rowTemplate: monthEventTemplate,
    groupStrategy: calendar,
  })
);
updateTitle();

function previousMonth() {
  var options = calendar.options;
  var currentDate = options.startDate;

  if (options.viewMode === 'Month') {
    currentDate = getMonth(currentDate, -1);
  } else if (options.viewMode === 'Week') {
    currentDate = getDay(currentDate, -7);
  } else {
    currentDate = getDay(currentDate, -1);
  }

  calendar.options.startDate = currentDate;
  updateTitle();
  dataView.invalidate();
}

function nextMonth() {
  var options = calendar.options;
  var currentDate = options.startDate;

  if (options.viewMode === 'Month') {
    currentDate = getMonth(currentDate, 1);
  } else if (options.viewMode === 'Week') {
    currentDate = getDay(currentDate, 7);
  } else {
    currentDate = getDay(currentDate, 1);
  }

  calendar.options.startDate = currentDate;
  updateTitle();
  dataView.invalidate();
}

function changeToDayView() {
  $('#commandPanel .btn').removeClass('active');
  $('#dayView').addClass('active');
  var options = dataView.options;
  options.rowHeight = 50;
  options.rowTemplate = dayEventTemplate;
  calendar.options.viewMode = 'Day';
  dataView.invalidate();
  updateTitle();
}

function changeToWeekView() {
  $('#commandPanel .btn').removeClass('active');
  $('#weekView').addClass('active');
  var options = dataView.options;
  options.rowHeight = 50;
  options.rowTemplate = dayEventTemplate;
  calendar.options.viewMode = 'Week';
  dataView.invalidate();
  updateTitle();
}

function changeToMonthView() {
  $('#commandPanel .btn').removeClass('active');
  $('#monthView').addClass('active');
  var options = dataView.options;
  options.rowHeight = 24;
  options.rowTemplate = monthEventTemplate;
  calendar.options.viewMode = 'Month';
  dataView.invalidate();
  updateTitle();
}

function createEditEventDialog() {
  var eventDialog = document.getElementById('editDialogTemplate').innerHTML;
  var div = document.createElement('div');
  div.innerHTML = eventDialog;
  var dialog = div.children[0];
  dialog.id = 'editDialog';
  return dialog;
}

window.saveEvent = function saveEvent() {
  if (editData) {
    var oldStart = editData.start;
    var oldEnd = editData.end;
    editData.topic = $('#editDialog #eventTitleContent')[0].value;
    var newStartValue = $('#editDialog #datetimepicker1')[0].value;
    var newEndValue = $('#editDialog #datetimepicker2')[0].value;

    if (newStartValue && newEndValue) {
      var newStart = newStartValue.split(':');
      var newEnd = newEndValue.split(':');

      while (newStart.length < 3) {
        newStart.push('0');
      }

      while (newEnd.length < 3) {
        newEnd.push('0');
      }

      editData.start = new Date(
        oldStart.getFullYear(),
        oldStart.getMonth(),
        oldStart.getDate(),
        parseInt(newStart[0], 10),
        parseInt(newStart[1], 10),
        parseInt(newStart[2], 10)
      );
      editData.end = new Date(
        oldEnd.getFullYear(),
        oldEnd.getMonth(),
        oldEnd.getDate(),
        parseInt(newEnd[0], 10),
        parseInt(newEnd[1], 10),
        parseInt(newEnd[2], 10)
      );
      dataView.invalidate();
    }

    removeDialog();
  }
};

window.deleteEvent = function deleteEvent(e) {
  dataView.data.removeDataItems(editData.sourceIndex, 1);
  removeDialog();
};

window.cancelEditEvent = function cancelEditEvent() {
  removeDialog();
};

window.removeDialog = function removeDialog() {
  var dialog = document.getElementById('editDialog');

  if (dialog) {
    dialog.parentNode.removeChild(dialog);
  }
};

function getDay(currentDate, daysCount) {
  var date = new Date(currentDate);
  var timeSpan = 1000 * 60 * 60 * 24 * (daysCount || 1);
  date.setTime(date.getTime() + timeSpan);
  return date;
}

function getMonth(currentDate, monthCount) {
  var year = currentDate.getFullYear();
  var month = currentDate.getMonth() + monthCount;
  var day = currentDate.getDate();

  if (month === 12) {
    month = 0;
    year += 1;
  } else if (month === -1) {
    month = 11;
    year -= 1;
  }

  return new Date(year, month, day, 0, 0, 0);
}

function updateTitle() {
  var options = calendar.options;
  var date = options.startDate;
  var title = document.getElementById('title');

  if (options.viewMode === 'Month') {
    var dateFormatter = new GC.DataViews.GeneralFormatter('mmmm yyyy');
    title.innerText = dateFormatter.format(date);
  } else if (options.viewMode === 'Week') {
    var _dateFormatter = new GC.DataViews.GeneralFormatter('d mmm yyyy');

    title.innerText = ''.concat(_dateFormatter.format(date), '-').concat(_dateFormatter.format(getDay(date, 6)));
  } else {
    var _dateFormatter2 = new GC.DataViews.GeneralFormatter('d mmm yyyy');

    title.innerText = ''.concat(_dateFormatter2.format(date), '-').concat(_dateFormatter2.format(getDay(date, 1)));
  }
}

$('#dayView').click(changeToDayView);
$('#weekView').click(changeToWeekView);
$('#monthView').click(changeToMonthView);
$('.app-prev').click(previousMonth);
$('.app-next').click(nextMonth);