How to Use DHTMLX Gantt with Vue.js Framework [Demo]

Firstly published on May 11, 2017. Updated on October 10, 2022

What’s extremely awesome about our Gantt Chart library (besides fast performance and vast variety of features) is that it allows integrations with almost all new and popular frameworks and technologies. And today we’ll show you the easiest way to use js gantt chart with Vue.js, progressive JavaScript framework.

So, follow the instructions below to create a Vue.js Gantt chart or jump to a complete demo on GitHub right away.

New to dhtmlxGantt? Learn more about the library now

How We Start

The first thing we need to do is to get an app skeleton. And for this, we’re going to use vue-cli.

Firstly, make sure you have the latest stable versions of Node.js and Vue.js. You can check your Node.js version with the help of node -v command or download the latest one on the Node.js website. You can find detailed instructions on how to install the latest Vue.js version as well as vue-cli in this article.

You can install vue-cli with the node package manager using command (npm install -g @vue/cli) or using yarn:

yarn global upgrade --latest @vue/cli
yarn global add @vue/cli

To create an app, run the following command:

vue create gantt-vue

It will request some project info. You can just leave default answers and press the enter button for each question or select functions manually.

Then you need to go to the app directory, install dependencies and run it.

cd gantt-vue

If you use yarn, you need to call the following commands:

yarn install
yarn serve

If you use npm, you need to call the following commands:

npm install
npm run serve

After these steps, the app should run on http://localhost:8080

vuejs-install

Moving to Gantt Chart Part

Now we should get the dhtmlxGantt code. First of all, we need to stop the app by pressing ctrl+c in the command line in order to run the following command afterward:

yarn add dhtmlx-gantt --save (for yarn)
npm install dhtmlx-gantt --save (for npm)

Then, to add a Gantt chart to an application, we should create a component.

So, we’ll start by creating a folder for the app components. Open the src folder and create components folder in it. Then, create GanttComponent.vue file in the components folder and put the following code into it:

{{ src/components/GanttComponent.vue }}
<template>
  <div ref="ganttContainer"></div>
</template>
 
<script>
import {gantt} from 'dhtmlx-gantt';
export default {
  props: {
    tasks: {
      type: Object,
      default () {
        return {data: [], links: []}
      }
    }
  },
 
  mounted: function () {
    gantt.config.date_format = "%Y-%m-%d";
 
    gantt.init(this.$refs.ganttContainer);
    gantt.parse(this.$props.tasks);
  }
}
</script>
 
<style>
    @import "~dhtmlx-gantt/codebase/dhtmlxgantt.css";
</style>

Now, the Gantt chart component is ready. When the element will be added to the page it will initialize the Gantt chart under “gantt” ref. Then, Gantt chart will load data from the tasks property.

And now it’s time to add the component to our app.

Open App.vue and add the following code instead of the one we’ve already had there.

{{ src/App.vue }}
<template>
  <div class="container">
    <GanttComponent class="left-container" :tasks="tasks"></GanttComponent>
  </div>
</template>
 
<script>
import GanttComponent from './components/GanttComponent.vue';
 
export default {
  name: 'app',
  components: {GanttComponent},
  data () {
    return {
      tasks: {
        data: [
          {id: 1, text: 'Task #1', start_date: '2020-01-17', duration: 3, progress: 0.6},
          {id: 2, text: 'Task #2', start_date: '2020-01-20', duration: 3, progress: 0.4}
        ],
        links: [
          {id: 1, source: 1, target: 2, type: '0'}
        ]
      },
    }
  }
}
</script>

<style>
  html, body {
    height: 100%;
    margin: 0;
    padding: 0;
  }
  .container {
    height: 100vh;
    width: 100%;
  }
  .left-container {
    overflow: hidden;
    position: relative;
    height: 100%;
  }
</style>

Now, we should see the Gantt chart with predefined tasks on a page.

gantt-vue

Listening changes and handling events

Let’s say we need to trace changes in Gantt made by the user and process them somehow – show the details of the selected item in a separate form, keep data model of the parent component up to date, or send these changes to the backend. In other words, we need a way to let the rest of the app know what happens inside Gantt.

To do so we can create a DataProcessor with a custom router object, where the router is a function and $emit DataProcessor events to the parent component.

As a simple demonstration, let’s implement a ‘changelog’ feature – we’ll write all changes made in Gantt in a neat list somewhere on the page.

Firstly, go into the Gantt component and add code that will trace and emit changes of dhtmlxGantt. Add the following code right after the gantt.init call:

   {{ src/components/GanttComponent.vue }}  
   gantt.createDataProcessor((entity, action, data, id) => {
      this.$emit(`${entity}-updated`, id, action, data);
   });

It adds handlers to the add/update/delete events for the links and tasks. If some particular handler is called, it will trigger vue event on our component with parameters.

The next step is to add listeners for these events into the app component and write a log of actions in another div.

Let’s extend the app component with the required functionality:

{{ src/App.vue }}
<script>
import GanttComponent from './components/GanttComponent.vue';
 
export default {
  name: 'app',
  components: {GanttComponent},
  data () {
    return {
      tasks: {
        data: [
          {id: 1, text: 'Task #1', start_date: '2020-01-17', duration: 3, progress: 0.6},
          {id: 2, text: 'Task #2', start_date: '2020-01-20', duration: 3, progress: 0.4}
        ],
        links: [
          {id: 1, source: 1, target: 2, type: '0'}
        ]
      },
      messages: []
    }
  },
  methods: {
    addMessage (message) {
      this.messages.unshift(message)
      if (this.messages.length > 40) {
        this.messages.pop()
      }
    },
 
    logTaskUpdate (id, mode, task) {
      let text = (task && task.text ? ` (${task.text})`: '')
      let message = `Task ${mode}: ${id} ${text}`
      this.addMessage(message)
    },
 
    logLinkUpdate (id, mode, link) {
      let message = `Link ${mode}: ${id}`
      if (link) {
        message += ` ( source: ${link.source}, target: ${link.target} )`
      }
      this.addMessage(message)
    }
  }
}
</script>

<style>
  html, body {
    height: 100%;
    margin: 0;
    padding: 0;
  }
  .container {
    height: 100vh;
    width: 100%;
  }
  .left-container {
    overflow: hidden;
    position: relative;
    height: 100%;
  }
  .right-container {
    border-right: 1px solid #cecece;
    float: right;
    height: 100%;
    width: 340px;
    box-shadow: 0 0 5px 2px #aaa;
    position: relative;
    z-index:2;
  }
  .gantt-messages {
    list-style-type: none;
    height: 50%;
    margin: 0;
    overflow-x: hidden;
    overflow-y: auto;
    padding-left: 5px;
  }
  .gantt-messages > .gantt-message {
    background-color: #f4f4f4;
    box-shadow:inset 5px 0 #d69000;
    font-family: Geneva, Arial, Helvetica, sans-serif;
    font-size: 14px;
    margin: 5px 0;
    padding: 8px 0 8px 10px;
  }
</style>

What you can see here – we’ve added an array property where we’re going to store log entries, a method that adds a new message to the top of that array (our log will show new entries first). Also, we’ve added two more methods that will create log messages for actions done with tasks and links and add them to the message stack.

And finally, update a template of the app component to utilize these functions:

{{ src/App.vue }}
<template>
  <div class="container">
    <div class="right-container">
      <ul class="gantt-messages">
        <li class="gantt-message" v-for="(message, index) in messages" v-bind:key="index">{{message}}</li>
      </ul>
    </div>
    <GanttComponent class="left-container" :tasks="tasks" @task-updated="logTaskUpdate" @link-updated="logLinkUpdate"></GanttComponent>
  </div>
</template>

We’ve added a simple two-column layout, attached our log handlers to the Gantt events that we emit from the Gantt module, added a container for log messages and bound them to our log messages stack.

Now, if we make some changes to Gantt, messages should be shown on the right side.

vuejs-gantt-chart

If you want to display some info about the selected tasks, proceed to the instructions below.

To show information about selected tasks, we can capture API events of DHTMLX Gantt (‘onTaskSelected‘) and $emit them to the parent component. We can also use the ‘onTaskIdChange‘ event to update information after changing the task id.

Open the Gantt chart component and add the following code right before gantt.init call:

{{ src/components/GanttComponent.vue }}      
gantt.attachEvent('onTaskSelected', (id) => {
      let task = gantt.getTask(id);
      this.$emit('task-selected', task);
});
 
    gantt.attachEvent('onTaskIdChange', (id, new_id) => {
       if (gantt.getSelectedId() == new_id) {
         let task = gantt.getTask(new_id);
         this.$emit('task-selected', task);
        }
     });

Here we’ve added the onTaskSelected handler that is going to trigger a ‘task-selected’ event.

Open the app component to add a selection handler to it. We need to add some necessary elements to our template as well. It should look like this:

{{ src/App.vue }}
<template>
  <div class="container">
    <div class="right-container">
      <div class="gantt-selected-info">
        <div v-if="selectedTask">
          <h2>{{ selectedTask.text }}</h2>
          <span><b>ID: </b>{{ selectedTask.id }}</span
          ><br />
          <span><b>Progress: </b> {{ progressPercentage }}</span
          ><br />
          <span><b>Start Date: </b
            >{{ formattedStartDate }}</span
          ><br />
          <span><b>End Date: </b>{{ formattedEndDate }}</span
          ><br />
        </div>
        <div v-else class="select-task-prompt">
          <h2>Click any task</h2>
        </div>
      </div>
      <ul class="gantt-messages">
        <li
          class="gantt-message"
          v-for="(message, index) in messages"
          v-bind:key="index"
        >
          {{ message }}
        </li>
      </ul>
    </div>
    <GanttComponent
      class="left-container"
      :tasks="tasks"
      @task-updated="logTaskUpdate"
      @link-updated="logLinkUpdate"
      @task-selected="selectTask"
    ></GanttComponent>
  </div>
</template>

It’s also necessary to set styles to show the information about the selected tasks in a nice-looking way:

<style>
...
.gantt-selected-info {
    border-bottom: 1px solid #cecece;
    box-sizing: border-box;
    font-family: Geneva, Arial, Helvetica, sans-serif;
    height: 50%;
    line-height: 28px;
    padding: 10px;
  }
  .gantt-selected-info h2 {
    border-bottom: 1px solid #cecece;
  }
  .select-task-prompt h2{
    color: #d9d9d9;
  }
</style>

Here we’ve added another container, which is bound to the selectedTask property of the app component using “v-if” directive. Besides, we’ve added a handler for the “task-selected” event we now emit.

Make sure to add this property to the app component:

   {{ src/App.vue }}    
   selectedTask: null

And add the selectTask method, which is used in the handler we’ve defined above:

    {{ src/App.vue }}    
    selectTask: function(task){
      this.selectedTask = task
    }

Thus, each time a user selects a task inside Gantt, the component emits the ‘task-selected’ event. Then this event is captured by the app component. Inside the event handler, we update the selectedTask property, which in its turn invokes a repaint of the .gantt-selected-info element with task details.

Note that the task object has the start_date/end_date properties of Date type and progress completion in float type – these should be formatted in a human-friendly form before being added to the page.

It’s implemented using progressPercentage,formattedStartDate, and formattedEndDate computed properties, which we define like this:

{{ src/App.vue }}  
computed: {
    progressPercentage() {
      let taskProgress = this.selectedTask.progress;
      if (!taskProgress) {
        return "0";
      }
      return `${Math.round(+taskProgress * 100)} %`;
    },
    formattedStartDate() {
      let taskStart = this.selectedTask.start_date;
      return `${taskStart.getFullYear()} / ${taskStart.getMonth() + 1} / ${taskStart.getDate()}`;
    },
    formattedEndDate() {
      let taskEnd = this.selectedTask.end_date;
      return `${taskEnd.getFullYear()} / ${taskEnd.getMonth() + 1} / ${taskEnd.getDate()}`;
    },
  },

Now, if we run our app and select a task, we should see that its info is shown on the right.

Gantt-chart-vuejs

Final touch

Note that our GanttComponent.vue component uses the global instance of the Gantt object. We do it in order to make our tutorial compatible with all versions of dhtmlxGantt, both paid and free ones.

But this approach hides one pitfall – different instances of the GanttComponent.vue component can be mounted and unmounted multiple times through pages lifecycle, and each time our ‘mounted’ hook will work with the same Gantt object which could have been already initialized.

It is not a problem by itself, but you’ll need to do certain precautions to prevent side effects that can be caused by the state of the reused instance of dhtmlxGantt.

Generally, you must be aware of two things:
1) Event handlers that are added using gantt.attachEvent should never be detached when the component is unmounted. You either need to detach them manually using gantt.detachEvent or to ensure that these events are added only once.

This can be done by moving gantt.attachEvent calls to a separate function and setting up a flag that will allow us to tell track that function has already been called:

methods: {
  $_initGanttEvents: function() {
    if (!gantt.$_eventsInitialized) {
      gantt.attachEvent('onTaskSelected', (id) => {
        let task = gantt.getTask(id);
        this.$emit('task-selected', task);
      });
      gantt.attachEvent('onTaskIdChange', (id, new_id) => {
        if (gantt.getSelectedId() == new_id) {
          let task = gantt.getTask(new_id);
          this.$emit('task-selected', task);
        }
      });
      gantt.$_eventsInitialized = true;
    }
  },

And call this method from the mounted hook:

mounted: function () {
  this.$_initGanttEvents();
  gantt.config.date_format = "%Y-%m-%d";
  gantt.init(this.$refs.ganttContainer);
  gantt.parse(this.$props.tasks);

2) The same works for the dataProcessor. It can be either destroyed from the unmounted hook, or you can ensure you’re attaching only one dataProcessor to the Gantt:

methods: {
  $_initDataProcessor: function() {
    if (!gantt.$_dataProcessorInitialized) {
      gantt.createDataProcessor((entity, action, data, id) => {
        this.$emit(`${entity}-updated`, id, action, data);
      });
      gantt.$_dataProcessorInitialized = true;
    }
  }

And then in mounted:

mounted: function () {
    this.$_initGanttEvents();
    gantt.config.date_format = "%Y-%m-%d";
 
    gantt.init(this.$refs.ganttContainer);
    gantt.parse(this.$props.tasks);
 
    this.$_initDataProcessor();
  }

So, we’ve created a simple Gantt chart with the help of DHTMLX Gantt and Vue.js. The results of our work can be found on GitHub. If you follow the instructions above and meet any difficulties, don’t hesitate to share them with us.

Which technologies/frameworks are you using?

We need your feedback to provide the right integrations at the right time. Please leave your requests here:

Thank you in advance and stay tuned for new tutorials!

Advance your web development with DHTMLX

Gantt chart
Event calendar
Diagram library
30+ other JS components