Popular features/Export and import hub
Loading live demo…from dhtmlxcode.com
Popular features
Export and import hub
Export the chart to PDF, PNG, iCal, Excel, MS Project, or Primavera P6, and import a project back from an uploaded MS Project or Primavera file.
gantt.plugins({ export_api: true });
let SERVER = "https://export.dhtmlx.com/gantt";
let PROJECT_SERVER = "https://export.dhtmlx.com/gantt/project";
gantt.config.date_format = "%Y-%m-%d %H:%i";
gantt.config.row_height = 32;
gantt.config.bar_height = 22;
gantt.config.scale_height = 50;
gantt.config.auto_types = true;
gantt.config.static_background = true;
gantt.config.min_column_width = 40;
gantt.config.columns = [
{ name: "text", label: "Task", tree: true, width: 260, resize: true },
{ name: "start_date", label: "Start", align: "center", width: 90, resize: true },
{ name: "duration", label: "Duration", align: "center", width: 70, resize: true }
];
gantt.config.scales = [
{ unit: "month", step: 1, format: "%F %Y" },
{ unit: "day", step: 1, format: "%j" }
];
/* imported files can carry summary rows without a type: normalize on every parse */
gantt.attachEvent("onParse", function () {
gantt.eachTask(function (task) {
if (gantt.isSummaryTask(task)) {
task.type = gantt.config.types.project;
} else if (task.duration === 0 && task.type !== gantt.config.types.milestone) {
task.type = gantt.config.types.milestone;
}
});
});
DHX.dd.initClickOutside();
gantt.init("gantt_here");
/* --- the seed plan (anchored to the current month, so it never looks stale) - */
function mondayOnOrAfter(d) {
d = gantt.date.day_start(new Date(d));
d.setDate(1);
while (d.getDay() !== 1) d = gantt.date.add(d, 1, "day");
return d;
}
function fmt(base, offsetDays) {
return gantt.date.date_to_str("%Y-%m-%d %H:%i")(gantt.date.add(base, offsetDays, "day"));
}
(function seed() {
let b = mondayOnOrAfter(new Date());
gantt.parse({
tasks: [
{ id: 1, text: "Mobile App Launch", type: "project", open: true, progress: 0.46 },
/* Discovery: three workstreams run side by side, gated by one milestone. */
{ id: 10, text: "Discovery", type: "project", parent: 1, open: true },
{ id: 11, text: "Market research", parent: 10, start_date: fmt(b, 0), duration: 5, progress: 1 },
{ id: 12, text: "User interviews", parent: 10, start_date: fmt(b, 0), duration: 4, progress: 1 },
{ id: 14, text: "Competitor analysis", parent: 10, start_date: fmt(b, 0), duration: 4, progress: 1 },
{ id: 13, text: "Requirements approved", parent: 10, type: "milestone", start_date: fmt(b, 5) },
/* Design: four parallel tracks off the same milestone. */
{ id: 20, text: "Design", type: "project", parent: 1, open: true },
{ id: 21, text: "Wireframes", parent: 20, start_date: fmt(b, 5), duration: 5, progress: 1 },
{ id: 22, text: "Visual design", parent: 20, start_date: fmt(b, 5), duration: 8, progress: .8 },
{ id: 23, text: "Design system", parent: 20, start_date: fmt(b, 7), duration: 6, progress: .6 },
{ id: 24, text: "Accessibility review", parent: 20, start_date: fmt(b, 5), duration: 5, progress: .4 },
/* Development: setup first, then six workstreams in parallel. */
{ id: 30, text: "Development", type: "project", parent: 1, open: true },
{ id: 31, text: "Project setup", parent: 30, start_date: fmt(b, 13), duration: 3, progress: 1 },
{ id: 32, text: "Auth & onboarding", parent: 30, start_date: fmt(b, 16), duration: 7, progress: .5 },
{ id: 33, text: "Core features", parent: 30, start_date: fmt(b, 16), duration: 12, progress: .25 },
{ id: 34, text: "Push notifications", parent: 30, start_date: fmt(b, 16), duration: 6, progress: 0 },
{ id: 35, text: "Offline sync", parent: 30, start_date: fmt(b, 16), duration: 5, progress: 0 },
{ id: 36, text: "Analytics integration", parent: 30, start_date: fmt(b, 16), duration: 4, progress: 0 },
{ id: 37, text: "Payments integration", parent: 30, start_date: fmt(b, 16), duration: 9, progress: .1 },
/* QA & Testing: plan first, then five checks in parallel. */
{ id: 40, text: "QA & Testing", type: "project", parent: 1, open: true },
{ id: 41, text: "Test plan", parent: 40, start_date: fmt(b, 28), duration: 3, progress: 0 },
{ id: 42, text: "Regression testing", parent: 40, start_date: fmt(b, 31), duration: 6, progress: 0 },
{ id: 44, text: "Performance testing", parent: 40, start_date: fmt(b, 31), duration: 4, progress: 0 },
{ id: 45, text: "Accessibility audit", parent: 40, start_date: fmt(b, 31), duration: 3, progress: 0 },
{ id: 43, text: "Beta feedback", parent: 40, start_date: fmt(b, 31), duration: 5, progress: 0 },
{ id: 46, text: "Load testing", parent: 40, start_date: fmt(b, 31), duration: 5, progress: 0 },
/* Launch: three workstreams run side by side into one milestone. */
{ id: 50, text: "Launch", type: "project", parent: 1, open: true },
{ id: 51, text: "Store submission", parent: 50, start_date: fmt(b, 37), duration: 4, progress: 0 },
{ id: 52, text: "Marketing campaign", parent: 50, start_date: fmt(b, 37), duration: 5, progress: 0 },
{ id: 54, text: "Press kit", parent: 50, start_date: fmt(b, 37), duration: 3, progress: 0 },
{ id: 53, text: "Go live", parent: 50, type: "milestone", start_date: fmt(b, 42) },
/* Post-launch: monitoring and support run side by side after go-live. */
{ id: 60, text: "Post-launch", type: "project", parent: 1, open: true },
{ id: 61, text: "Monitor crash reports", parent: 60, start_date: fmt(b, 42), duration: 5, progress: 0 },
{ id: 62, text: "Customer support triage", parent: 60, start_date: fmt(b, 42), duration: 5, progress: 0 },
{ id: 64, text: "Usage analytics review", parent: 60, start_date: fmt(b, 42), duration: 6, progress: 0 },
{ id: 63, text: "Hotfix release", parent: 60, start_date: fmt(b, 44), duration: 4, progress: 0 },
{ id: 65, text: "Retrospective", parent: 60, type: "milestone", start_date: fmt(b, 48) }
],
links: [
{ id: 1, source: 11, target: 13, type: "0" },
{ id: 2, source: 12, target: 13, type: "0" },
{ id: 3, source: 14, target: 13, type: "0" },
{ id: 4, source: 13, target: 21, type: "0" },
{ id: 5, source: 13, target: 22, type: "0" },
{ id: 6, source: 13, target: 23, type: "0" },
{ id: 7, source: 13, target: 24, type: "0" },
{ id: 8, source: 22, target: 31, type: "0" },
{ id: 9, source: 31, target: 32, type: "0" },
{ id: 10, source: 31, target: 33, type: "0" },
{ id: 11, source: 31, target: 34, type: "0" },
{ id: 12, source: 31, target: 35, type: "0" },
{ id: 13, source: 31, target: 36, type: "0" },
{ id: 14, source: 31, target: 37, type: "0" },
{ id: 15, source: 33, target: 41, type: "0" },
{ id: 16, source: 41, target: 42, type: "0" },
{ id: 17, source: 41, target: 44, type: "0" },
{ id: 18, source: 41, target: 45, type: "0" },
{ id: 19, source: 41, target: 43, type: "0" },
{ id: 20, source: 41, target: 46, type: "0" },
{ id: 21, source: 42, target: 51, type: "0" },
{ id: 22, source: 42, target: 52, type: "0" },
{ id: 23, source: 42, target: 54, type: "0" },
{ id: 24, source: 51, target: 53, type: "0" },
{ id: 25, source: 52, target: 53, type: "0" },
{ id: 26, source: 54, target: 53, type: "0" },
{ id: 27, source: 53, target: 61, type: "0" },
{ id: 28, source: 53, target: 62, type: "0" },
{ id: 29, source: 53, target: 64, type: "0" },
{ id: 30, source: 61, target: 63, type: "0" },
{ id: 31, source: 62, target: 65, type: "0" },
{ id: 32, source: 63, target: 65, type: "0" },
{ id: 33, source: 64, target: 65, type: "0" }
]
});
})();
/* --- export --------------------------------------------------------------- */
let EXPORTERS = {
ical: { fn: "exportToICal", name: "plan.ics", server: SERVER },
excel: { fn: "exportToExcel", name: "plan.xlsx", server: SERVER },
msp: { fn: "exportToMSProject", name: "plan.xml", server: PROJECT_SERVER },
p6: { fn: "exportToPrimaveraP6", name: "plan.xml", server: PROJECT_SERVER }
};
function doExport(kind) {
DHX.dd.closeAll();
if (!gantt.getTaskCount()) {
gantt.message({ type: "error", text: "Nothing to export yet." });
return;
}
let exporter = EXPORTERS[kind];
gantt[exporter.fn]({
server: exporter.server,
name: exporter.name
});
}
/* --- import: selected file or drag-and-drop ------------------------------- */
let selectedImportFile = null;
let importAfterPicker = false;
let importInProgress = false;
let fileInput = document.getElementById("file_input");
let uploadButton = document.getElementById("upload_file_btn");
let fileNameNode = document.getElementById("import_file_name");
let statusNode = document.getElementById("import_status");
let ganttNode = document.getElementById("gantt_here");
function fileExtension(file) {
let parts = String(file.name || "").split(".");
return parts.length > 1 ? parts.pop().toLowerCase() : "";
}
function setSelectedImportFile(file) {
selectedImportFile = file || null;
fileNameNode.textContent = file ? file.name : "No file selected";
fileNameNode.classList.toggle("has_file", !!file);
}
function setImportStatus(text, isError) {
statusNode.textContent = text || "";
statusNode.classList.toggle("error", !!isError);
}
function setImportBusy(busy) {
importInProgress = busy;
uploadButton.disabled = busy;
uploadButton.textContent = busy ? "Importing..." : "Upload File";
}
function chooseImportFile(autoImport) {
if (importInProgress) {
return;
}
importAfterPicker = !!autoImport;
fileInput.value = "";
fileInput.click();
}
function uploadSelectedFile() {
if (importInProgress) {
return;
}
if (selectedImportFile) {
sendFile(selectedImportFile);
} else {
chooseImportFile(true);
}
}
fileInput.addEventListener("change", function () {
let file = this.files && this.files[0];
if (!file) {
importAfterPicker = false;
return;
}
setSelectedImportFile(file);
setImportStatus("Ready to import", false);
if (importAfterPicker) {
importAfterPicker = false;
sendFile(file);
}
});
function readFileHead(file) {
return new Promise(function (resolve, reject) {
let reader = new FileReader();
reader.onload = function () {
resolve(String(reader.result || ""));
};
reader.onerror = function () {
reject(reader.error || new Error("The file could not be read."));
};
reader.readAsText(file.slice(0, 65536));
});
}
function detectXmlProjectType(file) {
return readFileHead(file).then(function (text) {
let source = text.toLowerCase();
if (
source.indexOf("apibusinessobjects") !== -1 ||
source.indexOf("xmlns.oracle.com/primavera") !== -1 ||
source.indexOf("primavera p6") !== -1
) {
return "p6";
}
if (
source.indexOf("schemas.microsoft.com/project") !== -1 ||
source.indexOf("<mspdi") !== -1
) {
return "msp";
}
/* Most generic Project XML files accepted by this demo are MS Project.
Primavera XML exported by P6 or DHTMLX contains a Primavera namespace. */
return "msp";
});
}
function detectImportType(file) {
let ext = fileExtension(file);
if (ext === "xer") {
return Promise.resolve("p6");
}
if (ext === "mpp") {
return Promise.resolve("msp");
}
if (ext === "xlsx" || ext === "xls") {
return Promise.resolve("excel");
}
if (ext === "xml") {
return detectXmlProjectType(file);
}
// The current functionality allows importing various files that are partially supported by MPXJ
// If the file is really not supported, the server returns an error
// Otherwise, allow users to experiment with their files
return Promise.resolve("p6");
// return Promise.reject(new Error("Unrecognized file type: ." + ext));
}
function sendFile(file) {
if (!file || importInProgress) {
return;
}
setSelectedImportFile(file);
setImportBusy(true);
setImportStatus("Detecting file format...", false);
detectImportType(file).then(function (kind) {
if (kind === "excel") {
importExcel(file);
} else if (kind === "p6") {
importProjectFile(file, {
fn: "importFromPrimaveraP6",
label: "Primavera P6",
durationUnit: "day"
});
} else {
importProjectFile(file, {
fn: "importFromMSProject",
label: "MS Project"
});
}
}).catch(function (error) {
setImportBusy(false);
importFailed(error && error.message);
});
}
function importExcel(file) {
setImportStatus("Importing Excel...", false);
try {
gantt.importFromExcel({
server: SERVER,
data: file,
callback: function (rows) {
setImportBusy(false);
if (rows && rows.length) {
gantt.clearAll();
gantt.parse({ tasks: excelRowsToTasks(rows), links: [] });
afterImport("Excel");
} else {
importFailed();
}
}
});
} catch (error) {
setImportBusy(false);
importFailed(error && error.message);
}
}
function importProjectFile(file, importer) {
setImportStatus("Importing " + importer.label + "...", false);
try {
gantt[importer.fn]({
server: PROJECT_SERVER,
data: file,
durationUnit: importer.durationUnit,
callback: function (project) {
setImportBusy(false);
if (project && project.data) {
gantt.clearAll();
if (project.config && project.config.duration_unit) {
gantt.config.duration_unit = project.config.duration_unit;
}
gantt.parse(project.data);
afterImport(importer.label);
} else {
importFailed();
}
}
});
} catch (error) {
setImportBusy(false);
importFailed(error && error.message);
}
}
function importFailed(details) {
let message = details || "The service could not read that file. Check the format and your connection.";
setImportStatus(message, true);
gantt.message({ type: "error", text: DHX.ui.escape(message) });
}
function afterImport(label) {
gantt.render();
let first = gantt.getTaskByIndex(0);
if (first) {
gantt.showTask(first.id);
}
setImportStatus(label + " imported successfully", false);
}
/* Drop a supported file anywhere inside the Gantt to import immediately. */
let dragDepth = 0;
function containsFiles(event) {
let types = event.dataTransfer && event.dataTransfer.types;
return types && Array.prototype.indexOf.call(types, "Files") !== -1;
}
ganttNode.addEventListener("dragenter", function (event) {
if (!containsFiles(event)) {
return;
}
event.preventDefault();
dragDepth++;
ganttNode.classList.add("is_file_dragover");
});
ganttNode.addEventListener("dragover", function (event) {
if (!containsFiles(event)) {
return;
}
event.preventDefault();
event.dataTransfer.dropEffect = "copy";
ganttNode.classList.add("is_file_dragover");
});
ganttNode.addEventListener("dragleave", function (event) {
if (!containsFiles(event)) {
return;
}
dragDepth = Math.max(0, dragDepth - 1);
if (!dragDepth) {
ganttNode.classList.remove("is_file_dragover");
}
});
ganttNode.addEventListener("drop", function (event) {
event.preventDefault();
dragDepth = 0;
ganttNode.classList.remove("is_file_dragover");
let files = event.dataTransfer && event.dataTransfer.files;
let file = files && files[0];
if (file) {
setSelectedImportFile(file);
sendFile(file);
}
});
/* Excel rows arrive as plain objects with spreadsheet column names as keys.
Guess which column is which gantt field from the header text, same heuristic
as 05_import_msp_excel.html, applied straight away instead of via a dialog. */
function guessField(header) {
let h = (header + "").toLowerCase();
if (h.indexOf("wbs") > -1) return "wbs";
if (h.indexOf("start") > -1 || h.indexOf("begin") > -1) return "start_date";
if (h.indexOf("end") > -1 || h.indexOf("finish") > -1) return "end_date";
if (h.indexOf("duration") > -1 || h.indexOf("days") > -1) return "duration";
if (h.indexOf("parent") > -1) return "parent";
if (h === "id" || h.indexOf("code") > -1) return "id";
return "text";
}
function excelRowsToTasks(rows) {
let header = Object.keys(rows[0]);
let mapping = {};
header.forEach(function (col) { mapping[col] = guessField(col); });
return rows.map(function (row) {
let task = { open: true };
for (let col in row) {
task[mapping[col]] = row[col];
}
if (task.wbs) { // rebuild the tree from WBS codes like 2.4.1
let parts = String(task.wbs).split(".");
task.id = String(task.wbs);
parts.pop();
task.parent = parts.join(".");
}
return task;
});
}<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Export & import hub: iCal, Excel, MS Project, Primavera P6, and other formats | dhtmlxGantt</title>
<script src="../../codebase/dhtmlxgantt.js?v=10.0.0"></script>
<link rel="stylesheet" href="../../codebase/dhtmlxgantt.css?v=10.0.0">
<link rel="stylesheet" href="../common/demo-controls/dhx_controls.css">
<script src="../common/demo-controls/dhx_controls.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
<style>
body { display: flex; flex-direction: column; }
#gantt_here {
position: relative;
width: 100%;
flex: 1 1 auto;
min-height: 0;
}
#gantt_here.is_file_dragover::after {
content: "Drop the file to import it";
position: absolute;
inset: 10px;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
border: 2px dashed var(--dhx-accent);
border-radius: 12px;
background: rgba(239, 246, 255, .92);
color: var(--dhx-accent);
font: 600 15px var(--dhx-font);
pointer-events: none;
}
.gantt_task_line { border: none; border-radius: 7px; box-shadow: 0 1px 2px rgba(16,24,40,.18); }
.gantt_task_line .gantt_task_progress { border-radius: 7px 0 0 7px; }
.gantt_task_line.gantt_project { border-radius: 5px; }
.hint { font: 500 12px var(--dhx-font); color: var(--dhx-muted); }
.hint b { color: var(--dhx-ink-2); font-weight: 600; }
.export_menu { min-width: 170px; padding: 6px; }
.import_control {
display: inline-flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.import_file {
display: inline-flex;
align-items: center;
gap: 8px;
min-width: 280px;
max-width: 420px;
height: 34px;
padding: 0 6px 0 10px;
box-sizing: border-box;
border: 1px solid var(--dhx-line);
border-radius: 8px;
background: var(--dhx-surface);
}
.import_file__name {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font: 500 12px var(--dhx-font);
color: var(--dhx-muted);
}
.import_file__name.has_file {
color: var(--dhx-ink-2);
}
.import_file__choose {
flex: none;
height: 26px;
padding: 0 9px;
border: 0;
border-radius: 6px;
background: #f2f4f7;
color: var(--dhx-ink-2);
font: 600 11px var(--dhx-font);
cursor: pointer;
}
.import_file__choose:hover {
background: #e7eaee;
color: var(--dhx-ink);
}
:root[data-gantt-theme="dark"] .import_file__choose {
background: #323a45;
color: var(--dhx-ink);
}
:root[data-gantt-theme="dark"] .import_file__choose:hover {
background: #3c4550;
}
.import_status {
max-width: 280px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font: 500 11px var(--dhx-font);
color: var(--dhx-muted);
}
.import_status.error {
color: #c0392b;
}
@media (max-width: 820px) {
.dhx_toolbar.import_toolbar {
height: auto;
flex-wrap: wrap;
padding-top: 8px;
padding-bottom: 8px;
}
.import_control {
flex-wrap: wrap;
justify-content: center;
}
.import_file {
min-width: min(360px, calc(100vw - 32px));
}
}
</style>
</head>
<body>
<div class="dhx_toolbar import_toolbar" style="justify-content:center; gap:10px">
<div class="dhx_dd" id="dd_export">
<button class="dhx_btn dhx_btn--surface" onclick="DHX.dd.toggle('dd_export')">
Export <span class="dhx_caret"><svg class="dhx_ic dhx_ic--sm" viewBox="0 0 24 24"><polyline points="6 9 12 15 18 9"/></svg></span>
</button>
<div class="dhx_menu export_menu" id="export_menu">
<div class="dhx_menu__item" onclick="doExport('ical')">iCal</div>
<div class="dhx_menu__item" onclick="doExport('excel')">Excel</div>
<div class="dhx_menu__item" onclick="doExport('msp')">MS Project</div>
<div class="dhx_menu__item" onclick="doExport('p6')">Primavera P6</div>
</div>
</div>
<div class="import_control">
<div class="import_file" title="Excel, MS Project or Primavera P6">
<span class="import_file__name" id="import_file_name">No file selected</span>
<button type="button" class="import_file__choose" onclick="chooseImportFile(false)">Choose file</button>
</div>
<button type="button" class="dhx_btn dhx_btn--primary" id="upload_file_btn" onclick="uploadSelectedFile()">
Upload File
</button>
<span class="import_status" id="import_status">or drop a file onto the Gantt</span>
<input type="file" id="file_input" accept=".mpp,.xml,.xer,.xlsx,.xls" hidden>
</div>
</div>
<div id="gantt_here"></div>
</body>
</html>