Skip to content

Commit

Permalink
feat: export to xls for compatibility mode (#1983)
Browse files Browse the repository at this point in the history
* test: add tests for exporting to xls

* feat: export to xls

* docs: update documents

* feat: create exports store

* feat: export xls via compatibility option

* fix: revert context menu items

* test: fix tests

* docs: update document

* chore: fix type errors
  • Loading branch information
jajugoguma authored Nov 16, 2023
1 parent f48c914 commit 2e341d2
Show file tree
Hide file tree
Showing 13 changed files with 243 additions and 152 deletions.
89 changes: 57 additions & 32 deletions packages/toast-ui.grid/cypress/integration/export.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,34 @@ import { cls } from '@/helper/dom';
import { RowKey } from '@t/store/data';
import { SinonStub } from 'cypress/types/sinon';
import { invokeFilter } from '../helper/util';
import { ExportFormat } from '@t/store/export';

before(() => {
cy.visit('/dist');
});

const DATA = [
{ name: 'Sugar', artist: 'Maroon5', price: 1000, typeCode: '2' },
{ name: 'Get Lucky', artist: 'Daft Punk', price: 2000, typeCode: '3' },
{ name: '21', artist: 'Adele', price: 3000, typeCode: '1' },
];
const COLUMNS = [
{
header: 'Name',
name: 'name',
filter: { type: 'text', showApplyBtn: true, showClearBtn: true },
},
{
header: 'Artist',
name: 'artist',
},
{
header: 'Price',
name: 'price',
hidden: true,
},
];

function showContextMenu(rowKey: RowKey, columnName: string) {
cy.getCell(rowKey, columnName).trigger('contextmenu');
}
Expand All @@ -16,7 +39,7 @@ function getMenuItemByText(text: string) {
return cy.contains(`.${cls('context-menu')} .menu-item`, text);
}

function clickExportMenuItemByFormat(format: 'txt' | 'csv' | 'xlsx') {
function clickExportMenuItemByFormat(format: ExportFormat) {
let text;

switch (format) {
Expand All @@ -36,32 +59,10 @@ function clickExportMenuItemByFormat(format: 'txt' | 'csv' | 'xlsx') {
}

describe('Export data', () => {
const data = [
{ name: 'Sugar', artist: 'Maroon5', price: 1000, typeCode: '2' },
{ name: 'Get Lucky', artist: 'Daft Punk', price: 2000, typeCode: '3' },
{ name: '21', artist: 'Adele', price: 3000, typeCode: '1' },
];
const columns = [
{
header: 'Name',
name: 'name',
filter: { type: 'text', showApplyBtn: true, showClearBtn: true },
},
{
header: 'Artist',
name: 'artist',
},
{
header: 'Price',
name: 'price',
hidden: true,
},
];

let callback: SinonStub;

beforeEach(() => {
cy.createGrid({ data, columns });
cy.createGrid({ data: DATA, columns: COLUMNS });
callback = cy.stub().callsFake((ev: GridEvent) => ev.stop());
cy.gridInstance().invoke('on', 'beforeExport', callback);
});
Expand All @@ -73,7 +74,7 @@ describe('Export data', () => {
describe('Default options', () => {
['txt', 'csv', 'xlsx'].forEach((format) => {
it(`should export data according to default export options to '${format}'`, () => {
clickExportMenuItemByFormat(format as 'txt' | 'csv' | 'xlsx');
clickExportMenuItemByFormat(format as ExportFormat);

cy.wrap(callback).should('be.calledWithMatch', {
data: [
Expand Down Expand Up @@ -157,10 +158,34 @@ describe('Export data', () => {
],
});
});

it(`should export data with complex column headers to 'xls'`, () => {
cy.gridInstance().invoke('setHeader', {
complexColumns: [
{
header: 'Basic',
name: 'complexColumn',
childNames: ['name', 'artist'],
},
],
});

cy.gridInstance().invoke('export', 'xls');

cy.wrap(callback).should('be.calledWithMatch', {
data: [
['Basic', 'Basic'],
['Name', 'Artist'],
['Sugar', 'Maroon5'],
['Get Lucky', 'Daft Punk'],
['21', 'Adele'],
],
});
});
});

describe('Without column headers (includeHeader = false)', () => {
['txt', 'csv', 'xlsx'].forEach((format) => {
['txt', 'csv', 'xlsx', 'xls'].forEach((format) => {
it(`should export data without column headers to '${format}'`, () => {
cy.gridInstance().invoke('export', format, { includeHeader: false });

Expand All @@ -176,7 +201,7 @@ describe('Export data', () => {
});

describe('With hidden columns (includeHiddenColumns = true)', () => {
['txt', 'csv', 'xlsx'].forEach((format) => {
['txt', 'csv', 'xlsx', 'xls'].forEach((format) => {
it(`should export data with hidden column to '${format}'`, () => {
cy.gridInstance().invoke('export', format, { includeHiddenColumns: true });

Expand All @@ -193,7 +218,7 @@ describe('Export data', () => {
});

describe('With column names (columnNames = [...selectedColumnNames])', () => {
['txt', 'csv', 'xlsx'].forEach((format) => {
['txt', 'csv', 'xlsx', 'xls'].forEach((format) => {
it(`should export data with selected columns to '${format}'`, () => {
cy.gridInstance().invoke('export', format, { columnNames: ['name', 'price'] });

Expand All @@ -210,7 +235,7 @@ describe('Export data', () => {
});

describe('With only filtered (onlyFiltered = true)', () => {
['txt', 'csv', 'xlsx'].forEach((format) => {
['txt', 'csv', 'xlsx', 'xls'].forEach((format) => {
it(`should export only filtered data to '${format}'`, () => {
invokeFilter('name', [{ code: 'eq', value: '21' }]);
cy.gridInstance().invoke('export', format, { onlyFiltered: true });
Expand All @@ -227,7 +252,7 @@ describe('Export data', () => {

describe('With only selected (onlySelected = true)', () => {
describe('With selected range', () => {
['txt', 'csv', 'xlsx'].forEach((format) => {
['txt', 'csv', 'xlsx', 'xls'].forEach((format) => {
it(`should export data of selected range to '${format}'`, () => {
const range = { start: [0, 0], end: [1, 1] };
cy.gridInstance().invoke('setSelectionRange', range);
Expand Down Expand Up @@ -279,7 +304,7 @@ describe('Export data', () => {
});

describe('Without selected range', () => {
['txt', 'csv', 'xlsx'].forEach((format) => {
['txt', 'csv', 'xlsx', 'xls'].forEach((format) => {
it(`should export all visible data to '${format}' when no selected range`, () => {
cy.gridInstance().invoke('export', format, { onlySelected: true });

Expand Down Expand Up @@ -366,7 +391,7 @@ describe('Export data', () => {
cy.gridInstance().invoke('setColumns', formatColumn);
});

['txt', 'csv', 'xlsx'].forEach((format) => {
['txt', 'csv', 'xlsx', 'xls'].forEach((format) => {
it(`should export formatted data to '${format}' (useFormattedValue = true)`, () => {
cy.gridInstance().invoke('export', format, { useFormattedValue: true });

Expand Down
21 changes: 17 additions & 4 deletions packages/toast-ui.grid/docs/ko/export.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 내보내기 💾

TOAST UI Grid는 `v4.19.0` 버전 부터 `csv`와 엑셀(`xlsx`)로 내보내기 기능을 제공한다. `export` API 또는 컨텍스트 메뉴의 `내보내기` 하위 메뉴를 통해 내보내기를 진행할 수 있다.
TOAST UI Grid는 `v4.19.0` 버전 부터 `csv`와 엑셀(`xlsx`, `xls`(`v4.21.19` 이후))로 내보내기 기능을 제공한다. `export` API 또는 컨텍스트 메뉴의 `내보내기` 하위 메뉴를 통해 내보내기를 진행할 수 있다.

## 옵션

Expand All @@ -23,7 +23,7 @@ TOAST UI Grid는 `v4.19.0` 버전 부터 `csv`와 엑셀(`xlsx`)로 내보내기

인자로 주어진 포맷과 내보내기 옵션에 따라 파일을 내보낸다.(`ExportOpt`은 위에서 설명한 옵션과 같은 객체이다)

`export(format: 'txt' | 'csv' | 'xlsx', exportOpt?: ExportOpt)`
`export(format: 'txt' | 'csv' | 'xlsx' | 'xls', exportOpt?: ExportOpt)`

```js
const options = {
Expand Down Expand Up @@ -125,7 +125,7 @@ const grid = Grid({
```js
grid.on('beforeExport', ev => {
console.log(ev);
// ev.exportFormat - Export format (csv or xlsx)
// ev.exportFormat - Export format ('txt' | 'csv' | 'xlsx' | 'xls')
// ev.exportOptions - Used export options
// ev.data - Data to be finally exported (string[][])
});
Expand Down Expand Up @@ -160,7 +160,7 @@ grid.on('beforeExport', ev => {
```js
grid.on('afterExport', ev => {
console.log(ev);
// ev.exportFormat - Export format (csv or xlsx)
// ev.exportFormat - Export format ('txt' | 'csv' | 'xlsx' | 'xls')
// ev.exportOptions - Used export options
// ev.data - Data to be finally exported (string[][])
});
Expand All @@ -184,6 +184,19 @@ grid.on('afterExport', ev => {
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.1/xlsx.full.min.js"></script>
```

`xlsx` 포맷을 지원하지 않는 구 버전 Excel 등에서 열 수 있는 파일 내보내기를 위해 `xls` 포맷을 지원한다.
또한 기본 엑셀 내보내기 포맷을 `xlsx`에서 `xls`로 변경하기 위해 `excelCompatibilityMode` 옵션을 사용할 수 있다.

```js
const grid = Grid({
// ...
exportOptions: {
excelCompatibilityMode: true,
},
// ...
});
```

### 브라우저 지원 범위
| <img src="https://user-images.githubusercontent.com/1215767/34348387-a2e64588-ea4d-11e7-8267-a43365103afe.png" alt="Chrome" width="16px" height="16px" /> Chrome | <img src="https://user-images.githubusercontent.com/1215767/34348590-250b3ca2-ea4f-11e7-9efb-da953359321f.png" alt="IE" width="16px" height="16px" /> Internet Explorer | <img src="https://user-images.githubusercontent.com/1215767/34348380-93e77ae8-ea4d-11e7-8696-9a989ddbbbf5.png" alt="Edge" width="16px" height="16px" /> Edge | <img src="https://user-images.githubusercontent.com/1215767/34348394-a981f892-ea4d-11e7-9156-d128d58386b9.png" alt="Safari" width="16px" height="16px" /> Safari | <img src="https://user-images.githubusercontent.com/1215767/34348383-9e7ed492-ea4d-11e7-910c-03b39d52f496.png" alt="Firefox" width="16px" height="16px" /> Firefox |
| :---: | :---: | :---: | :---: | :---: |
Expand Down
Loading

0 comments on commit 2e341d2

Please sign in to comment.