Why can't I add other Elements to DOM?

Here is my code. I want to add a div called gantt-control that would have some filters, undo and redo button, and maybe my legend. The problem is that when I try to add other element than the container div that have de ref attribute, I have an error (joined picture). For now, I’ve used gantt.$root.append to add new element to DOM, but i’m unsatisfied because I don’t want the control to be inside the chart, but above it.

If anyone can help, thank you :))

<template>
  <div ref="ganttContainer" style="height: 80vh;"></div>
</template>
 
<script>
import {gantt} from 'dhtmlx-gantt';

var dateToStr = gantt.date.date_to_str(gantt.config.task_date);

var dateEditor = {type: "date", map_to: "start_date"};
var durationEditor = {type: "number", map_to: "duration", min:0, max: 100};

var stateEditor = {type: "select", options: [
        {key: "Opened", label: "Opened"},
        {key: "Closed", label: "Closed"},
        {key: "InProgress", label: "In progress"},
        {key: "Late", label: "Late"},
        {key: "Unscheduled", label: "Unscheduled"}
    ], map_to: "state"};

let undoBtn = document.createElement('button');
undoBtn.className = 'gantt-undo';
undoBtn.textContent = 'Undo';
undoBtn.onclick = function(){
    gantt.undo();
};

let redoBtn = document.createElement('button');
redoBtn.className = 'gantt-redo';
redoBtn.textContent = 'Redo';
redoBtn.onclick = function(){
    gantt.redo();
};

let controlsDiv = document.createElement('div');
controlsDiv.className = 'gantt-controls';
controlsDiv.appendChild(undoBtn);
controlsDiv.appendChild(redoBtn);


let legend = document.createElement('div');
legend.className = 'gantt-legend';
legend.id = 'gantt-legend';

let header = document.createElement('header');
header.className = 'legend-head';

let h3 = document.createElement('h3');
h3.textContent = 'Legend';

header.appendChild(h3);
legend.appendChild(header);

let legendList = document.createElement('div');
legendList.className = 'legend-list';

let states = ['Opened', 'Closed', 'In progress', 'Late', 'Unscheduled', 'folder'];
let descriptions = ['Opened issues', 'Closed issues', 'In progress', 'Late issues', 'Unscheduled', 'Parent'];

for (let i = 0; i < states.length; i++) {
  let row = document.createElement('div');
  row.className = 'legend-row';

  let label = document.createElement('div');
  label.className = 'legend-label ' + states[i].toLowerCase().replace(' ', '-');

  let description = document.createElement('div');
  description.textContent = descriptions[i];

  row.appendChild(label);
  row.appendChild(description);
  legendList.appendChild(row);
}

legend.appendChild(legendList);



var daysStyle = function(date){
    var dateToStr = gantt.date.date_to_str("%D");
    if (dateToStr(date) == "Sun"||dateToStr(date) == "Sat")  return "weekend";
 
    return "";
};

export default {
  props: {
    tasks: {
      type: Object,
      default () {
        return {data: [], links: []}
      }
    }
  },
 
  mounted: function () {

    gantt.plugins({ 
        marker: true,
        multiselect: true ,
        tooltip: true ,
        undo: true
    }); 

    gantt.config.undo = true;
    gantt.config.redo = true;

    gantt.config.date_format = "%Y-%m-%d";

    gantt.config.scales = [
      {unit: "month", step: 1, format: "%F, %Y"},
      {unit: "day", step: 1, format: "%j, %D",  css:daysStyle}  
    ];
    gantt.config.fit_tasks = true;

    gantt.config.columns = [
      {name: "name", label: "<div class='searchEl'>Task name <input id='filter' style='width: 120px;' type='text'"+"placeholder='Search tasks...'></div>", tree: true, width: 170, tree : true, resize: true },
      {name: "start_date", label: "Start time", align: "center", width: 150 , resize: true, editor: dateEditor},
      {name: "duration", label: "Duration", align: "center", width: 50, editor: durationEditor},
      {name: "user", label: "User", align: "center", width: 100},
      {name: "state", label: "State", align: "center", width: 100, editor: stateEditor}
    ];

    gantt.config.lightbox.sections = [
      {name: "name", label: "Name", height:30, map_to:"name", type:"textarea", focus:true},
      {name:"state",    height:22, map_to:"state", type:"select", options: stateEditor.options},
      {name:"template", height:37, type:"template", map_to:"my_template"}, 
      {name: "time", height:72, map_to:"auto", type:"duration"}
    ];

    gantt.config.lightbox.project_sections = [
      {name: "name", label: "Name", height:30, map_to:"name", type:"textarea", focus:true},
      {name: "time", height:72, map_to:"auto", type:"duration"}
    ];

    gantt.locale.labels.section_name = "Title";
    gantt.locale.labels.section_state = "State";
    gantt.locale.labels.section_template = "Details";

    gantt.config.lightbox.project_sections.allow_root = false;

    gantt.attachEvent("onBeforeLightbox", function(id) {
    var task = gantt.getTask(id);
    task.my_template = "<span id='lightbox_users'>Assign to: </span>"+ task.user
    +"<br>  <span id='lightbox_progress'>Progress: </span>"+ task.progress*100 +" %";
    return true;
});


    gantt.config.lightbox.allow_root = false;

    gantt.templates.task_text = function(start, end, task) {
      return "<b>Name:</b> " + task.name + (task.user ? ",<b> Assign to:</b> " + task.user : "");
    };
    gantt.templates.tooltip_text = function(start, end, task) {
      // Personnalisez ici le texte du tooltip pour chaque tâche (hover)
      return "<b>" + task.name + "</b><br/>" 
      + "Start: " + gantt.templates.tooltip_date_format(start) + "<br/>" 
      + "End: " + gantt.templates.tooltip_date_format(end) + "<br/>" 
      + "Duration: " + task.duration + " days" + "<br/>"
      + (task.state ? "<br/>State: " + task.state : "")
      + (task.user ? "<br/>User: " + task.user : "");
    };

    gantt.templates.task_class = function(start, end, task){
      var css = "";

      switch(task.state){
          case "Opened":
              css = "opened";
              break;
          case "Closed":
              css = "closed";
              break;
          case "InProgress":
              css = "in-progress";
              break;
          case "Late":
              css = "late";
              break;
          case "Unscheduled":
              css = "unscheduled";
              break;
          default:
              css = "";
      }
      return css;
    };


    gantt.config.columns_resizable = true;
    gantt.config.columns_autoresize = true;


    gantt.addMarker({
      start_date: new Date(), //a Date object that sets the marker's date
      css: "today", //a CSS class applied to the marker
      text: "Now", //the marker title
      title: dateToStr( new Date()) // the marker's tooltip
    });

    if (this.$props.tasks.data.length < 50) {
      gantt.config.open_tree_initially = true;   //if more than 50 tasks, tasks will be closed by default
    }
    

    gantt.init(this.$refs.ganttContainer);
    gantt.parse(this.$props.tasks);

    gantt.attachEvent("onDataRender", function () {
    const filterEl = document.querySelector("#filter")
    filterEl.addEventListener('input', function (e) {
        filterValue = filterEl.value;
        gantt.refreshData();
    });
    });


    let filterValue = "";

    function filterLogic(task, match) {
        match = match || false;
        // check children
        gantt.eachTask(function (child) {
            if (filterLogic(child)) {
                match = true;
            }
        }, task.id);

        // check task
        if (task.name.toLowerCase().indexOf(filterValue.toLowerCase()) > -1) {
            match = true;
        }
        return match;
    }

    gantt.attachEvent("onBeforeTaskDisplay", function (id, task) {
        if (!filterValue) {
            return true;
        }
        return filterLogic(task);
    });

    gantt.$root.appendChild(controlsDiv);
    gantt.$root.appendChild(legend);

  }
}
</script>
 <style>

Hello,
It is not necessary to create HTML elements inside Gantt, then append them to the Gantt container. Actually, it is not recommended to modify the DOM structure of Gantt, as you lose the changes after Gantt is repainted or reinitialized:
https://files.dhtmlx.com/30d/7d55dfe42c1c413574596aeb37f9c8f2/vokoscreen-2024-04-24_11-50-22.mp4

A better way would be to create a layout configuration with the html cell and add the HTML elements there:
https://docs.dhtmlx.com/gantt/desktop__layout_config.html#htmlasinnerview

Here are examples:
https://snippet.dhtmlx.com/suwiy3qu

https://snippet.dhtmlx.com/5/65cf311a3

https://snippet.dhtmlx.com/5/d35dcb45f

However, you don’t even need that in your case. You can add HTML elements in different Vue components or just in a different place. Then, if you want to interact with Gantt, you need to add event handlers in the files with the Gantt configuration.

For example:

window.addEventListener("click", function (e) {
  if (e.target.closest(".className")) {
    yourFunction();
  }
});

Here are the demos:
https://files.dhtmlx.com/30d/3021f0e5ae7575845a1e0e7c9f48b61a/vue3+export_to_pdf.zip

https://files.dhtmlx.com/30d/13ccf300cfea3854cc0387a15a192344/vue3+advanced_demo.zip

https://files.dhtmlx.com/30d/51c04cdc580cfc9da54a3ec63dce8fcb/vue3+gantt+html_panel_above_gantt.zip

1 Like