Lattice Grid Buy a licence

demo D277

Open a Microsoft Project plan in React

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

This reads a Microsoft Project XML file straight into a schedule, tasks, links, progress and people intact, draws it in the browser, and writes it back out, all with nothing sent to a server. It lets you open and save real Project plans in the page, so existing plans are not locked away in one tool.

Building…
Loading a live grid…

This is the React version. The grid mounts through the createLatticeGrid adapter, which takes its configuration as ordinary props and hands back the live grid through a ref. The grid below is the same one every other tab runs.

The configuration

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/lattice-grid.min.css">

<div id="app"></div>

<script type="module">
  import React from 'https://esm.sh/react@18';
  import { createRoot } from 'https://esm.sh/react-dom@18/client';
  import { createGantt, importMSPDI, exportMSPDI } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/modules/gantt.esm.min.js';

  // mountSplit and the file round-trip are both outside what <LatticeGantt>
  // covers, so this stays an imperative build against a ref'd container.
  function App() {
    const planRef = React.useRef(null);
    const modelRef = React.useRef(null);
    const ganttRef = React.useRef(null);
    const [status, setStatus] = React.useState('');

    React.useEffect(() => {
      // importMSPDI reads a Project .xml into a { tasks, dependencies,
      // resources, projectStart, calendar } model: the task tree, typed
      // dependency links with their lag, per-task progress and the people on
      // each task.
      const xml = ''; // the text of a Project .xml document
      const model = importMSPDI(xml);
      modelRef.current = model;
      setStatus('Read ' + model.tasks.length + ' tasks and ' + model.dependencies.length + ' links.');

      const gantt = createGantt({
        tasks: model.tasks, dependencies: model.dependencies, resources: model.resources,
        projectStart: model.projectStart, calendar: 'weekends', autoSchedule: true,
      });
      ganttRef.current = gantt;
      gantt.mountSplit(planRef.current, {
        gridWidth: 300, rowHeight: 34, height: 420, zoom: 'day',
        nonWorking: 'weekends', showArrows: true, showProgress: true,
      });
      return () => gantt.destroy();
    }, []);

    const save = () => {
      // Write the plan back out as a Project file, so a round-trip keeps the
      // tasks, typed links and assignments intact. gantt.toMSPDI() is the
      // same over the plan.
      const model = modelRef.current;
      const out = exportMSPDI({
        tasks: model.tasks, dependencies: model.dependencies,
        schedule: ganttRef.current.schedule, resources: model.resources,
      });
      const url = URL.createObjectURL(new Blob([out], { type: 'application/xml' }));
      const a = document.createElement('a');
      a.href = url; a.download = 'plan.xml'; a.click();
      URL.revokeObjectURL(url);
    };

    return (
      <>
        <div style={{ marginBottom: '10px' }}>
          <button onClick={save}>Save as Project .xml</button> <span>{status}</span>
        </div>
        <div ref={planRef} style={{ height: '420px', overflow: 'auto' }} />
      </>
    );
  }

  createRoot(document.getElementById('app')).render(<App />);
</script>