데이터 매니저 테이블에서 CellBindingSource를 만들 수 있습니다. 생성자는 spread.dataManager().addTable이 반환한 테이블 인스턴스나 테이블 이름을 받을 수 있습니다. 그런 다음 setBindingPath 메서드로 셀을 필드에 바인딩하고, 바인딩 원본을 워크시트 데이터 원본으로 설정합니다.
예제:
데이터 매니저 테이블에 여러 레코드가 있으면 activeRecordIndex를 사용하여 현재 워크시트에 바인딩된 레코드를 전환할 수 있습니다. 동일한 셀 바인딩을 재사용하면서 한 번에 하나의 레코드를 표시하는 상세 보기에 유용합니다.
이 샘플은 중첩 컬렉션을 셀 수준 바인딩과 함께 사용하는 방법도 보여 줍니다. 활성 직원 레코드는 바인딩된 셀에 표시되고, 테이블은 bindingPath를 사용하여 현재 레코드의 emergencyContacts 목록을 표시합니다.
var spreadNS = GC.Spread.Sheets;
var spreadInstance;
var bindingSource;
var activeRecordIndex = 0;
var data = [
{
"empId": "EMP-001",
"fullName": "Sarah Jenkins",
"department": "Engineering",
"location": "San Francisco, USA",
"email": "sarah.j@company.com",
"emergencyContacts": [
{ "name": "David Jenkins", "relationship": "Spouse", "phone": "+1 (555) 019-2834" },
{ "name": "Mary Jenkins", "relationship": "Mother", "phone": "+1 (555) 019-5821" }
]
},
{
"empId": "EMP-002",
"fullName": "Yuki Tanaka",
"department": "Product Design",
"location": "Tokyo, Japan",
"email": "yuki.t@company.com",
"emergencyContacts": [
{ "name": "Kenji Tanaka", "relationship": "Brother", "phone": "+81 90-1234-5678" },
{ "name": "Chiyo Tanaka", "relationship": "Mother", "phone": "+81 90-8765-4321" }
]
},
{
"empId": "EMP-003",
"fullName": "Carlos Silva",
"department": "Global Sales",
"location": "Sao Paulo, Brazil",
"email": "carlos.s@company.com",
"emergencyContacts": [
{ "name": "Ana Silva", "relationship": "Spouse", "phone": "+55 11 98765-4321" },
{ "name": "Roberto Silva", "relationship": "Father", "phone": "+55 11 91234-5678" }
]
},
{
"empId": "EMP-004",
"fullName": "Amara Diallo",
"department": "Human Resources",
"location": "London, UK",
"email": "amara.d@company.com",
"emergencyContacts": [
{ "name": "Ibrahim Diallo", "relationship": "Father", "phone": "+44 7700 900077" },
{ "name": "Fatoumata Diallo", "relationship": "Sister", "phone": "+44 7700 900088" }
]
},
{
"empId": "EMP-005",
"fullName": "Liam Nguyen",
"department": "Marketing",
"location": "Singapore",
"email": "liam.n@company.com",
"emergencyContacts": [
{ "name": "Linh Nguyen", "relationship": "Spouse", "phone": "+65 9123 4567" },
{ "name": "Minh Nguyen", "relationship": "Brother", "phone": "+65 8123 4567" }
]
},
{
"empId": "EMP-006",
"fullName": "Elena Rostova",
"department": "Finance",
"location": "Berlin, Germany",
"email": "elena.r@company.com",
"emergencyContacts": [
{ "name": "Dmitry Rostov", "relationship": "Spouse", "phone": "+49 151 2345678" },
{ "name": "Olga Rostova", "relationship": "Mother", "phone": "+49 160 8765432" }
]
},
{
"empId": "EMP-007",
"fullName": "David Chen",
"department": "Legal & Compliance",
"location": "Sydney, Australia",
"email": "david.c@company.com",
"emergencyContacts": [
{ "name": "Grace Chen", "relationship": "Sister", "phone": "+61 491 570 156" },
{ "name": "Peter Chen", "relationship": "Father", "phone": "+61 491 570 157" }
]
}
];
window.onload = function () {
spreadInstance = new spreadNS.Workbook(document.getElementById("ss"), { sheetCount: 1 });
initSpread(spreadInstance);
initRecordPanel(data);
};
function initSpread(spread) {
spread.suspendPaint();
var sheet = spread.sheets[0];
applyEmployeeDetailView(sheet);
var dataManagerTable = spread.dataManager().addTable("table1", { data: data });
bindingSource = new spreadNS.Bindings.CellBindingSource(dataManagerTable);
sheet.setDataSource(bindingSource);
bindingSource.activeRecordIndex(activeRecordIndex);
spread.resumePaint();
}
function initRecordPanel(records) {
var list = document.getElementById("recordList");
if (!list) {
return;
}
list.innerHTML = "";
records.forEach(function (record, index) {
var item = document.createElement("button");
item.type = "button";
item.className = "record-item";
item.setAttribute("data-index", index);
item.setAttribute("aria-pressed", index === activeRecordIndex ? "true" : "false");
item.innerHTML =
'<span class="record-item-index">레코드 ' + (index + 1) + '</span>' +
'<span class="record-item-name">' + escapeHtml(record.fullName) + '</span>' +
'<span class="record-item-meta">' + escapeHtml(record.empId + " - " + record.department) + '</span>' +
'<span class="record-item-submeta">' + escapeHtml(record.location) + '</span>';
item.addEventListener("click", function () {
updateRecordIndex(bindingSource, index);
});
list.appendChild(item);
});
syncActiveRecordPanel();
}
function syncActiveRecordPanel() {
var items = document.querySelectorAll(".record-item");
for (var i = 0; i < items.length; i++) {
var item = items[i];
var isActive = Number(item.getAttribute("data-index")) === activeRecordIndex;
item.classList.toggle("active", isActive);
item.setAttribute("aria-pressed", isActive ? "true" : "false");
}
var activeLabel = document.getElementById("activeRecordLabel");
if (activeLabel && data[activeRecordIndex]) {
activeLabel.textContent = data[activeRecordIndex].fullName + " (" + data[activeRecordIndex].empId + ")";
}
}
function applyEmployeeDetailView(sheet) {
var Sheets = GC.Spread.Sheets;
var HAlign = Sheets.HorizontalAlign;
var VAlign = Sheets.VerticalAlign;
var LineStyle = Sheets.LineStyle;
var SheetArea = Sheets.SheetArea;
var Style = Sheets.Style;
var colors = {
navy: "Accent 1 -25",
white: "Background 1 -5",
darkGray: "Text 1 15"
};
function setCell(row, col, value, style, bindingPath) {
var cell = sheet.getCell(row, col, SheetArea.viewport);
if (value !== undefined) {
cell.value(value);
}
if (!style) {
return cell;
}
if (style.font) {
cell.font(style.font);
}
if (style.foreColor) {
cell.foreColor(style.foreColor);
}
if (style.backColor) {
cell.backColor(style.backColor);
}
if (style.hAlign !== undefined) {
cell.hAlign(style.hAlign);
}
if (style.vAlign !== undefined) {
cell.vAlign(style.vAlign);
}
if (style.textDecoration !== undefined) {
cell.textDecoration(style.textDecoration);
}
if (style.wordWrap !== undefined) {
cell.wordWrap(style.wordWrap);
}
if (bindingPath) {
cell.bindingPath(bindingPath);
}
return cell;
}
function setBottomBorder(row, col, colCount, color, lineStyle) {
sheet.getRange(row, col, 1, colCount, SheetArea.viewport)
.setBorder(new Sheets.LineBorder(color, lineStyle), { bottom: true });
}
sheet.suspendPaint();
sheet.setRowCount(15);
sheet.setColumnCount(5);
sheet.setColumnWidth(0, 21);
sheet.setColumnWidth(1, 175);
sheet.setColumnWidth(2, 210);
sheet.setColumnWidth(3, 175);
sheet.setColumnWidth(4, 35);
sheet.setRowHeight(1, 53);
sheet.addSpan(1, 1, 1, 4);
setCell(1, 1, " Employee Profile & Emergency Contacts", {
font: "bold 21.3333px 'Segoe UI'",
foreColor: colors.white,
backColor: colors.navy,
hAlign: HAlign.left,
vAlign: VAlign.center
});
setCell(3, 1, "Employee ID:", {
font: "bold 13.3333px 'Segoe UI'",
foreColor: colors.darkGray,
hAlign: HAlign.left,
vAlign: VAlign.bottom
});
setCell(3, 2, undefined, {
font: "bold 14.6667px 'Segoe UI'",
foreColor: colors.navy,
hAlign: HAlign.center,
vAlign: VAlign.bottom
});
sheet.setBindingPath(3, 2, "empId");
setCell(5, 1, "PRIMARY INFORMATION", {
font: "bold 16px 'Segoe UI'",
foreColor: colors.navy,
hAlign: HAlign.left,
vAlign: VAlign.bottom
});
setBottomBorder(5, 1, 1, colors.navy, LineStyle.medium);
sheet.getRange(6, 1, 4, 1).setStyle(new Style({
font: "bold 13.3333px 'Segoe UI'",
foreColor: colors.darkGray,
hAlign: HAlign.right,
vAlign: VAlign.bottom
}));
sheet.setValue(6, 1, "이름:");
sheet.setValue(7, 1, "부서:");
sheet.setValue(8, 1, "근무지:");
sheet.setValue(9, 1, "이메일:");
sheet.getRange(6, 2, 4, 1).setStyle(new Style({
font: "13.3333px 'Segoe UI'",
hAlign: HAlign.left,
vAlign: VAlign.bottom
}));
sheet.setBindingPath(6, 2, "fullName");
sheet.setBindingPath(7, 2, "department");
sheet.setBindingPath(8, 2, "location");
sheet.setBindingPath(9, 2, "email");
setCell(11, 1, "EMERGENCY CONTACTS", {
font: "bold 16px 'Segoe UI'",
foreColor: colors.navy,
hAlign: HAlign.left,
vAlign: VAlign.bottom
});
var table = sheet.tables.add("emergencyContacts", 12, 1, 3, 3);
table.bindingPath("emergencyContacts");
sheet.resumePaint();
}
function updateRecordIndex(source, index) {
if (!source || index < 0 || index >= data.length) {
return;
}
activeRecordIndex = index;
source.activeRecordIndex(index);
syncActiveRecordPanel();
}
function escapeHtml(text) {
return String(text)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
<!doctype html>
<html style="height:100%;font-size:14px;">
<head>
<meta name="spreadjs culture" content="ko-kr"/>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="$DEMOROOT$/ko/purejs/node_modules/@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css">
<script src="$DEMOROOT$/ko/purejs/node_modules/@mescius/spread-sheets/dist/gc.spread.sheets.all.min.js" type="text/javascript"></script>
<script src="$DEMOROOT$/ko/purejs/node_modules/@mescius/spread-sheets-resources-ko/dist/gc.spread.sheets.resources.ko.min.js" type="text/javascript"></script>
<script src="$DEMOROOT$/spread/source/js/license.js" type="text/javascript"></script>
<script src="app.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<div class="sample-tutorial">
<div id="ss" class="sample-spreadsheets"></div>
<aside class="options-container">
<div class="panel-header">
<div class="panel-eyebrow">활성 레코드</div>
<div id="activeRecordLabel" class="panel-title">Sarah Jenkins (EMP-001)</div>
<div class="panel-description">
목록에서 레코드를 선택하여 스프레드시트가 사용하는 활성 데이터 바인딩 인덱스를 전환합니다.
</div>
</div>
<div id="recordList" class="record-list"></div>
</aside>
</div>
</body>
</html>
html,
body {
height: 100%;
margin: 0;
}
body {
position: absolute;
inset: 0;
font-family: "Segoe UI", sans-serif;
background: #eef2f6;
color: #213547;
}
.sample-tutorial {
display: flex;
height: 100%;
overflow: hidden;
}
.sample-spreadsheets {
flex: 1 1 auto;
min-width: 0;
height: 100%;
}
.options-container {
width: 320px;
padding: 20px 16px;
box-sizing: border-box;
background: linear-gradient(180deg, #f8fbff 0%, #f1f6fb 100%);
border-left: 1px solid #d8e2ee;
overflow: auto;
}
.panel-header {
margin-bottom: 16px;
}
.panel-eyebrow {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #6c8299;
margin-bottom: 8px;
}
.panel-title {
font-size: 20px;
line-height: 1.3;
font-weight: 700;
color: #18344f;
margin-bottom: 8px;
}
.panel-description {
font-size: 13px;
line-height: 1.5;
color: #5f748a;
}
.record-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.record-item {
width: 100%;
border: 1px solid #d6e2ee;
border-radius: 12px;
background: #ffffff;
padding: 14px 14px 13px;
text-align: left;
cursor: pointer;
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
box-shadow: 0 6px 16px rgba(26, 57, 88, 0.04);
}
.record-item:hover {
border-color: #8eb4d8;
box-shadow: 0 10px 24px rgba(26, 57, 88, 0.08);
transform: translateY(-1px);
}
.record-item.active {
border-color: #2d6ea3;
background: linear-gradient(180deg, #eef7ff 0%, #e5f0fb 100%);
box-shadow: 0 12px 28px rgba(45, 110, 163, 0.14);
}
.record-item-index,
.record-item-name,
.record-item-meta,
.record-item-submeta {
display: block;
}
.record-item-index {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #6d8195;
margin-bottom: 7px;
}
.record-item-name {
font-size: 16px;
font-weight: 700;
color: #16324a;
margin-bottom: 6px;
}
.record-item-meta {
font-size: 12px;
color: #31506f;
margin-bottom: 4px;
}
.record-item-submeta {
font-size: 12px;
color: #6a7f93;
}