Een vraag die we vaak horen van mensen die sgcHTML evalueren: het bevat veel kant-en-klare componenten, maar wat als ik al een JavaScript-bibliotheek heb die ik graag gebruik? Misschien geef je de voorkeur aan grafieken van D3.js of Chart.js, of heb je betaald voor een DevExtreme Grid en wil je die gebruiken. Het korte antwoord is dat sgcHTML is gebouwd om open te zijn. Het genereert complete pagina's op de server vanuit Delphi-, C++Builder- of .NET-code, en elke bibliotheek die in een browser draait, integreer je met drie kleine hooks. Als je regelmatig dezelfde bibliotheek gebruikt, kun je deze verpakken in een herbruikbare component, zodat de rest van je app de JavaScript helemaal niet meer ziet.
De drie hooks
Elke widget van derden heeft dezelfde drie dingen nodig op de pagina: de bibliotheek moet worden geladen, er moet een element zijn waarin deze kan renderen, en er moet een regel initialisatiecode worden uitgevoerd zodra de DOM gereed is. De component TsgcHTMLComponent_Site geeft je voor elk hiervan één property of methode.
CustomHeadinjecteert markup in <head>, de natuurlijke plaats voor een <script>- of <link>-tag die de bibliotheek laadt.AddContentplaatst het doelelement in de pagina-inhoud (body).BodyEndHTMLvoert je initialisatiecode uit vlak voor </body>, nadat de bibliotheek is geladen.
Hier is een compleet D3.js-voorbeeld. Laad de bibliotheek, voeg een container toe en laat D3 daarin tekenen.
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;
Dat is het hele patroon. Vervang de URL en de initialisatiecode, en dezelfde drie regels dragen Chart.js, een DevExtreme-widget, een kalender, een kaart, of wat dan ook.
CDN of zelf gehost
Het voorbeeld hierboven verwijst naar een publieke CDN, wat de snelste manier is om iets uit te proberen. Voor een intranettoepassing, een offline implementatie, of wanneer je een exacte versie wilt vastleggen, serveer je het bibliotheekbestand zelf vanaf de sgc-server en verwijs je ernaar met een lokaal pad. Verschillende ingebouwde componenten doen dit al: de meegeleverde Chart-component laadt Chart.js vanaf een lokaal /chart.umd.min.js in plaats van een CDN, zodat de pagina geen uitgaande afhankelijkheid heeft.
// 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>';
Als je de head liever opbouwt als nodes in plaats van een ruwe string, biedt de template-laag de lijsten HeadNodes en BodyEndNodes met de helpers AddScript en AddStylesheet, plus de nodes TsgcHTMLScript (met Src, Code, Defer, Async) en TsgcHTMLStylesheet. Ruwe strings en getypeerde nodes zijn onderling verwisselbaar, gebruik wat het beste leesbaar is in jouw code.
Chart.js in een paar regels
Chart.js is een veelgevraagde optie, dus hier is het via dezelfde drie hooks. Laad de bibliotheek, plaats een <canvas>, en bouw vervolgens de grafiek op.
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>';
Als je alleen grafieken wilt en geen specifieke bibliotheek nodig hebt, let er dan op dat sgcHTML al een component TsgcHTMLComponent_Chart meelevert die Chart.js kant-en-klaar voor je aansluit, met een CustomOptions-property die elke ruwe Chart.js-configuratie direct doorgeeft. Deze sectie is voor wanneer je de bibliotheek zelf wilt aansturen.
Een DevExtreme Grid
DevExtreme is een goed voorbeeld van een zwaardere commerciële suite. De DataGrid heeft zijn stylesheet en bundel nodig, een element om op te mounten, en een configuratieobject. Aan de aanpak verandert niets.
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>';
Het interessante deel is waar dataSource vandaan komt. Je kunt een dataset op de server serialiseren naar JSON en deze zoals getoond inline opnemen, of de grid laten wijzen naar een endpoint dat je sgc-server aanbiedt en die live laten laden. Er is een kant-en-klaar DevExtreme Grid-voorbeeld in de demos-map dat een grid voedt vanuit een sgc WebSocket-server, zodat de gegevens in realtime worden bijgewerkt zonder dat de pagina opnieuw hoeft te laden.
Verpak het in een herbruikbare component
Ruwe strings doorgeven is prima voor een eenmalig gebruik. Zodra je merkt dat je dezelfde bibliotheek keer op keer gebruikt, promoveer je deze tot een component: leid een subklasse af van TsgcHTMLComponent en override GetHTML om je eigen markup plus de bibliotheek-koppeling terug te geven. Overal elders in je app stel je vervolgens een property in en roep je HTML aan, precies zoals bij een ingebouwde 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;
Dit is geen speciaal geval, het is hoe een groot aantal van de meegeleverde componenten is gebouwd. De Map-component verpakt Leaflet, de RichEditor verpakt Quill, het KanbanBoard verpakt SortableJS, en de PDFViewer verpakt pdf.js, elk van hen laadt zijn bibliotheek en voert zijn initialisatiecode uit via precies deze hooks. Je eigen wrapper staat naast hen als volwaardige component, en je kunt een bijpassende GetCSS-override toevoegen als de bibliotheek stijlen nodig heeft, die sgcHTML voor je verzamelt en over de pagina dedupliceert.
Probeer het
De boodschap is dat sgcHTML je nooit beperkt. De ingebouwde componenten dekken veel af, en wanneer je iets specifieks nodig hebt, D3.js, Chart.js, een DevExtreme Grid of Chart, welke browserbibliotheek dan ook, zijn het drie hooks om in te bedden en één kleine subklasse om het herbruikbaar te maken.
Vragen of feedback? Neem contact op, je krijgt antwoord van de mensen die de code hebben geschreven.
