테이블 시트는 텍스트, 숫자, 날짜 등의 조건을 포함하는 Excel과 유사한 필터를 지원할 수 있습니다.
대용량의 데이터가 있을 때 필터 대화 상자를 여는 성능을 개선하기 위해 테이블 시트는 특정 필드 필터 인덱스 캐시를 만들 수 있는 옵션을 제공합니다.
경우에 따라 체크리스트 또는 필터 대화 상자의 일부분이 사용자에게 필요하지 않을 수 있으므로, 테이블 시트는 테이블 시트 필터 대화 상자의 각 부분의 가시성을 제어하기 위해 열에 여러 가지 옵션을 제공합니다.
allowSort, allowFilterByValue, allowFilterByList가 모두 false인 경우에는 열 헤더의 필터 버튼이 보이지 않습니다.
API
tableSheet.filter(filterInfos?)로 활성 필터 정의를 가져오거나 설정하고, tableSheet.removeFilter()로 모든 활성 필터를 지웁니다.
filterInfos를 전달하면 이전 필터 정의를 지우고 새 정의를 한 번에 적용합니다. 인수 없이 tableSheet.filter()를 호출하면 활성 필터 정의의 복제본을 반환하므로, 반환된 배열을 변경한 뒤 tableSheet.filter(filterInfos)에 다시 전달해야 테이블 시트가 업데이트됩니다.
필터 정의는 field를 식별하고 다음 형태 중 하나를 사용합니다.
값 필터는 values로 필드의 하나 이상의 값을 일치시킵니다.
직렬화 및 역직렬화 과정에서 유지해야 하는 날짜 값에는 type: "date"를 사용합니다. 날짜 값은 문자열, 숫자 또는 Date 인스턴스로 제공할 수 있습니다. null 또는 undefined 값에는 type: "blank"를 사용하며, 다른 값은 기본 "value" 유형을 사용합니다.
계층 필터는 표시할 정확한 계층 경로를 paths로 지정합니다.
조건 필터는 복잡한 규칙에 GC.Spread.Sheets.ConditionalFormatting.Condition을 사용합니다. 여러 조건을 결합하려면 관계 조건을 사용합니다.
생성된 열에서는 데이터 뷰에서 열 정보를 읽고 열 정보의 value를 필드 이름으로 사용합니다.
명령
명령 관리자를 통해 테이블 시트 필터 명령을 사용할 수 있으며 각 명령은 실행 취소와 다시 실행을 지원합니다.
TableSheetFilterColumn은 한 열을 필터링합니다. 옵션은 sheetName, col 및 선택적 values, condition, paths입니다. 필터 옵션을 둘 이상 지정하면 values > condition > paths 순서로 적용됩니다.
TableSheetRemoveFilterColumn은 한 열의 필터를 제거하며 sheetName과 col 옵션을 사용합니다.
이벤트
필터 대화 상자나 관련 테이블 시트 필터 명령으로 필터를 적용하거나 지울 때 다음 이벤트가 발생합니다.
TableSheetFiltering: 열을 필터링하기 전에 발생하며 args.cancel = true로 취소할 수 있습니다. 페이로드는 sheetName, col, values, condition, paths, cancel입니다.
TableSheetFiltered: 열 필터링 후 발생합니다. 페이로드는 sheetName, col, values, condition, paths입니다.
TableSheetFilterClearing: 열 필터를 지우기 전에 발생하며 취소할 수 있습니다. 페이로드는 sheetName, col, cancel입니다.
TableSheetFilterCleared: 열 필터를 지운 후 발생합니다. 페이로드는 sheetName, col입니다.
/*REPLACE_MARKER*/
/*DO NOT DELETE THESE COMMENTS*/
var tableName = "Employee";
var baseApiUrl = getBaseApiUrl();
var apiUrl = baseApiUrl + "/" + tableName;
var sheet, spread;
var budgetDepartmentField = '=CONCAT([@department]," (L",LEVEL(),"-",LEVELROWNUMBER(),")")';
var filterFieldCaptions = {
id: 'ID', firstName: '이름', lastName: '성', birth: '생일',
state: '주', dept: '부서 번호', title: '직책', salary: '급여',
Course: '과목', Term: '학기', Credit: '학점', Score: '점수', Teacher: '교사',
department: '부서', budget: '예산', location: '위치', phone: '전화', country: '국가'
};
var filterFields = ['id', 'firstName', 'lastName', 'birth', 'state', 'dept', 'title', 'salary'];
var conditionCompareTypes = {
numberCondition: [
{ value: 'equalsTo', text: '같음' },
{ value: 'notEqualsTo', text: '같지 않음' },
{ value: 'greaterThan', text: '보다 큼' },
{ value: 'greaterThanOrEqualsTo', text: '보다 크거나 같음' },
{ value: 'lessThan', text: '보다 작음' },
{ value: 'lessThanOrEqualsTo', text: '보다 작거나 같음' }
],
textCondition: [
{ value: 'equalsTo', text: '같음' },
{ value: 'notEqualsTo', text: '같지 않음' },
{ value: 'beginsWith', text: '다음으로 시작' },
{ value: 'doesNotBeginWith', text: '다음으로 시작하지 않음' },
{ value: 'endsWith', text: '다음으로 끝남' },
{ value: 'doesNotEndWith', text: '다음으로 끝나지 않음' },
{ value: 'contains', text: '포함' },
{ value: 'doesNotContain', text: '포함하지 않음' }
],
dateCondition: [
{ value: 'equalsTo', text: '같음' },
{ value: 'notEqualsTo', text: '같지 않음' },
{ value: 'before', text: '이전' },
{ value: 'beforeEqualsTo', text: '이전 또는 같음' },
{ value: 'after', text: '이후' },
{ value: 'afterEqualsTo', text: '이후 또는 같음' }
],
top10Condition: [
{ value: 'top', text: '상위' },
{ value: 'bottom', text: '하위' }
],
averageCondition: [
{ value: 'above', text: '초과' },
{ value: 'below', text: '미만' }
]
};
window.onload = function () {
spread = new GC.Spread.Sheets.Workbook(document.getElementById("ss"), { sheetCount: 0 });
//register self-defined row action command
initSpread();
bindEvents();
};
function initSpread() {
spread = GC.Spread.Sheets.findControl(document.getElementById("ss"));
spread.suspendPaint();
//1. init a sheet
spread.clearSheets();
spread.clearSheetTabs();
sheet = spread.addSheetTab(0, "TableSheet1", GC.Spread.Sheets.SheetType.tableSheet);
var data = generateData(+getElementById("dataRows").value);
var timeBeforeCreate = new Date();
var dataManager = spread.dataManager();
var employeeTable = dataManager.addTable("employeeTable", {
data: data.employees,
schema: {
columns: {
id: {
indexed: getProperty("createIdIndexes", 'checked')
},
birth: {
dataType: "date",
indexed: getProperty("createBirthIndexes", 'checked')
}
}
}
});
var departmentTable = dataManager.addTable("departmentTable", {
data: data.departments
});
dataManager.addRelationship(employeeTable, "dept", "department", departmentTable, "dept_no", "employees");
dataManager.addRelationship(departmentTable, "leader_id", "manager", employeeTable, "id", "a");
spread.resumePaint();
var numericStyle = new GC.Spread.Sheets.Style();
numericStyle.formatter = "$ #,##0.00";
var formatStringStyle = new GC.Spread.Sheets.Style();
formatStringStyle.formatter = 'yyyy-mm-dd';
var visibleInfo_id = {};
if (!getProperty("sortByValue_id", 'checked')) {
visibleInfo_id.sortByValue = false;
}
if (!getProperty("filterByValue_id", 'checked')) {
visibleInfo_id.filterByValue = false;
}
if (!getProperty("listFilterArea_id", 'checked')) {
visibleInfo_id.listFilterArea = false;
}
var visibleInfo_birthday = {};
if (!getProperty("sortByValue_birthday", 'checked')) {
visibleInfo_birthday.sortByValue = false;
}
if (!getProperty("filterByValue_birthday", 'checked')) {
visibleInfo_birthday.filterByValue = false;
}
if (!getProperty("listFilterArea_birthday", 'checked')) {
visibleInfo_birthday.listFilterArea = false;
}
var cols = [
{ value: 'id', caption: 'ID', allowSort: visibleInfo_id.sortByValue, allowFilterByValue: visibleInfo_id.filterByValue, allowFilterByList: visibleInfo_id.listFilterArea},
{ value: 'firstName', caption: '이름', width: 100},
{ value: 'lastName', caption: '성', width: 100},
{ value: 'birth', caption: '생일', width: 100, style: formatStringStyle, allowSort: visibleInfo_birthday.sortByValue, allowFilterByValue: visibleInfo_birthday.filterByValue, allowFilterByList: visibleInfo_birthday.listFilterArea},
{ value: 'state', caption: '주', width: 100},
{ value: 'dept', caption: '부서 번호', width: 130},
{ value: 'title', caption: '직책', width: 120},
{ value: 'salary', caption: '급여', style: numericStyle, width: 100},
];
var employeeView = employeeTable.addView("employeeView", cols, undefined);
employeeView.fetch().then(function (args) {
sheet.suspendPaint();
sheet.setDataView(employeeView);
sheet.resumePaint();
var timeGap = new Date() - timeBeforeCreate;
getElementById('showEventArgs').value = ("데이터 가져오기 및 표시 - " + (timeGap) + " ms");
var filterApiStart = new Date();
applyInitialEmployeeFilters(sheet);
getElementById('showEventArgs').value = getElementById('showEventArgs').value + "\r\n필터 - " + (new Date() - filterApiStart) + " ms";
});
initMultiFilterSample(spread);
}
function initMultiFilterSample (spread) {
spread.suspendPaint();
var dataManager = spread.dataManager();
var table = dataManager.addTable("CourseTable", {
data: [
{ Course: "Calculus", Term: 1, Credit: 5, Score: 80, Teacher: "Nancy Feehafer" },
{ Course: "P.E.", Term: 1, Credit: 3.5, Score: 85, Teacher: "Andrew Cencini" },
{ Course: "Political Economics", Term: 1, Credit: 3.5, Score: 95, Teacher: "Jan Kotas" },
{ Course: "Basic of Computer", Term: 1, Credit: 2, Score: 85, Teacher: "Steven Thorpe" },
{ Course: "Micro-Economics", Term: 1, Credit: 4, Score: 62, Teacher: "Jan Kotas" },
{ Course: "Linear Algebra", Term: 2, Credit: 5, Score: 73, Teacher: "Nancy Feehafer" },
{ Course: "Accounting", Term: 2, Credit: 3.5, Score: 86, Teacher: "Nancy Feehafer" },
{ Course: "Statistics", Term: 2, Credit: 5, Score: 85, Teacher: "Robert Zare" },
{ Course: "Marketing", Term: 2, Credit: 4, Score: 70, Teacher: "Laura Giussani" }
],
schema: {
type: 'json'
}
});
var sheet = spread.addSheetTab(1, "Course", GC.Spread.Sheets.SheetType.tableSheet);
table.fetch().then(function () {
var myView = table.addView("CourseTable", [
{ value: "Course", caption: "과목", width: 130 },
{ value: "Term", caption: "학기", width: 100 },
{ value: "Credit", caption: "학점", width: 100 },
{ value: "Score", caption: "점수", width: 100 },
{ value: "Teacher", caption: "교사", width: 120 },
]);
spread.suspendPaint();
sheet.setDataView(myView);
spread.resumePaint();
});
initMultiFilterHierarchySample(spread, dataManager);
spread.resumePaint();
}
function initMultiFilterHierarchySample(spread, dataManager) {
var table = dataManager.addTable("Table", {
remote: {
read: {
url: getBaseApiUrl() + "/Hierarchy_Formula"
}
},
schema: {
hierarchy: {
type: 'Parent',
column: 'parent',
summaryFields: {
'budget':'=SUM(CHILDREN(1,"budget"))'
}
},
columns: {
id: {
isPrimaryKey: true,
},
},
}
});
var sheet = spread.addSheetTab(2, "Budget", GC.Spread.Sheets.SheetType.tableSheet);
sheet.options.allowAddNew = false;
table.fetch().then(function () {
var myView = table.addView("myView", [
{ value: budgetDepartmentField, caption: '부서', width: 265, outlineColumn: true },
{ value: "budget", width: 100, caption: '예산' },
{ value: '=IF(LEVEL()=0,"",[@budget]/PARENT(1,"budget"))', width: 120, caption: '백분율', style: { formatter: '0.00%' } },
{ value: "location", width: 100, caption: '위치' },
{ value: "phone", width: 150, caption: '전화' },
{ value: "country", width: 100, caption: '국가' },
]);
spread.suspendPaint();
sheet.setDataView(myView);
applyInitialBudgetFilters(sheet);
spread.resumePaint();
});
}
function createNumberCondition(compareType, expected) {
var conditionalFormatting = GC.Spread.Sheets.ConditionalFormatting;
return new conditionalFormatting.Condition(conditionalFormatting.ConditionType.numberCondition, {
compareType: compareType,
expected: expected
});
}
function createSalaryRangeCondition() {
var conditionalFormatting = GC.Spread.Sheets.ConditionalFormatting;
return new conditionalFormatting.Condition(conditionalFormatting.ConditionType.relationCondition, {
compareType: conditionalFormatting.LogicalOperators.and,
item1: createNumberCondition(conditionalFormatting.GeneralComparisonOperators.greaterThan, 10000),
item2: createNumberCondition(conditionalFormatting.GeneralComparisonOperators.lessThan, 19500)
});
}
function applyInitialEmployeeFilters(tableSheet) {
tableSheet.filter([
{
field: "state",
values: [{ value: "New York" }, { value: "Texas" }]
},
{
field: "salary",
condition: createSalaryRangeCondition()
}
]);
}
function applyInitialBudgetFilters(tableSheet) {
tableSheet.filter([
{
field: budgetDepartmentField,
paths: [{ path: ["Corporate Headquarters (L0-1)", "Sales and Marketing (L1-1)", "Field Office: East Coast (L2-2)"] }]
},
{
field: "location",
paths: [{ path: ["Monterey", "San Francisco", "Boston"] }]
}
]);
}
function randomFromList(list) {
return list[~~(Math.random() * list.length)];
}
function generateData(itemCount) {
var data = {employees:[], departments: departments};
var states = ["Texas", "New York", "Florida", "Washington", "Ohio"];
var department_id = ["D001", "D002", "D003", "D004", "D005", "D006", "D007", "D008", "D009"];
var title = ["Senior Engineer", "Staff", "Engineer", "Senior Staff", "Assistant Engineer", "Technique Leader", "Manager"];
for (var i = 0; i < itemCount; i++) {
var date = new Date(parseInt(Math.random() * 12052666) * 24 * 3600); //The timestamp
date.setHours(0,0,0,0);
var item = {
id: i + 1,
firstName: randomFromList(firstNames),
lastName: randomFromList(lastNames),
birth: date,
state: randomFromList(states),
dept: i < 9 ? department_id[i] : randomFromList(department_id),
title: i < 9 ? "Manager" : randomFromList(title),
salary: 3000 + parseInt(Math.random() * 100) * 500,
};
data.employees.push(item);
}
return data;
}
function bindEvents() {
var showButton = document.getElementById('setDataSource');
showButton.addEventListener('click', function () {
initSpread();
});
var sortStart;
spread.bind(GC.Spread.Sheets.Events.TableSheetSorting, function(e,args) {
sortStart = new Date();
});
spread.bind(GC.Spread.Sheets.Events.TableSheetSorted, function(e,args) {
getElementById('showEventArgs').value = (getElementById('showEventArgs').value + "\r\n정렬 - " + (new Date()-sortStart) + " ms");
});
var sortClearStart;
spread.bind(GC.Spread.Sheets.Events.TableSheetSortClearing, function(e,args) {
sortClearStart = new Date();
});
spread.bind(GC.Spread.Sheets.Events.TableSheetSortCleared, function(e,args) {
var tableSheet = args.sheet || spread.getActiveSheetTab();
var sortInfos = tableSheet && tableSheet.sort ? tableSheet.sort() : [];
var label = sortInfos.length > 1 ? "지우기 및 정렬" : "정렬 지우기";
getElementById('showEventArgs').value = (getElementById('showEventArgs').value + "\r\n" + label + " - " + (new Date()-sortClearStart) + " ms");
});
var filterStart;
spread.bind(GC.Spread.Sheets.Events.TableSheetFiltering, function(e,args) {
filterStart = new Date();
});
spread.bind(GC.Spread.Sheets.Events.TableSheetFiltered, function(e,args) {
getElementById('showEventArgs').value = (getElementById('showEventArgs').value + "\r\n필터 - " + (new Date()-filterStart) + " ms");
if (document.getElementById('filterapi-tab').classList.contains('active')) {
handleReadFilters();
}
});
var filterClearStart;
spread.bind(GC.Spread.Sheets.Events.TableSheetFilterClearing, function(e,args) {
filterClearStart = new Date();
});
spread.bind(GC.Spread.Sheets.Events.TableSheetFilterCleared, function(e,args) {
var tableSheet = args.sheet || spread.getActiveSheetTab();
var filterInfos = tableSheet && tableSheet.filter ? tableSheet.filter() : [];
var label = filterInfos.length > 1 ? "지우기 및 필터" : "필터 지우기";
getElementById('showEventArgs').value = (getElementById('showEventArgs').value + "\r\n" + label + " - " + (new Date()-filterClearStart) + " ms");
if (document.getElementById('filterapi-tab').classList.contains('active')) {
handleReadFilters();
}
});
// Filter API tab events
getElementById('addFilterEntry').addEventListener('click', function () {
addFilterEntryRow(getFirstUnusedFilterField(), 'values');
});
getElementById('applyFilters').addEventListener('click', handleApplyFilters);
getElementById('clearFilters').addEventListener('click', handleClearFilters);
// Re-read filters when switching spreadsheet tabs
spread.bind(GC.Spread.Sheets.Events.ActiveSheetChanged, function () {
if (document.getElementById('filterapi-tab').classList.contains('active')) {
handleReadFilters();
}
});
// Initialize first tab
document.querySelector('.options-tab-link').click();
}
function setTooltip(options, tooltip) {
options.tooltip = tooltip;
return options;
}
// --- Tab switching ---
function openOptionsTab(event, tabId) {
var i, tabcontent, tablinks;
tabcontent = document.getElementsByClassName("tab-content");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
tabcontent[i].classList.remove("active");
}
tablinks = document.getElementsByClassName("options-tab-link");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
document.getElementById(tabId).style.display = "block";
document.getElementById(tabId).classList.add("active");
event.currentTarget.className += " active";
if (tabId === 'filterapi-tab') {
handleReadFilters();
}
}
// --- Filter entry management ---
function getUsedFilterFields(excludeEntry) {
var rows = getElementById('filterEntries').getElementsByClassName('filter-entry-row');
var usedFields = {};
for (var i = 0; i < rows.length; i++) {
if (excludeEntry && rows[i] === excludeEntry) {
continue;
}
var fieldSelect = rows[i].querySelector('.filter-field');
if (fieldSelect && fieldSelect.value) {
usedFields[fieldSelect.value] = true;
}
}
return usedFields;
}
function getFirstUnusedFilterField() {
var sheetFields = getFilterFieldsFromSheet();
var usedFields = getUsedFilterFields();
for (var i = 0; i < sheetFields.fields.length; i++) {
if (!usedFields[sheetFields.fields[i]]) {
return sheetFields.fields[i];
}
}
return null;
}
function getAvailableFilterFields(entry) {
var fieldSelect = entry.querySelector('.filter-field');
var currentField = fieldSelect ? fieldSelect.value : null;
var sheetFields = getFilterFieldsFromSheet();
var usedFields = getUsedFilterFields(entry);
var availableFields = [];
var hasCurrentField = false;
for (var i = 0; i < sheetFields.fields.length; i++) {
var field = sheetFields.fields[i];
if (field === currentField) {
hasCurrentField = true;
}
if (field === currentField || !usedFields[field]) {
availableFields.push(field);
}
}
if (currentField && !hasCurrentField) {
availableFields.unshift(currentField);
}
return { fields: availableFields, captions: sheetFields.captions };
}
function updateAddFilterEntryButton() {
getElementById('addFilterEntry').disabled = !getFirstUnusedFilterField();
}
function updateFilterFieldOptions() {
var rows = getElementById('filterEntries').getElementsByClassName('filter-entry-row');
for (var i = 0; i < rows.length; i++) {
var entry = rows[i];
var fieldSelect = entry.querySelector('.filter-field');
var currentField = fieldSelect.value;
var availableFields = getAvailableFilterFields(entry);
fieldSelect.innerHTML = '';
for (var j = 0; j < availableFields.fields.length; j++) {
var field = availableFields.fields[j];
var opt = document.createElement('option');
opt.value = field;
opt.textContent = availableFields.captions[field] || field;
if (field === currentField) {
opt.selected = true;
}
fieldSelect.appendChild(opt);
}
}
updateAddFilterEntryButton();
}
function getUniqueFilterInfos(filterInfos) {
var usedFields = {};
var uniqueFilterInfos = [];
for (var i = 0; i < filterInfos.length; i++) {
var field = filterInfos[i].field;
if (field && !usedFields[field]) {
usedFields[field] = true;
uniqueFilterInfos.push(filterInfos[i]);
}
}
return uniqueFilterInfos;
}
function addFilterEntryRow(field, filterType, configData) {
field = field || getFirstUnusedFilterField();
if (!field) {
updateAddFilterEntryButton();
return;
}
var container = getElementById('filterEntries');
var entry = document.createElement('div');
entry.className = 'filter-entry-row';
var header = document.createElement('div');
header.className = 'filter-entry-header';
var fieldSelect = document.createElement('select');
fieldSelect.className = 'filter-field';
var sheetFields = getFilterFieldsFromSheet();
var usedFields = getUsedFilterFields();
for (var i = 0; i < sheetFields.fields.length; i++) {
if (sheetFields.fields[i] !== field && usedFields[sheetFields.fields[i]]) {
continue;
}
var opt = document.createElement('option');
opt.value = sheetFields.fields[i];
opt.textContent = sheetFields.captions[sheetFields.fields[i]] || sheetFields.fields[i];
if (sheetFields.fields[i] === field) opt.selected = true;
fieldSelect.appendChild(opt);
}
fieldSelect.addEventListener('change', updateFilterFieldOptions);
var typeSelect = document.createElement('select');
typeSelect.className = 'filter-type';
var types = [
{ value: 'values', text: '값' },
{ value: 'condition', text: '조건' }
];
if (currentSheetHasHierarchy() || filterType === 'paths') {
types.push({ value: 'paths', text: '경로' });
}
for (var j = 0; j < types.length; j++) {
var tOpt = document.createElement('option');
tOpt.value = types[j].value;
tOpt.textContent = types[j].text;
if (types[j].value === (filterType || 'values')) tOpt.selected = true;
typeSelect.appendChild(tOpt);
}
var removeBtn = document.createElement('button');
removeBtn.className = 'filter-entry-remove';
removeBtn.type = 'button';
removeBtn.textContent = 'X';
removeBtn.title = '필터 정보 제거';
removeBtn.onclick = function () {
entry.remove();
updateFilterFieldOptions();
};
var row1 = document.createElement('div');
row1.className = 'filter-header-row';
row1.appendChild(createLabel('필드:'));
row1.appendChild(fieldSelect);
row1.appendChild(removeBtn);
var row2 = document.createElement('div');
row2.className = 'filter-header-row';
row2.appendChild(createLabel('필터 유형:'));
row2.appendChild(typeSelect);
var spacer = document.createElement('span');
spacer.className = 'filter-header-spacer';
row2.appendChild(spacer);
header.appendChild(row1);
header.appendChild(row2);
var configPanel = document.createElement('div');
configPanel.className = 'filter-config-panel';
var valuesPanel = document.createElement('div');
valuesPanel.className = 'filter-values-panel';
buildValuesPanel(valuesPanel, configData && configData.values);
var conditionPanel = document.createElement('div');
conditionPanel.className = 'filter-condition-panel';
buildConditionPanel(conditionPanel, configData && configData.condition);
var pathsPanel = document.createElement('div');
pathsPanel.className = 'filter-paths-panel';
buildPathsPanel(pathsPanel, configData && configData.paths);
configPanel.appendChild(valuesPanel);
configPanel.appendChild(conditionPanel);
configPanel.appendChild(pathsPanel);
entry.appendChild(header);
entry.appendChild(configPanel);
container.appendChild(entry);
showFilterConfigPanel(configPanel, typeSelect.value);
typeSelect.addEventListener('change', function () {
showFilterConfigPanel(configPanel, typeSelect.value);
});
updateFilterFieldOptions();
}
function showFilterConfigPanel(configPanel, type) {
var panels = configPanel.children;
for (var i = 0; i < panels.length; i++) {
panels[i].classList.remove('active');
}
if (type === 'values') {
panels[0].classList.add('active');
} else if (type === 'condition') {
panels[1].classList.add('active');
} else if (type === 'paths') {
panels[2].classList.add('active');
}
}
function clearFilterEntries() {
getElementById('filterEntries').innerHTML = '';
updateFilterFieldOptions();
}
function detectFilterType(filterInfo) {
if (filterInfo.values) return 'values';
if (filterInfo.condition) return 'condition';
if (filterInfo.paths) return 'paths';
return 'values';
}
// --- Values panel ---
function buildValuesPanel(panel, valuesData) {
var sectionLabel = document.createElement('div');
sectionLabel.className = 'filter-section-label';
sectionLabel.textContent = '값으로 필터링:';
panel.appendChild(sectionLabel);
var entries = document.createElement('div');
entries.className = 'filter-value-entries';
panel.appendChild(entries);
var addBtn = document.createElement('button');
addBtn.type = 'button';
addBtn.className = 'filter-value-add';
addBtn.textContent = '+ 값 추가';
addBtn.onclick = function () {
addFilterValueRow(entries, '', 'value');
};
panel.appendChild(addBtn);
if (valuesData && valuesData.length > 0) {
for (var i = 0; i < valuesData.length; i++) {
var v = valuesData[i];
var valStr = (v.value instanceof Date) ? formatDate(v.value) : String(v.value != null ? v.value : '');
addFilterValueRow(entries, valStr, v.type || 'value');
}
} else {
addFilterValueRow(entries, '', 'value');
}
}
function addFilterValueRow(container, value, type) {
var row = document.createElement('div');
row.className = 'filter-value-row';
var fieldDiv = document.createElement('div');
fieldDiv.className = 'filter-value-field';
var input = document.createElement('input');
input.type = 'text';
input.className = 'filter-value-input';
input.value = value;
input.placeholder = '필터 값...';
var removeBtn = document.createElement('button');
removeBtn.type = 'button';
removeBtn.className = 'filter-value-remove';
removeBtn.textContent = 'X';
removeBtn.title = '값 제거';
removeBtn.onclick = function () { row.remove(); };
fieldDiv.appendChild(createLabel('값:'));
fieldDiv.appendChild(input);
fieldDiv.appendChild(removeBtn);
var metaDiv = document.createElement('div');
metaDiv.className = 'filter-value-meta';
var typeSelect = document.createElement('select');
typeSelect.className = 'filter-value-type';
var typeOptions = [
{ value: 'value', text: '값' },
{ value: 'date', text: '날짜' },
{ value: 'blank', text: '공백' }
];
for (var i = 0; i < typeOptions.length; i++) {
var opt = document.createElement('option');
opt.value = typeOptions[i].value;
opt.textContent = typeOptions[i].text;
if (typeOptions[i].value === type) opt.selected = true;
typeSelect.appendChild(opt);
}
metaDiv.appendChild(createLabel('유형:'));
metaDiv.appendChild(typeSelect);
var valueSpacer = document.createElement('span');
valueSpacer.className = 'filter-meta-spacer';
metaDiv.appendChild(valueSpacer);
row.appendChild(fieldDiv);
row.appendChild(metaDiv);
container.appendChild(row);
}
// --- Condition panel ---
var _conditionPanelId = 0;
function buildConditionPanel(panel, conditionData) {
var sectionLabel = document.createElement('div');
sectionLabel.className = 'filter-section-label';
sectionLabel.textContent = '조건으로 필터링:';
panel.appendChild(sectionLabel);
var isRelation = false;
var group1Data = null, group2Data = null;
var relationType = 'and';
if (conditionData) {
var cf = GC.Spread.Sheets.ConditionalFormatting;
var conType = conditionData.conType ? conditionData.conType() : undefined;
if (conType === cf.ConditionType.relationCondition) {
isRelation = true;
group1Data = conditionData.item1();
group2Data = conditionData.item2();
relationType = (conditionData.compareType() === cf.LogicalOperators.or) ? 'or' : 'and';
} else {
group1Data = conditionData;
}
}
var group1 = createConditionGroup('1', group1Data);
panel.appendChild(group1);
var panelId = ++_conditionPanelId;
var radioName = 'conditionRelation_' + panelId;
var relationRow = document.createElement('div');
relationRow.className = 'condition-relation';
var andRadio = document.createElement('input');
andRadio.type = 'radio';
andRadio.name = radioName;
andRadio.value = 'and';
andRadio.checked = (relationType === 'and');
var andLabel = document.createElement('label');
andLabel.textContent = 'AND';
var orRadio = document.createElement('input');
orRadio.type = 'radio';
orRadio.value = 'or';
orRadio.name = radioName;
orRadio.checked = (relationType === 'or');
var orLabel = document.createElement('label');
orLabel.textContent = 'OR';
relationRow.appendChild(andRadio);
relationRow.appendChild(andLabel);
relationRow.appendChild(orRadio);
relationRow.appendChild(orLabel);
panel.appendChild(relationRow);
var group2 = createConditionGroup('2', group2Data);
group2.style.display = isRelation ? 'block' : 'none';
panel.appendChild(group2);
var addGroupBtn = document.createElement('button');
addGroupBtn.type = 'button';
addGroupBtn.className = 'condition-add-group';
addGroupBtn.textContent = '+ 조건 추가';
addGroupBtn.style.display = isRelation ? 'none' : 'inline-block';
addGroupBtn.onclick = function () {
group2.style.display = 'block';
relationRow.style.display = 'flex';
addGroupBtn.style.display = 'none';
};
panel.appendChild(addGroupBtn);
if (!isRelation) {
relationRow.style.display = 'none';
}
}
function createConditionGroup(groupNum, conditionData) {
var group = document.createElement('div');
group.className = 'condition-group';
group.setAttribute('data-group', groupNum);
var groupLabel = document.createElement('div');
groupLabel.className = 'condition-group-label';
groupLabel.textContent = '조건 ' + groupNum + ':';
group.appendChild(groupLabel);
var initialType = 'numberCondition';
var initialCompare = 'equalsTo';
var initialValue = '';
var isPercent = false;
if (conditionData) {
var cf = GC.Spread.Sheets.ConditionalFormatting;
var conType = conditionData.conType ? conditionData.conType() : undefined;
if (conType === cf.ConditionType.numberCondition) {
initialType = 'numberCondition';
initialCompare = enumKeyFromValue(cf.GeneralComparisonOperators, conditionData.compareType());
initialValue = conditionData.expected() != null ? String(conditionData.expected()) : '';
} else if (conType === cf.ConditionType.textCondition) {
initialType = 'textCondition';
initialCompare = enumKeyFromValue(cf.TextCompareType, conditionData.compareType());
initialValue = conditionData.expected() != null ? String(conditionData.expected()) : '';
} else if (conType === cf.ConditionType.dateCondition) {
initialType = 'dateCondition';
initialCompare = enumKeyFromValue(cf.DateCompareType, conditionData.compareType());
var exp = conditionData.expected();
initialValue = exp != null ? (exp instanceof Date ? formatDate(exp) : String(exp)) : '';
} else if (conType === cf.ConditionType.top10Condition) {
initialType = 'top10Condition';
initialCompare = conditionData.type() === 0 ? 'top' : 'bottom';
initialValue = conditionData.expected() != null ? String(conditionData.expected()) : '';
isPercent = !!conditionData.isPercent();
} else if (conType === cf.ConditionType.averageCondition) {
initialType = 'averageCondition';
initialCompare = conditionData.compareType() === 0 ? 'above' : 'below';
}
}
var typeSelect = document.createElement('select');
typeSelect.className = 'condition-type';
var condTypes = ['numberCondition', 'textCondition', 'dateCondition', 'top10Condition', 'averageCondition'];
for (var i = 0; i < condTypes.length; i++) {
var opt = document.createElement('option');
opt.value = condTypes[i];
opt.textContent = {
numberCondition: '숫자', textCondition: '텍스트', dateCondition: '날짜',
top10Condition: '상위/하위 10', averageCondition: '평균'
}[condTypes[i]];
if (condTypes[i] === initialType) opt.selected = true;
typeSelect.appendChild(opt);
}
var compareSelect = document.createElement('select');
compareSelect.className = 'condition-compare';
populateCompareTypes(compareSelect, initialType, initialCompare);
var valueInput = document.createElement('input');
valueInput.type = 'text';
valueInput.className = 'condition-value';
valueInput.value = initialValue;
// Type row
var typeRow = document.createElement('div');
typeRow.className = 'condition-field-row';
typeRow.appendChild(createLabel('유형:'));
typeRow.appendChild(typeSelect);
group.appendChild(typeRow);
// Compare row
var compareRow = document.createElement('div');
compareRow.className = 'condition-field-row';
compareRow.appendChild(createLabel('비교:'));
compareRow.appendChild(compareSelect);
group.appendChild(compareRow);
// Value row
var valueRow = document.createElement('div');
valueRow.className = 'condition-field-row';
valueRow.appendChild(createLabel('값:'));
valueRow.appendChild(valueInput);
group.appendChild(valueRow);
var top10Row = document.createElement('div');
top10Row.className = 'top10-options' + (initialType === 'top10Condition' ? ' active' : '');
var percentCheck = document.createElement('input');
percentCheck.type = 'checkbox';
percentCheck.className = 'condition-is-percent';
percentCheck.checked = isPercent;
var percentLabel = document.createElement('label');
percentLabel.textContent = '백분율';
top10Row.appendChild(percentCheck);
top10Row.appendChild(percentLabel);
group.appendChild(top10Row);
if (groupNum === '2') {
var removeGroupBtn = document.createElement('button');
removeGroupBtn.type = 'button';
removeGroupBtn.className = 'condition-remove-group';
removeGroupBtn.textContent = '- 조건 제거';
removeGroupBtn.onclick = function () {
group.style.display = 'none';
valueInput.value = '';
var addBtn = group.parentElement.querySelector('.condition-add-group');
if (addBtn) addBtn.style.display = 'inline-block';
};
group.appendChild(removeGroupBtn);
}
typeSelect.addEventListener('change', function () {
populateCompareTypes(compareSelect, typeSelect.value, null);
top10Row.classList.toggle('active', typeSelect.value === 'top10Condition');
});
return group;
}
function populateCompareTypes(selectEl, conditionType, selectedValue) {
selectEl.innerHTML = '';
var items = conditionCompareTypes[conditionType] || [];
for (var i = 0; i < items.length; i++) {
var opt = document.createElement('option');
opt.value = items[i].value;
opt.textContent = items[i].text;
if (items[i].value === selectedValue) opt.selected = true;
selectEl.appendChild(opt);
}
}
function enumKeyFromValue(enumObj, value) {
for (var key in enumObj) {
if (enumObj[key] === value) return key;
}
var keys = Object.keys(enumObj);
return keys.length > 0 ? keys[0] : 'equalsTo';
}
// --- Paths panel ---
function buildPathsPanel(panel, pathsData) {
var sectionLabel = document.createElement('div');
sectionLabel.className = 'filter-section-label';
sectionLabel.textContent = '경로로 필터링(쉼표로 구분):';
panel.appendChild(sectionLabel);
var entries = document.createElement('div');
entries.className = 'filter-path-entries';
panel.appendChild(entries);
var addBtn = document.createElement('button');
addBtn.type = 'button';
addBtn.className = 'filter-path-add';
addBtn.textContent = '+ 경로 추가';
addBtn.onclick = function () {
addFilterPathRow(entries, '', 'value');
};
panel.appendChild(addBtn);
if (pathsData && pathsData.length > 0) {
for (var i = 0; i < pathsData.length; i++) {
var p = pathsData[i];
var pathStr = p.path ? p.path.join(', ') : '';
addFilterPathRow(entries, pathStr, p.type || 'value');
}
} else {
addFilterPathRow(entries, '', 'value');
}
}
function addFilterPathRow(container, pathValue, type) {
var row = document.createElement('div');
row.className = 'filter-path-row';
var fieldDiv = document.createElement('div');
fieldDiv.className = 'filter-path-field';
var input = document.createElement('input');
input.type = 'text';
input.className = 'filter-path-input';
input.value = pathValue;
input.placeholder = '세그먼트1, 세그먼트2, ...';
var removeBtn = document.createElement('button');
removeBtn.type = 'button';
removeBtn.className = 'filter-path-remove';
removeBtn.textContent = 'X';
removeBtn.title = '경로 제거';
removeBtn.onclick = function () { row.remove(); };
fieldDiv.appendChild(createLabel('경로:'));
fieldDiv.appendChild(input);
fieldDiv.appendChild(removeBtn);
var metaDiv = document.createElement('div');
metaDiv.className = 'filter-path-meta';
var typeSelect = document.createElement('select');
typeSelect.className = 'filter-path-type';
var typeOptions = [
{ value: 'value', text: '값' },
{ value: 'date', text: '날짜' },
{ value: 'blank', text: '공백' }
];
for (var i = 0; i < typeOptions.length; i++) {
var opt = document.createElement('option');
opt.value = typeOptions[i].value;
opt.textContent = typeOptions[i].text;
if (typeOptions[i].value === type) opt.selected = true;
typeSelect.appendChild(opt);
}
metaDiv.appendChild(createLabel('유형:'));
metaDiv.appendChild(typeSelect);
var pathSpacer = document.createElement('span');
pathSpacer.className = 'filter-meta-spacer';
metaDiv.appendChild(pathSpacer);
row.appendChild(fieldDiv);
row.appendChild(metaDiv);
container.appendChild(row);
}
// --- Build filter infos from UI ---
function getFilterEntriesFromUI() {
var rows = getElementById('filterEntries').getElementsByClassName('filter-entry-row');
var filterInfos = [];
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
var field = row.querySelector('.filter-field').value;
var type = row.querySelector('.filter-type').value;
var info = { field: field };
if (type === 'values') {
var valueRows = row.querySelectorAll('.filter-value-row');
var values = [];
for (var v = 0; v < valueRows.length; v++) {
var valInput = valueRows[v].querySelector('.filter-value-input').value.trim();
var valType = valueRows[v].querySelector('.filter-value-type').value;
if (valType === 'blank') {
values.push({ value: null, type: 'blank' });
} else if (valInput !== '') {
if (valType === 'date') {
values.push({ value: new Date(valInput), type: 'date' });
} else if (!isNaN(valInput)) {
values.push({ value: parseFloat(valInput), type: valType });
} else {
values.push({ value: valInput, type: valType });
}
}
}
if (values.length > 0) {
info.values = values;
filterInfos.push(info);
}
} else if (type === 'condition') {
var cond = buildConditionFromPanel(row.querySelector('.filter-condition-panel'));
if (cond) {
info.condition = cond;
filterInfos.push(info);
}
} else if (type === 'paths') {
var pathRows = row.querySelectorAll('.filter-path-row');
var paths = [];
for (var p = 0; p < pathRows.length; p++) {
var pathInput = pathRows[p].querySelector('.filter-path-input').value.trim();
var pathType = pathRows[p].querySelector('.filter-path-type').value;
if (pathInput !== '') {
var pathSegments = pathInput.split(',');
var trimmed = [];
for (var s = 0; s < pathSegments.length; s++) {
var seg = pathSegments[s].trim();
if (seg !== '') trimmed.push(seg);
}
if (trimmed.length > 0) {
var pathObj = { path: trimmed };
if (pathType !== 'value') {
pathObj.type = pathType;
}
paths.push(pathObj);
}
}
}
if (paths.length > 0) {
info.paths = paths;
filterInfos.push(info);
}
}
}
return filterInfos;
}
// --- Build Condition objects ---
function buildConditionFromPanel(panel) {
var groups = panel.querySelectorAll('.condition-group');
var group1 = groups[0];
var group2 = groups.length > 1 ? groups[1] : null;
var cond1 = buildSingleCondition(group1);
if (!cond1) return null;
if (group2 && group2.style.display !== 'none') {
var cond2 = buildSingleCondition(group2);
if (cond2) {
var cf = GC.Spread.Sheets.ConditionalFormatting;
var relationRadios = panel.querySelectorAll('.condition-relation input[type=radio]');
var isOr = false;
for (var r = 0; r < relationRadios.length; r++) {
if (relationRadios[r].checked && relationRadios[r].value === 'or') {
isOr = true;
}
}
return new cf.Condition(cf.ConditionType.relationCondition, {
compareType: isOr ? cf.LogicalOperators.or : cf.LogicalOperators.and,
item1: cond1,
item2: cond2
});
}
}
return cond1;
}
function buildSingleCondition(group) {
var typeSelect = group.querySelector('.condition-type');
var compareSelect = group.querySelector('.condition-compare');
var valueInput = group.querySelector('.condition-value');
var isPercentCheck = group.querySelector('.condition-is-percent');
var condTypeStr = typeSelect.value;
var compareStr = compareSelect.value;
var valueStr = valueInput.value.trim();
var isPercent = isPercentCheck ? isPercentCheck.checked : false;
var cf = GC.Spread.Sheets.ConditionalFormatting;
var expected = valueStr;
if (condTypeStr !== 'averageCondition' && valueStr !== '' && !isNaN(valueStr)) {
expected = parseFloat(valueStr);
}
switch (condTypeStr) {
case 'numberCondition':
return new cf.Condition(cf.ConditionType.numberCondition, {
compareType: cf.GeneralComparisonOperators[compareStr],
expected: expected
});
case 'textCondition':
return new cf.Condition(cf.ConditionType.textCondition, {
compareType: cf.TextCompareType[compareStr],
expected: expected
});
case 'dateCondition':
var dateVal = expected instanceof Date ? expected : new Date(expected);
return new cf.Condition(cf.ConditionType.dateCondition, {
compareType: cf.DateCompareType[compareStr],
expected: dateVal
});
case 'top10Condition':
return new cf.Condition(cf.ConditionType.top10Condition, {
type: compareStr === 'top' ? 0 : 1,
expected: expected,
isPercent: isPercent
});
case 'averageCondition':
return new cf.Condition(cf.ConditionType.averageCondition, {
compareType: compareStr === 'above' ? 0 : 1
});
default:
return null;
}
}
// --- API handlers ---
function getActiveTableSheet() {
var activeSheet = spread.getActiveSheetTab();
if (activeSheet && activeSheet.filter) {
return activeSheet;
}
return sheet;
}
function currentSheetHasHierarchy() {
var activeSheet = getActiveTableSheet();
if (!activeSheet) return false;
try {
var view = activeSheet.getDataView();
if (!view) return false;
var table = typeof view.getTable === 'function' ? view.getTable() : null;
if (table) {
var schema = table.schema || (table.options && table.options.schema);
if (schema && schema.hierarchy) return true;
}
} catch (e) {}
return false;
}
function getFilterFieldsFromSheet() {
var activeSheet = getActiveTableSheet();
if (!activeSheet) return { fields: filterFields, captions: filterFieldCaptions };
try {
var view = activeSheet.getDataView();
if (!view) return { fields: filterFields, captions: filterFieldCaptions };
var cols = view.getColumn();
if (cols && cols.length) {
var fields = [];
var captions = {};
for (var i = 0; i < cols.length; i++) {
if (cols[i] && cols[i].value) {
fields.push(cols[i].value);
captions[cols[i].value] = filterFieldCaptions[cols[i].value] || cols[i].caption || cols[i].value;
}
}
if (fields.length > 0) return { fields: fields, captions: captions };
}
} catch (e) {}
return { fields: filterFields, captions: filterFieldCaptions };
}
function handleApplyFilters() {
var filterInfos = getFilterEntriesFromUI();
if (filterInfos.length === 0) return;
var activeSheet = getActiveTableSheet();
if (activeSheet && activeSheet.filter) {
activeSheet.filter(filterInfos);
}
}
function handleReadFilters() {
var activeSheet = getActiveTableSheet();
if (activeSheet && activeSheet.filter) {
var filterInfos = activeSheet.filter();
if (filterInfos && filterInfos.length > 0) {
populateFilterEntries(filterInfos);
} else {
clearFilterEntries();
}
}
}
function handleClearFilters() {
var activeSheet = getActiveTableSheet();
if (activeSheet && activeSheet.removeFilter) {
activeSheet.removeFilter();
}
clearFilterEntries();
}
function populateFilterEntries(filterInfos) {
clearFilterEntries();
var uniqueFilterInfos = getUniqueFilterInfos(filterInfos);
for (var i = 0; i < uniqueFilterInfos.length; i++) {
var info = uniqueFilterInfos[i];
var type = detectFilterType(info);
var configData = {};
if (type === 'values') configData.values = info.values;
else if (type === 'condition') configData.condition = info.condition;
else if (type === 'paths') configData.paths = info.paths;
addFilterEntryRow(info.field, type, configData);
}
updateFilterFieldOptions();
}
// --- Utility ---
function createLabel(text) {
var label = document.createElement('label');
label.textContent = text;
return label;
}
function formatDate(date) {
if (!(date instanceof Date)) return String(date);
var y = date.getFullYear();
var m = ('0' + (date.getMonth() + 1)).slice(-2);
var d = ('0' + date.getDate()).slice(-2);
return y + '-' + m + '-' + d;
}
function getProperty(domId, prop) {
return getElementById(domId)[prop];
}
function getElementById (domId) {
return document.getElementById(domId);
}
function setProperty(domId, prop, value) {
getElementById(domId)[prop] = value;
}
function getBaseApiUrl() {
return window.location.href.match(/http.+spreadjs\/learn-spreadjs\//)[0] + 'server/api';
}
<!doctype html>
<html style="height:100%;font-size:14px;">
<head>
<meta charset="utf-8" />
<meta name="spreadjs culture" content="ko-kr" />
<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">
<!-- Promise Polyfill for IE, https://www.npmjs.com/package/promise-polyfill -->
<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script>
<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-tablesheet/dist/gc.spread.sheets.tablesheet.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/data/departments.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>
<div id="options-container" class="options-container">
<div class="options-tabs">
<button class="options-tab-link active" onclick="openOptionsTab(event, 'options-tab')">옵션</button>
<button class="options-tab-link" onclick="openOptionsTab(event, 'filterapi-tab')">필터 API</button>
</div>
<div class="options-tab-panels">
<div id="options-tab" class="tab-content active">
<fieldset>
<legend>필터 인덱스 만들기</legend>
<input type="checkbox" id="createIdIndexes" checked/>
<label for="createIdIndexes">ID 인덱스 만들기</label>
<br>
<input type="checkbox" id="createBirthIndexes" checked/>
<label for="createBirthIndexes">생일 인덱스 만들기</label>
<br>
</fieldset>
<fieldset>
<legend>ID 열 필터 대화 상자 옵션</legend>
<input type="checkbox" id="sortByValue_id" checked/>
<label for="sortByValue_id">정렬 허용</label>
<br>
<input type="checkbox" id="filterByValue_id" checked/>
<label for="filterByValue_id">값으로 필터링 허용</label>
<br>
<input type="checkbox" id="listFilterArea_id" checked/>
<label for="listFilterArea_id">목록으로 필터링 허용</label>
<br>
</fieldset>
<fieldset>
<legend>생일 열 필터 대화 상자 옵션</legend>
<input type="checkbox" id="sortByValue_birthday" checked/>
<label for="sortByValue_birthday">정렬 허용</label>
<br>
<input type="checkbox" id="filterByValue_birthday" checked/>
<label for="filterByValue_birthday">값으로 필터링 허용</label>
<br>
<input type="checkbox" id="listFilterArea_birthday" checked/>
<label for="listFilterArea_birthday">목록으로 필터링 허용</label>
<br>
</fieldset>
<fieldset class="data-source-controls">
<legend>데이터 원본</legend>
<label for="dataRows">행 수: </label>
<select id="dataRows">
<option value="1000" selected="selected">1000</option>
<option value="3000">3000</option>
<option value="10000">10000</option>
<option value="30000">30000</option>
<option value="100000">100000</option>
<option value="300000">300000</option>
<option value="1000000">1000000</option>
</select>
<input type="button" id="setDataSource" value="데이터 원본 설정"/>
</fieldset>
<fieldset style="height: 130px;">
<legend>성능</legend>
<textarea id="showEventArgs" style="width: 224px;height: 105px;" cols="64" rows="15"></textarea>
</fieldset>
</div>
<div id="filterapi-tab" class="tab-content">
<fieldset>
<legend>필터 정보</legend>
<div class="filter-actions-row">
<input type="button" id="addFilterEntry" value="+ 필터 정보 추가"/>
</div>
<div id="filterEntries"></div>
<div class="filter-actions-row">
<input type="button" id="applyFilters" value="필터 적용"/>
<input type="button" id="clearFilters" value="모든 필터 지우기"/>
</div>
</fieldset>
</div>
</div>
</div>
</div>
</html>
body {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
fieldset {
padding: 6px;
margin: 0;
margin-top: 10px;
}
.sample-tutorial {
position: relative;
height: 100%;
overflow: hidden;
}
.sample-spreadsheets {
width: calc(100% - 280px);
height: 100%;
overflow: hidden;
float: left;
}
.options-container {
float: right;
width: 280px;
padding: 12px;
height: 100%;
box-sizing: border-box;
background: #fbfbfb;
overflow: auto;
}
fieldset span,
fieldset input,
fieldset select {
display: inline-block;
text-align: left;
}
fieldset span {
width: 50px;
}
fieldset input[type=text] {
width: calc(100% - 58px);
}
fieldset input[type=button] {
width: 100%;
text-align: center;
}
.data-source-controls label,
.data-source-controls select,
.data-source-controls input[type=button] {
display: block;
width: 100%;
box-sizing: border-box;
}
.data-source-controls select,
.data-source-controls input[type=button] {
margin-top: 4px;
}
fieldset select {
width: calc(100% - 50px);
}
.field-line {
margin-top: 4px;
}
/* Tab styles */
.options-tabs {
display: flex;
border-bottom: 1px solid #ccc;
margin-bottom: 8px;
}
.options-tab-link {
padding: 6px 12px;
background-color: #f1f1f1;
border: none;
cursor: pointer;
font-size: 13px;
flex: 1;
text-align: center;
transition: background-color 0.3s;
}
.options-tab-link:hover {
background-color: #ddd;
}
.options-tab-link.active {
background-color: #ccc;
font-weight: bold;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
/* Filter entry rows */
#filterEntries {
max-height: 400px;
overflow-y: auto;
margin-bottom: 4px;
}
.filter-actions-row {
display: flex;
justify-content: flex-end;
gap: 6px;
margin-bottom: 4px;
}
.filter-actions-row input[type=button] {
width: auto;
padding: 2px 10px;
font-size: 13px;
}
/* Filter entry card */
.filter-entry-row {
border: 1px solid #ddd;
border-radius: 3px;
padding: 8px;
margin-bottom: 10px;
background: #fff;
}
/* Header - two rows stacked vertically */
.filter-entry-header {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 6px;
}
.filter-header-row {
display: flex;
align-items: center;
gap: 4px;
}
.filter-header-row label {
font-size: 12px;
color: #444;
white-space: nowrap;
min-width: 75px;
}
.filter-header-spacer {
display: inline-block;
width: 22px;
}
.filter-meta-spacer {
display: inline-block;
width: 18px;
}
.filter-header-row select {
flex: 1;
font-size: 12px;
padding: 2px;
min-width: 0;
}
.filter-entry-remove {
width: 22px;
height: 22px;
padding: 0;
font-size: 12px;
line-height: 22px;
text-align: center;
border: none;
background: transparent;
cursor: pointer;
color: #999;
}
/* Config panels */
.filter-config-panel {
margin-top: 6px;
border-top: 1px solid #eee;
padding-top: 6px;
}
.filter-values-panel,
.filter-condition-panel,
.filter-paths-panel {
display: none;
}
.filter-values-panel.active,
.filter-condition-panel.active,
.filter-paths-panel.active {
display: block;
}
.filter-section-label {
font-size: 11px;
color: #555;
margin-bottom: 6px;
font-weight: bold;
}
/* Value rows - two-line layout */
.filter-value-row {
margin-bottom: 6px;
padding: 4px;
background: #fafafa;
border-radius: 2px;
border: 1px solid #f0f0f0;
}
.filter-value-field {
display: flex;
align-items: center;
gap: 4px;
margin-bottom: 3px;
}
.filter-value-field input[type=text] {
flex: 1;
font-size: 12px;
padding: 2px 4px;
min-width: 0;
}
.filter-value-meta {
display: flex;
align-items: center;
gap: 4px;
}
.filter-value-meta select {
flex: 1;
font-size: 12px;
padding: 1px 2px;
min-width: 0;
}
.filter-value-remove {
width: 18px;
height: 18px;
padding: 0;
font-size: 11px;
line-height: 18px;
text-align: center;
border: none;
background: transparent;
cursor: pointer;
color: #999;
}
/* Condition groups - vertical field layout */
.condition-group {
border: 1px solid #e0e0e0;
padding: 6px;
margin-bottom: 6px;
border-radius: 3px;
background: #fafafa;
}
.condition-group-label {
font-size: 11px;
color: #555;
margin-bottom: 4px;
font-weight: bold;
}
.condition-field-row {
display: flex;
align-items: center;
gap: 4px;
margin-bottom: 4px;
}
.condition-field-row label {
font-size: 11px;
color: #555;
min-width: 55px;
white-space: nowrap;
}
.condition-field-row select,
.condition-field-row input[type=text] {
flex: 1;
font-size: 12px;
padding: 2px 4px;
min-width: 0;
box-sizing: border-box;
}
.top10-options {
display: none;
align-items: center;
gap: 4px;
margin-left: 59px;
margin-bottom: 2px;
}
.top10-options.active {
display: flex;
}
.top10-options label {
font-size: 11px;
color: #555;
}
.condition-relation {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 6px;
font-size: 12px;
}
/* Path rows - two-line layout */
.filter-path-row {
margin-bottom: 6px;
padding: 4px;
background: #fafafa;
border-radius: 2px;
border: 1px solid #f0f0f0;
}
.filter-path-field {
display: flex;
align-items: center;
gap: 4px;
margin-bottom: 3px;
}
.filter-path-field input[type=text] {
flex: 1;
font-size: 12px;
padding: 2px 4px;
min-width: 0;
}
.filter-path-meta {
display: flex;
align-items: center;
gap: 4px;
}
.filter-path-meta select {
flex: 1;
font-size: 12px;
padding: 1px 2px;
min-width: 0;
}
.filter-path-remove {
width: 18px;
height: 18px;
padding: 0;
font-size: 11px;
line-height: 18px;
text-align: center;
border: none;
background: transparent;
cursor: pointer;
color: #999;
}
/* Small buttons */
.filter-value-add,
.filter-path-add,
.condition-add-group,
.condition-remove-group {
font-size: 12px;
padding: 2px 8px;
border: 1px solid #ccc;
background: #f9f9f9;
cursor: pointer;
margin-top: 2px;
}
/* Consistent heights for all filter selects and inputs */
.filter-header-row select,
.condition-field-row select,
.condition-field-row input[type=text],
.filter-value-field input[type=text],
.filter-value-meta select,
.filter-path-field input[type=text],
.filter-path-meta select {
height: 24px;
box-sizing: border-box;
}
/* Labels in value/path field rows */
.filter-value-field label,
.filter-value-meta label,
.filter-path-field label,
.filter-path-meta label {
font-size: 12px;
color: #444;
white-space: nowrap;
min-width: 40px;
}