데이터 매니저에 바인딩

SpreadJS에서는 데이터 매니저 테이블 또는 테이블 이름으로 셀 바인딩 원본을 만들 수 있습니다. 이 샘플은 워크시트를 하나의 활성 레코드에 바인딩하고 activeRecordIndex를 업데이트하여 레코드를 전환하는 방법을 보여 줍니다.

데이터 매니저 테이블에서 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, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/"/g, "&quot;") .replace(/'/g, "&#39;"); }
<!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; }