FreeGridv1.15.1English

셀 병합

merge: true로 같은 값이 이어지는 셀을 한 칸으로 합치고, rowSpan으로 셀이 차지할 행 수를 정해 보고서처럼 보여 줍니다.

실행 예제셀 병합

같은 값이 이어지는 셀을 보고서처럼 한 칸으로 합칩니다.

이 데모의 코드
import { createGrid, type ColumnDef, type Row } from '@freegridjs/core';
import { makeRows } from '../data';
import { button, select, separator, text, toggle, tr, type DemoContext } from '../ui';

export default function mount(ctx: DemoContext) {
  const { lang } = ctx;
  const h = (en: string, ko: string) => tr(lang, { en, ko });
  // sorted by country, then company, so identical neighbours sit together
  const data: Row[] = makeRows(300).sort((a, b) => a.country.localeCompare(b.country) || a.company.localeCompare(b.company));
  let byCompany = true;
  let align: 'top' | 'middle' | 'bottom' = 'top';
  const columns = (): ColumnDef[] => [
    { field: 'country', header: h('Country', '국가'), width: 150, merge: true, mergeAlign: align },
    // companies merge only inside the same country
    { field: 'company', header: h('Company', '회사'), width: 150,
      merge: byCompany ? (a, b, rowA, rowB) => a === b && rowA.country === rowB.country : false, mergeAlign: align },
    { field: 'name', header: h('Name', '이름'), width: 170 },
    { field: 'q1', header: h('Q1 revenue', '1분기 매출'), type: 'number', format: '#,##0', width: 150 },
    { field: 'q2', header: h('Q2 revenue', '2분기 매출'), type: 'number', format: '#,##0', width: 150 },
  ];
  // sortWithinMerges: sorting by revenue keeps each country and company block in place
  const grid = createGrid(ctx.host, { columns: columns(), data, locale: lang, theme: 'simple', rowNumbers: true, sortWithinMerges: true, rowSelection: 'multiple' });

  text(ctx, h('Countries and companies with the same value one after another become one tall cell. Sort by a revenue column: rows move only inside their block.',
    '같은 국가·회사가 이어지면 한 칸으로 합쳐집니다. 매출 컬럼으로 정렬해 보세요. 행은 자기 블록 안에서만 움직입니다.'));
  separator(ctx);
  toggle(ctx, h('Merge companies too', '회사도 병합'), true, on => { byCompany = on; grid.setColumns(columns()); ctx.log(`company merge: ${on}`); });
  select(ctx, h('Text', '글자 위치'), [['top', h('Top', '위')], ['middle', h('Middle', '가운데')], ['bottom', h('Bottom', '아래')]], 'top',
    v => { align = v; grid.setColumns(columns()); ctx.log(`mergeAlign: ${v}`); });
  toggle(ctx, h('Sort within blocks', '병합 안에서만 정렬'), true, on => { grid.setSortWithinMerges(on); ctx.log(`sortWithinMerges: ${on}`); });
  separator(ctx);
  // mergeSelection: one checkbox per row, per country block (level 1) or per company block (level 2)
  select(ctx, h('Checkboxes', '체크박스'), [['rows', h('Every row', '모든 행')], ['1', h('Level 1 (country)', '1차(국가)')], ['2', h('Level 2 (company)', '2차(회사)')]], 'rows', v => {
    grid.setMergeSelection(v === 'rows' ? 'rows' : Number(v));
    ctx.log(`mergeSelection: ${v}`);
  });
  button(ctx, h('Show selected groups', '선택한 그룹 보기'), () => {
    const groups = grid.getSelectedGroups();
    if (!groups.length) {
      ctx.log(grid.getMergeSelection() === 'rows'
        ? h(`${grid.getSelectedRows().length} rows selected (pick level 1 or 2 to get groups)`, `${grid.getSelectedRows().length}행 선택됨(1차·2차를 고르면 그룹으로 나옵니다)`)
        : h('No group selected', '선택한 그룹이 없습니다'));
      return;
    }
    for (const g of groups) ctx.log(`${Object.values(g.values).join(' / ')}: ${g.rows.length}${h(' rows', '행')}`);
  });
  button(ctx, h('Groups with all rows', '그룹 전체 행 보기'), () => {
    const groups = grid.getSelectedGroupsAllRows();
    if (!groups.length) { ctx.log(h('No group selected (pick level 1 or 2)', '선택한 그룹이 없습니다(1차·2차를 고르세요)')); return; }
    for (const g of groups) ctx.log(`${Object.values(g.values).join(' / ')}: ${h('all', '전체')} ${g.rows.length}${h(' rows', '행')}`);
  });
  separator(ctx);
  // merged cells take the theme's colours, borders and fonts like any other cell
  select(ctx, h('Theme', '테마'), [
    ['simple', h('Simple', '심플형')],
    ['erp', h('ERP (Korean)', '관리자·ERP형')],
    ['light', 'Light'], ['dark', 'Dark'],
    ['board', h('Board (Korean)', '게시판형')],
    ['excel', h('Excel-like', '엑셀형')],
    ['classic', h('Classic navy', '진한 헤더형')],
  ], 'simple', v => { grid.setTheme(v); ctx.log(`theme: ${v}`); });
  return grid;
}

셀 병합은 같은 값이 이어지는 셀을 한 칸으로 합칩니다. 인쇄된 보고서에서 지역 이름을 그 지역의 행들 옆에 한 번만 적는 것과 같습니다. 컬럼에 merge: true를 주면 이어지는 행의 값이 같을 때 합쳐집니다. 같은 값이 붙어 있도록 먼저 그 컬럼으로 정렬해 두세요. 함수를 주면 직접 정할 수 있어서, 예를 들어 같은 지역 안에서만 팀을 합칠 수 있습니다.

rowSpan은 셀이 몇 행을 차지할지 데이터로 직접 정합니다. 다음 세 작업에 걸친 프로젝트 이름 같은 경우입니다. 한 컬럼에 둘 다 주면 rowSpan이 우선합니다. 병합은 정렬과 필터를 따라 다시 계산되고, 엑셀로 내보내도 병합된 셀로 들어갑니다.

createGrid(el, {
  columns: [
    // neighbours with the same value become one tall cell; sort by this column first
    { field: 'region', merge: true },
    // a function decides: merge teams only inside the same region
    { field: 'team', merge: (a, b, rowA, rowB) => a === b && rowA.region === rowB.region },
    // or say how many rows a cell covers; rowSpan wins over merge
    { field: 'project', rowSpan: ({ row }) => row.projectRows ?? 1 },
    { field: 'q1', type: 'number' },
  ],
  data: rowsSortedByRegion,
});

글자 위치는 mergeAlign('top'·'middle'·'bottom')으로 정합니다. 블록이 화면보다 길어도 스크롤하는 동안 보이는 부분 안에 글자가 머뭅니다. sortWithinMerges: true를 주면 정렬해도 블록은 제자리에 있고 블록 안의 행만 순서가 바뀝니다. 예를 들어 국가·회사로 병합해 두고 매출로 정렬하면 회사마다 매출 순으로 정렬됩니다.

블록 단위 체크박스

rowSelection을 켠 그리드에서는 mergeSelection으로 체크박스 위치를 정합니다. 'rows'(기본)는 행마다 체크박스를 둡니다. 숫자를 주면 merge가 있는 컬럼을 왼쪽부터 세어 그 컬럼의 블록마다 체크박스가 하나씩 생깁니다. 1이면 국가마다, 2면 국가 안의 회사마다 하나입니다. 체크박스는 병합된 셀처럼 블록 위에 하나만 그려지고, 누르면 블록의 행이 모두 선택됩니다. Shift를 누르고 클릭하면 사이의 블록까지 선택되고, 일부 행만 선택된 블록은 '-' 표시로 보입니다. 선택한 내용은 getSelectedGroups()로 블록별로 꺼낼 수 있습니다. 여기에는 블록에서 선택된 행만 들어 있고, 일부 행만 체크한 블록이라도 그 블록의 행 전체가 필요하면 getSelectedGroupsAllRows()를 쓰세요.

const grid = createGrid(el, {
  columns: [
    { field: 'country', merge: true },
    { field: 'company', merge: true },
    { field: 'name' },
  ],
  data,
  rowSelection: 'multiple',
  mergeSelection: 1, // 국가마다 체크박스 하나
});

grid.setMergeSelection(2); // 국가 안의 회사마다 하나

grid.getSelectedGroups();
// [{ level: 2, values: { country: 'KR', company: 'Acme' }, rows: [...] }, ...]
grid.getSelectedGroupsAllRows(); // 같은 블록, rows에는 블록의 모든 행
grid.getSelectedRows(); // 선택된 행 전체(블록 구분 없이)

병합은 브라우저에 있는 행에서만 동작합니다. 행을 그룹으로 묶었을 때와 서버 데이터(datasource)에서는 적용되지 않습니다. 행이 아니라 열을 합치려면 무료 기능인 colSpan을 쓰세요.

컬럼 옵션

이름타입설명
mergeboolean | (a, b, rowA, rowB) => boolean이어진 행의 값이 같으면(함수라면 true를 돌려주면) 한 칸으로 합칩니다.
rowSpan({ value, row, rowIndex }) => number이 셀이 차지할 행 수를 데이터로 정합니다. 같은 컬럼의 merge보다 우선합니다.
mergeAlign'top' | 'middle' | 'bottom'컬럼: 병합된 셀의 글자 위치(기본 'top'). 스크롤하는 동안에도 블록 중 화면에 보이는 부분 안에 머뭅니다.
sortWithinMergesboolean (option)그리드: 정렬해도 병합된 블록 안에서만 행 순서가 바뀌고 블록 순서는 그대로입니다. 정렬 기준인 병합 컬럼은 블록을 나누지 않습니다.
setSortWithinMerges(on) / getSortWithinMerges()void / booleansortWithinMerges를 켜고 끕니다. 바로 다시 정렬합니다.
mergeSelection'rows' | number (option)그리드(rowSelection 필요): 'rows'(기본)는 행마다 체크박스를 둡니다. 숫자 n이면 왼쪽에서 n번째 병합 컬럼의 블록마다 체크박스가 하나 생기고(1 = 첫 번째, 2 = 첫 번째와 두 번째로 나뉜 블록), 누르면 블록의 행이 모두 선택됩니다.
setMergeSelection(level) / getMergeSelection()void / 'rows' | number체크박스 위치를 바꿉니다. 이미 선택된 행은 그대로 선택돼 있습니다.
getSelectedGroupsAllRows(){ level, values, rows }[]getSelectedGroups()와 같지만, rows에 선택 여부와 관계없이 그 블록의 모든 행(필터를 통과한 행)이 들어 있습니다.
getSelectedGroups(){ level, values, rows }[]선택을 블록 단위로 돌려줍니다(화면 순서). values에는 블록의 병합 컬럼 값이 필드 이름으로, rows에는 블록에서 선택된 행이 들어 있습니다. 행마다 체크박스일 때는 []입니다.

이 페이지에 대한 질문

같은 값의 셀을 병합할 수 있나요?

네. 컬럼에 merge: true를 주면 같은 값이 이어지는 셀이 한 칸으로 합쳐지고, rowSpan으로 셀이 차지할 행 수를 직접 정할 수도 있습니다.