Integrating Third-Party JavaScript Libraries in sgcHTML | eSeGeCe Blog

Integrating Third-Party JavaScript Libraries in sgcHTML

· Components
Integrating Third-Party JavaScript Libraries in sgcHTML

A question we hear from people evaluating sgcHTML: it ships a lot of ready-made components, but what if I already have a JavaScript library I like? Maybe you prefer charts from D3.js or Chart.js, or you have paid for a DevExtreme Grid and want to use it. The short answer is that sgcHTML was built to be open. It generates complete pages on the server from Delphi, C++Builder or .NET code, and any library that runs in a browser drops in with three small hooks. If you reach for the same library often, you can wrap it in a reusable component so the rest of your app never sees the JavaScript at all.

The three hooks

Every third-party widget needs the same three things on the page: the library has to be loaded, there has to be an element for it to render into, and a line of init code has to run once the DOM is ready. The TsgcHTMLComponent_Site component gives you one property or method for each.

Here is a complete D3.js example. Load the library, drop in a container, then let D3 draw into it.

uses
  sgcHTML_Component_Site;

var
  oSite: TsgcHTMLComponent_Site;
begin
  oSite := TsgcHTMLComponent_Site.Create(nil);
  try
    oSite.Title := 'Sales Dashboard';

    // 1) Load the library (a public CDN, or a file you serve yourself)
    oSite.CustomHead := '<script src="https://d3js.org/d3.v7.min.js"></script>';

    // 2) Emit the element your library renders into
    oSite.AddContent('<div id="chart"></div>');

    // 3) Run your init code at the end of the body
    oSite.BodyEndHTML :=
      '<script>' +
      '  const data = [4, 8, 15, 16, 23, 42];' +
      '  d3.select("#chart").selectAll("div").data(data).enter()' +
      '    .append("div").attr("class", "bar")' +
      '    .style("width", d => (d * 12) + "px").text(d => d);' +
      '</script>';

    WebModule.Response := oSite.HTML;
  finally
    oSite.Free;
  end;
end;

That is the whole pattern. Swap the URL and the init code, and the same three lines carry Chart.js, a DevExtreme widget, a calendar, a map, or anything else.

CDN or self-hosted

The example above points at a public CDN, which is the quickest way to try something. For an intranet app, an offline deployment, or when you want to pin an exact version, serve the library file yourself from the sgc server and reference it with a local path. Several built-in components already do this: the bundled Chart component loads Chart.js from a local /chart.umd.min.js rather than a CDN, so the page has no outbound dependency.

// The .js and .css files sit next to your server executable and are
// served as static files, so the page references them by local path:
oSite.CustomHead :=
  '<link rel="stylesheet" href="/assets/mylib.min.css">' +
  '<script src="/assets/mylib.min.js"></script>';

If you prefer to build the head as nodes instead of a raw string, the template layer exposes HeadNodes and BodyEndNodes lists with AddScript and AddStylesheet helpers, plus TsgcHTMLScript (with Src, Code, Defer, Async) and TsgcHTMLStylesheet nodes. Raw strings and typed nodes are interchangeable, use whichever reads better in your code.

Chart.js in a few lines

Chart.js is a common request, so here it is through the same three hooks. Load the library, emit a <canvas>, then construct the chart.

oSite.CustomHead := '<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>';

oSite.AddContent('<canvas id="revenue" width="600" height="300"></canvas>');

oSite.BodyEndHTML :=
  '<script>' +
  '  new Chart(document.getElementById("revenue"), {' +
  '    type: "bar",' +
  '    data: {' +
  '      labels: ["Q1", "Q2", "Q3", "Q4"],' +
  '      datasets: [{ label: "Revenue", data: [12, 19, 7, 15] }]' +
  '    }' +
  '  });' +
  '</script>';

If you only want charts and do not need a specific library, note that sgcHTML already ships a TsgcHTMLComponent_Chart component that is Chart.js wired up for you, with a CustomOptions property that passes any raw Chart.js configuration straight through. This section is for when you want to drive the library yourself.

A DevExtreme Grid

DevExtreme is a good example of a heavier commercial suite. The DataGrid needs its stylesheet and its bundle loaded, an element to mount on, and a configuration object. Nothing changes about the approach.

oSite.CustomHead :=
  '<link rel="stylesheet" href="https://cdn3.devexpress.com/jslib/23.2.3/css/dx.light.css">' +
  '<script src="https://cdn3.devexpress.com/jslib/23.2.3/js/dx.all.js"></script>';

oSite.AddContent('<div id="grid"></div>');

oSite.BodyEndHTML :=
  '<script>' +
  '  new DevExpress.ui.dxDataGrid(document.getElementById("grid"), {' +
  '    dataSource: [' +
  '      { id: 1, name: "Widget", price: 9.90 },' +
  '      { id: 2, name: "Gadget", price: 14.50 }' +
  '    ],' +
  '    columns: ["id", "name", "price"],' +
  '    showBorders: true' +
  '  });' +
  '</script>';

The interesting part is where dataSource comes from. You can serialize a dataset to JSON on the server and inline it as shown, or point the grid at an endpoint your sgc server exposes and let it load live. There is a ready DevExtreme Grid sample in the demos folder that feeds a grid from an sgc WebSocket server, so the data updates in real time without a page reload.

Wrap it in a reusable component

Passing raw strings around is fine for a one-off. When you find yourself using the same library on page after page, promote it to a component: subclass TsgcHTMLComponent and override GetHTML to return your own markup plus the library glue. Everywhere else in your app you then set a property and call HTML, exactly like a built-in component.

uses
  sgcHTML_Component;

type
  TsgcHTMLComponent_D3Bars = class(TsgcHTMLComponent)
  private
    FData: string;   // a JSON array, e.g. '[4,8,15,16,23,42]'
  published
    property Data: string read FData write FData;
  public
    function GetHTML: string; override;
  end;

function TsgcHTMLComponent_D3Bars.GetHTML: string;
begin
  Result :=
    '<div id="d3bars"></div>' +
    '<script src="https://d3js.org/d3.v7.min.js"></script>' +
    '<script>' +
    '  const data = ' + FData + ';' +
    '  d3.select("#d3bars").selectAll("span").data(data).enter()' +
    '    .append("span").style("padding-right", d => d + "px").text(d => d);' +
    '</script>';
end;

This is not a special case, it is how a number of the shipped components are built. The Map component wraps Leaflet, the RichEditor wraps Quill, the KanbanBoard wraps SortableJS, and the PDFViewer wraps pdf.js, each of them loading its library and emitting its init code through exactly these hooks. Your own wrapper sits alongside them as a first-class component, and you can add a matching GetCSS override if the library needs styles, which sgcHTML collects and de-duplicates across the page for you.

Try it

The takeaway is that sgcHTML never boxes you in. The built-in components cover a lot of ground, and when you need something specific, D3.js, Chart.js, a DevExtreme Grid or Chart, any browser library at all, it is three hooks to embed and one small subclass to make reusable.

Questions or feedback? Get in touch, you will get a reply from the people who wrote the code.