---
title: "Javascript Timeline Task Progress Tracking Example | Mobiscroll"
component: "Timeline"
framework: "JavaScript"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/javascript/timeline/show-task-progress-on-event"
---

# Task Progress Tracking - JavaScript Timeline demo

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

- [Live demo](https://demo.mobiscroll.com/javascript/timeline/show-task-progress-on-event)
- [View code on GitHub](https://github.com/acidb/mobiscroll-demos-javascript/tree/main/src/demos/eventcalendar/timeline/show-task-progress-on-event)
- This demo in other frameworks: [jQuery](https://demo.mobiscroll.com/jquery/timeline/show-task-progress-on-event.md), [Angular](https://demo.mobiscroll.com/angular/timeline/show-task-progress-on-event.md), [React](https://demo.mobiscroll.com/react/timeline/show-task-progress-on-event.md), [Vue](https://demo.mobiscroll.com/vue/timeline/show-task-progress-on-event.md)

## Description

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`](https://mobiscroll.com/docs/javascript/eventcalendar/timeline#renderer-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.

## What this demo shows

- A desktop monthly timeline for project planning and progress tracking, with days arranged horizontally and resources listed vertically on the left.
- **Header navigation** The month and year label opens date navigation. The previous and next arrows and the Today button move between months and return to the current day.
- **Timeline header** The fixed date strip shows the days of the selected month in `D DDD` format, with the current date highlighted.
- **Resources** Hierarchical resource groups organize team members into departments such as Designer Team, Development Team, and QA Team.
- **Resource details** Individual resources show custom information, including the team member's role or job title.
- **Event colors** Events for the same team member or group keep a consistent color in the timeline.
- **Event cards** Events are rendered as colored cards with the project name, a progress percentage, and an inline progress slider.
- **Inline progress** The progress slider reacts on hover and lets users update completion directly on the event.
- **Event positioning** Events are placed by assigned resource and by start and end date.
- **Event creation** Users can create events by double-clicking a resource row or by clicking and dragging across a date range.
- **Add form** Creating an event opens a form with title, start date, end date, and progress slider fields, plus Cancel and Add actions.
- **Event editing** Clicking an event selects it and opens a popup for modifying the event details.
- **Drag and resize** Events show drag and resize handles on hover for moving tasks or changing their duration.
- **Vertical scrolling** The resource area scrolls vertically so the timeline can display more resources in the same view.

## Best for

- **Project management** Tracking tasks, ownership, timing, and completion progress in a single timeline.
- **Sprint planning** Planning work across team members and date ranges while keeping progress visible.
- **Software development teams** Coordinating designers, developers, and QA resources across overlapping project tasks.
- **Task tracking** Updating task progress directly from the timeline without opening a separate detail view.
- **Team workload management** Reviewing who is assigned to what, when each task runs, and how far along the work is.
- **Gantt-style planning** Showing project spans and completion percentages without requiring a full Gantt chart implementation.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-themeVariant) - option
- [`setOptions`](https://mobiscroll.com/docs/javascript/popup/api#method-setOptions) - method
- [`anchor`](https://mobiscroll.com/docs/javascript/popup/api#opt-anchor) - option
- [`headerText`](https://mobiscroll.com/docs/javascript/popup/api#opt-headerText) - option
- [`buttons`](https://mobiscroll.com/docs/javascript/popup/api#opt-buttons) - option
- [`updateEvent`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-updateEvent) - method
- [`close`](https://mobiscroll.com/docs/javascript/popup/api#method-close) - method
- [`cssClass`](https://mobiscroll.com/docs/javascript/popup/api#opt-cssClass) - option
- [`onClose`](https://mobiscroll.com/docs/javascript/popup/api#event-onClose) - event
- [`removeEvent`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-removeEvent) - method
- [`open`](https://mobiscroll.com/docs/javascript/popup/api#method-open) - method
- [`dragToMove`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-dragToMove) - option
- [`dragToResize`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-dragToResize) - option
- [`dragToCreate`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-dragToCreate) - option
- [`clickToCreate`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-clickToCreate) - option
- [`view`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-view) - option
- [`data`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-data) - option
- [`resources`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-resources) - option
- [`onEventClick`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventClick) - event
- [`onEventCreated`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventCreated) - event
- [`onEventUpdated`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventUpdated) - event
- [`renderTimelineEvent`](https://mobiscroll.com/docs/javascript/eventcalendar/api#renderer-renderTimelineEvent) - renderer
- [`renderResource`](https://mobiscroll.com/docs/javascript/eventcalendar/api#renderer-renderResource) - renderer
- [`select`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-select) - option
- [`startInput`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-startInput) - option
- [`endInput`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-endInput) - option
- [`showRangeLabels`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-showRangeLabels) - option
- [`touchUi`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-touchUi) - option
- [`responsive`](https://mobiscroll.com/docs/javascript/datepicker/api#opt-responsive) - option
- [`onChange`](https://mobiscroll.com/docs/javascript/datepicker/api#event-onChange) - event
- [`display`](https://mobiscroll.com/docs/javascript/popup/api#opt-display) - option
- [`fullScreen`](https://mobiscroll.com/docs/javascript/popup/api#opt-fullScreen) - option
- [`scrollLock`](https://mobiscroll.com/docs/javascript/popup/api#opt-scrollLock) - option
- [`responsive`](https://mobiscroll.com/docs/javascript/popup/api#opt-responsive) - option
- [`width`](https://mobiscroll.com/docs/javascript/popup/api#opt-width) - option
- [`touchUi`](https://mobiscroll.com/docs/javascript/popup/api#opt-touchUi) - option

## Code

### JS

```js
mobiscroll.setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

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,
);
```

### HTML

```html
  <div id="demo-task-progression"></div>
  <div style="display: none;">
    <div id="demo-add-edit-popup">
      <div class="mbsc-form-group">
        <label>
          Title
          <input mbsc-input class="mds-popup-event-title" />
        </label>
      </div>
      <div class="mbsc-form-group">
        <label>
          <input mbsc-input data-label="Starts" id="popup-event-start" />
        </label>
        <label>
          <input mbsc-input data-label="Ends" id="popup-event-end" />
        </label>
      </div>
      <div class="mbsc-form-group">
        <div class="mbsc-flex mbsc-align-items-center mbsc-padding">
          <label for="progress-slider">Progress</label>
          <input id="progress-slider" class="mds-popup-progress-slider mbsc-flex-1-0" type="range" min="0" max="100" />
          <span class="mds-popup-progress-label">0%</span> 
        </div>
        <div id="popup-event-dates"></div>
      </div>
    </div>
  </div>
```

### CSS

```css
.mds-progress-calendar .mbsc-timeline-row-gutter {
  height: 6px;
}

.mds-progress-calendar .mbsc-timeline-parent {
  height: 34px;  
}

.mds-popup-progress-slider {
  margin: 0 10px;
}

.mds-popup-progress-label {
  margin-left: 10px;
  width: 50px;
  text-align: right;
}

.mds-progress-event {
  border-radius: 4px;
  position: relative;
  overflow: hidden;
  line-height: 19px;
}

.mds-progress-event-content {
  padding: 10px 0;
  margin-right: 50px;
}

.mds-progress-event-title {
  position: relative;
  color: white;
  font-size: 14px;
  padding: 0 10px;
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}

.mds-progress-bar {
  position: absolute;
  top: 0;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.4);
}

.mds-progress-arrow {
  position: absolute;
  right: -9px;
  bottom: 0;
  border-style: solid;
  border-width: 0 9px 9px 9px;
  border-color: transparent transparent rgba(255, 255, 255, 0.5) transparent;
  cursor: ew-resize;
}

.mds-progress-dragging {
  background-color: rgba(0, 0, 0, 0.5);
}

.mds-progress-dragging .mds-progress-arrow,
.mds-progress-arrow:hover {
  right: -12px;
  border-width: 0 12px 12px 12px;
  border-color: transparent transparent white transparent;
}

.mds-progress-label {
  position: absolute;
  right: 5px;
  top: 50%;
  margin-top: -10px;
  color: white;
  font-weight: bold;
  font-size: 14px;
}

.mds-progress-employee-name {
  font-size: 14px;
}

.mds-progress-employee-title {
  font-size: 12px;
  font-weight: 400;
  margin-top: 5px;
}
```

## Implementation notes

- Use `type: 'month'` with `eventDisplay: 'fill'` for a month-level Gantt-style view where tasks span multiple days.
- Give each event a custom `progress` property (0-100) representing task completion percentage.
- Define resources as a two-level tree of team groups (`eventCreation: false`) and individual members with a custom `title` (job role). Use `renderResource` (Angular: `resourceTemplate`, Vue: `#resource` slot) to show the member name and role beneath it.
- Use `renderTimelineEvent` (Angular: `timelineEventTemplate`, Vue: `#timelineEvent` slot) to render the progress bar layout: a container styled with the resource color, a progress fill div with `width` set to `${progress}%`, a draggable arrow handle element with a `data-event-id` attribute, the task title, and a percentage label.
- Implement inline progress dragging by attaching `onMouseDownCapture` on the calendar wrapper. On mousedown on the arrow handle, call `stopPropagation` to prevent Mobiscroll from treating it as an event click or drag, then attach `mousemove`/`mouseup` listeners to the document. On mousemove, compute new progress from the x-offset relative to the event container width. On mouseup, write the new value to the event in state and remove the listeners.
- Track drag state with an `isDraggingProgress` ref. In `onEventClick`, return early if `isDraggingProgress.current` is true so the edit popup does not open when the user finishes dragging the handle. Use a short `setTimeout` before resetting the ref to prevent the mouseup from immediately triggering a click.
- Enable `clickToCreate: true` and `dragToCreate: true` for event creation. Use `onEventCreated` to open the add popup with `args.target` as the anchor. Use `onEventClick` to open the edit popup with `args.domEvent.target` as the anchor. Use `onEventUpdated` to persist drag-to-move and drag-to-resize changes.
- Build the popup with `Popup`; default to full-screen bottom sheet on mobile and switch to an anchored 400px popup at the medium breakpoint.
- Include a `Datepicker` in range mode with `startInput`/`endInput` refs for editing the task date range, and a native `<input type="range">` (0-100) for updating the progress value directly in the form.
- On popup close when the add form is canceled, refresh the events list to remove the temporary placeholder event.

## Run it in your project

1. If not signed up yet, or don't own a license, you can [start a free trial](https://mobiscroll.com/starttrial).

2. Clone the repository:

   ```
   git clone https://github.com/acidb/mobiscroll-demos-javascript.git
   ```

3. If not installed yet, install the Mobiscroll CLI from NPM:

   ```
   npm install -g @mobiscroll/cli
   ```

4. Config Mobiscroll - open a terminal window in the root directory and run:

   ```
   mobiscroll config javascript
   ```

5. Install dependencies - open a terminal window in the root directory and run:

   ```
   npm install
   ```

6. Run the project:

   ```
   npm run dev
   ```

On a trial, add `--trial` to the config command: `mobiscroll config javascript --trial`.

Running a demo locally requires a Mobiscroll trial or a license; setup is per framework at https://mobiscroll.com/docs/javascript/getting-started/installation

## Related demos

- Previous: [Dual Synchronized Flight Scheduling](https://demo.mobiscroll.com/javascript/timeline/flight-scheduling-two-synchronized-timelines.md) - Use synchronized Javascript Timeline views to build a flight scheduling UI — drag flights to assign them to aircraft, with fixed event times and conflict detection.
- Next: [Shifts and Tasks](https://demo.mobiscroll.com/javascript/timeline/tasks-subtasks-under-shifts.md) - Display shifts and linked tasks on a Javascript Timeline — group tasks under parent shifts and restrict movement to each shift's time range.
