@01group/fxl 中文文档教程

发布于 3年前 浏览 50 项目主页 更新于 3年前

fxl.js/ˈfɪk.səl/ 或带有 f 的“像素”)是一个面向数据的 JavaScript 电子表格库,构建于ExcelJS。 该库侧重于可组合性,旨在提供一种使用模块化、类似乐高积木的块来构建电子表格的方法。 它的主要用例是基于人工设计的非表格模板构建电子表格。

fxl.js 是对原始 Clojure 库 fxl 的 JavaScript 改编。

Contents

Installation

npm install @01group/fxl

TypeDoc 生成的文档可以在此处找到。

Why fxl.js?

与其他 JavaScript 电子表格库相比,fxl.js 试图做三件不同的事情,即:

  1. immutability: the entire API requires no side effects or mutations except for the IO operations at the very start or end for reading and writing the spreadsheets respectively. With fxl.js, it is more ergonomic to work with data and pure functions until near the end of the application, where all the side effects are isolated and happen in one go - see Functional Core, Imperative Shell.
  2. data orientation: the data model is represented as plain, nested JavaScript objects with literal child nodes. This allows us to reuse common JavaScript functions/methods to manipulate objects, and easily integrate fxl.js with functional utility libraries such as Lodash and Ramda - see Alan Perlis' Epigram on common functions.
  3. cells as unordered collections of objects: by expressing value, coordinate and style as three separate, orthogonal properties, we can work on the three components that make up spreadsheet separately. We can deal with interactions of the components only when we put them together. Expressing columns and rows as ordered sequences introduces complexity - see Rich Hickey's take on the list-and-order problem.

fxl.js 没有在构建时考虑到性能。 它建立在 ExcelJS 之上,因此设置了库的性能限制。 fxl.js 擅长基于人工设计的模板构建电子表格,这些模板通常不能很好地转换为表格格式,例如 CSV、记录或嵌套列表。

Examples

Cells as Plain Data

fxl.js 单元格是一个具有三个属性的对象,即值、坐标和可选样式。 以下是有效的单元格:

{ value: 'abc', coord: { row: 0, col: 0 } }

{
  value: 1.23,
  coord: { row: 2, col: 3, sheet: 'Sheet 1' },
  style: {
    numFmt: '0.00%',
    border: {
      right: { style: 'medium', color: { argb: 'FF00FF00' } },
      left: { style: 'medium', color: { argb: 'FF00FF00' } },
    },
    font: { name: 'Roboto', size: 16, bold: true },
  },
}

通过理解 fxl.Cell 接口,您将非常接近于使用 fxl.js 非常高效! 库的其余部分由 IO 函数(例如 fxl.readXlsxfxl.writeXlsx)和快捷函数组成,它们使处理单元格对象时变得非常轻松。

要了解有关 fxl.js 单元接口的更多信息,请参阅接口声明ExcelJS 的单元格值和样式

Creating a Spreadsheet (The Wrong Way)

假设我们想要创建一个普通的电子表格,如下所示:

| Item     | Cost     |
| -------- | -------- |
| Rent     | 1000     |
| Gas      | 100      |
| Food     | 300      |
| Gym      | 50       |
| Total    | 1450     |

从现有的 JavaScript 对象数组,如下所示:

const costs = [
  { item: "Rent", cost: 1000 },
  { item: "Gas", cost: 100 },
  { item: "Food", cost: 300 },
  { item: "Gym", cost: 50 },
];

我们将电子表格分解为三个部分,即标题、正文和总计。 以下不是最漂亮的代码片段(也不是推荐的使用 fxl.js 的方式),但它会起作用:

const headerCells = [
  { value: 'Item', coord: { row: 0, col: 0 } },
  { value: 'Cost', coord: { row: 0, col: 1 } },
];

const bodyCells = costs.flatMap((record, index) => {
  return [
    { value: record.item, coord: { row: index + 1, col: 0 } },
    { value: record.cost, coord: { row: index + 1, col: 1 } },
  ];
});

const totalCells = [
  { value: 'Total', coord: { row: costs.length + 2, col: 0 } },
  {
    value: costs.map((x) => x.cost).reduce((x, y) => x + y),
    coord: { row: costs.length + 2, col: 1 },
  },
];

然后我们将它们连接起来,并询问 fxl.js > 将单元格写入 XLSX 文件:

import * as fxl from '@01group/fxl';

const allCells = headerCells.concat(bodyCells).concat(totalCells);
await fxl.writeXlsx(allCells, 'costs.xlsx')

以上总结了使用 fxl.js 构建电子表格的本质。 它是关于获取一段数据,在最终调用 IO 函数之前将其转换为单元格对象。

Loading a Spreadsheet

const cells = await fxl.readXlsx('costs.xlsx')

Coordinate Shortcuts

fxl.js 的一个重要部分是快捷函数的集合,它使创建单元格对象变得容易。 我们可以将上面的示例归结为以下内容:

import * as fxl from '@01group/fxl';

const costs = [
  { item: "Rent", cost: 1000 },
  { item: "Gas", cost: 100 },
  { item: "Food", cost: 300 },
  { item: "Gym", cost: 50 },
];
const totalCost = costs.map((x) => x.cost).reduce((x, y) => x + y);

const headerCells = fxl.rowToCells(["Item", "Cost"]);
const bodyCells = fxl.recordsToCells(["item", "cost"], costs);
const totalCells = fxl.rowToCells(["Total", totalCost]);
const allCells = fxl.concatBelow(headerCells, bodyCells, totalCells);

await fxl.writeXlsx(allCells, 'costs.xlsx')

fxl.js 提供了从普通值创建行、单元格和表格的快捷方式(例如 fxl.rowToCellsfxl.colToCellsfxl.tableToCellsfxl.recordToCells),以及将单元格组组合在一起的快捷方式(例如 fxl.concatRightfxl.concatBelow)。 这使我们能够将一个大的电子表格分解成非常小的组件,然后只在更高的抽象层次上将它们组合在一起。

Style Shortcuts

假设我们想按如下方式设置简单电子表格的样式:

  • The header row's font should be bold with a light gray background.
  • The footer row should be the same as the header row, but with a dark red font colour.
  • The item column of the body should be in italic.
  • All cells should be horizontally aligned center.

在这种情况下,我们会将每个项目符号点放入其自己的函数中,并将其应用于正确的单元格组件:

function setHeaderStyle(cell: fxl.Cell): fxl.Cell {
  return fxl.pipe(cell, fxl.setBold(true), fxl.setSolidFg('light_gray'));
}

function setTotalStyle(cell: fxl.Cell): fxl.Cell {
  return fxl.pipe(cell, setHeaderStyle, fxl.setFontColor('dark_red'));
}

function setBodyStyle(cell: fxl.Cell): fxl.Cell {
  if (cell.coord.col == 0) {
    return fxl.setItalic(true)(cell);
  } else {
    return cell;
  }
}

const allCells = fxl
  .concatBelow(
    headerCells.map(setHeaderStyle),
    bodyCells.map(setBodyStyle),
    totalCells.map(setTotalStyle)
  )
  .map(fxl.setHorizontalAlignement('center'));

注意 fxl.js带有一些方便的高阶函数以促进函数组合,例如 fxl.pipefxl.compose

Putting Things Together

当我们将运行示例放在一起时,我们实际上看到了使用 fxl.js 构建电子表格时的一种相对常见的模式。 通常,在使用 fxl.js 构建电子表格时,我们遵循许多高级步骤:

  1. prepare the data to be used as cell values;
  2. build small spreadsheet components with those values;
  3. prepare the styles of each component;
  4. put together the styled components in their relative coordinates; and
  5. finally executing the IO operation.
import * as fxl from '@01group/fxl';

// ------------------------------------------------------------------
// data
// ------------------------------------------------------------------
const costs = [
  { item: "Rent", cost: 1000 },
  { item: "Gas", cost: 100 },
  { item: "Food", cost: 300 },
  { item: "Gym", cost: 50 },
];
const totalCost = costs.map((x) => x.cost).reduce((x, y) => x + y);

// ------------------------------------------------------------------
// spreadsheet components
// ------------------------------------------------------------------
const headerCells = fxl.rowToCells(["Item", "Cost"]);
const bodyCells = fxl.recordsToCells(["item", "cost"], costs);
const totalCells = fxl.rowToCells(["Total", totalCost]);
const allCells = fxl.concatBelow(headerCells, bodyCells, totalCells);

// ------------------------------------------------------------------
// styles
// ------------------------------------------------------------------
function setHeaderStyle(cell: fxl.Cell): fxl.Cell {
  return fxl.pipe(cell, fxl.setBold(true), fxl.setSolidFg('light_gray'));
}

function setTotalStyle(cell: fxl.Cell): fxl.Cell {
  return fxl.pipe(cell, setHeaderStyle, fxl.setFontColor('dark_red'));
}

function setBodyStyle(cell: fxl.Cell): fxl.Cell {
  if (cell.coord.col == 0) {
    return fxl.setItalic(true)(cell);
  } else {
    return cell;
  }
}

// ------------------------------------------------------------------
// relative coordinates
// ------------------------------------------------------------------
const allCells = fxl
  .concatBelow(
    headerCells.map(setHeaderStyle),
    bodyCells.map(setBodyStyle),
    totalCells.map(setTotalStyle)
  )
  .map(fxl.setHorizontalAlignement('center'));

// ------------------------------------------------------------------
// IO
// ------------------------------------------------------------------
await fxl.writeXlsx(allCells, 'costs.xlsx')

另请参阅inventory-spreadsheet walkthrough 及其随附脚本,以获得基于真实用例的更详细示例。

Known Issues

  • Column widths and row heights are not persisted after writing the spreadsheet. Loading an existing spreadsheet will contain no information regarding column widths and row heights.

另请参阅 ExcelJS 的已知问题

Contributing

fxl.js 正在开发中。 虽然它正在 Zero One Group 的生产中使用,但它可能还不稳定。 我们很乐意得到您的帮助,让它做好生产准备! 非常欢迎任何类型的贡献(问题、拉取请求或一般反馈)!

请参阅贡献文档

Further Resources

License

版权所有 2021 零一集团。

fxl.js 已获得 Apache License v2.0 许可。 这意味着“用户可以(几乎)使用代码做他们想做的任何事情,只有极少数例外”。 有关详细信息,请参阅此处

fxl.js (/ˈfɪk.səl/ or "pixel" with an f) is a data-oriented JavaScript spreadsheet library built on top of ExcelJS. The library focuses on composability, and aims to provide a way to build spreadsheets using modular, lego-like blocks. Its primary use case is for building spreadsheets based on human-designed templates that are not tabular.

fxl.js is a JavaScript adaptation of the original Clojure library fxl.

Contents

Installation

npm install @01group/fxl

The TypeDoc generated documentation can be found here.

Why fxl.js?

There are three things that fxl.js tries to do differently compared to other JavaScript spreadsheet libraries, namely:

  1. immutability: the entire API requires no side effects or mutations except for the IO operations at the very start or end for reading and writing the spreadsheets respectively. With fxl.js, it is more ergonomic to work with data and pure functions until near the end of the application, where all the side effects are isolated and happen in one go - see Functional Core, Imperative Shell.
  2. data orientation: the data model is represented as plain, nested JavaScript objects with literal child nodes. This allows us to reuse common JavaScript functions/methods to manipulate objects, and easily integrate fxl.js with functional utility libraries such as Lodash and Ramda - see Alan Perlis' Epigram on common functions.
  3. cells as unordered collections of objects: by expressing value, coordinate and style as three separate, orthogonal properties, we can work on the three components that make up spreadsheet separately. We can deal with interactions of the components only when we put them together. Expressing columns and rows as ordered sequences introduces complexity - see Rich Hickey's take on the list-and-order problem.

fxl.js is not built with performance in mind. It is built on top of ExcelJS, which thus sets the performance limit for the library. fxl.js shines at building spreadsheets based on human-designed templates, which typically do not translate well to tabular formats such as CSVs, records or nested lists.

Examples

Cells as Plain Data

A fxl.js cell is an object with three properties, namely value, coordinate and optionally style. The following are valid cells:

{ value: 'abc', coord: { row: 0, col: 0 } }

{
  value: 1.23,
  coord: { row: 2, col: 3, sheet: 'Sheet 1' },
  style: {
    numFmt: '0.00%',
    border: {
      right: { style: 'medium', color: { argb: 'FF00FF00' } },
      left: { style: 'medium', color: { argb: 'FF00FF00' } },
    },
    font: { name: 'Roboto', size: 16, bold: true },
  },
}

By understanding the fxl.Cell interface, you are very close to being very productive with fxl.js! The rest of the library is composed of IO functions (such as fxl.readXlsx and fxl.writeXlsx) and shortcut functions that make life very easy when massaging the cell objects.

To find out more about fxl.js' cell interface, see the interface declaration and ExcelJS' cell value and style.

Creating a Spreadsheet (The Wrong Way)

Let's suppose that we would like to create a plain spreadsheet such as the following:

| Item     | Cost     |
| -------- | -------- |
| Rent     | 1000     |
| Gas      | 100      |
| Food     | 300      |
| Gym      | 50       |
| Total    | 1450     |

from an existing JavaScript array of objects such as the following:

const costs = [
  { item: "Rent", cost: 1000 },
  { item: "Gas", cost: 100 },
  { item: "Food", cost: 300 },
  { item: "Gym", cost: 50 },
];

We would break the spreadsheet down into three components, namely the header, the body and the total. The following is not the prettiest piece of code (and not the recommended way of using fxl.js), but it would work:

const headerCells = [
  { value: 'Item', coord: { row: 0, col: 0 } },
  { value: 'Cost', coord: { row: 0, col: 1 } },
];

const bodyCells = costs.flatMap((record, index) => {
  return [
    { value: record.item, coord: { row: index + 1, col: 0 } },
    { value: record.cost, coord: { row: index + 1, col: 1 } },
  ];
});

const totalCells = [
  { value: 'Total', coord: { row: costs.length + 2, col: 0 } },
  {
    value: costs.map((x) => x.cost).reduce((x, y) => x + y),
    coord: { row: costs.length + 2, col: 1 },
  },
];

We then concatenate them, and ask fxl.js to write the cells into an XLSX file:

import * as fxl from '@01group/fxl';

const allCells = headerCells.concat(bodyCells).concat(totalCells);
await fxl.writeXlsx(allCells, 'costs.xlsx')

The above summarises the essence of spreadsheet building with fxl.js. It is about taking a piece of data, transform it into the cell objects before finally calling an IO function.

Loading a Spreadsheet

const cells = await fxl.readXlsx('costs.xlsx')

Coordinate Shortcuts

An important part of fxl.js is the collection of shortcut functions that makes it easy to create the cell objects. We can boil down the above example to the following:

import * as fxl from '@01group/fxl';

const costs = [
  { item: "Rent", cost: 1000 },
  { item: "Gas", cost: 100 },
  { item: "Food", cost: 300 },
  { item: "Gym", cost: 50 },
];
const totalCost = costs.map((x) => x.cost).reduce((x, y) => x + y);

const headerCells = fxl.rowToCells(["Item", "Cost"]);
const bodyCells = fxl.recordsToCells(["item", "cost"], costs);
const totalCells = fxl.rowToCells(["Total", totalCost]);
const allCells = fxl.concatBelow(headerCells, bodyCells, totalCells);

await fxl.writeXlsx(allCells, 'costs.xlsx')

fxl.js provides shortcuts for creating rows, cells and tables from plain values (such as fxl.rowToCells, fxl.colToCells, fxl.tableToCells and fxl.recordToCells), as well as shortcuts for combining groups of cells together (such as fxl.concatRight and fxl.concatBelow). This allows us to break down a big spreadsheet into very small components, and only to put them together later at a higher level of abstraction.

Style Shortcuts

Let's suppose that we would like to style our simple spreadsheet as follows:

  • The header row's font should be bold with a light gray background.
  • The footer row should be the same as the header row, but with a dark red font colour.
  • The item column of the body should be in italic.
  • All cells should be horizontally aligned center.

In this case, we would take each bullet point into its own function, and apply it to the right cell components:

function setHeaderStyle(cell: fxl.Cell): fxl.Cell {
  return fxl.pipe(cell, fxl.setBold(true), fxl.setSolidFg('light_gray'));
}

function setTotalStyle(cell: fxl.Cell): fxl.Cell {
  return fxl.pipe(cell, setHeaderStyle, fxl.setFontColor('dark_red'));
}

function setBodyStyle(cell: fxl.Cell): fxl.Cell {
  if (cell.coord.col == 0) {
    return fxl.setItalic(true)(cell);
  } else {
    return cell;
  }
}

const allCells = fxl
  .concatBelow(
    headerCells.map(setHeaderStyle),
    bodyCells.map(setBodyStyle),
    totalCells.map(setTotalStyle)
  )
  .map(fxl.setHorizontalAlignement('center'));

Notice that fxl.js comes with a few handy higher-order functions in order to facilitate function compositions, such as fxl.pipe and fxl.compose.

Putting Things Together

When we put our running example together, we actually see a relatively common pattern when building spreadsheets with fxl.js. In general, when building a spreadsheet using fxl.js, we follow a number of high-level steps:

  1. prepare the data to be used as cell values;
  2. build small spreadsheet components with those values;
  3. prepare the styles of each component;
  4. put together the styled components in their relative coordinates; and
  5. finally executing the IO operation.
import * as fxl from '@01group/fxl';

// ------------------------------------------------------------------
// data
// ------------------------------------------------------------------
const costs = [
  { item: "Rent", cost: 1000 },
  { item: "Gas", cost: 100 },
  { item: "Food", cost: 300 },
  { item: "Gym", cost: 50 },
];
const totalCost = costs.map((x) => x.cost).reduce((x, y) => x + y);

// ------------------------------------------------------------------
// spreadsheet components
// ------------------------------------------------------------------
const headerCells = fxl.rowToCells(["Item", "Cost"]);
const bodyCells = fxl.recordsToCells(["item", "cost"], costs);
const totalCells = fxl.rowToCells(["Total", totalCost]);
const allCells = fxl.concatBelow(headerCells, bodyCells, totalCells);

// ------------------------------------------------------------------
// styles
// ------------------------------------------------------------------
function setHeaderStyle(cell: fxl.Cell): fxl.Cell {
  return fxl.pipe(cell, fxl.setBold(true), fxl.setSolidFg('light_gray'));
}

function setTotalStyle(cell: fxl.Cell): fxl.Cell {
  return fxl.pipe(cell, setHeaderStyle, fxl.setFontColor('dark_red'));
}

function setBodyStyle(cell: fxl.Cell): fxl.Cell {
  if (cell.coord.col == 0) {
    return fxl.setItalic(true)(cell);
  } else {
    return cell;
  }
}

// ------------------------------------------------------------------
// relative coordinates
// ------------------------------------------------------------------
const allCells = fxl
  .concatBelow(
    headerCells.map(setHeaderStyle),
    bodyCells.map(setBodyStyle),
    totalCells.map(setTotalStyle)
  )
  .map(fxl.setHorizontalAlignement('center'));

// ------------------------------------------------------------------
// IO
// ------------------------------------------------------------------
await fxl.writeXlsx(allCells, 'costs.xlsx')

See also the inventory-spreadsheet walkthrough and its accompanying script for a more detailed example based on a real use case.

Known Issues

  • Column widths and row heights are not persisted after writing the spreadsheet. Loading an existing spreadsheet will contain no information regarding column widths and row heights.

See also ExcelJS' known issues.

Contributing

fxl.js is very much a work-in-progress. Whilst it is being used in production at Zero One Group, it may not be stable just yet. We would love your help to make it production ready! Any sort of contributions (issues, pull requests or general feedback) are all very welcomed!

See the contributing document.

Further Resources

License

Copyright 2021 Zero One Group.

fxl.js is licensed under Apache License v2.0. It means that "users can do (nearly) anything they want with the code, with very few exceptions". See here for more information.

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文