sgcHTML에서 서드파티 JavaScript 라이브러리 통합하기 | eSeGeCe Blog

sgcHTML에서 서드파티 JavaScript 라이브러리 통합하기

· 컴포넌트

sgcHTML을 검토하는 분들에게서 자주 듣는 질문이 있습니다. 많은 기본 제공 컴포넌트를 갖추고 있지만, 이미 마음에 드는 JavaScript 라이브러리가 있다면 어떻게 하나요? D3.jsChart.js로 차트를 그리고 싶을 수도 있고, 이미 구매한 DevExtreme Grid를 사용하고 싶을 수도 있습니다. 간단히 답하면, sgcHTML은 처음부터 개방적으로 설계되었습니다. Delphi, C++Builder 또는 .NET 코드로 서버에서 완전한 페이지를 생성하며, 브라우저에서 동작하는 어떤 라이브러리든 세 가지 작은 훅만으로 붙일 수 있습니다. 같은 라이브러리를 자주 사용한다면 재사용 가능한 컴포넌트로 감싸서, 앱의 나머지 부분에서는 JavaScript를 전혀 신경 쓰지 않도록 만들 수도 있습니다.

세 가지 훅

모든 서드파티 위젯은 페이지에서 동일한 세 가지가 필요합니다. 라이브러리가 로드되어야 하고, 렌더링될 요소가 있어야 하며, DOM이 준비되면 초기화 코드 한 줄이 실행되어야 합니다. TsgcHTMLComponent_Site 컴포넌트는 이 각각에 대해 하나씩 속성 또는 메서드를 제공합니다.

다음은 완전한 D3.js 예제입니다. 라이브러리를 로드하고, 컨테이너를 삽입한 다음, D3가 그 안에 그리도록 합니다.

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;

패턴은 이것이 전부입니다. URL과 초기화 코드를 바꾸기만 하면, 동일한 세 줄로 Chart.js, DevExtreme 위젯, 캘린더, 지도, 그 밖의 무엇이든 담아낼 수 있습니다.

CDN 또는 자체 호스팅

위 예제는 빠르게 시험해보기에 가장 좋은 방법인 공개 CDN을 가리키고 있습니다. 인트라넷 앱, 오프라인 배포, 또는 정확한 버전을 고정하고 싶은 경우에는 sgc 서버에서 라이브러리 파일을 직접 제공하고 로컬 경로로 참조하십시오. 이미 여러 기본 제공 컴포넌트가 이렇게 하고 있습니다. 번들로 제공되는 Chart 컴포넌트는 CDN이 아니라 로컬 /chart.umd.min.js에서 Chart.js를 로드하므로, 페이지에 외부 의존성이 전혀 없습니다.

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

원시 문자열 대신 노드로 head를 구성하고 싶다면, 템플릿 레이어는 AddScriptAddStylesheet 헬퍼가 포함된 HeadNodesBodyEndNodes 목록과, Src, Code, Defer, Async를 갖춘 TsgcHTMLScript, 그리고 TsgcHTMLStylesheet 노드를 제공합니다. 원시 문자열과 타입이 지정된 노드는 서로 바꿔 쓸 수 있으므로, 코드에서 더 읽기 좋은 쪽을 사용하면 됩니다.

몇 줄로 끝내는 Chart.js

Chart.js는 자주 요청받는 라이브러리이므로, 동일한 세 가지 훅으로 살펴보겠습니다. 라이브러리를 로드하고, <canvas>를 출력한 다음, 차트를 생성합니다.

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

차트만 필요하고 특정 라이브러리가 필요하지 않다면, sgcHTML에는 이미 Chart.js를 미리 연결해 둔 TsgcHTMLComponent_Chart 컴포넌트가 포함되어 있다는 점을 참고하십시오. 이 컴포넌트는 원시 Chart.js 설정을 그대로 전달하는 CustomOptions 속성도 제공합니다. 이번 절은 라이브러리를 직접 다루고 싶은 경우를 위한 내용입니다.

DevExtreme Grid 사용하기

DevExtreme은 더 무거운 상용 제품군의 좋은 예입니다. DataGrid는 스타일시트와 번들이 로드되어야 하고, 마운트할 요소가 필요하며, 설정 객체도 필요합니다. 접근 방식은 전혀 달라지지 않습니다.

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

흥미로운 부분은 dataSource가 어디서 오는가입니다. 위 예제처럼 서버에서 데이터셋을 JSON으로 직렬화해 인라인으로 넣을 수도 있고, sgc 서버가 노출하는 엔드포인트를 그리드가 가리키게 하여 실시간으로 로드하게 할 수도 있습니다. 데모 폴더에는 sgc WebSocket 서버로부터 데이터를 공급받는 DevExtreme Grid 샘플이 이미 준비되어 있어서, 페이지를 새로고침하지 않아도 데이터가 실시간으로 갱신됩니다.

재사용 가능한 컴포넌트로 감싸기

원시 문자열을 그때그때 전달하는 방식은 일회성 작업에는 문제가 없습니다. 하지만 같은 라이브러리를 페이지마다 계속 사용하게 된다면, 이를 컴포넌트로 승격시키십시오. TsgcHTMLComponent를 서브클래싱하고 GetHTML을 오버라이드하여 자체 마크업과 라이브러리 연결 코드를 반환하면 됩니다. 그러면 앱의 다른 모든 곳에서는 기본 제공 컴포넌트와 똑같이 속성을 설정하고 HTML을 호출하기만 하면 됩니다.

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;

이는 특별한 경우가 아니라, 기본 제공 컴포넌트 상당수가 만들어진 방식 그대로입니다. Map 컴포넌트는 Leaflet을 감싸고, RichEditor는 Quill을, KanbanBoard는 SortableJS를, PDFViewer는 pdf.js를 감쌉니다. 이들 각각은 바로 이 훅들을 통해 라이브러리를 로드하고 초기화 코드를 출력합니다. 여러분이 만든 래퍼도 이들과 나란히 일급 컴포넌트로 자리잡게 되며, 라이브러리에 스타일이 필요하다면 짝이 되는 GetCSS 오버라이드를 추가할 수 있습니다. sgcHTML이 페이지 전체에서 이를 수집하고 중복을 제거해 줍니다.

직접 사용해 보세요

결론적으로 sgcHTML은 여러분을 특정 틀에 가두지 않습니다. 기본 제공 컴포넌트가 폭넓은 영역을 다루지만, D3.js든 Chart.js든 DevExtreme Grid나 Chart든, 그 밖의 어떤 브라우저 라이브러리든 특정한 것이 필요할 때는 세 가지 훅으로 붙이고 작은 서브클래스 하나로 재사용 가능하게 만들 수 있습니다.

질문이나 의견이 있으신가요? 문의하기, 코드를 작성한 사람들에게서 직접 답변을 받으실 수 있습니다.