Hướng Dẫn Tích Hợp & Nhúng
Tài liệu hướng dẫn chi tiết dành cho nhà phát triển về cách nhúng, cấu hình, tùy biến giao diện và tích hợp nguồn cấp dữ liệu cho biểu đồ VNInvestCharts chuyên nghiệp.
Các Phương Thức Nhúng Widget
Widget hỗ trợ ba kiến trúc nhúng độc lập tùy thuộc vào yêu cầu kiểm soát CSS và bảo mật của ứng dụng ký chủ (Host App):
React Component
Nhúng trực tiếp component vào cây React DOM của ứng dụng. Chia sẻ bộ nhớ và Context của ứng dụng Host.
Shadow DOM Isolation
Bọc widget trong một Shadow Root độc lập. Cách ly hoàn toàn CSS của widget khỏi ứng dụng Host.
Iframe PostMessage SDK
Cách ly hoàn toàn cả mã nguồn Javascript lẫn CSS thông qua thẻ iframe bảo mật đa miền.
Nhúng React Component Tiêu Chuẩn
Phương pháp nhúng trực tiếp component vào cây thư mục của ứng dụng React.
Import Component và Styles
import { VNInvestCharts, widgetMessagesVi, widgetMessagesEn } from "vninvestcharts-widget";
// Import stylesheet hỗ trợ giao diện đầy đủ (CSS Modules)
import "vninvestcharts-widget/dist/index.css";Sử dụng trong Component
function ChartContainer() {
return (
<div style={{ width: "100%", height: "600px", position: "relative" }}>
<VNInvestCharts
symbol="VCB"
timeframe="1D"
theme="dark"
locale="vi"
messages={widgetMessagesVi}
drawingUiEnabled={true}
/>
</div>
);
}Nhúng Cách Ly Với Shadow DOM (Shadow DOM Isolation)
Để ngăn chặn việc CSS của biểu đồ làm vỡ layout của Host App (hoặc ngược lại), bạn có thể nhúng widget bên trong một Shadow Root.
Bản vá Cơ chế Click-Outside & Dropdown
Khi nằm trong Shadow DOM, các sự kiện click sẽ bị trình duyệt che giấu nguồn gốc (e.target trỏ vào chính shadow host). Để dropdown, ô tìm kiếm mã, và thanh công cụ vẽ hoạt động chính xác:
- Thư viện sử dụng e.composedPath()[0] thay vì e.target để lấy chính xác phần tử được nhấp chuột bên trong Shadow DOM.
- Cần dẫn truyền Shadow Root context xuống widget qua biến toàn cục window.__vnscShadowRoot hoặc thuộc tính shadowRoot.
Cấu hình Khởi tạo (IIFE Entry)
// Tạo phần tử chứa widget
const hostContainer = document.getElementById("chart-widget-host");
const shadowRoot = hostContainer.attachShadow({ mode: "open" });
// 1. Inject thẻ CSS trực tiếp vào bên trong Shadow Root
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = "vninvestcharts-widget/dist/index.css";
shadowRoot.appendChild(link);
// 2. Tạo điểm gắn kết React inside Shadow DOM
const mountPoint = document.createElement("div");
mountPoint.id = "vnsc-shadow-root-mount";
shadowRoot.appendChild(mountPoint);
// 3. Khai báo biến toàn cục để thông báo ranh giới Shadow Root
window.__vnscShadowRoot = shadowRoot;
// 4. Khởi tạo Widget
window.VNInvestCharts.mount(mountPoint, {
symbol: "VCB",
timeframe: "1D",
theme: "dark",
locale: "vi",
shadowRoot: shadowRoot // Cung cấp shadowRoot trực tiếp cho event listeners
});Nhúng Qua Iframe SDK (PostMessage Bridge)
Phương pháp này cách ly hoàn toàn mã nguồn của Widget khỏi Host App. Phù hợp khi bạn nhúng biểu đồ sang một tên miền khác.
Bước 1: Setup File iframe.html phía Widget Server
Tệp iframe.html sẽ tải mã đóng gói dist/iframe/vnbrokerchart.iframe.js và tự động lắng nghe các lệnh điều khiển:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>VNInvestCharts Iframe Node</title>
<link rel="stylesheet" href="injected.css">
<style>
html, body, #root { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; }
</style>
</head>
<body>
<div id="root"></div>
<script src="vnbrokerchart.iframe.js"></script>
</body>
</html>Bước 2: Sử dụng Host SDK bên phía Host App
Host App sử dụng class WidgetController để giao tiếp bất đồng bộ thông qua window.postMessage:
import { WidgetController } from "vninvestcharts-widget/iframe";
// 1. Tạo controller trỏ tới thẻ iframe
const iframeElement = document.getElementById("my-chart-iframe") as HTMLIFrameElement;
const controller = new WidgetController({
iframe: iframeElement,
targetOrigin: "https://chart.domain.com", // Bảo mật: Chỉ định rõ domain nguồn của iframe
allowAnyOrigin: false // Bắt buộc đặt false trên production để tránh tin tặc giả mạo lệnh
});
// 2. Lắng nghe các sự kiện gửi ra từ biểu đồ
controller.on("symbolChange", (data) => {
console.log("Người dùng đổi mã trên biểu đồ sang:", data.symbol);
});
controller.on("drawingsChange", (data) => {
console.log("Danh sách hình vẽ kỹ thuật vừa cập nhật:", data.drawings);
});
// 3. Gửi lệnh điều khiển vào biểu đồ (không cần re-render iframe)
async function updateChartState() {
// Đổi mã
await controller.setSymbol("FPT");
// Đổi khung thời gian
await controller.setTimeframe("15m");
// Áp dụng chỉ báo
await controller.addIndicator("StrengthRelative", { period: 14 });
}Tích Hợp Data Adapter & Cơ Chế Khóa Biên Lịch Sử (Boundary Lock)
Để nạp dữ liệu nến cho biểu đồ nhúng, bạn cần cung cấp một StockDataAdapter. Bản nâng cấp v3.0 cải tiến cơ chế đối chiếu lịch sử để loại bỏ hoàn toàn hiện tượng lặp yêu cầu mạng vô hạn.
Cơ Chế Hoạt Động Của Bộ Backfill
- Khi biểu đồ được kéo sang trái hoặc sang phải để zoom/pan, hook useWidgetDataBackfill sẽ tính toán xem khoảng thời gian hiển thị có bị thiếu nến không.
- Nếu thiếu nến, hệ thống sẽ phát tín hiệu tải thêm qua fetchBarsBefore (lùi) hoặc fetchBarsInRange (tiến).
Điểm Cần Lưu Ý Khi Viết Adapter
Khi API của bạn đã hết sạch dữ liệu lịch sử hoặc dữ liệu thời gian thực (đã chạm biên của mã giao dịch), Adapter bắt buộc phải trả về một mảng nến rỗng hoặc báo cáo trạng thái boundary-exhausted.
export const mySecureAdapter: StockDataAdapter = {
fetchBarsBefore: async (symbol, timeframe, beforeDate, limit) => {
const bars = await api.getBars({ symbol, timeframe, before: beforeDate.getTime(), limit });
// Dữ liệu trống -> khóa biên lịch sử (tương tự [] hoặc boundary-exhausted)
// Hệ thống sẽ khóa biên trái và không bao giờ gọi lại trước mốc thời gian đó nữa.
return bars;
},
fetchBarsInRange: async (symbol, timeframe, fromDate, toDate) => {
const bars = await api.getBars({ symbol, timeframe, from: fromDate.getTime(), to: toDate.getTime() });
// Nạp theo mốc, nếu không có nến mới hơn, hệ thống sẽ khóa biên phải tại nến cuối cùng
return bars;
}
};Xác Thực (Authentication) & Cấu Hình Giấy Phép (License Key)
Các tính năng nâng cấp bảo mật và giấy phép mới cho phiên bản phân phối thương mại:
Cấu hình PAT (Personal Access Token) cho Data Adapter
Với các adapter yêu cầu bảo mật thông tin như VNInvestDataAdapter, mã token của người dùng (PAT) không được mã hóa cứng mà được truyền động qua bộ xử lý xác thực:
const authHandler = {
getPAT: () => localStorage.getItem("vninvest_pat_token") ?? "",
setPAT: (token: string) => localStorage.setItem("vninvest_pat_token", token)
};
// Sử dụng trong React Render
<VNInvestCharts
adapter={vninvestDataAdapter}
authHandler={authHandler}
// ... các cấu hình khác
/>Xác thực Bản quyền Nhúng (License Key)
Widget cung cấp cơ chế kiểm tra bản quyền nhúng. Khi truyền prop licenseKey, widget sẽ hiển thị tab thông tin bản quyền trong phần Cài đặt và thực hiện xác thực chữ ký tên miền (Domain Signature Validation):
<VNInvestCharts
licenseKey="vninvest-lic-xxxx-yyyy-zzzz"
// ...
/>Giao diện tab "Giấy phép" (Licensing) sẽ trình bày chi tiết:
- Trạng thái bản quyền (Đang hoạt động / Không hợp lệ).
- Tên miền đăng ký được ủy quyền.
- Ngày hết hạn bản quyền.
Tài Liệu Props Chi Tiết Cho Bản Nâng Cấp v3.0
Liệt kê chi tiết các props đầu vào của biểu đồ <VNInvestCharts> để cấu hình theo nhu cầu. Sử dụng thanh tìm kiếm để định vị nhanh:
| Thuộc tính | Kiểu dữ liệu | Trạng thái | Ý nghĩa & Mô tả |
|---|---|---|---|
| symbol | string | Required | Mã chứng khoán hoặc mã coin cần hiển thị trên biểu đồ (ví dụ: FPT, HPG, BTCUSDT). |
| timeframe | string | Required | Khung thời gian mặc định của biểu đồ (ví dụ: 5m, 15m, 1h, 1d, 1w, 1M). |
| theme | "light" | "dark" | Default: dark | Chủ đề màu sắc hiển thị của giao diện biểu đồ. |
| locale | "vi" | "en" | Default: vi | Ngôn ngữ hiển thị của giao diện và các chú thích chỉ báo. |
| messages | Record<string, string> | Optional | Bản dịch từ điển ngôn ngữ tùy biến bổ sung để ghi đè giao diện. |
| drawingUiEnabled | boolean | Default: false | Bật/Tắt hiển thị thanh công cụ vẽ kỹ thuật bên trái. |
| showToolbar | boolean | Default: false | Hiển thị/Ẩn thanh công cụ vẽ bên trái. |
| showHeader | boolean | Default: false | Hiển thị/Ẩn thanh điều khiển phía trên (chọn mã, khung thời gian, chỉ báo). |
| showFooter | boolean | Default: false | Hiển thị/Ẩn thanh thông tin trạng thái phía dưới. |
| licenseKey | string | Optional | Chuỗi mã số giấy phép kích hoạt tính năng thương mại của widget. |
| authHandler | WidgetAuthHandler | Optional | Trình điều khiển lấy/lưu trữ mã xác thực cá nhân (PAT) của người dùng cuối. |
| shadowRoot | ShadowRoot | Optional | Ranh giới Shadow Root bọc ngoài. Bắt buộc truyền nếu nhúng biểu đồ trong chế độ Shadow DOM cô lập. |
| useWebGL | boolean | Default: false | Bật tăng tốc phần cứng thông qua WebGL cho các đường vẽ chỉ báo kỹ thuật nặng. |
| onError | (err: Error) => void | Optional | Tiếp nhận lỗi nạp dữ liệu (như 504 Timeout) để hiển thị thông báo thay vì gây crash giao diện. |