Task Progress Tracking

Showing percentage progress, or even having a visual overlay that indicates the progress of a task is sometimes necessary, not just for full fledged Gantt charts. Although being usually associated with Gantt charts, having a clear overview of progress with tools that enable live update is useful in a lot of situations.

Using the event templating capabilities, it is possible to render a live slider that reacts on hover and enables updating the progress inline. Use the renderTimelineEvent function to customize the template and adjust it to your needs.

Besides being able to update progress right on the event, it is also possible to add a slider that controls task progression - saved in a custom property of the event - inside an add/edit dialog.

Download and try example

Demo

Loading the live demo

Preview in full screen

Show task completion progress on Timeline events with an inline hover slider for live updates and a progress field in the add/edit dialog.

Code

mobiscroll.setOptions({
  locale:  ,
  theme: ' ',
  themeVariant: ' '
});

var calendar;
var addEditPopup;
var eventId;
var eventTitle;
var eventStart;
var eventEnd;
var eventProgress;
var isDraggingProgress;

var eventProgressLabelElm = document.querySelector('.mds-popup-progress-label');
var eventProgressElm = document.querySelector('.mds-popup-progress-slider');
var eventTitleElm = document.querySelector('.mds-popup-event-title');

var myEvents = [
  {
    start: '2026-10-22T00:00',
    end: '2026-10-25T00:00',
    title: 'Design Homepage',
    resource: 'alice',
    progress: 100,
  },
  {
    start: '2026-10-22T00:00',
    end: '2026-10-26T00:00',
    title: 'Create Wireframes',
    resource: 'bob',
    progress: 100,
  },
  {
    start: '2026-10-24T00:00',
    end: '2026-10-29T00:00',
    title: 'Develop Frontend',
    resource: 'charlie',
    progress: 45,
  },
  {
    start: '2026-10-24T00:00',
    end: '2026-10-29T00:00',
    title: 'Develop Backend',
    resource: 'dave',
    progress: 35,
  },
  {
    start: '2026-10-29T00:00',
    end: '2026-11-02T00:00',
    title: 'Test Website',
    resource: 'erin',
    progress: 0,
  },
  {
    start: '2026-10-26T00:00',
    end: '2026-11-02T00:00',
    title: 'Fix Bugs',
    resource: 'frank',
    progress: 0,
  },
  {
    start: '2026-11-02T00:00',
    end: '2026-11-05T00:00',
    title: 'Deploy Website',
    resource: 'george',
    progress: 0,
  },
];

var myResources = [
  {
    id: 'gro1',
    name: 'Designer Team',
    color: '#76e083',
    eventCreation: false,
    children: [
      {
        id: 'alice',
        name: 'Alice',
        title: 'Designer',
        color: '#1dab2f',
      },
      {
        id: 'bob',
        name: 'Bob',
        title: 'Designer',
        color: '#76e083',
      },
    ],
  },
  {
    id: 'gro2',
    name: 'Development Team',
    color: '#ff1717',
    eventCreation: false,
    children: [
      {
        id: 'charlie',
        name: 'Charlie',
        title: 'Frontend Developer',
        color: '#4981d6',
      },
      {
        id: 'dave',
        name: 'Dave',
        title: 'Backend Developer',
        color: '#f7961e',
      },
      {
        id: 'frank',
        name: 'Frank',
        title: 'Full-Stack Developer',
        color: '#34c8e0',
      },
      {
        id: 'george',
        name: 'George',
        title: 'DevOps Engineer',
        color: '#e25dd2',
      },
    ],
  },

  {
    id: 'gro3',
    name: 'QA Team',
    color: '#d6d145',
    eventCreation: false,
    children: [
      {
        id: 'erin',
        name: 'Erin',
        title: 'QA Tester',
        color: '#d6d145',
      },
    ],
  },
];

function renderEvent(data) {
  var event = data.original;
  var color = data.color;
  var progress = event.progress || 0;

  return (
    '<div class="mds-progress-event" style="background:' +
    color +
    ';">' +
    '<div class="mds-progress-bar" style="width:' +
    progress +
    '%;">' +
    '<div class="mds-progress-arrow" data-event-id="' +
    event.id +
    '"></div>' +
    '</div>' +
    '<div class="mds-progress-event-content">' +
    '<div class="mds-progress-event-title">' +
    event.title +
    '</div>' +
    '</div>' +
    '<div class="mds-progress-label" >' +
    progress +
    '%</div>' +
    '</div>'
  );
}

function renderCustomResource(resource) {
  return (
    '<div class="mds-progress-employee-name">' +
    resource.name +
    '</div>' +
    (resource.title !== undefined ? '<div class="mds-progress-employee-title">' + resource.title + '</div>' : '') +
    '</div>'
  );
}

function createAddPopup(event, target) {
  var success = false;

  addEditPopup.setOptions({
    anchor: target,
    headerText: 'New event',
    buttons: [
      'cancel',
      {
        text: 'Add',
        keyCode: 'enter',
        handler: function () {
          event.title = eventTitle;
          event.start = eventStart;
          event.end = eventEnd;
          event.progress = eventProgress;
          myEvents.push(event);
          calendar.updateEvent(event);
          success = true;
          addEditPopup.close();
        },
        cssClass: 'mbsc-popup-button-primary',
      },
    ],
    onClose: function () {
      if (!success) {
        calendar.removeEvent(event);
      }
    },
  });

  fillPopup(event);
  addEditPopup.open();
}

function createEditPopup(event, target) {
  addEditPopup.setOptions({
    headerText: 'Edit event',
    anchor: target,
    buttons: [
      'cancel',
      {
        text: 'Save',
        keyCode: 'enter',
        handler: function () {
          var eventToUpdate = myEvents.find(function (event) {
            return event.id === eventId;
          });
          eventToUpdate.title = eventTitle;
          eventToUpdate.start = eventStart;
          eventToUpdate.end = eventEnd;
          eventToUpdate.progress = eventProgress;
          calendar.updateEvent(eventToUpdate);
          addEditPopup.close();
        },
        cssClass: 'mbsc-popup-button-primary',
      },
    ],
  });

  fillPopup(event);
  addEditPopup.open();
}

function fillPopup(event) {
  eventId = event.id;
  eventTitle = event.title || '';
  eventStart = event.start;
  eventEnd = event.end;
  eventProgress = event.progress || 0;

  mobiscroll.getInst(eventTitleElm).value = eventTitle;
  eventProgressLabelElm.textContent = eventProgress + ' %';
  eventProgressElm.value = eventProgress;
  eventStartEndPicker.setVal([eventStart, eventEnd]);
}

calendar = mobiscroll.eventcalendar('#demo-task-progression', {
  class: 'mds-progress-calendar',
  dragToMove: true,
  dragToResize: true,
  dragToCreate: true,
  clickToCreate: true,
  view: { timeline: { type: 'month', eventDisplay: 'fill' } },
  data: myEvents,
  resources: myResources,
  onEventClick: function (args) {
    if (isDraggingProgress) {
      isDraggingProgress = false;
      return;
    }
    createEditPopup(args.event, args.domEvent.currentTarget);
  },
  onEventCreated: function (args) {
    createAddPopup(args.event, args.target);
  },
  onEventUpdated: function (args) {
    var eventToUpdate = myEvents.find(function (event) {
      return event.id === args.event.id;
    });
    eventToUpdate.start = args.event.start;
    eventToUpdate.end = args.event.end;
    eventToUpdate.resource = args.event.resource;
  },
  renderTimelineEvent: renderEvent,
  renderResource: renderCustomResource,
});

var eventStartEndPicker = mobiscroll.datepicker('#popup-event-dates', {
  select: 'range',
  startInput: '#popup-event-start',
  endInput: '#popup-event-end',
  showRangeLabels: false,
  touchUi: true,
  responsive: { medium: { touchUi: false } },
  onChange: function (args) {
    var dates = args.value;
    eventStart = dates[0];
    eventEnd = dates[1];
  },
});

addEditPopup = mobiscroll.popup('#demo-add-edit-popup', {
  display: 'bottom',
  contentPadding: false,
  fullScreen: true,
  scrollLock: false,
  responsive: {
    medium: {
      display: 'anchored',
      width: 510,
      fullScreen: false,
      touchUi: false,
    },
  },
});

eventProgressElm.addEventListener('input', function () {
  eventProgress = this.value;
  eventProgressLabelElm.textContent = eventProgress + ' %';
});

eventTitleElm.addEventListener('input', function () {
  eventTitle = this.value;
});

document.getElementById('demo-task-progression').addEventListener(
  'mousedown',
  function (event) {
    var progressArrow = event.target.closest('.mds-progress-arrow');

    if (!progressArrow) {
      return;
    }

    event.stopPropagation();

    var progressBar = progressArrow.closest('.mds-progress-bar');
    var eventContainerWidth = progressBar.parentElement.offsetWidth;
    var initialMouseX = event.pageX;
    var initialProgress = (progressBar.offsetWidth / eventContainerWidth) * 100;

    function onMouseMove(e) {
      var mouseXOffset = e.pageX - initialMouseX;
      var newProgress = Math.round(initialProgress + (mouseXOffset / eventContainerWidth) * 100);

      eventProgress = Math.max(0, Math.min(100, newProgress));

      progressBar.classList.add('mds-progress-dragging');
      progressBar.style.width = eventProgress + '%';

      var progressLabel = progressArrow.closest('.mds-progress-event').querySelector('.mds-progress-label');
      progressLabel.textContent = eventProgress.toFixed(0) + '%';

      isDraggingProgress = true;
    }

    function onMouseUp() {
      document.removeEventListener('mousemove', onMouseMove);
      document.removeEventListener('mouseup', onMouseUp);

      setTimeout(function () {
        isDraggingProgress = false;
      }, 100);

      progressBar.classList.remove('mds-progress-dragging');

      var eventId = progressArrow.dataset.eventId;
      var eventToUpdate = myEvents.find(function (event) {
        return event.id === eventId;
      });

      eventToUpdate.progress = eventProgress;
      calendar.updateEvent(eventToUpdate);
    }

    document.addEventListener('mousemove', onMouseMove);
    document.addEventListener('mouseup', onMouseUp);
  },
  true,
);
  

Duplicate Session

You have signed in on a different machine. To keep everything in sync, you have been signed out of this session.

If you need to invite team members, manage seats and accounts you can do it from the licenses page.

Duplicate Session

We noticed you are logged in on a different machine and have closed your old session.

If you need to invite team members, manage seats and accounts you can do it from the licenses page.