Lattice Grid Buy a licence

demo D277

Open a Microsoft Project plan

Read a Project .xml straight into a schedule, tasks, links, progress and people intact, drawn in the browser, and write it back out again, all with nothing sent to a server

importMSPDI · exportMSPDI

Building…
Loading a live grid…

The configuration

'gantt-msproject': () => ({
  rows: [],
  config: {},
  foot: [
    'a Microsoft Project file is read straight into a schedule: the task tree, the typed dependency links with their lag, per-task progress, and the people on each task',
    'load your own .xml to see your plan drawn the same way, entirely in the browser with nothing sent anywhere',
    'write the plan back out as a Project file, so a round-trip in and out keeps the tasks, links and assignments intact',
  ],
  mount: (el: HTMLElement, LG: any) => {
    void LG;
    const bar = document.createElement('div');
    bar.style.cssText = 'display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-bottom:12px';
    const sampleBtn = toolButton('Load the sample');
    const exportBtn = toolButton('Save as Project .xml');
    const fileLabel = document.createElement('label');
    fileLabel.style.cssText = 'display:inline-flex;align-items:center;gap:6px;font-size:.8125rem;color:var(--ink-2);cursor:pointer';
    const fileInput = document.createElement('input');
    fileInput.type = 'file';
    fileInput.accept = '.xml';
    fileInput.style.cssText = 'font:inherit;font-size:.8125rem';
    fileLabel.append('Open a .xml', fileInput);
    const status = document.createElement('span');
    status.style.cssText = 'font-size:.8125rem;color:var(--ink-2)';
    bar.append(sampleBtn, fileLabel, exportBtn, status);
    const wrap = document.createElement('div');
    wrap.style.cssText = 'border:1px solid var(--rule);border-radius:9px;background:var(--paper);min-width:0;overflow:auto';
    el.append(bar, wrap);
    let G: any;
    let gantt: any;
    let view: any;
    let model: any;
    let disposed = false;
    const draw = (xml: string) => {
      const back = G.importMSPDI(xml);
      if (!back || !back.ok) {
        status.textContent = 'That file could not be read as a Project document.';
        status.style.color = '#c0392b';
        return;
      }
      model = back;
      gantt?.destroy?.();
      gantt = G.createGantt({
        tasks: back.tasks,
        dependencies: back.dependencies,
        resources: back.resources,
        projectStart: back.projectStart,
        calendar: 'weekends',
        autoSchedule: true,
      });
      view?.destroy?.();
      view = gantt.mountSplit(wrap, {
        gridWidth: 300,
        rowHeight: 34,
        height: 420,
        zoom: 'day',
        nonWorking: 'weekends',
        showArrows: true,
        showProgress: true,
      });
      status.textContent = `Read ${back.tasks.length} tasks and ${back.dependencies.length} links.`;
      status.style.color = 'var(--ink-2)';
    };
    loadGantt()
      .then((mod: any) => {
        if (disposed) return;
        G = mod;
        draw(SAMPLE_MSPDI);
        sampleBtn.addEventListener('click', () => draw(SAMPLE_MSPDI));
        fileInput.addEventListener('change', () => {
          const file = fileInput.files?.[0];
          if (!file) return;
          const reader = new FileReader();
          reader.onload = () => draw(String(reader.result ?? ''));
          reader.readAsText(file);
        });
        exportBtn.addEventListener('click', () => {
          if (!gantt || !model) return;
          const xml = G.exportMSPDI({
            tasks: model.tasks,
            dependencies: model.dependencies,
            schedule: gantt.schedule,
            resources: model.resources,
          });
          const blob = new Blob([xml], { type: 'application/xml' });
          const url = URL.createObjectURL(blob);
          const a = document.createElement('a');
          a.href = url;
          a.download = 'plan.xml';
          document.body.append(a);
          a.click();
          a.remove();
          URL.revokeObjectURL(url);
        });
      })
      .catch((err) => console.error('[gantt-mspdi]', err));
    return () => {
      disposed = true;
      view?.destroy?.();
      gantt?.destroy?.();
    };
  },
})