Integrando Bibliotecas JavaScript de Terceiros no sgcHTML | eSeGeCe Blog

Integrando Bibliotecas JavaScript de Terceiros no sgcHTML

· Componentes

Uma pergunta que ouvimos de quem está avaliando o sgcHTML: ele traz muitos componentes prontos, mas e se eu já tiver uma biblioteca JavaScript que gosto de usar? Talvez você prefira gráficos do D3.js ou do Chart.js, ou já tenha pago por um Grid do DevExtreme e queira usá-lo. A resposta curta é que o sgcHTML foi criado para ser aberto. Ele gera páginas completas no servidor a partir de código Delphi, C++Builder ou .NET, e qualquer biblioteca que rode no navegador se encaixa com três pequenos hooks. Se você recorre à mesma biblioteca com frequência, pode encapsulá-la em um componente reutilizável para que o restante da sua aplicação nunca precise lidar com o JavaScript.

Os três hooks

Todo widget de terceiros precisa das mesmas três coisas na página: a biblioteca precisa ser carregada, precisa existir um elemento para ela renderizar dentro, e uma linha de código de inicialização precisa rodar assim que o DOM estiver pronto. O componente TsgcHTMLComponent_Site oferece uma propriedade ou método para cada uma delas.

Aqui está um exemplo completo com D3.js. Carregue a biblioteca, insira um container e deixe o D3 desenhar dentro dele.

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;

Esse é todo o padrão. Troque a URL e o código de inicialização, e as mesmas três linhas funcionam para Chart.js, um widget do DevExtreme, um calendário, um mapa ou qualquer outra coisa.

CDN ou hospedagem própria

O exemplo acima aponta para um CDN público, que é a forma mais rápida de testar algo. Para uma aplicação de intranet, uma implantação offline, ou quando você quer fixar uma versão exata, sirva o arquivo da biblioteca você mesmo a partir do servidor sgc e referencie-o com um caminho local. Vários componentes internos já fazem isso: o componente Chart embutido carrega o Chart.js a partir de um /chart.umd.min.js local em vez de um CDN, então a página não tem nenhuma dependência externa.

// 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>';

Se preferir montar o head como nodes em vez de uma string bruta, a camada de templates expõe as listas HeadNodes e BodyEndNodes com os helpers AddScript e AddStylesheet, além dos nodes TsgcHTMLScript (com Src, Code, Defer, Async) e TsgcHTMLStylesheet. Strings brutas e nodes tipados são intercambiáveis, use o que ficar mais legível no seu código.

Chart.js em poucas linhas

Chart.js é um pedido comum, então aqui está ele usando os mesmos três hooks. Carregue a biblioteca, emita um <canvas> e depois construa o gráfico.

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>';

Se você só quer gráficos e não precisa de uma biblioteca específica, note que o sgcHTML já traz um componente TsgcHTMLComponent_Chart que é o Chart.js pronto para uso, com uma propriedade CustomOptions que repassa diretamente qualquer configuração bruta do Chart.js. Esta seção é para quando você quer controlar a biblioteca por conta própria.

Um Grid do DevExtreme

DevExtreme é um bom exemplo de um pacote comercial mais pesado. O DataGrid precisa que sua folha de estilos e seu bundle sejam carregados, de um elemento para ser montado e de um objeto de configuração. A abordagem não muda em nada.

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>';

A parte interessante é de onde vem o dataSource. Você pode serializar um conjunto de dados em JSON no servidor e incluí-lo diretamente, como mostrado, ou apontar o grid para um endpoint que o seu servidor sgc expõe e deixar que ele carregue os dados ao vivo. Há um exemplo pronto de Grid do DevExtreme na pasta de demos que alimenta um grid a partir de um servidor sgc WebSocket, de modo que os dados são atualizados em tempo real sem recarregar a página.

Encapsule em um componente reutilizável

Passar strings brutas de um lado para outro é aceitável para algo pontual. Quando você perceber que está usando a mesma biblioteca página após página, promova-a a um componente: crie uma subclasse de TsgcHTMLComponent e sobrescreva GetHTML para retornar sua própria marcação junto com a integração da biblioteca. No restante da sua aplicação, basta então definir uma propriedade e chamar HTML, exatamente como em um componente embutido.

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;

Isso não é um caso especial, é assim que vários dos componentes já incluídos são construídos. O componente Map encapsula o Leaflet, o RichEditor encapsula o Quill, o KanbanBoard encapsula o SortableJS, e o PDFViewer encapsula o pdf.js, cada um deles carregando sua biblioteca e emitindo seu código de inicialização exatamente através destes hooks. Seu próprio wrapper fica ao lado deles como um componente de primeira classe, e você pode adicionar um GetCSS correspondente se a biblioteca precisar de estilos, que o sgcHTML coleta e remove duplicidades em toda a página automaticamente.

Experimente

A conclusão é que o sgcHTML nunca limita você. Os componentes embutidos cobrem muito terreno, e quando você precisa de algo específico, D3.js, Chart.js, um Grid ou Chart do DevExtreme, qualquer biblioteca de navegador, bastam três hooks para incorporar e uma pequena subclasse para torná-la reutilizável.

Dúvidas ou sugestões? Fale conosco, você receberá uma resposta de quem escreveu o código.