├── .gitbook.yaml ├── .github ├── .codecov.yml ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── renovate.json5 └── workflows │ ├── main.yml │ └── publish-npm-latest.yml ├── .gitignore ├── .npmrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── biome.json ├── docs ├── README.md ├── TOC.md ├── alignment.md ├── background-fillers.md ├── column-sizing-collapsing.md ├── fonts-and-colors.md ├── formulas.md ├── inserting-pictures.md ├── number-date-formatting.md ├── row-height-style.md ├── tables-summaries.md ├── tables.md ├── theming-tables.md ├── workbook-create.md ├── worksheet-add-data.md ├── worksheet-create.md └── worksheet-headers-footers.md ├── lerna.json ├── package.json ├── packages ├── demo │ ├── CHANGELOG.md │ ├── index.html │ ├── package.json │ ├── public │ │ ├── favicon.svg │ │ ├── github-mark-white.svg │ │ ├── github-mark.svg │ │ └── vite.svg │ ├── src │ │ ├── app-routing.ts │ │ ├── examples │ │ │ ├── example-standalone-iife.html │ │ │ ├── example01.html │ │ │ ├── example01.scss │ │ │ ├── example01.ts │ │ │ ├── example02.html │ │ │ ├── example02.scss │ │ │ ├── example02.ts │ │ │ ├── example03.html │ │ │ ├── example03.scss │ │ │ ├── example03.ts │ │ │ ├── example04.html │ │ │ ├── example04.scss │ │ │ ├── example04.ts │ │ │ ├── example05.html │ │ │ ├── example05.scss │ │ │ ├── example05.ts │ │ │ ├── example06.html │ │ │ ├── example06.scss │ │ │ ├── example06.ts │ │ │ ├── example07.html │ │ │ ├── example07.scss │ │ │ ├── example07.ts │ │ │ ├── example08.html │ │ │ ├── example08.scss │ │ │ ├── example08.ts │ │ │ ├── example09.html │ │ │ ├── example09.scss │ │ │ ├── example09.ts │ │ │ ├── example10.html │ │ │ ├── example10.scss │ │ │ ├── example10.ts │ │ │ ├── example11.html │ │ │ ├── example11.scss │ │ │ ├── example11.ts │ │ │ ├── example12.html │ │ │ ├── example12.scss │ │ │ ├── example12.ts │ │ │ ├── example13.html │ │ │ ├── example13.scss │ │ │ ├── example13.ts │ │ │ ├── example14.html │ │ │ ├── example14.scss │ │ │ └── example14.ts │ │ ├── getting-started.html │ │ ├── getting-started.ts │ │ ├── images │ │ │ ├── strawberry.jpg │ │ │ └── strawberry.png │ │ ├── index.d.ts │ │ ├── main.html │ │ ├── main.ts │ │ ├── style.scss │ │ └── vite-env.d.ts │ ├── tsconfig.json │ └── vite.config.mts ├── excel-builder-vanilla-types │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── LICENSE.md │ ├── README.md │ ├── biome.json │ ├── dist │ │ ├── index.d.ts │ │ └── index.js │ └── package.json └── excel-builder-vanilla │ ├── .npmignore │ ├── CHANGELOG.md │ ├── LICENSE.md │ ├── README.md │ ├── copy-types.mjs │ ├── index.d.ts │ ├── package.json │ ├── src │ ├── Excel │ │ ├── Drawing │ │ │ ├── AbsoluteAnchor.ts │ │ │ ├── Chart.ts │ │ │ ├── Drawing.ts │ │ │ ├── OneCellAnchor.ts │ │ │ ├── Picture.ts │ │ │ └── TwoCellAnchor.ts │ │ ├── Drawings.ts │ │ ├── Pane.ts │ │ ├── Paths.ts │ │ ├── Positioning.ts │ │ ├── RelationshipManager.ts │ │ ├── SharedStrings.ts │ │ ├── SheetView.ts │ │ ├── StyleSheet.ts │ │ ├── Table.ts │ │ ├── Util.ts │ │ ├── Workbook.ts │ │ ├── Worksheet.ts │ │ ├── XMLDOM.ts │ │ └── __tests__ │ │ │ ├── Util.spec.ts │ │ │ ├── Worksheet.spec.ts │ │ │ └── XMLDOM.spec.ts │ ├── __tests__ │ │ ├── excel-builder.spec.ts │ │ └── factory.spec.ts │ ├── factory.ts │ ├── index.ts │ ├── interfaces.ts │ ├── utilities │ │ ├── README.md │ │ ├── __tests__ │ │ │ ├── isTypeOf.spec.ts │ │ │ └── uniqueId.spec.ts │ │ ├── escape.ts │ │ ├── isTypeOf.ts │ │ ├── pick.ts │ │ └── uniqueId.ts │ └── vite-env.d.ts │ ├── tsconfig.json │ └── vite.config.mts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── vitest ├── vitest-pretest.ts └── vitest.config.mts /.gitbook.yaml: -------------------------------------------------------------------------------- 1 | root: ./docs/ 2 | 3 | structure: 4 | readme: README.md 5 | summary: TOC.md -------------------------------------------------------------------------------- /.github/.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | precision: 1 3 | round: up 4 | range: "50...95" 5 | 6 | status: 7 | project: 8 | default: 9 | target: auto 10 | # Fail the status if coverage drops by >= x 11 | threshold: 5% 12 | branches: null 13 | patch: 14 | default: 15 | threshold: 5% 16 | github_checks: 17 | annotations: false -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ghiscoding 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: ghiscoding 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: # Replace with a single custom sponsorship URL 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug report 2 | description: Report an issue with Excel-Builder-Vanilla 3 | labels: [pending triage] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! 9 | - type: textarea 10 | id: bug-description 11 | attributes: 12 | label: Describe the bug 13 | description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks! 14 | placeholder: Bug description 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: reproduction 19 | attributes: 20 | label: Reproduction 21 | description: Please provide a way to reproduce the problem you ran into. A [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) would be nice unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. 22 | placeholder: Reproduction 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: expectation 27 | attributes: 28 | label: Expectation 29 | description: You can optionally tell us what is your expectation from the lib? 30 | placeholder: Expectation 31 | validations: 32 | required: false 33 | - type: textarea 34 | id: system-info 35 | attributes: 36 | label: Environment Info 37 | description: output of `npx envinfo --system --npmPackages 'excel-builder-vanilla' --binaries --browsers` 38 | render: shell 39 | placeholder: | 40 | System 41 | Browsers 42 | Excel-Builder-Vanilla (x.y) 43 | TypeScript (x.y) 44 | validations: 45 | required: true 46 | - type: checkboxes 47 | id: checkboxes 48 | attributes: 49 | label: Validations 50 | description: Before submitting the issue, please make sure you do the following 51 | options: 52 | - label: Check that there isn't [already an issue](https://github.com/ghiscoding/excel-builder-vanilla/issues) that reports the same bug to avoid creating a duplicate. 53 | required: true 54 | - label: Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/ghiscoding/excel-builder-vanilla/discussions). 55 | required: true 56 | - label: The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug. 57 | required: true 58 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Questions & Discussions 4 | url: https://github.com/ghiscoding/excel-builder-vanilla/discussions 5 | about: Use GitHub discussions for message-board style questions and discussions. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 New feature proposal 2 | description: Propose a new feature to be added to Excel-Builder-Vanilla 3 | labels: ['enhancement: pending triage'] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for your interest in the project and taking the time to fill out this feature report! 9 | - type: textarea 10 | id: feature-description 11 | attributes: 12 | label: Clear and concise description of the problem 13 | description: 'As a developer using Excel-Builder-Vanilla I want [goal / wish] so that [benefit]. If you intend to submit a PR for this issue, tell us in the description. Thanks!' 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: suggested-solution 18 | attributes: 19 | label: Suggested solution 20 | description: We could provide following implementation... 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: alternative 25 | attributes: 26 | label: Alternative 27 | description: Clear and concise description of any alternative solutions or features you've considered. 28 | - type: textarea 29 | id: additional-context 30 | attributes: 31 | label: Additional context 32 | description: Any other context or screenshots about the feature request here. 33 | - type: checkboxes 34 | id: checkboxes 35 | attributes: 36 | label: Validations 37 | description: Before submitting the issue, please make sure you do the following 38 | options: 39 | - label: Check that there isn't already an issue that request the same feature to avoid creating a duplicate. 40 | required: true 41 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | extends: ['config:base', 'group:allNonMajor'], 3 | labels: ['dependencies'], 4 | dependencyDashboard: false, 5 | lockFileMaintenance: { 6 | enabled: false 7 | }, 8 | pin: false, 9 | rangeStrategy: 'bump', 10 | packageRules: [], 11 | ignoreDeps: ['node', 'pnpm'], 12 | schedule: ['every 3 months'], 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI Build 2 | on: 3 | push: 4 | branches: [main, next] 5 | pull_request: 6 | branches: 7 | - "**" 8 | 9 | permissions: 10 | checks: write 11 | pages: write 12 | contents: write 13 | pull-requests: write 14 | 15 | jobs: 16 | run: 17 | if: github.repository_owner == 'ghiscoding' || github.repository_owner == 'renovate-bot' || contains(github.event.pull_request.labels.*.name, 'run workflow') 18 | name: Node ${{ matrix.node }} 19 | runs-on: ubuntu-latest 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | node: [20] 24 | 25 | steps: 26 | - name: Retrieve current Date Time in EST 27 | shell: bash 28 | run: echo "START_TIME=$(TZ=":America/New_York" date -R|sed 's/.....$//')" >> $GITHUB_ENV 29 | 30 | - name: Echo date - ${{ env.START_TIME }} 31 | run: echo ${{ env.START_TIME }} 32 | 33 | - name: Clone repository 34 | uses: actions/checkout@v4 35 | with: 36 | fetch-depth: 3 37 | 38 | - name: Set NodeJS 39 | uses: actions/setup-node@v4 40 | with: 41 | node-version: ${{ matrix.node }} 42 | 43 | - name: Install pnpm 44 | uses: pnpm/action-setup@v3 45 | with: 46 | version: 10 47 | run_install: false 48 | 49 | - name: Get pnpm store directory 50 | shell: bash 51 | run: | 52 | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV 53 | 54 | - uses: actions/cache@v4 55 | name: Setup pnpm cache 56 | with: 57 | path: ${{ env.STORE_PATH }} 58 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 59 | restore-keys: | 60 | ${{ runner.os }}-pnpm-store- 61 | 62 | - name: Run pnpm install dependencies 63 | run: pnpm install 64 | 65 | - run: pnpm --version 66 | 67 | - name: Biome Lint Check 68 | run: pnpm biome:lint:check 69 | 70 | - name: Biome Format Check 71 | run: pnpm biome:format:check 72 | 73 | - name: Build Library 74 | run: pnpm build:lib 75 | 76 | - name: Run Vitest unit tests 77 | if: | 78 | !contains(github.event.head_commit.message, 'chore(release)') 79 | run: pnpm test:coverage 80 | 81 | - name: Upload test coverage to Codecov 82 | uses: codecov/codecov-action@v5 83 | env: 84 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 85 | 86 | - name: Build Website (GitHub demo site) 87 | run: pnpm build:demo 88 | 89 | - name: Deploy Demo to gh-pages 90 | if: | 91 | github.ref == 'refs/heads/main' && 92 | (contains(github.event.head_commit.message, 'chore(release)') || contains(github.event.head_commit.message, '[refresh gh-pages]')) 93 | uses: peaceiris/actions-gh-pages@v4 94 | with: 95 | github_token: ${{ secrets.GITHUB_TOKEN }} 96 | publish_dir: ./packages/demo/dist 97 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .idea 17 | .DS_Store 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | 24 | # Environment variables 25 | .env 26 | 27 | # builds 28 | tsconfig.tsbuildinfo 29 | 30 | # misc 31 | /coverage 32 | 33 | # Playwright 34 | playwright-report 35 | test-results 36 | /playwright/.cache -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | enable-pre-post-scripts=true 2 | registry-url=https://registry.npmjs.org/ -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["biomejs.biome"] 3 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Chrome Debugger", 6 | "request": "launch", 7 | "type": "chrome", 8 | "sourceMaps": true, 9 | "url": "http://localhost:3000", 10 | "webRoot": "${workspaceFolder}/packages/demo", 11 | "pathMapping": { 12 | "/@fs/": "" 13 | } 14 | }, 15 | { 16 | "name": "MS Edge Debugger", 17 | "request": "launch", 18 | "type": "msedge", 19 | "sourceMaps": true, 20 | "url": "http://localhost:3000", 21 | "webRoot": "${workspaceFolder}/packages/demo", 22 | "pathMapping": { 23 | "/@fs/": "" 24 | } 25 | }, 26 | { 27 | "type": "node", 28 | "request": "launch", 29 | "name": "Vitest - Debug Current Test File", 30 | "autoAttachChildProcesses": true, 31 | "skipFiles": ["/**", "**/node_modules/**"], 32 | "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs", 33 | "args": ["run", "${relativeFile}", "--no-watch", "--config", "./vitest/vitest.config.mts"], 34 | "smartStep": true, 35 | "console": "integratedTerminal" 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "biomejs.biome", 3 | "editor.formatOnSave": true, 4 | "editor.formatOnPaste": false, 5 | "[typescript]": { 6 | "editor.defaultFormatter": "biomejs.biome" 7 | }, 8 | "typescript.tsdk": "node_modules\\typescript\\lib" 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Build Library", 6 | "type": "shell", 7 | "command": "pnpm build", 8 | "problemMatcher": [] 9 | }, 10 | { 11 | "label": "Start Library Development", 12 | "type": "shell", 13 | "command": "pnpm dev", 14 | "problemMatcher": [] 15 | }, 16 | { 17 | "label": "Prepare New Release", 18 | "type": "shell", 19 | "command": "pnpm roll-new-release", 20 | "problemMatcher": [] 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We'd love for you to contribute and to make this project even better than it is today! If this interests you, go ahead and submit a Pull Request. 4 | 5 | Before accepting any Pull Request, we need to make sure that you followed the step shown below. 6 | 7 | **Note**: this project uses [pnpm workspaces](https://pnpm.io/workspaces), you can install pnpm by following their [installation](https://pnpm.io/installation) or via `corepack enable` to run any of the pnpm scripts shown below: 8 | 9 | ### Steps 10 | 11 | 1. clone the lib: 12 | - `git clone https://github.com/ghiscoding/excel-builder-vanilla` 13 | 2. install with **pnpm** from the root: 14 | - `pnpm install` OR `npx pnpm install` 15 | 3. run Biome Lint script (or simply execute step 5.) 16 | - `pnpm biome:lint:write` 17 | 4. run Biome Format script 18 | - `pnpm biome:format:write` 19 | 5. run a full TypeScript (TSC) build (this will also run Biome Lint & Format) 20 | - `pnpm build` OR `npx pnpm build` 21 | 6. add/run Vitest unit tests (make sure to run the previous steps first): 22 | - `pnpm test` (watch mode) 23 | - `pnpm test:coverage` (full test coverage) 24 | 7. after achieving step 3 to 6, then the final step would be to create the Pull Request... -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009-present Stephen Liberty and Ghislain B. 4 | https://github.com/ghiscoding/excel-builder-vanilla 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", 3 | "assist": { 4 | "actions": { 5 | "source": { 6 | "organizeImports": "on" 7 | } 8 | } 9 | }, 10 | "html": { 11 | "formatter": { 12 | "enabled": true 13 | } 14 | }, 15 | "formatter": { 16 | "enabled": true, 17 | "indentWidth": 2, 18 | "lineEnding": "lf", 19 | "formatWithErrors": true, 20 | "lineWidth": 140, 21 | "indentStyle": "space", 22 | "includes": ["**", "!**/.cache", "!**/dist/**", "!**/*.json", "!**/node_modules/**"] 23 | }, 24 | "javascript": { 25 | "parser": { 26 | "unsafeParameterDecoratorsEnabled": true 27 | }, 28 | "formatter": { 29 | "arrowParentheses": "asNeeded", 30 | "quoteProperties": "asNeeded", 31 | "semicolons": "always", 32 | "indentStyle": "space", 33 | "quoteStyle": "single" 34 | } 35 | }, 36 | "json": { 37 | "formatter": { 38 | "indentStyle": "space" 39 | }, 40 | "parser": { 41 | "allowComments": true 42 | } 43 | }, 44 | "linter": { 45 | "enabled": true, 46 | "includes": ["**", "!**/.cache", "!**/dist/**", "!**/*.json", "!**/node_modules/**"], 47 | "rules": { 48 | "correctness": { 49 | "useImportExtensions": "error" 50 | }, 51 | "recommended": true, 52 | "complexity": { 53 | "noForEach": "off", 54 | "noStaticOnlyClass": "off" 55 | }, 56 | "performance": { 57 | "noBarrelFile": "off", 58 | "noDelete": "off" 59 | }, 60 | "suspicious": { 61 | "noExplicitAny": "off", 62 | "noPrototypeBuiltins": "off" 63 | }, 64 | "style": { 65 | "noNonNullAssertion": "off", 66 | "noParameterAssign": "off", 67 | "useExponentiationOperator": "off", 68 | "useAsConstAssertion": "error", 69 | "useDefaultParameterLast": "error", 70 | "useEnumInitializers": "error", 71 | "useSelfClosingElements": "error", 72 | "useSingleVarDeclarator": "error", 73 | "noUnusedTemplateLiteral": "error", 74 | "useNumberNamespace": "error", 75 | "noInferrableTypes": "error", 76 | "noUselessElse": "error" 77 | } 78 | } 79 | }, 80 | "vcs": { 81 | "clientKind": "git", 82 | "enabled": false, 83 | "root": "./" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## Excel-Builder-Vanilla (forked from [`excel-builder.js`](https://github.com/stephenliberty/excel-builder.js)) 2 | 3 | Excel Builder (abbreviated as 'EB' for the sake of typing) is a relatively simple way of creating Office 2007 Excel files in JavaScript. 4 | 5 | ### Download 6 | 7 | ```ts 8 | // install 9 | npm install excel-builder-vanilla 10 | 11 | // ESM (preferred) 12 | import { createWorksheet } from 'excel-builder-vanilla'; 13 | 14 | // or CJS 15 | const { createWorksheet } = require('excel-builder-vanilla'); 16 | ``` 17 | 18 | or from CDN with standalone script (IIFE) 19 | ```html 20 | 21 | 24 | ``` 25 | 26 | ### Features Supported 27 | 28 | - Number and date formatting 29 | - Font sizes and colors 30 | - Borders 31 | - Multiple worksheets (with customizable names) 32 | - Table views 33 | - Setting page layout 34 | - Setting page headers and footers 35 | - Formula support 36 | - Insertion of pictures 37 | 38 | ### Why on earth would you build an excel file in JavaScript?!?! 39 | 40 | Excellent question! 41 | 42 | There are a couple of reasons. First, consider that quite often the data one wants in an excel file is the same data that is on the screen. If it's already there, why have the server fetch the same information and then burn through some CPU/IO/RAM time to build an excel file from it, and then serve it back to the user? Seems a bit.. ridiculous doesn't it? 43 | 44 | Second, consider that the user may already have all of the transformations they want done to the data on the screen - sorting, columns, colors, etc. This may take a while for the user to do - possibly rendering a caching mechanism useless. Or perhaps timing them out. You already have to collect their preferences in JavaScript - why bother creating a way to send the server those preferences, and then work through applying said preferences? 45 | 46 | Third, take a look at what an Office Excel file really is. It's a zip file full of XML files. The zip format is a very standardized format with a library that was written by some brainiac for JavaScript readily available. JavaScript eats XML files for breakfast - browsers already have the native ability to work with XML. With the eclipse of IE6 (and IE7 coming shortly), the JavaScript engines that may show up to grab a copy of that report are becoming increasingly powerful. More and more you should be pushing as much processing as you can onto the client and off of your server. 47 | 48 | Heck, if you've been in the development business for more than 5 years, you probably have realized that some of your 'powerful' first webservers are vastly outpaced by today's laptop. I know that my first webserver was slower than my phone is now! 49 | 50 | ### OK - So how do I "download" a file that the browser creates? 51 | 52 | Here's the tricky part. There are a couple different ways of doing this, so pick the one that is least painful. 53 | 54 | - Use the Downloadify project - this is what I'll be using in my demos 55 | - Use the newer browser's API's to create a Blob and save it. Google always helps here.. 56 | - Chrome has a non-standard attribute called 'download' on anchor tags. Assign a data-uri representing the file you want downloaded to the href and then specify the file name in the 'download' attribute. When the user clicks on the link, it will download the file just like normal. 57 | - Create a very simple and inexpensive web service to 'echo' back anything you send it. It still takes the pain away from creating the entire file in the backend with all the data fetching, translations and etc. However it also will work for all browses. You could also look into a simple Google App Engine setup. 58 | - Combining some of these approaches seems like a noteworthy goal.. perhaps a script in the future to do so? 59 | 60 | ### References 61 | The project builds an Excel file by following the SpreadsheetML document API (see Ms [API](https://learn.microsoft.com/en-us/office/open-xml/spreadsheet/structure-of-a-spreadsheetml-document?tabs=cs)) and for more detailed info on how to put all the pieces of the puzzle together then take a look this article [How To Create an Excel Spreadsheet with Pure JavaScript](https://www.shaunpoore.com/excel-spreadsheet-pure-javascript/) 62 | 63 | ### License 64 | 65 | MIT License 66 | -------------------------------------------------------------------------------- /docs/TOC.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | - [Introduction](README.md) 4 | 5 | ## Cookbook 6 | 7 | - [Creating Workbooks](workbook-create.md) 8 | - [Creating Worksheets](worksheet-create.md) 9 | - [Adding data to a Worksheet](worksheet-add-data.md) 10 | - [Sizing/Hiding Columns](column-sizing-collapsing.md) 11 | - [Setting row height/style](row-height-style.md) 12 | - [Fonts and Colors](fonts-and-colors.md) 13 | - [Formatting numbers, dates, etc](number-date-formatting.md) 14 | - [Alignment](alignment.md) 15 | - [Background Fillers](background-fillers.md) 16 | - [Formulas](formulas.md) 17 | - [Tables](tables.md) 18 | - [Theming Tables](theming-tables.md) 19 | - [Tables Summaries](tables-summaries.md) 20 | - [Adding Headers and Footers to a Worksheet](worksheet-headers-footers.md) 21 | - [Inserting images into spreadsheets](inserting-pictures.md) 22 | -------------------------------------------------------------------------------- /docs/alignment.md: -------------------------------------------------------------------------------- 1 | ## Aligning Values of Cells 2 | 3 | Aligning data is very straightforward. You need either horizontal or vertical (or both) keys set with the type of alignment you want. 4 | 5 | Horizontal alignment types can be found [here](http://www.datypic.com/sc/ooxml/t-ssml_ST_HorizontalAlignment.html) 6 | 7 | Vertical alignment types can be found [here](http://www.datypic.com/sc/ooxml/t-ssml_ST_VerticalAlignment.html) 8 | 9 | ```ts 10 | import { ExcelBuilder } from 'excel-builder-vanilla'; 11 | 12 | const artistWorkbook = createWorkbook(); 13 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 14 | 15 | const centerAlign = artistWorkbook.getStyleSheet().createFormat({ 16 | alignment: { 17 | horizontal: 'center', 18 | }, 19 | }); 20 | 21 | const originalData = [ 22 | [ 23 | { value: 'Artist', metadata: { style: centerAlign.id } }, 24 | { value: 'Album', metadata: { style: centerAlign.id } }, 25 | { value: 'Price', metadata: { style: centerAlign.id } }, 26 | ], 27 | ['Buckethead', 'Albino Slug', 8.99], 28 | ['Buckethead', 'Electric Tears', 13.99], 29 | ['Buckethead', 'Colma', 11.34], 30 | ['Crystal Method', 'Vegas', 10.54], 31 | ['Crystal Method', 'Tweekend', 10.64], 32 | ['Crystal Method', 'Divided By Night', 8.99], 33 | ]; 34 | 35 | albumList.setData(originalData); 36 | albumList.setColumns([{ width: 30 }, { width: 30 }, { width: 30 }]); 37 | 38 | artistWorkbook.addWorksheet(albumList); 39 | 40 | const data = createExcelFile(artistWorkbook); 41 | downloader('Artist WB.xlsx', data); 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/background-fillers.md: -------------------------------------------------------------------------------- 1 | ## Fillers 2 | 3 | No, not like what you get in most hotdogs. These are the patterns and colors for a cell fill. 4 | 5 | There are two possible types of fill. A pattern fill and a gradient fill. These two types take different instructions. 6 | 7 | The pattern fill requires a pattern type (or solid, if one just wants a solid background color). It also requires a foreground and background color. The trick to remember is that a foreground color is for the pattern (or in the case of a solid background, the actual color you want the background to be). The background color is for whatever the pattern goes on top of. 8 | 9 | A gradient fill requires a `degree` (or if no degree, a `left`, `right`, `top` and `bottom`). Then, the start and end instructions. The start and end instructions can be simple colors, by which EB will just assume that you want the start color to be pure at 'zero' (i.e. the beginning) and the end color to be pure at the 'one' (i.e. the end). In the example below, I want the end color to be pure 80% into the cell. 10 | 11 | **Note:** HTML color requires the `#` prefix to be escaped as `FF`, for example the HTML color `#0000FF` (blue) must be converted to `FF0000FF` 12 | 13 | ```ts 14 | import { ExcelBuilder } from 'excel-builder-vanilla'; 15 | 16 | const artistWorkbook = createWorkbook(); 17 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 18 | const stylesheet = artistWorkbook.getStyleSheet(); 19 | 20 | const blue = 'FF0000FF'; 21 | const header = stylesheet.createFormat({ 22 | font: { 23 | bold: true, 24 | color: blue, 25 | }, 26 | fill: { 27 | type: 'pattern', 28 | patternType: 'solid', 29 | fgColor: 'FF00FF00', 30 | }, 31 | }); 32 | 33 | const artistNameFormat = stylesheet.createFormat({ 34 | font: { 35 | color: 'FFFFFFFF', 36 | }, 37 | fill: { 38 | type: 'gradient', 39 | degree: 180, 40 | start: 'FF92D050', 41 | end: { pureAt: 0.8, color: 'FF0070C0' }, 42 | }, 43 | }); 44 | 45 | const originalData = [ 46 | [ 47 | { value: 'Artist', metadata: { style: header.id } }, 48 | { value: 'Album', metadata: { style: header.id } }, 49 | { value: 'Price', metadata: { style: header.id } }, 50 | ], 51 | [{ value: 'Buckethead', metadata: { style: artistNameFormat.id } }, 'Albino Slug', 8.99], 52 | [{ value: 'Buckethead', metadata: { style: artistNameFormat.id } }, 'Electric Tears', 13.99], 53 | [{ value: 'Buckethead', metadata: { style: artistNameFormat.id } }, 'Colma', 11.34], 54 | [{ value: 'Crystal Method', metadata: { style: artistNameFormat.id } }, 'Vegas', 10.54], 55 | [{ value: 'Crystal Method', metadata: { style: artistNameFormat.id } }, 'Tweekend', 10.64], 56 | [{ value: 'Crystal Method', metadata: { style: artistNameFormat.id } }, 'Divided By Night', 8.99], 57 | ]; 58 | 59 | albumList.setData(originalData); 60 | albumList.setColumns([{ width: 30 }, { width: 20 }, { width: 10 }]); 61 | 62 | artistWorkbook.addWorksheet(albumList); 63 | 64 | const data = createExcelFile(artistWorkbook); 65 | downloader('Artist WB.xlsx', data); 66 | ``` 67 | -------------------------------------------------------------------------------- /docs/column-sizing-collapsing.md: -------------------------------------------------------------------------------- 1 | ## Sizing/Collapsing Columns 2 | 3 | Having all of the data showing up is great and all, but without column widths set, Excel just chops everything off. Also, sometimes you want the column to exist, but make it collapsed so it doesn't show. 4 | 5 | Width is explained in some of the documentation for spreadsheetml. You have to do some calculation to get the exact widths you want, but generally all I really need is 'about right'. 6 | 7 | The method you're looking for is 'setColumns', which takes in an array of column definitions. The 'width' attribute will set a width. The 'hidden' attribute will hide the column. 8 | 9 | ```ts 10 | import { ExcelBuilder } from 'excel-builder-vanilla'; 11 | 12 | const artistWorkbook = createWorkbook(); 13 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 14 | albumList.mergeCells('A1', 'C1'); 15 | 16 | const stylesheet = artistWorkbook.getStyleSheet(); 17 | const header = stylesheet.createFormat({ 18 | alignment: { 19 | horizontal: 'center', 20 | }, 21 | font: { 22 | bold: true, 23 | color: 'FF2b995d', 24 | size: 13, 25 | }, 26 | }); 27 | 28 | const originalData = [ 29 | [{ value: 'Centered Header', metadata: { style: header.id } }], 30 | ['Artist', 'Album', 'Price'], 31 | ['Buckethead', 'Albino Slug', 8.99], 32 | ['Buckethead', 'Electric Tears', 13.99], 33 | ['Buckethead', 'Colma', 11.34], 34 | ['Crystal Method', 'Vegas', 10.54], 35 | ['Crystal Method', 'Tweekend', 10.64], 36 | ['Crystal Method', 'Divided By Night', 8.99], 37 | ]; 38 | 39 | albumList.setData(originalData); 40 | albumList.setColumns([{ width: 30 }, { width: 20, hidden: true }, { width: 10 }]); 41 | 42 | artistWorkbook.addWorksheet(albumList); 43 | 44 | const data = createExcelFile(artistWorkbook); 45 | downloader('Artist WB.xlsx', data); 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/fonts-and-colors.md: -------------------------------------------------------------------------------- 1 | ## Fonts and Colors 2 | 3 | A plain black and white Excel sheet is a bit boring, especially one that doesn't even have the occasional text font changes. Every workbook comes with a stylesheet - all you need to do is request it via `getStyleSheet()` 4 | 5 | ```ts 6 | const stylesheet = artistWorkbook.getStyleSheet(); 7 | ``` 8 | 9 | Now, first thing first, we need a style. 10 | 11 | ### Creating a style 12 | 13 | ```ts 14 | const boldStyle = stylesheet.createFontStyle({ 15 | bold: true, 16 | size: 14, 17 | }); 18 | ``` 19 | 20 | This will give you a style object back, which you can then use on a cell formatter. Pass the ID of the style, not the entire style instance. 21 | 22 | ```ts 23 | const boldFormatter = stylesheet.createFormat({ 24 | font: boldStyle.id, 25 | }); 26 | ``` 27 | 28 | You can reuse this style on multiple cell formats. Alternatively, an easier way might be to just use the createFormat method to also create the style. 29 | 30 | ```ts 31 | const boldFormatter = stylesheet.createFormat({ 32 | font: { 33 | bold: true, 34 | size: 14, 35 | }, 36 | }); 37 | ``` 38 | 39 | The downside to this is the lack of a shared font style - you might have ten different 'bold' definitions. 40 | 41 | ### Colors 42 | 43 | There are two different ways to specify a color of something. First is to reference the theme color, second is to reference an ARGB hexidecimal. All of my colors have been solid, opaque colors - so they've started with 'FF' (hex 255). 44 | 45 | Colors can be set as a simple string or an object that represents some information about the color. That information might be the tint or it's theme color index. 46 | 47 | By default, the theme used is the 'office' theme. 48 | 49 | ```ts 50 | const red = 'FFFF0000'; 51 | const importantFormatter = stylesheet.createFormat({ 52 | font: { 53 | bold: true, 54 | color: red, 55 | }, 56 | border: { 57 | bottom: { color: red, style: 'thin' }, 58 | top: { color: red, style: 'thin' }, 59 | left: { color: red, style: 'thin' }, 60 | right: { color: red, style: 'thin' }, 61 | }, 62 | }); 63 | 64 | const themeColor = stylesheet.createFormat({ 65 | font: { 66 | bold: true, 67 | color: { theme: 2 }, 68 | }, 69 | }); 70 | ``` 71 | 72 | So, to bring it all together in an example: 73 | 74 | ```ts 75 | import { ExcelBuilder } from 'excel-builder-vanilla'; 76 | 77 | const artistWorkbook = createWorkbook(); 78 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 79 | const stylesheet = artistWorkbook.getStyleSheet(); 80 | 81 | const red = 'FFFF0000'; 82 | const importantFormatter = stylesheet.createFormat({ 83 | font: { 84 | bold: true, 85 | color: red, 86 | }, 87 | border: { 88 | bottom: { color: red, style: 'thin' }, 89 | top: { color: red, style: 'thin' }, 90 | left: { color: red, style: 'thin' }, 91 | right: { color: red, style: 'thin' }, 92 | }, 93 | }); 94 | 95 | const themeColor = stylesheet.createFormat({ 96 | font: { 97 | bold: true, 98 | color: { theme: 3 }, 99 | }, 100 | }); 101 | 102 | const originalData = [ 103 | [ 104 | { value: 'Artist', metadata: { style: importantFormatter.id } }, 105 | { value: 'Album', metadata: { style: themeColor.id } }, 106 | { value: 'Price', metadata: { style: themeColor.id } }, 107 | ], 108 | ['Buckethead', 'Albino Slug', 8.99], 109 | ['Buckethead', 'Electric Tears', 13.99], 110 | ['Buckethead', 'Colma', 11.34], 111 | ['Crystal Method', 'Vegas', 10.54], 112 | ['Crystal Method', 'Tweekend', 10.64], 113 | ['Crystal Method', 'Divided By Night', 8.99], 114 | ]; 115 | 116 | albumList.setData(originalData); 117 | albumList.setColumns([{ width: 30 }, { width: 20 }, { width: 10 }]); 118 | 119 | artistWorkbook.addWorksheet(albumList); 120 | 121 | const data = createExcelFile(artistWorkbook); 122 | downloader('Artist WB.xlsx', data); 123 | ``` 124 | -------------------------------------------------------------------------------- /docs/formulas.md: -------------------------------------------------------------------------------- 1 | ## Formulas 2 | 3 | Formulas are the bread and butter of excel. Thankfully they're also ridiculously easy to make (if not very verbose). 4 | 5 | ```ts 6 | import { ExcelBuilder } from 'excel-builder-vanilla'; 7 | 8 | const artistWorkbook = createWorkbook(); 9 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 10 | 11 | const originalData = [ 12 | [{ value: 'Artist' }, { value: 'Album' }, { value: 'Price' }, { value: 'Quantity' }, { value: 'Total' }], 13 | ['Buckethead', 'Albino Slug', 8.99, 5, { value: 'C2+D2', metadata: { type: 'formula' } }], 14 | ['Buckethead', 'Electric Tears', 13.99, 7, { value: 'C3+D3', metadata: { type: 'formula' } }], 15 | ['Buckethead', 'Colma', 11.34, 9, { value: 'C4+D4', metadata: { type: 'formula' } }], 16 | ['Crystal Method', 'Vegas', 10.54, 3, { value: 'C5+D5', metadata: { type: 'formula' } }], 17 | ['Crystal Method', 'Tweekend', 10.64, 1, { value: 'C6+D6', metadata: { type: 'formula' } }], 18 | ['Crystal Method', 'Divided By Night', 8.99, 56, { value: 'C7+D7', metadata: { type: 'formula' } }], 19 | ]; 20 | 21 | albumList.setData(originalData); 22 | albumList.setColumns([{ width: 30 }, { width: 20 }, { width: 10 }]); 23 | 24 | artistWorkbook.addWorksheet(albumList); 25 | 26 | const data = createExcelFile(artistWorkbook); 27 | downloader('Artist WB.xlsx', data); 28 | ``` 29 | 30 | If you want to get the `R1C1` position, you can use the `util.positionToLetterRef(x, y)` method, which accepts the `X` position and the `Y` position, then returns an `R1C1` based off of that. 31 | -------------------------------------------------------------------------------- /docs/inserting-pictures.md: -------------------------------------------------------------------------------- 1 | ## Inserting pictures 2 | 3 | Creating pictures in Excel is a bit complicated, mostly due to the many, many different tweaks that can be done in the picture. As of 10/22/2013, options are fairly limited - 4 | 5 | - Two-cell anchors - specify which cell the picture starts (at the top left) and which cell it ends at (in the bottom left). Offsets also available by specifying an xOff and yOff on each parameter. 6 | - One-cell anchors - specify which cell the picture starts, and the width/height of the image. 7 | - Absolute anchors - specify the offset of the image, and the width/height of the image. 8 | 9 | OpenXML Drawings have an odd (understandable, but still odd) positioning system. Use the pixelsToEMUs method available in the Positioning.js to turn a pixel amount into EMU's, which is what is needed for any offset specification. 10 | 11 | > **Note** Please note that pictures **must be provided as `base64` format**, you can look on the internet on how to do that or if you're using ViteJS then look at the Vite loader plugin at the end of this document. 12 | 13 | ```ts 14 | import { Drawings, ExcelBuilder, Picture, Positioning } from 'excel-builder-vanilla'; 15 | import strawberryImageData from './images/strawberry.jpg?base64'; // using an optional Vite loader plugin 16 | 17 | const fruitWorkbook = createWorkbook(); 18 | const berryList = fruitWorkbook.createWorksheet({ name: 'Berry List' }); 19 | const stylesheet = fruitWorkbook.getStyleSheet(); 20 | 21 | const drawings = new Drawings(); 22 | 23 | const picRef = fruitWorkbook.addMedia('image', 'strawberry.jpg', strawberryImageData); 24 | 25 | const strawberryPicture1 = new Picture(); 26 | strawberryPicture1.createAnchor('twoCellAnchor', { 27 | from: { 28 | x: 0, 29 | y: 0, 30 | }, 31 | to: { 32 | x: 3, 33 | y: 3, 34 | }, 35 | }); 36 | 37 | strawberryPicture1.setMedia(picRef); 38 | drawings.addDrawing(strawberryPicture1); 39 | 40 | const strawberryPicture2 = new Picture(); 41 | strawberryPicture2.createAnchor('absoluteAnchor', { 42 | x: Positioning.pixelsToEMUs(300), 43 | y: Positioning.pixelsToEMUs(300), 44 | width: Positioning.pixelsToEMUs(300), 45 | height: Positioning.pixelsToEMUs(300), 46 | }); 47 | 48 | strawberryPicture2.setMedia(picRef); 49 | drawings.addDrawing(strawberryPicture2); 50 | 51 | const strawberryPicture3 = new Picture(); 52 | strawberryPicture3.createAnchor('oneCellAnchor', { 53 | x: 1, 54 | y: 4, 55 | width: Positioning.pixelsToEMUs(300), 56 | height: Positioning.pixelsToEMUs(300), 57 | }); 58 | 59 | strawberryPicture3.setMedia(picRef); 60 | drawings.addDrawing(strawberryPicture3); 61 | 62 | berryList.addDrawings(drawings); 63 | fruitWorkbook.addDrawings(drawings); 64 | fruitWorkbook.addWorksheet(berryList); 65 | 66 | const data = createExcelFile(fruitWorkbook); 67 | downloader('Fruit WB.xlsx', data); 68 | ``` 69 | 70 | ### Vite `base64` loader plugin 71 | 72 | For loading an image as `base64` with ViteJS, you could do it easily with a Vite loader plugin. 73 | 74 | > The code below was pulled from this Stack Overflow [answer](https://stackoverflow.com/a/78012267/1212166) 75 | 76 | ```ts 77 | import { readFileSync } from 'node:fs'; 78 | import { defineConfig, type Plugin } from 'vite'; 79 | 80 | const base64Loader: Plugin = { 81 | name: 'base64-loader', 82 | transform(_: any, id: string) { 83 | const [path, query] = id.split('?'); 84 | if (query !== 'base64') return null; 85 | 86 | const data = readFileSync(path); 87 | const base64 = data.toString('base64'); 88 | 89 | return `export default '${base64}';`; 90 | }, 91 | }; 92 | 93 | export default defineConfig({ 94 | // ... 95 | plugins: [base64Loader], 96 | }); 97 | ``` 98 | -------------------------------------------------------------------------------- /docs/number-date-formatting.md: -------------------------------------------------------------------------------- 1 | ## Number, Date, etc Formatting 2 | 3 | Formatting data is very straightforward. You create a 'formatter' and a style to take advantage of that formatter, then apply that style to any cells that need it. 4 | 5 | If you don't know what a 'format' should contain, open Excel and go to the cell formatter. 6 | 7 | ![](https://github.com/ghiscoding/excel-builder-vanilla/assets/643976/badc2d94-e0be-4c05-9360-cdfc3e654f20) 8 | 9 | There you should see a list of different predefined formats - choose or create as you feel necessary. 10 | 11 | ![](https://github.com/ghiscoding/excel-builder-vanilla/assets/643976/53e74ac0-c7c9-431b-bf1e-3890b819c2fa) 12 | 13 | Once you have the format how you'd like, click on the 'Custom' option. This will have the code that youhad selected/setup in the 'Type' box. Just copy and paste that code into the 'format' property. 14 | 15 | ![](https://github.com/ghiscoding/excel-builder-vanilla/assets/643976/1f3d1229-fb22-4b6b-b8fc-7bceac963d18) 16 | 17 | ```ts 18 | import { ExcelBuilder } from 'excel-builder-vanilla'; 19 | 20 | const artistWorkbook = createWorkbook(); 21 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 22 | 23 | const currency = artistWorkbook.getStyleSheet().createFormat({ 24 | format: '$#,##0.00', 25 | }); 26 | 27 | // you can get the Date format directly form Excel-Builder 28 | const date = artistWorkbook.getStyleSheet().createSimpleFormatter('date'); 29 | 30 | const originalData = [ 31 | ['Artist', 'Album', 'Price'], 32 | ['Buckethead', 'Albino Slug', { value: 8.99, metadata: { style: currency.id } }], 33 | ['Buckethead', 'Electric Tears', { value: 13.99, metadata: { style: currency.id } }], 34 | ['Buckethead', 'Colma', { value: 11.34, metadata: { style: currency.id } }], 35 | ['Crystal Method', 'Vegas', { value: 10.54, metadata: { style: currency.id } }], 36 | ['Crystal Method', 'Tweekend', { value: 10.64, metadata: { style: currency.id } }], 37 | ['Crystal Method', 'Divided By Night', { value: 8.99, metadata: { style: currency.id } }], 38 | ]; 39 | 40 | albumList.setData(originalData); 41 | artistWorkbook.addWorksheet(albumList); 42 | 43 | const data = createExcelFile(artistWorkbook); 44 | downloader('Artist WB.xlsx', data); 45 | ``` 46 | 47 | Note that the currency formatter could also have been done as follows: 48 | 49 | ```ts 50 | const currencyFormat = artistWorkbook.getStyleSheet().createNumberFormatter('$#,##0.00'); 51 | const currency = artistWorkbook.getStyleSheet().createFormat({ format: currencyFormat.id }); 52 | ``` 53 | 54 | This would allow the reuse of the currency number format by multiple formatters. 55 | -------------------------------------------------------------------------------- /docs/row-height-style.md: -------------------------------------------------------------------------------- 1 | ## Setting row information 2 | 3 | ```ts 4 | import { ExcelBuilder } from 'excel-builder-vanilla'; 5 | 6 | const originalData = [ 7 | ['Artist', 'Album', 'Price'], 8 | ['Buckethead', 'Albino Slug', 8.99], 9 | ['Buckethead', 'Electric Tears', 13.99], 10 | ['Buckethead', 'Colma', 11.34], 11 | ['Crystal Method', 'Vegas', 10.54], 12 | ['Crystal Method', 'Tweekend', 10.64], 13 | ['Crystal Method', 'Divided By Night', 8.99], 14 | ]; 15 | 16 | const artistWorkbook = createWorkbook(); 17 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 18 | const stylesheet = artistWorkbook.getStyleSheet(); 19 | 20 | const boldDXF = stylesheet.createDifferentialStyle({ 21 | font: { 22 | italic: true, 23 | }, 24 | }); 25 | albumList.setRowInstructions(1, { 26 | height: 30, 27 | style: boldDXF.id, 28 | }); 29 | albumList.setData(originalData); //<-- Here's the important part 30 | 31 | artistWorkbook.addWorksheet(albumList); 32 | 33 | const data = createExcelFile(artistWorkbook); 34 | downloader('Artist WB.xlsx', data); 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/tables-summaries.md: -------------------------------------------------------------------------------- 1 | ## Adding "Summaries" to tables 2 | 3 | Basically you need to tell the table what kind of operation the column is expected to do at the end. You also need to tell the table that there will, in fact, be a total row, and you have to make sure the total row is defined in the sheet data. There is a bit of redundancy here, be aware of that. 4 | 5 | Basically, there are some things that you have to tell the table, and unfortunately you still need to add it to the data: 6 | 7 | totalsRowCount - this will always be "1" if you want a footer. Theoretically, you could get into two row footers, but I don't think MS even supports that yet. 8 | Each column must have a label or function. This is not an option. The label must match the value in the cell. The formula must match the formula type being used. These formula types can be found here and it needs to be matched to a 'function_num' for subtotal (in most cases) which you can find here 9 | So, it causes the code to be a little wordier. 10 | 11 | ```ts 12 | import { ExcelBuilder, Table } from 'excel-builder-vanilla'; 13 | 14 | const albumTable = new Table(); 15 | 16 | const originalData = [ 17 | ['Artist', 'Album', 'Price'], 18 | ['Buckethead', 'Albino Slug', 8.99], 19 | ['Buckethead', 'Electric Tears', 13.99], 20 | ['Buckethead', 'Colma', 11.34], 21 | ['Crystal Method', 'Vegas', 10.54], 22 | ['Crystal Method', 'Tweekend', 10.64], 23 | ['Crystal Method', 'Divided By Night', 8.99], 24 | ['Highest Price', 'test', { value: 'SUBTOTAL(104,' + albumTable.name + '[Price])', metadata: { type: 'formula' } }], 25 | ]; 26 | 27 | const artistWorkbook = createWorkbook(); 28 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 29 | 30 | albumTable.styleInfo.themeStyle = 'TableStyleDark2'; // This is a predefined table style 31 | albumTable.setReferenceRange([1, 1], [3, originalData.length]); 32 | albumTable.totalsRowCount = 1; 33 | 34 | // Table columns are required, even if headerRowCount is zero. The name of the column also must match the 35 | // data in the column cell that is the header - keep this in mind for localization 36 | albumTable.setTableColumns([ 37 | { name: 'Artist', totalsRowLabel: 'Highest Price' }, 38 | { name: 'Album', totalsRowLabel: 'test' }, 39 | { name: 'Price', totalsRowFunction: 'max' }, 40 | ]); 41 | 42 | albumList.setData(originalData); 43 | artistWorkbook.addWorksheet(albumList); 44 | 45 | albumList.addTable(albumTable); 46 | artistWorkbook.addTable(albumTable); 47 | const data = createExcelFile(artistWorkbook); 48 | downloader('Artist WB.xlsx', data); 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/tables.md: -------------------------------------------------------------------------------- 1 | ## Tables 2 | 3 | Tables are a feature that is apparently new to Office 2007+, with a comparable feature called a `list` in 2003 and below. 4 | 5 | Basically, by putting data in a table, it gives the user some ways to filter and sort the data through UI. There are also some formula benefits. 6 | 7 | Creating a table takes a few extra steps, mostly because of how a table's definition is really detached from a worksheet. 8 | 9 | ```ts 10 | import { ExcelBuilder, Table } from 'excel-builder-vanilla'; 11 | 12 | const originalData = [ 13 | ['Artist', 'Album', 'Price'], 14 | ['Buckethead', 'Albino Slug', 8.99], 15 | ['Buckethead', 'Electric Tears', 13.99], 16 | ['Buckethead', 'Colma', 11.34], 17 | ['Crystal Method', 'Vegas', 10.54], 18 | ['Crystal Method', 'Tweekend', 10.64], 19 | ['Crystal Method', 'Divided By Night', 8.99], 20 | ]; 21 | 22 | const artistWorkbook = createWorkbook(); 23 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 24 | 25 | const albumTable = new Table(); 26 | albumTable.styleInfo.themeStyle = 'TableStyleDark2'; // this is a predefined table style 27 | albumTable.setReferenceRange([1, 1], [3, originalData.length]); // X,Y position where the table starts and stops. 28 | 29 | // Table columns are required, even if headerRowCount is zero. The name of the column also must match the 30 | // data in the column cell that is the header - keep this in mind for localization 31 | albumTable.setTableColumns(['Artist', 'Album', 'Price']); 32 | 33 | albumList.setData(originalData); 34 | artistWorkbook.addWorksheet(albumList); 35 | 36 | albumList.addTable(albumTable); 37 | artistWorkbook.addTable(albumTable); 38 | const data = createExcelFile(artistWorkbook); 39 | downloader('Artist WB.xlsx', data); 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/theming-tables.md: -------------------------------------------------------------------------------- 1 | ## Theming Tables 2 | 3 | Every once in a while you need a table theme that isn't available from the Custom Themes. 4 | 5 | ```ts 6 | import { ExcelBuilder, Table } from 'excel-builder-vanilla'; 7 | 8 | const originalData = [ 9 | ['Artist', 'Album', 'Price'], 10 | ['Buckethead', 'Albino Slug', 8.99], 11 | ['Buckethead', 'Electric Tears', 13.99], 12 | ['Buckethead', 'Colma', 11.34], 13 | ['Crystal Method', 'Vegas', 10.54], 14 | ['Crystal Method', 'Tweekend', 10.64], 15 | ['Crystal Method', 'Divided By Night', 8.99], 16 | ]; 17 | 18 | const artistWorkbook = createWorkbook(); 19 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 20 | 21 | const stylesheet = artistWorkbook.getStyleSheet(); 22 | 23 | const boldDXF = stylesheet.createDifferentialStyle({ 24 | font: { 25 | italic: true, 26 | }, 27 | }); 28 | 29 | stylesheet.createTableStyle({ 30 | name: 'SlightlyOffColorBlue', 31 | wholeTable: boldDXF.id, 32 | headerRow: stylesheet.createDifferentialStyle({ 33 | alignment: { horizontal: 'center' }, 34 | }).id, 35 | }); 36 | 37 | const albumTable = new Table(); 38 | albumTable.styleInfo.themeStyle = 'SlightlyOffColorBlue'; 39 | albumTable.setReferenceRange([1, 1], [3, originalData.length]); // X,Y position where the table starts and stops. 40 | 41 | // Table columns are required, even if headerRowCount is zero. The name of the column also must match the 42 | // data in the column cell that is the header - keep this in mind for localization 43 | albumTable.setTableColumns(['Artist', 'Album', 'Price']); 44 | 45 | albumList.setData(originalData); 46 | artistWorkbook.addWorksheet(albumList); 47 | 48 | albumList.addTable(albumTable); 49 | artistWorkbook.addTable(albumTable); 50 | const data = createExcelFile(artistWorkbook); 51 | downloader('Artist WB.xlsx', data); 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/workbook-create.md: -------------------------------------------------------------------------------- 1 | ## Creating a Workbook 2 | 3 | Creating a workbook can be done one of two ways, depending on how you include the EB project. 4 | 5 | #### Factory Style 6 | 7 | ```ts 8 | import { createWorkbook } from 'excel-builder-vanilla'; 9 | 10 | const workbook = createWorkbook(); 11 | ``` 12 | 13 | #### Constructor Style 14 | 15 | ```ts 16 | import { Workbook } from 'excel-builder-vanilla'; 17 | 18 | const workbook = new Workbook(); 19 | ``` 20 | 21 | This will eventually require you to include the 'excel-builder' module so you can export the workbook, so it's more verbose. However, this is also the best option for creating templates and the like. 22 | 23 | Workbooks with no worksheet (i.e. data) will build, but Excel will throw an error while attempting to open it. 24 | -------------------------------------------------------------------------------- /docs/worksheet-add-data.md: -------------------------------------------------------------------------------- 1 | ## Adding data to a worksheet 2 | 3 | Adding data to a worksheet is very straightforward. 4 | 5 | ```ts 6 | import { ExcelBuilder } from 'excel-builder-vanilla'; 7 | 8 | const originalData = [ 9 | ['Artist', 'Album', 'Price'], 10 | ['Buckethead', 'Albino Slug', 8.99], 11 | ['Buckethead', 'Electric Tears', 13.99], 12 | ['Buckethead', 'Colma', 11.34], 13 | ['Crystal Method', 'Vegas', 10.54], 14 | ['Crystal Method', 'Tweekend', 10.64], 15 | ['Crystal Method', 'Divided By Night', 8.99], 16 | ]; 17 | 18 | const artistWorkbook = createWorkbook(); 19 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 20 | 21 | albumList.setData(originalData); // <-- Here's the important part 22 | 23 | artistWorkbook.addWorksheet(albumList); 24 | 25 | const data = createExcelFile(artistWorkbook); 26 | downloader('Artist WB.xlsx', data); 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/worksheet-create.md: -------------------------------------------------------------------------------- 1 | ## Creating a worksheet 2 | 3 | Worksheets are essentially the entire point of having an Excel document. It holds all of the data in whatever way the user wanted it so that they can display or manipulate at will. 4 | 5 | There are two ways of building the worksheet. 6 | 7 | ### Option #1 - Factory method 8 | 9 | ```ts 10 | const workbook = ........ 11 | const accountSummarySheet = workbook.createWorksheet(); 12 | ``` 13 | 14 | ### Option #2 - Constructor method 15 | 16 | ```ts 17 | const workbook = ........ 18 | const accountSummarySheet = new Worksheet(); 19 | ``` 20 | 21 | Once you've created the worksheet, it must be added to the workbook. 22 | 23 | ```ts 24 | workbook.addWorksheet(accountSummarySheet); 25 | ``` 26 | 27 | There! Now we've got a worksheet that we can fill with data. Without data, the sheet will error out if you try and open it in Excel. You can continue to add sheets to the workbook like this. 28 | 29 | ### Giving the worksheet a proper name 30 | 31 | 'Sheet1' and etc are pretty boring names. Try this instead: 32 | 33 | ```ts 34 | const accountSummarySheet = new Worksheet({ name: 'Account Summary' }); 35 | ``` 36 | 37 | This will set the 'name' of the worksheet to 'Account Summary' so it doesn't show up as just 'Sheet1'. You can pass construction params to the factory pattern as well: 38 | 39 | ```ts 40 | const accountSummarySheet = workbook.createWorksheet({ name: 'Account Summary' }); 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/worksheet-headers-footers.md: -------------------------------------------------------------------------------- 1 | ## Adding headers and footers to a worksheet 2 | 3 | Headers and footers are there mostly for when the user prints. A good example is the `"3 out of 12"` that you might get on the bottom of some pages, showing that you're looking at page three out of twelve. Giving print titles (such as `"CONFIDENTIAL"` or the name of the organization that this is being printed for) is pretty common practice. The problem with having this data in the worksheet is that you're potentially messing up your cells just in the name of slapping a header in so the person knows what they are looking at when it gets printed. 4 | 5 | In office 2007 and up, go to the `View` and change to the `Page Layout` view to see the headers on a worksheet (or add/alter them). 6 | 7 | Headers and footers are set with the `setHeader` and `setFooter` methods - each method expects an array with the length of three. Position one is for the leftmost block in the header/footer, the second is the for the center block and the third is for the right block. Each block takes a set of instructions: 8 | 9 | A plain string with text 10 | An object with a `text` property and one or all of `bold`, `underline` and `size`. Size will be a number, the rest will be booleans. 11 | An array of items (either text or objects) 12 | Some special codes: 13 | 14 | `&P` - The page number that is being printed/looked at (generally the `#` in `"# of 12"`) 15 | `&N` - The total number of pages that will be printed 16 | `&D` - The current date 17 | `&T` - The current time 18 | `&Z` - The path to the folder that the file is in 19 | `&F` - The filename 20 | `&A` - The name of the worksheet 21 | 22 | ```ts 23 | import { ExcelBuilder } from 'excel-builder-vanilla'; 24 | 25 | const originalData = [ 26 | ['Artist', 'Album', 'Price'], 27 | ['Buckethead', 'Albino Slug', 8.99], 28 | ['Buckethead', 'Electric Tears', 13.99], 29 | ['Buckethead', 'Colma', 11.34], 30 | ['Crystal Method', 'Vegas', 10.54], 31 | ['Crystal Method', 'Tweekend', 10.64], 32 | ['Crystal Method', 'Divided By Night', 8.99], 33 | ]; 34 | 35 | const artistWorkbook = createWorkbook(); 36 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 37 | 38 | albumList.setData(originalData); // <-- Here's the important part 39 | 40 | albumList.setHeader([ 41 | 'This will be on the left', 42 | ['In the middle', { text: 'I shall be', bold: true }], 43 | { text: 'Right, underlined and size of 16', font: 16, underline: true }, 44 | ]); 45 | 46 | albumList.setFooter(['Date of print: &D &T', '&A', 'Page &P of &N']); 47 | artistWorkbook.addWorksheet(albumList); 48 | 49 | const data = createExcelFile(artistWorkbook); 50 | downloader('Artist WB.xlsx', data); 51 | ``` 52 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/@lerna-lite/cli/schemas/lerna-schema.json", 3 | "version": "4.0.1", 4 | "npmClient": "pnpm", 5 | "loglevel": "verbose", 6 | "command": { 7 | "publish": { 8 | "cleanupTempFiles": true, 9 | "removePackageFields": [ 10 | "devDependencies", 11 | "scripts" 12 | ] 13 | }, 14 | "version": { 15 | "conventionalCommits": true, 16 | "createRelease": "github", 17 | "changelogIncludeCommitsClientLogin": " - by @%l", 18 | "changelogHeaderMessage": "## Visit the [Excel-Builder-Vanilla](https://github.com/ghiscoding/excel-builder-vanilla) GitHub project or take a look at the [Live Demo](https://ghiscoding.github.io/excel-builder-vanilla)", 19 | "message": "chore(release): publish new version %s", 20 | "syncWorkspaceLock": true 21 | } 22 | }, 23 | "changelogPreset": "conventionalcommits", 24 | "packages": [ 25 | "packages/*" 26 | ] 27 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "excel-builder-vanilla-root", 3 | "description": "An easy way of building Excel files with javascript", 4 | "private": true, 5 | "keywords": [ 6 | "excel", 7 | "javascript", 8 | "xlsx", 9 | "spreadsheet" 10 | ], 11 | "author": "Stephen Liberty", 12 | "contributors": [ 13 | { 14 | "name": "Ghislain B." 15 | } 16 | ], 17 | "license": "MIT", 18 | "homepage": "https://github.com/ghiscoding/excel-builder-vanilla", 19 | "bugs": { 20 | "url": "https://github.com/ghiscoding/excel-builder-vanilla/issues" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/ghiscoding/excel-builder-vanilla.git" 25 | }, 26 | "funding": { 27 | "type": "ko_fi", 28 | "url": "https://ko-fi.com/ghiscoding" 29 | }, 30 | "scripts": { 31 | "clean": "rimraf --glob **/dist **/tsconfig.tsbuildinfo", 32 | "prebuild": "pnpm run clean && pnpm run biome:lint:write && pnpm run biome:format:write", 33 | "build": "pnpm -r --stream build", 34 | "build:demo": "pnpm -r --stream --filter \"./packages/demo/**\" build", 35 | "build:lib": "pnpm -r --stream --filter \"./packages/excel-builder-vanilla/**\" build", 36 | "dev": "pnpm -r dev:init && pnpm -r --parallel dev", 37 | "biome:lint:check": "biome lint ./packages", 38 | "biome:lint:write": "biome lint --write ./packages", 39 | "biome:format:check": "biome format ./packages", 40 | "biome:format:write": "biome format --write ./packages", 41 | "preview:version": "lerna version --dry-run", 42 | "preview:publish": "lerna publish from-package --dry-run", 43 | "new-version": "lerna version", 44 | "new-publish": "lerna publish from-package", 45 | "roll-new-release": "pnpm build && pnpm new-version && pnpm new-publish", 46 | "serve:demo": "pnpm -r --stream --filter \"./packages/demo/**\" dev", 47 | "test": "vitest --watch --config ./vitest/vitest.config.mts", 48 | "test:coverage": "vitest --coverage --config ./vitest/vitest.config.mts" 49 | }, 50 | "engines": { 51 | "node": "^20.17.0 || >=22.9.0", 52 | "pnpm": "10.x" 53 | }, 54 | "packageManager": "pnpm@10.10.0", 55 | "devDependencies": { 56 | "@biomejs/biome": "^2.0.0-beta.6", 57 | "@lerna-lite/cli": "^4.3.0", 58 | "@lerna-lite/publish": "^4.3.0", 59 | "@types/node": "^22.15.3", 60 | "@vitest/coverage-v8": "^3.2.2", 61 | "conventional-changelog-conventionalcommits": "^9.0.0", 62 | "happy-dom": "^17.6.3", 63 | "rimraf": "^6.0.1", 64 | "typescript": "catalog:", 65 | "vitest": "^3.2.2" 66 | } 67 | } -------------------------------------------------------------------------------- /packages/demo/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | ## Visit the [Excel-Builder-Vanilla](https://github.com/ghiscoding/excel-builder-vanilla) GitHub project or take a look at the [Live Demo](https://ghiscoding.github.io/excel-builder-vanilla) 3 | 4 | All notable changes to this project will be documented in this file. 5 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 6 | 7 | ## [4.0.1](https://github.com/ghiscoding/excel-builder-vanilla/compare/v4.0.0...v4.0.1) (2025-04-21) 8 | 9 | **Note:** Version bump only for package excel-builder-vanilla-demo 10 | 11 | ## [4.0.0](https://github.com/ghiscoding/excel-builder-vanilla/compare/v3.1.0...v4.0.0) (2025-04-12) 12 | 13 | ### ⚠ BREAKING CHANGES 14 | 15 | * build as ESM-Only, drop CJS 16 | 17 | ### Features 18 | 19 | * build as ESM-Only, drop CJS ([ee22a7b](https://github.com/ghiscoding/excel-builder-vanilla/commit/ee22a7bfa6f3cec1c324aca85dd97b2fb2aef027)) - by @ghiscoding 20 | 21 | ### Bug Fixes 22 | 23 | * **deps:** update all non-major dependencies ([53504a7](https://github.com/ghiscoding/excel-builder-vanilla/commit/53504a739e8f86378d83d547c6173a65e4b0c322)) - by @renovate-bot 24 | -------------------------------------------------------------------------------- /packages/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Excel-Builder-Vanilla demo with Vite + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "excel-builder-vanilla-demo", 3 | "version": "4.0.1", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@excel-builder-vanilla/types": "workspace:*", 13 | "@popperjs/core": "^2.11.8", 14 | "bootstrap": "^5.3.6", 15 | "excel-builder-vanilla": "workspace:*", 16 | "font-awesome": "^4.7.0" 17 | }, 18 | "devDependencies": { 19 | "fflate": "catalog:", 20 | "sass": "catalog:", 21 | "typescript": "catalog:", 22 | "vite": "catalog:" 23 | } 24 | } -------------------------------------------------------------------------------- /packages/demo/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/demo/public/github-mark-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/demo/public/github-mark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/demo/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/demo/src/app-routing.ts: -------------------------------------------------------------------------------- 1 | import Example01 from './examples/example01.js'; 2 | import Example02 from './examples/example02.js'; 3 | import Example03 from './examples/example03.js'; 4 | import Example04 from './examples/example04.js'; 5 | import Example05 from './examples/example05.js'; 6 | import Example06 from './examples/example06.js'; 7 | import Example07 from './examples/example07.js'; 8 | import Example08 from './examples/example08.js'; 9 | import Example09 from './examples/example09.js'; 10 | import Example10 from './examples/example10.js'; 11 | import Example11 from './examples/example11.js'; 12 | import Example12 from './examples/example12.js'; 13 | import Example13 from './examples/example13.js'; 14 | import Example14 from './examples/example14.js'; 15 | import GettingStarted from './getting-started.js'; 16 | 17 | export const navbarRouting = [ 18 | { name: 'getting-started', view: '/src/getting-started.html', viewModel: GettingStarted, title: 'Getting Started' }, 19 | { name: 'examples', view: '/src/examples/example01.html', viewModel: Example01, title: 'Examples' }, 20 | { name: 'documentation', href: 'https://ghiscoding.gitbook.io/excel-builder-vanilla/', title: '📘 Documentation' }, 21 | ]; 22 | 23 | export const exampleRouting = [ 24 | { 25 | name: 'References', 26 | routes: [{ name: 'documentation', href: 'https://ghiscoding.gitbook.io/excel-builder-vanilla/', title: '📘 Documentation' }], 27 | }, 28 | { 29 | name: 'Examples', 30 | routes: [ 31 | { name: 'example01', view: '/src/examples/example01.html', viewModel: Example01, title: '01- Create Worksheet' }, 32 | { name: 'example02', view: '/src/examples/example02.html', viewModel: Example02, title: '02- Sizing/Collapsing Columns' }, 33 | { name: 'example03', view: '/src/examples/example03.html', viewModel: Example03, title: '03- Setting row information' }, 34 | { name: 'example04', view: '/src/examples/example04.html', viewModel: Example04, title: '04- Fonts and Colors' }, 35 | { name: 'example05', view: '/src/examples/example05.html', viewModel: Example05, title: '05- Number, Date, etc Formatting' }, 36 | { name: 'example06', view: '/src/examples/example06.html', viewModel: Example06, title: '06- Alignment' }, 37 | { name: 'example07', view: '/src/examples/example07.html', viewModel: Example07, title: '07- Backgroud Fillers' }, 38 | { name: 'example08', view: '/src/examples/example08.html', viewModel: Example08, title: '08- Formulas' }, 39 | { name: 'example09', view: '/src/examples/example09.html', viewModel: Example09, title: '09- Tables' }, 40 | { name: 'example10', view: '/src/examples/example10.html', viewModel: Example10, title: '10- Theming Tables' }, 41 | { name: 'example11', view: '/src/examples/example11.html', viewModel: Example11, title: '11- Theming Summaries' }, 42 | { name: 'example12', view: '/src/examples/example12.html', viewModel: Example12, title: '12- Worksheet Headers/Footers' }, 43 | { name: 'example13', view: '/src/examples/example13.html', viewModel: Example13, title: '13- Pictures with 2 anchors' }, 44 | { name: 'example14', view: '/src/examples/example14.html', viewModel: Example14, title: '14- Pictures with different anchors' }, 45 | ], 46 | }, 47 | ]; 48 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example01.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | Example 01: Create Worksheet 6 | 7 | Code 8 | 9 | html 14 | | 15 | ts 18 | 19 | 20 |

21 |
Add data to export.
22 |
23 |
24 | 25 |
26 |
27 | 30 |
31 | 32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
ArtistAlbumPrice
BucketheadAlbino Slug8.99
BucketheadElectric Tears13.99
BucketheadColma11.34
Crystal MethodVegas10.54
Crystal MethodTweekend10.64
Crystal MethodDivided By Night8.99
75 |
76 |
77 |
78 |
79 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example01.scss: -------------------------------------------------------------------------------- 1 | .example01 { 2 | tr { 3 | th { 4 | font-weight: normal; 5 | } 6 | td:last-child { 7 | text-align: right; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example01.ts: -------------------------------------------------------------------------------- 1 | import { Workbook, downloadExcelFile } from 'excel-builder-vanilla'; 2 | // import type { ExcelStyleInstruction } from '@excel-builder-vanilla/types'; 3 | 4 | import './example01.scss'; 5 | 6 | export default class Example { 7 | exportBtnElm!: HTMLButtonElement; 8 | 9 | mount() { 10 | this.exportBtnElm = document.querySelector('#export') as HTMLButtonElement; 11 | this.exportBtnElm.addEventListener('click', this.startProcess.bind(this)); 12 | } 13 | 14 | unmount() { 15 | // remove event listeners to avoid DOM leaks 16 | this.exportBtnElm.removeEventListener('click', this.startProcess.bind(this)); 17 | } 18 | 19 | startProcess() { 20 | const originalData = [ 21 | ['Artist', 'Album', 'Price'], 22 | ['Buckethead', 'Albino Slug', 8.99], 23 | ['Buckethead', 'Electric Tears', 13.99], 24 | ['Buckethead', 'Colma', 11.34], 25 | ['Crystal Method', 'Vegas', 10.54], 26 | ['Crystal Method', 'Tweekend', 10.64], 27 | ['Crystal Method', 'Divided By Night', 8.99], 28 | ]; 29 | const artistWorkbook = new Workbook(); 30 | const albumList = artistWorkbook.createWorksheet({ name: 'Artists' }); 31 | albumList.setData(originalData); 32 | artistWorkbook.addWorksheet(albumList); 33 | 34 | downloadExcelFile(artistWorkbook, 'Artist WB.xlsx'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example02.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | Example 02: Sizing/Collapsing Columns 6 | 7 | Code 8 | 9 | html 14 | | 15 | ts 18 | 19 | 20 |

21 |
22 | The column widthattribute will set a width. The hiddenattribute will hide the column in Excel. The example 23 | below has the "Artist" column wider and the next column "Album" to be hidden in the exported Excel file. 24 |
25 |
26 |
27 | 28 |
29 |
30 | 33 |
34 | 35 |
36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
Merged Header
ArtistAlbum (hidden column)Price
BucketheadAlbino Slug8.99
BucketheadElectric Tears13.99
BucketheadColma11.34
Crystal MethodVegas10.54
Crystal MethodTweekend10.64
Crystal MethodDivided By Night8.99
81 |
82 |
83 |
84 |
85 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example02.scss: -------------------------------------------------------------------------------- 1 | .example02 { 2 | thead { 3 | tr:first-child th { 4 | font-size: 20px; 5 | font-weight: bold; 6 | color: #2b995d; 7 | } 8 | tr { 9 | th:nth-child(1) { 10 | width: 60%; 11 | } 12 | th:nth-child(2) { 13 | color: rgb(143, 143, 143); 14 | font-style: italic; 15 | } 16 | } 17 | } 18 | 19 | tr { 20 | th { 21 | font-weight: normal; 22 | } 23 | td:last-child { 24 | text-align: right; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example02.ts: -------------------------------------------------------------------------------- 1 | import { createWorkbook, downloadExcelFile } from 'excel-builder-vanilla'; 2 | 3 | import './example02.scss'; 4 | 5 | export default class Example { 6 | exportBtnElm!: HTMLButtonElement; 7 | 8 | mount() { 9 | this.exportBtnElm = document.querySelector('#export') as HTMLButtonElement; 10 | this.exportBtnElm.addEventListener('click', this.startProcess.bind(this)); 11 | } 12 | 13 | unmount() { 14 | // remove event listeners to avoid DOM leaks 15 | this.exportBtnElm.removeEventListener('click', this.startProcess.bind(this)); 16 | } 17 | 18 | startProcess() { 19 | const artistWorkbook = createWorkbook(); 20 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 21 | albumList.mergeCells('A1', 'C1'); 22 | 23 | const stylesheet = artistWorkbook.getStyleSheet(); 24 | const header = stylesheet.createFormat({ 25 | alignment: { 26 | horizontal: 'center', 27 | }, 28 | font: { 29 | bold: true, 30 | color: 'FF2b995d', 31 | size: 13, 32 | }, 33 | }); 34 | 35 | const originalData = [ 36 | [{ value: 'Merged Header', metadata: { style: header.id } }], 37 | ['Artist', 'Album', 'Price'], 38 | ['Buckethead', 'Albino Slug', 8.99], 39 | ['Buckethead', 'Electric Tears', 13.99], 40 | ['Buckethead', 'Colma', 11.34], 41 | ['Crystal Method', 'Vegas', 10.54], 42 | ['Crystal Method', 'Tweekend', 10.64], 43 | ['Crystal Method', 'Divided By Night', 8.99], 44 | ]; 45 | 46 | albumList.setData(originalData); 47 | albumList.setColumns([{ width: 30 }, { width: 20, hidden: true }, { width: 10 }]); 48 | 49 | artistWorkbook.addWorksheet(albumList); 50 | 51 | downloadExcelFile(artistWorkbook, 'Artist WB.xlsx'); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example03.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | Example 03: Setting row information 6 | 7 | Code 8 | 9 | html 14 | | 15 | ts 18 | 19 | 20 |

21 |
22 | Set different row options via setRowInstructions()method. For example, we changed the row height of the first row and 23 | change the text style to italic. 24 |
25 |
26 |
27 | 28 |
29 |
30 | 33 |
34 | 35 |
36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |
ArtistAlbumPrice
BucketheadAlbino Slug8.99
BucketheadElectric Tears13.99
BucketheadColma11.34
Crystal MethodVegas10.54
Crystal MethodTweekend10.64
Crystal MethodDivided By Night8.99
78 |
79 |
80 |
81 |
82 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example03.scss: -------------------------------------------------------------------------------- 1 | .example03 { 2 | tr { 3 | th { 4 | font-weight: normal; 5 | } 6 | td:last-child { 7 | text-align: right; 8 | } 9 | } 10 | tbody tr:nth-child(1) { 11 | height: 80px; 12 | } 13 | tbody tr:first-child { 14 | font-style: italic; 15 | text-decoration: underline; 16 | vertical-align: bottom; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example03.ts: -------------------------------------------------------------------------------- 1 | import { createWorkbook, downloadExcelFile } from 'excel-builder-vanilla'; 2 | 3 | import './example03.scss'; 4 | 5 | export default class Example { 6 | exportBtnElm!: HTMLButtonElement; 7 | 8 | mount() { 9 | this.exportBtnElm = document.querySelector('#export') as HTMLButtonElement; 10 | this.exportBtnElm.addEventListener('click', this.startProcess.bind(this)); 11 | } 12 | 13 | unmount() { 14 | // remove event listeners to avoid DOM leaks 15 | this.exportBtnElm.removeEventListener('click', this.startProcess.bind(this)); 16 | } 17 | 18 | startProcess() { 19 | const originalData = [ 20 | ['Artist', 'Album', 'Price'], 21 | ['Buckethead', 'Albino Slug', 8.99], 22 | ['Buckethead', 'Electric Tears', 13.99], 23 | ['Buckethead', 'Colma', 11.34], 24 | ['Crystal Method', 'Vegas', 10.54], 25 | ['Crystal Method', 'Tweekend', 10.64], 26 | ['Crystal Method', 'Divided By Night', 8.99], 27 | ]; 28 | 29 | const artistWorkbook = createWorkbook(); 30 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 31 | // const stylesheet = artistWorkbook.getStyleSheet(); 32 | 33 | // TODO: createDifferentialStyle() is causing issue, however the same code works fine with createFormat() 34 | // const boldDXF = stylesheet.createDifferentialStyle({ 35 | const boldDXF = artistWorkbook.getStyleSheet().createFormat({ 36 | font: { 37 | italic: true, 38 | underline: true, 39 | }, 40 | }); 41 | 42 | albumList.setRowInstructions(1, { 43 | height: 40, 44 | style: boldDXF.id, 45 | }); 46 | albumList.setData(originalData); 47 | 48 | artistWorkbook.addWorksheet(albumList); 49 | 50 | downloadExcelFile(artistWorkbook, 'Artist WB.xlsx'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example04.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | Example 04: Fonts and Colors 6 | 7 | Code 8 | 9 | html 14 | | 15 | ts 18 | 19 | 20 |

21 |
22 | Set different fonts and colors via the createFormat()method, we can provide an object with the fontand 23 | borderproperties. 24 |
25 |
26 |
27 | 28 |
29 |
30 | 33 |
34 | 35 |
36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |
ArtistAlbumPrice
BucketheadAlbino Slug8.99
BucketheadElectric Tears13.99
BucketheadColma11.34
Crystal MethodVegas10.54
Crystal MethodTweekend10.64
Crystal MethodDivided By Night8.99
78 |
79 |
80 |
81 |
82 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example04.scss: -------------------------------------------------------------------------------- 1 | .example04 { 2 | tr { 3 | th:nth-child(1) { 4 | color: red; 5 | border: 1px solid red; 6 | border-right: 1px dashed red; 7 | } 8 | th:nth-child(2) { 9 | border-left: 0px; 10 | } 11 | td:last-child { 12 | text-align: right; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example04.ts: -------------------------------------------------------------------------------- 1 | import { createWorkbook, downloadExcelFile } from 'excel-builder-vanilla'; 2 | 3 | import './example04.scss'; 4 | 5 | export default class Example { 6 | exportBtnElm!: HTMLButtonElement; 7 | 8 | mount() { 9 | this.exportBtnElm = document.querySelector('#export') as HTMLButtonElement; 10 | this.exportBtnElm.addEventListener('click', this.startProcess.bind(this)); 11 | } 12 | 13 | unmount() { 14 | // remove event listeners to avoid DOM leaks 15 | this.exportBtnElm.removeEventListener('click', this.startProcess.bind(this)); 16 | } 17 | 18 | startProcess() { 19 | const artistWorkbook = createWorkbook(); 20 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 21 | const stylesheet = artistWorkbook.getStyleSheet(); 22 | 23 | const red = 'FFFF0000'; 24 | const importantFormatter = stylesheet.createFormat({ 25 | font: { 26 | bold: true, 27 | color: red, 28 | }, 29 | border: { 30 | bottom: { color: red, style: 'thin' }, 31 | top: { color: red, style: 'thin' }, 32 | left: { color: red, style: 'thin' }, 33 | right: { color: red, style: 'dotted' }, 34 | }, 35 | }); 36 | 37 | const themeColor = stylesheet.createFormat({ 38 | font: { 39 | bold: true, 40 | color: { theme: 3 }, 41 | }, 42 | }); 43 | 44 | const originalData = [ 45 | [ 46 | { value: 'Artist', metadata: { style: importantFormatter.id } }, 47 | { value: 'Album', metadata: { style: themeColor.id } }, 48 | { value: 'Price', metadata: { style: themeColor.id } }, 49 | ], 50 | ['Buckethead', 'Albino Slug', 8.99], 51 | ['Buckethead', 'Electric Tears', 13.99], 52 | ['Buckethead', 'Colma', 11.34], 53 | ['Crystal Method', 'Vegas', 10.54], 54 | ['Crystal Method', 'Tweekend', 10.64], 55 | ['Crystal Method', 'Divided By Night', 8.99], 56 | ]; 57 | 58 | albumList.setData(originalData); 59 | albumList.setColumns([{ width: 30 }, { width: 20 }, { width: 10 }]); 60 | 61 | artistWorkbook.addWorksheet(albumList); 62 | 63 | downloadExcelFile(artistWorkbook, 'Artist WB.xlsx'); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example05.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | Example 05: Number, Date, etc Formatting 6 | 7 | Code 8 | 9 | html 14 | | 15 | ts 18 | 19 | 20 |

21 |
22 | We can create custom format by using the createFormat()method, in this example we formatted the "Price" column as 23 | currency and the Modified Date is a Date format. 24 |
25 |
26 |
27 | 28 |
29 |
30 | 33 |
34 | 35 |
36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
ArtistAlbumPriceDate Modified
BucketheadAlbino Slug$8.992024-02-01
BucketheadElectric Tears$13.992024-02-02
BucketheadColma$11.342024-02-03
Crystal MethodVegas$10.542024-02-04
Crystal MethodTweekend$10.642024-02-05
Crystal MethodDivided By Night$8.992024-02-06
85 |
86 |
87 |
88 |
89 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example05.scss: -------------------------------------------------------------------------------- 1 | .example05 { 2 | tr { 3 | th { 4 | font-weight: normal; 5 | } 6 | td:last-child { 7 | text-align: right; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example05.ts: -------------------------------------------------------------------------------- 1 | import { createWorkbook, downloadExcelFile } from 'excel-builder-vanilla'; 2 | 3 | import './example05.scss'; 4 | 5 | export default class Example { 6 | exportBtnElm!: HTMLButtonElement; 7 | 8 | mount() { 9 | this.exportBtnElm = document.querySelector('#export') as HTMLButtonElement; 10 | this.exportBtnElm.addEventListener('click', this.startProcess.bind(this)); 11 | } 12 | 13 | unmount() { 14 | // remove event listeners to avoid DOM leaks 15 | this.exportBtnElm.removeEventListener('click', this.startProcess.bind(this)); 16 | } 17 | 18 | startProcess() { 19 | const artistWorkbook = createWorkbook(); 20 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 21 | const currency = artistWorkbook.getStyleSheet().createFormat({ 22 | format: '$#,##0.00', 23 | }); 24 | 25 | // or by using 26 | // const currencyFormat = artistWorkbook.getStyleSheet().createNumberFormatter('$#,##0.00'); 27 | // const currency = artistWorkbook.getStyleSheet().createFormat({format: currencyFormat.id}); 28 | 29 | // you can get the Date format directly form Excel-Builder 30 | const date = artistWorkbook.getStyleSheet().createSimpleFormatter('date'); 31 | 32 | const originalData = [ 33 | ['Artist', 'Album', 'Price', 'Date Modified'], 34 | [ 35 | 'Buckethead', 36 | 'Albino Slug', 37 | { value: 8.99, metadata: { style: currency.id } }, 38 | { value: new Date(2024, 1, 1), metadata: { type: 'date', style: date.id } }, 39 | ], 40 | [ 41 | 'Buckethead', 42 | 'Electric Tears', 43 | { value: 13.99, metadata: { style: currency.id } }, 44 | { value: new Date(2024, 1, 2), metadata: { type: 'date', style: date.id } }, 45 | ], 46 | [ 47 | 'Buckethead', 48 | 'Colma', 49 | { value: 11.34, metadata: { style: currency.id } }, 50 | { value: new Date(2024, 1, 3), metadata: { type: 'date', style: date.id } }, 51 | ], 52 | [ 53 | 'Crystal Method', 54 | 'Vegas', 55 | { value: 10.54, metadata: { style: currency.id } }, 56 | { value: new Date(2024, 1, 4), metadata: { type: 'date', style: date.id } }, 57 | ], 58 | [ 59 | 'Crystal Method', 60 | 'Tweekend', 61 | { value: 10.64, metadata: { style: currency.id } }, 62 | { value: new Date(2024, 1, 5), metadata: { type: 'date', style: date.id } }, 63 | ], 64 | [ 65 | 'Crystal Method', 66 | 'Divided By Night', 67 | { value: 8.99, metadata: { style: currency.id } }, 68 | { value: new Date(2024, 1, 6), metadata: { type: 'date', style: date.id } }, 69 | ], 70 | ]; 71 | 72 | albumList.setData(originalData); 73 | albumList.setColumns([{ width: 15 }, { width: 15 }, { width: 15 }, { width: 15 }]); 74 | artistWorkbook.addWorksheet(albumList); 75 | 76 | downloadExcelFile(artistWorkbook, 'Artist WB.xlsx'); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example06.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | Example 06: Alignment 6 | 7 | Code 8 | 9 | html 14 | | 15 | ts 18 | 19 | 20 |

21 |
22 | Set different alignments, in this example we horizontally aligned to the middle all header titles in the exported Excel file. 23 |
24 |
25 |
26 | 27 |
28 | 31 |
32 | 33 |
34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
ArtistAlbumPrice
BucketheadAlbino Slug8.99
BucketheadElectric Tears13.99
BucketheadColma11.34
Crystal MethodVegas10.54
Crystal MethodTweekend10.64
Crystal MethodDivided By Night8.99
76 |
77 |
78 |
79 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example06.scss: -------------------------------------------------------------------------------- 1 | .example06 { 2 | tr { 3 | th { 4 | font-weight: normal; 5 | } 6 | td:last-child { 7 | text-align: right; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example06.ts: -------------------------------------------------------------------------------- 1 | import { createWorkbook, downloadExcelFile } from 'excel-builder-vanilla'; 2 | 3 | import './example06.scss'; 4 | 5 | export default class Example { 6 | exportBtnElm!: HTMLButtonElement; 7 | 8 | mount() { 9 | this.exportBtnElm = document.querySelector('#export') as HTMLButtonElement; 10 | this.exportBtnElm.addEventListener('click', this.startProcess.bind(this)); 11 | } 12 | 13 | unmount() { 14 | // remove event listeners to avoid DOM leaks 15 | this.exportBtnElm.removeEventListener('click', this.startProcess.bind(this)); 16 | } 17 | 18 | startProcess() { 19 | const artistWorkbook = createWorkbook(); 20 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 21 | 22 | const centerAlign = artistWorkbook.getStyleSheet().createFormat({ 23 | alignment: { 24 | horizontal: 'center', 25 | }, 26 | }); 27 | 28 | const originalData = [ 29 | [ 30 | { value: 'Artist', metadata: { style: centerAlign.id } }, 31 | { value: 'Album', metadata: { style: centerAlign.id } }, 32 | { value: 'Price', metadata: { style: centerAlign.id } }, 33 | ], 34 | ['Buckethead', 'Albino Slug', 8.99], 35 | ['Buckethead', 'Electric Tears', 13.99], 36 | ['Buckethead', 'Colma', 11.34], 37 | ['Crystal Method', 'Vegas', 10.54], 38 | ['Crystal Method', 'Tweekend', 10.64], 39 | ['Crystal Method', 'Divided By Night', 8.99], 40 | ]; 41 | 42 | albumList.setData(originalData); 43 | albumList.setColumns([{ width: 30 }, { width: 30 }, { width: 30 }]); 44 | 45 | artistWorkbook.addWorksheet(albumList); 46 | 47 | downloadExcelFile(artistWorkbook, 'Artist WB.xlsx'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example07.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | Example 07: Background Fillers 6 | 7 | Code 8 | 9 | html 14 | | 15 | ts 18 | 19 | 20 |

21 |
22 | Set different background filling by using fillproperty which accepts a wide range of options like background color type 23 | of gradient or pattern and different colors. 24 |
25 |
26 |
27 | 28 |
29 | 32 |
33 | 34 |
35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
ArtistAlbumPrice
BucketheadAlbino Slug8.99
BucketheadElectric Tears13.99
BucketheadColma11.34
Crystal MethodVegas10.54
Crystal MethodTweekend10.64
Crystal MethodDivided By Night8.99
77 |
78 |
79 |
80 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example07.scss: -------------------------------------------------------------------------------- 1 | .example07 { 2 | tr { 3 | th { 4 | font-weight: bold; 5 | color: #0000ff; 6 | background-color: #00ff00; 7 | border: 1px solid transparent; 8 | } 9 | td { 10 | border: 1px solid transparent; 11 | } 12 | td:first-child { 13 | background-image: linear-gradient(to right,#0070c0, #92d050); 14 | color: #fff; 15 | } 16 | td:last-child { 17 | text-align: right; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example07.ts: -------------------------------------------------------------------------------- 1 | import { createWorkbook, downloadExcelFile } from 'excel-builder-vanilla'; 2 | 3 | import './example07.scss'; 4 | 5 | export default class Example { 6 | exportBtnElm!: HTMLButtonElement; 7 | 8 | mount() { 9 | this.exportBtnElm = document.querySelector('#export') as HTMLButtonElement; 10 | this.exportBtnElm.addEventListener('click', this.startProcess.bind(this)); 11 | } 12 | 13 | unmount() { 14 | // remove event listeners to avoid DOM leaks 15 | this.exportBtnElm.removeEventListener('click', this.startProcess.bind(this)); 16 | } 17 | 18 | startProcess() { 19 | const artistWorkbook = createWorkbook(); 20 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 21 | const stylesheet = artistWorkbook.getStyleSheet(); 22 | 23 | const blue = 'FF0000FF'; 24 | const header = stylesheet.createFormat({ 25 | font: { 26 | bold: true, 27 | color: blue, 28 | }, 29 | fill: { 30 | type: 'pattern', 31 | patternType: 'solid', 32 | fgColor: 'FF00FF00', 33 | }, 34 | }); 35 | 36 | const artistNameFormat = stylesheet.createFormat({ 37 | font: { 38 | color: 'FFFFFFFF', 39 | }, 40 | fill: { 41 | type: 'gradient', 42 | degree: 180, 43 | start: 'FF92D050', 44 | end: { pureAt: 0.8, color: 'FF0070C0' }, 45 | }, 46 | }); 47 | 48 | const originalData: any = [ 49 | [ 50 | { value: 'Artist', metadata: { style: header.id } }, 51 | { value: 'Album', metadata: { style: header.id } }, 52 | { value: 'Price', metadata: { style: header.id } }, 53 | ], 54 | [{ value: 'Buckethead', metadata: { style: artistNameFormat.id } }, 'Albino Slug', 8.99], 55 | [{ value: 'Buckethead', metadata: { style: artistNameFormat.id } }, 'Electric Tears', 13.99], 56 | [{ value: 'Buckethead', metadata: { style: artistNameFormat.id } }, 'Colma', 11.34], 57 | [{ value: 'Crystal Method', metadata: { style: artistNameFormat.id } }, 'Vegas', 10.54], 58 | [{ value: 'Crystal Method', metadata: { style: artistNameFormat.id } }, 'Tweekend', 10.64], 59 | [{ value: 'Crystal Method', metadata: { style: artistNameFormat.id } }, 'Divided By Night', 8.99], 60 | ]; 61 | 62 | albumList.setData(originalData); 63 | albumList.setColumns([{ width: 30 }, { width: 20 }, { width: 10 }]); 64 | 65 | artistWorkbook.addWorksheet(albumList); 66 | 67 | downloadExcelFile(artistWorkbook, 'Artist WB.xlsx'); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example08.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | Example 08: Formulas 6 | 7 | Code 8 | 9 | html 14 | | 15 | ts 18 | 19 | 20 |

21 |
22 | We can set a formula by using the metadata object { value: 'C2+D2', metadata: { type: 'formula' } } 23 |
24 |
25 |
26 | 27 |
28 | 31 |
32 | 33 |
34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 |
ArtistAlbumPriceQuantityTotal
BucketheadAlbino Slug8.99513.99
BucketheadElectric Tears13.99720.99
BucketheadColma11.34920.34
Crystal MethodVegas10.54313.54
Crystal MethodTweekend10.64111.64
Crystal MethodDivided By Night8.995664.99
90 |
91 |
92 |
93 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example08.scss: -------------------------------------------------------------------------------- 1 | .example08 { 2 | tr { 3 | th { 4 | font-weight: normal; 5 | } 6 | td:nth-last-child(-n + 3) { 7 | text-align: right; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example08.ts: -------------------------------------------------------------------------------- 1 | import { createWorkbook, downloadExcelFile } from 'excel-builder-vanilla'; 2 | 3 | import './example08.scss'; 4 | 5 | export default class Example { 6 | exportBtnElm!: HTMLButtonElement; 7 | 8 | mount() { 9 | this.exportBtnElm = document.querySelector('#export') as HTMLButtonElement; 10 | this.exportBtnElm.addEventListener('click', this.startProcess.bind(this)); 11 | } 12 | 13 | unmount() { 14 | // remove event listeners to avoid DOM leaks 15 | this.exportBtnElm.removeEventListener('click', this.startProcess.bind(this)); 16 | } 17 | 18 | startProcess() { 19 | const artistWorkbook = createWorkbook(); 20 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 21 | 22 | const originalData = [ 23 | [{ value: 'Artist' }, { value: 'Album' }, { value: 'Price' }, { value: 'Quantity' }, { value: 'Total' }], 24 | ['Buckethead', 'Albino Slug', 8.99, 5, { value: 'C2+D2', metadata: { type: 'formula' } }], 25 | ['Buckethead', 'Electric Tears', 13.99, 7, { value: 'C3+D3', metadata: { type: 'formula' } }], 26 | ['Buckethead', 'Colma', 11.34, 9, { value: 'C4+D4', metadata: { type: 'formula' } }], 27 | ['Crystal Method', 'Vegas', 10.54, 3, { value: 'C5+D5', metadata: { type: 'formula' } }], 28 | ['Crystal Method', 'Tweekend', 10.64, 1, { value: 'C6+D6', metadata: { type: 'formula' } }], 29 | ['Crystal Method', 'Divided By Night', 8.99, 56, { value: 'C7+D7', metadata: { type: 'formula' } }], 30 | ]; 31 | 32 | albumList.setData(originalData); 33 | albumList.setColumns([{ width: 30 }, { width: 20 }, { width: 10 }]); 34 | 35 | artistWorkbook.addWorksheet(albumList); 36 | 37 | downloadExcelFile(artistWorkbook, 'Artist WB.xlsx'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example09.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | Example 09: Tables 6 | 7 | Code 8 | 9 | html 14 | | 15 | ts 18 | 19 | 20 |

21 |
22 | Tables are a feature that is apparently new to Office 2007+, with a comparable feature called a listin 2003 and below. 23 | Basically, by putting data in a table, it gives the user some ways to filter and sort the data through UI. There are also some 24 | formula benefits. Creating a table takes a few extra steps, mostly because of how a table's definition is really detached from a 25 | worksheet. 26 |
27 |
28 |
29 | 30 |
31 | 34 |
35 | 36 |
37 |
38 | 39 | 40 | 41 | 45 | 49 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 |
42 | Artist 43 | 44 | 46 | Album 47 | 48 | 50 | Price 51 | 52 |
BucketheadAlbino Slug8.99
BucketheadElectric Tears13.99
BucketheadColma11.34
Crystal MethodVegas10.54
Crystal MethodTweekend10.64
Crystal MethodDivided By Night8.99
88 |
89 |
90 |
91 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example09.scss: -------------------------------------------------------------------------------- 1 | .example09 { 2 | .fa.fa-caret-square-o-down { 3 | font-size: 20px; 4 | } 5 | tr { 6 | th { 7 | font-weight: normal; 8 | background-color: #000; 9 | color: #fff; 10 | span.fa { 11 | float: right; 12 | } 13 | } 14 | td { 15 | background-color: #4472c4; 16 | color: #fff; 17 | border: 1px solid transparent; 18 | } 19 | td:last-child { 20 | text-align: right; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example09.ts: -------------------------------------------------------------------------------- 1 | import { Table, createWorkbook, downloadExcelFile } from 'excel-builder-vanilla'; 2 | 3 | import './example09.scss'; 4 | 5 | export default class Example { 6 | exportBtnElm!: HTMLButtonElement; 7 | 8 | mount() { 9 | this.exportBtnElm = document.querySelector('#export') as HTMLButtonElement; 10 | this.exportBtnElm.addEventListener('click', this.startProcess.bind(this)); 11 | } 12 | 13 | unmount() { 14 | // remove event listeners to avoid DOM leaks 15 | this.exportBtnElm.removeEventListener('click', this.startProcess.bind(this)); 16 | } 17 | 18 | startProcess() { 19 | const originalData = [ 20 | ['Artist', 'Album', 'Price'], 21 | ['Buckethead', 'Albino Slug', 8.99], 22 | ['Buckethead', 'Electric Tears', 13.99], 23 | ['Buckethead', 'Colma', 11.34], 24 | ['Crystal Method', 'Vegas', 10.54], 25 | ['Crystal Method', 'Tweekend', 10.64], 26 | ['Crystal Method', 'Divided By Night', 8.99], 27 | ]; 28 | 29 | // require(['excel-builder.js/excel-builder', 'excel-builder.js/Excel/Table','download'], function (EB, Table, downloader) { 30 | const artistWorkbook = createWorkbook(); 31 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 32 | 33 | const albumTable = new Table(); 34 | albumTable.styleInfo.themeStyle = 'TableStyleDark2'; //This is a predefined table style 35 | albumTable.setReferenceRange([1, 1], [3, originalData.length]); //X/Y position where the table starts and stops. 36 | 37 | //Table columns are required, even if headerRowCount is zero. The name of the column also must match the 38 | //data in the column cell that is the header - keep this in mind for localization 39 | albumTable.setTableColumns(['Artist', 'Album', 'Price']); 40 | 41 | albumList.setData(originalData); 42 | artistWorkbook.addWorksheet(albumList); 43 | 44 | albumList.addTable(albumTable); 45 | artistWorkbook.addTable(albumTable); 46 | 47 | downloadExcelFile(artistWorkbook, 'Artist WB.xlsx'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example10.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | Example 10: Theming Tables 6 | 7 | Code 8 | 9 | html 14 | | 15 | ts 18 | 19 | 20 |

21 |
22 | Every once in a while you need a table theme that isn't available from the custom themes. You can use 23 | createTableStyle()to change style for a section like the header row and/or the whole table. 24 |
25 |
26 |
27 | 28 |
29 | 32 |
33 | 34 |
35 |
36 | 37 | 38 | 39 | 43 | 47 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
40 | Artist 41 | 42 | 44 | Album 45 | 46 | 48 | Price 49 | 50 |
BucketheadAlbino Slug8.99
BucketheadElectric Tears13.99
BucketheadColma11.34
Crystal MethodVegas10.54
Crystal MethodTweekend10.64
Crystal MethodDivided By Night8.99
86 |
87 |
88 |
89 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example10.scss: -------------------------------------------------------------------------------- 1 | .example10 { 2 | .fa.fa-caret-square-o-down { 3 | font-size: 20px; 4 | } 5 | tr { 6 | th { 7 | font-weight: normal; 8 | span.fa { 9 | float: right; 10 | margin-right: 5px; 11 | } 12 | } 13 | td:last-child { 14 | text-align: right; 15 | } 16 | } 17 | tr { 18 | th, 19 | td { 20 | font-style: italic; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example10.ts: -------------------------------------------------------------------------------- 1 | import { Table, createWorkbook, downloadExcelFile } from 'excel-builder-vanilla'; 2 | 3 | import './example10.scss'; 4 | 5 | export default class Example { 6 | exportBtnElm!: HTMLButtonElement; 7 | 8 | mount() { 9 | this.exportBtnElm = document.querySelector('#export') as HTMLButtonElement; 10 | this.exportBtnElm.addEventListener('click', this.startProcess.bind(this)); 11 | } 12 | 13 | unmount() { 14 | // remove event listeners to avoid DOM leaks 15 | this.exportBtnElm.removeEventListener('click', this.startProcess.bind(this)); 16 | } 17 | 18 | startProcess() { 19 | const originalData = [ 20 | ['Artist', 'Album', 'Price'], 21 | ['Buckethead', 'Albino Slug', 8.99], 22 | ['Buckethead', 'Electric Tears', 13.99], 23 | ['Buckethead', 'Colma', 11.34], 24 | ['Crystal Method', 'Vegas', 10.54], 25 | ['Crystal Method', 'Tweekend', 10.64], 26 | ['Crystal Method', 'Divided By Night', 8.99], 27 | ]; 28 | 29 | const artistWorkbook = createWorkbook(); 30 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 31 | 32 | const stylesheet = artistWorkbook.getStyleSheet(); 33 | 34 | const boldDXF = stylesheet.createDifferentialStyle({ 35 | font: { 36 | italic: true, 37 | }, 38 | }); 39 | 40 | stylesheet.createTableStyle({ 41 | name: 'SlightlyOffColorBlue', 42 | wholeTable: boldDXF.id, 43 | headerRow: stylesheet.createDifferentialStyle({ 44 | alignment: { horizontal: 'center' }, 45 | }).id, 46 | }); 47 | 48 | const albumTable = new Table(); 49 | albumTable.styleInfo.themeStyle = 'SlightlyOffColorBlue'; 50 | albumTable.setReferenceRange([1, 1], [3, originalData.length]); //X/Y position where the table starts and stops. 51 | 52 | //Table columns are required, even if headerRowCount is zero. The name of the column also must match the 53 | //data in the column cell that is the header - keep this in mind for localization 54 | albumTable.setTableColumns(['Artist', 'Album', 'Price']); 55 | 56 | albumList.setData(originalData); 57 | artistWorkbook.addWorksheet(albumList); 58 | 59 | albumList.addTable(albumTable); 60 | artistWorkbook.addTable(albumTable); 61 | 62 | downloadExcelFile(artistWorkbook, 'Artist WB.xlsx'); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example11.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | Example 11: Tables Summaries 6 | 7 | Code 8 | 9 | html 14 | | 15 | ts 18 | 19 | 20 |

21 |
22 | Adding "Summaries" to tables Basically you need to tell the table what kind of operation the column is expected to do at the end. 23 | You also need to tell the table that there will, in fact, be a total row, and you have to make sure the total row is defined in the 24 | sheet data. 25 |
26 |
27 |
28 | 29 |
30 | 33 |
34 | 35 |
36 |
37 | 38 | 39 | 40 | 44 | 48 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |
41 | Artist 42 | 43 | 45 | Album 46 | 47 | 49 | Price 50 | 51 |
BucketheadAlbino Slug8.99
BucketheadElectric Tears13.99
BucketheadColma11.34
Crystal MethodVegas10.54
Crystal MethodTweekend10.64
Crystal MethodDivided By Night8.99
Highest Pricetest13.99
92 |
93 |
94 |
95 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example11.scss: -------------------------------------------------------------------------------- 1 | .example11 { 2 | .fa.fa-caret-square-o-down { 3 | font-size: 20px; 4 | } 5 | tr { 6 | th { 7 | font-weight: normal; 8 | background-color: #000; 9 | color: #fff; 10 | border-bottom: 1.2px solid #fff; 11 | span.fa { 12 | float: right; 13 | } 14 | } 15 | td { 16 | background-color: #4472c4; 17 | color: #fff; 18 | border: 1px solid transparent; 19 | } 20 | td:last-child { 21 | text-align: right; 22 | } 23 | } 24 | tbody { 25 | tr:last-child td { 26 | background-color: #203764; 27 | border-top: 1.2px solid #fff; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example11.ts: -------------------------------------------------------------------------------- 1 | import { Table, createWorkbook, downloadExcelFile } from 'excel-builder-vanilla'; 2 | 3 | import './example11.scss'; 4 | 5 | export default class Example { 6 | exportBtnElm!: HTMLButtonElement; 7 | 8 | mount() { 9 | this.exportBtnElm = document.querySelector('#export') as HTMLButtonElement; 10 | this.exportBtnElm.addEventListener('click', this.startProcess.bind(this)); 11 | } 12 | 13 | unmount() { 14 | // remove event listeners to avoid DOM leaks 15 | this.exportBtnElm.removeEventListener('click', this.startProcess.bind(this)); 16 | } 17 | 18 | startProcess() { 19 | const albumTable = new Table(); 20 | 21 | const originalData = [ 22 | ['Artist', 'Album', 'Price'], 23 | ['Buckethead', 'Albino Slug', 8.99], 24 | ['Buckethead', 'Electric Tears', 13.99], 25 | ['Buckethead', 'Colma', 11.34], 26 | ['Crystal Method', 'Vegas', 10.54], 27 | ['Crystal Method', 'Tweekend', 10.64], 28 | ['Crystal Method', 'Divided By Night', 8.99], 29 | ['Highest Price', 'test', { value: `SUBTOTAL(104,${albumTable.name}[Price])`, metadata: { type: 'formula' } }], 30 | ]; 31 | 32 | const artistWorkbook = createWorkbook(); 33 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 34 | 35 | albumTable.styleInfo.themeStyle = 'TableStyleDark2'; //This is a predefined table style 36 | albumTable.setReferenceRange([1, 1], [3, originalData.length]); 37 | albumTable.totalsRowCount = 1; 38 | 39 | //Table columns are required, even if headerRowCount is zero. The name of the column also must match the 40 | //data in the column cell that is the header - keep this in mind for localization 41 | albumTable.setTableColumns([ 42 | { name: 'Artist', totalsRowLabel: 'Highest Price' }, 43 | { name: 'Album', totalsRowLabel: 'test' }, 44 | { name: 'Price', totalsRowFunction: 'max' }, 45 | ]); 46 | 47 | albumList.setData(originalData); 48 | artistWorkbook.addWorksheet(albumList); 49 | 50 | albumList.addTable(albumTable); 51 | artistWorkbook.addTable(albumTable); 52 | 53 | downloadExcelFile(artistWorkbook, 'Artist WB.xlsx'); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example12.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | Example 12: Worksheet Headers/Footers 6 | 7 | Code 8 | 9 | html 14 | | 15 | ts 18 | 19 | 20 |

21 |
22 | Headers and footers are there mostly for when the user prints. A good example is the "3 out of 12" that you might get on the bottom 23 | of some pages, showing that you're looking at page three out of twelve. Giving print titles (such as 'CONFIDENTIAL' or the name of 24 | the organization that this is being printed for) is pretty common practice. The problem with having this data in the worksheet is 25 | that you're potentially messing up your cells just in the name of slapping a header in so the person knows what they are looking at 26 | when it gets printed. 27 |
28 |
29 |
30 | 31 |
32 | 35 |
36 | 37 |
38 | Header 39 |
40 |
41 |
42 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | 52 |
This will be on the left 47 | In the middle I shall be 48 | Right, underlined and size of 16
53 | 54 |
55 | Body 56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 |
ArtistAlbumPrice
BucketheadAlbino Slug8.99
BucketheadElectric Tears13.99
BucketheadColma11.34
Crystal MethodVegas10.54
Crystal MethodTweekend10.64
Crystal MethodDivided By Night8.99
Highest Pricetest13.99
103 |
104 |
105 |
106 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example12.scss: -------------------------------------------------------------------------------- 1 | .example12 { 2 | .table.header { 3 | tr th:last-child { 4 | font-size: 18px; 5 | text-decoration: underline; 6 | text-align: right; 7 | } 8 | } 9 | 10 | tr { 11 | th { 12 | font-weight: normal; 13 | } 14 | td:last-child { 15 | text-align: right; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example12.ts: -------------------------------------------------------------------------------- 1 | import { createWorkbook, downloadExcelFile } from 'excel-builder-vanilla'; 2 | 3 | import './example12.scss'; 4 | 5 | export default class Example { 6 | exportBtnElm!: HTMLButtonElement; 7 | 8 | mount() { 9 | this.exportBtnElm = document.querySelector('#export') as HTMLButtonElement; 10 | this.exportBtnElm.addEventListener('click', this.startProcess.bind(this)); 11 | } 12 | 13 | unmount() { 14 | // remove event listeners to avoid DOM leaks 15 | this.exportBtnElm.removeEventListener('click', this.startProcess.bind(this)); 16 | } 17 | 18 | startProcess() { 19 | const originalData = [ 20 | ['Artist', 'Album', 'Price'], 21 | ['Buckethead', 'Albino Slug', 8.99], 22 | ['Buckethead', 'Electric Tears', 13.99], 23 | ['Buckethead', 'Colma', 11.34], 24 | ['Crystal Method', 'Vegas', 10.54], 25 | ['Crystal Method', 'Tweekend', 10.64], 26 | ['Crystal Method', 'Divided By Night', 8.99], 27 | ]; 28 | 29 | const artistWorkbook = createWorkbook(); 30 | const albumList = artistWorkbook.createWorksheet({ name: 'Album List' }); 31 | 32 | albumList.setData(originalData); 33 | 34 | albumList.setHeader([ 35 | 'This will be on the left', 36 | ['In the middle ', { text: 'I shall be', bold: true }], 37 | { text: 'Right, underlined and size of 16', font: 16, underline: true }, 38 | ]); 39 | 40 | albumList.setFooter(['Date of print: &D &T', '&A', 'Page &P of &N']); 41 | artistWorkbook.addWorksheet(albumList); 42 | 43 | downloadExcelFile(artistWorkbook, 'Artist WB.xlsx'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example13.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | Example 13: Pictures with 2 cell anchors 6 | 7 | Code 8 | 9 | html 14 | | 15 | ts 18 | 19 | 20 |

21 |
22 | You can insert pictures/images in Excel but it must be provided in base64format. 23 |
24 |
25 |
26 | 27 |
28 | 31 |
32 | 33 |
34 |
35 | 36 | 37 | 38 | 41 | 44 | 47 | 50 | 53 | 56 | 59 | 62 | 63 | 64 | 68 | 72 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 |
39 | A 40 | 42 | B 43 | 45 | C 46 | 48 | D 49 | 51 | E 52 | 54 | F 55 | 57 | G 58 | 60 | H 61 |
65 | Artist 66 | 67 | 69 | Album 70 | 71 | 73 | Price 74 | 75 |
BucketheadAlbino Slug8.99   
BucketheadElectric Tears13.99 98 | 99 |
BucketheadColma11.34
Crystal MethodVegas10.54
Crystal MethodTweekend10.64
Crystal MethodDivided By Night8.99
128 |
129 |
130 |
131 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example13.scss: -------------------------------------------------------------------------------- 1 | .example13 { 2 | .fa.fa-caret-square-o-down { 3 | font-size: 20px; 4 | } 5 | 6 | tr { 7 | th { 8 | border: 1px solid transparent; 9 | min-width: 60px; 10 | span.fa { 11 | float: right; 12 | } 13 | } 14 | th.table-col { 15 | background-color: #000; 16 | color: #fff; 17 | } 18 | td { 19 | border: 1px solid transparent; 20 | &.table-cell { 21 | background-color: #4472c4; 22 | color: #fff; 23 | border: 1px solid transparent; 24 | } 25 | &.text-right { 26 | text-align: right; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example14.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | Example 14: Pictures with one/two/absolute cell anchors 6 | 7 | Code 8 | 9 | html 14 | | 15 | ts 18 | 19 | 20 |

21 |
22 | You can insert pictures/images in Excel but it must be provided in base64format. There are multiple type of anchors 23 | that you can use: oneCellAnchor/twoCellAnchor/absoluteAnchorcell anchors. 24 |
25 |
26 |
27 | 28 |
29 | 32 |
33 | 34 |
35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 |
ABCDEFGHI
1 55 | 56 |
2
3
4 74 | 75 |
5
6
7
8 99 | 100 |
9
10
11
12
124 |
125 |
126 |
127 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example14.scss: -------------------------------------------------------------------------------- 1 | .example12 { 2 | .table.header { 3 | tr th:last-child { 4 | font-size: 18px; 5 | text-decoration: underline; 6 | text-align: right; 7 | } 8 | } 9 | 10 | tr { 11 | th { 12 | font-weight: normal; 13 | } 14 | td:last-child { 15 | text-align: right; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/demo/src/examples/example14.ts: -------------------------------------------------------------------------------- 1 | import { createWorkbook, downloadExcelFile, Drawings, Picture, Positioning } from 'excel-builder-vanilla'; 2 | import strawberryImageData from '../images/strawberry.jpg?base64'; // images must be provided in the `base64` format, use a Vite loader plugin 3 | import strawberryUrl from '../images/strawberry.jpg?url'; 4 | 5 | // jpg/png are all valid 6 | // import strawberryImageData from '../images/strawberry.png?base64'; 7 | // import strawberryUrl from '../images/strawberry.png?url'; 8 | 9 | import './example14.scss'; 10 | 11 | export default class Example { 12 | exportBtnElm!: HTMLButtonElement; 13 | 14 | mount() { 15 | this.exportBtnElm = document.querySelector('#export') as HTMLButtonElement; 16 | this.exportBtnElm.addEventListener('click', this.startProcess.bind(this)); 17 | document.querySelector('#pic1')!.src = strawberryUrl; 18 | document.querySelector('#pic2')!.src = strawberryUrl; 19 | document.querySelector('#pic3')!.src = strawberryUrl; 20 | } 21 | 22 | unmount() { 23 | // remove event listeners to avoid DOM leaks 24 | this.exportBtnElm.removeEventListener('click', this.startProcess.bind(this)); 25 | } 26 | 27 | startProcess() { 28 | const fruitWorkbook = createWorkbook(); 29 | const berryList = fruitWorkbook.createWorksheet({ name: 'Berry List' }); 30 | 31 | const drawings = new Drawings(); 32 | 33 | const picRef = fruitWorkbook.addMedia('image', 'strawberry.jpg', strawberryImageData); 34 | 35 | const strawberryPicture1 = new Picture(); 36 | strawberryPicture1.createAnchor('oneCellAnchor', {}); 37 | strawberryPicture1.createAnchor('twoCellAnchor', { 38 | from: { 39 | x: 0, 40 | y: 0, 41 | }, 42 | to: { 43 | x: 3, 44 | y: 5, 45 | }, 46 | }); 47 | 48 | strawberryPicture1.setMedia(picRef); 49 | drawings.addDrawing(strawberryPicture1); 50 | 51 | const strawberryPicture2 = new Picture(); 52 | strawberryPicture2.createAnchor('absoluteAnchor', { 53 | x: Positioning.pixelsToEMUs(300), 54 | y: Positioning.pixelsToEMUs(300), 55 | width: Positioning.pixelsToEMUs(300), 56 | height: Positioning.pixelsToEMUs(300), 57 | }); 58 | 59 | strawberryPicture2.setMedia(picRef); 60 | drawings.addDrawing(strawberryPicture2); 61 | 62 | const strawberryPicture3 = new Picture(); 63 | strawberryPicture3.createAnchor('oneCellAnchor', { 64 | x: 1, 65 | y: 4, 66 | width: Positioning.pixelsToEMUs(300), 67 | height: Positioning.pixelsToEMUs(300), 68 | }); 69 | 70 | strawberryPicture3.setMedia(picRef); 71 | drawings.addDrawing(strawberryPicture3); 72 | 73 | berryList.addDrawings(drawings); 74 | fruitWorkbook.addDrawings(drawings); 75 | fruitWorkbook.addWorksheet(berryList); 76 | 77 | downloadExcelFile(fruitWorkbook, 'Fruits.xlsx'); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/demo/src/getting-started.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Getting Started

4 |
5 |
6 | 7 |
8 |

Download

9 |
10 | 11 |
12 |
13 | GitHub 14 |
15 |

16 | https://github.com/ghiscoding/excel-builder-vanilla 17 |

18 |
19 | 20 |
21 |
CDN
22 |

23 | jsDelivrgraciously provide CDNs for many JavaScript libraries including 24 | Excel-Builder-Vanilla. Just use the following CDN links. 25 |

26 |

27 | The project now ships as ESM-Only, if you still wish to use the legacy CommonJS (CJS) format with require(), then use 28 | previous 3.x version. 29 |

30 | 31 |
32 |
33 | <!-- (IIFE Standalone Script) Latest compiled and minified JavaScript -->
34 | <script type="module" src="https://cdn.jsdelivr.net/npm/excel-builder-vanilla@4.0.0/dist/excel-builder.iife.js"></script>
35 |     
36 | 37 | 38 | Note: the excel-builder.iife.js is the only dist bundle providing the ExcelBuilder on the 39 | window object. 40 | 41 |
42 |
43 | 44 | You can find a Standalone Script (IIFE) example at the location 45 | examples/example-standalone-iife.html 48 | 49 |
50 | 51 |
52 |
NPM
53 |

Install and manage Excel-Builder-Vanilla JavaScript using NPM.

54 | 55 |
56 |
$ npm install excel-builder-vanilla
57 |
58 |
59 | 60 |
61 | ESM import from 62 |
63 |

The library provides both CommonJS or ESM, see the example below:

64 |
65 |
66 | // CommonJS
67 | const { createWorkbook, Workbook } = require('excel-builder-vanilla');
68 | 
69 | // ESM
70 | import { createWorkbook } from 'excel-builder-vanilla';
71 | 
72 | // use it
73 | const artistWorkbook = createWorkbook(); // or new Workbook();
74 | const albumList = artistWorkbook.createWorksheet({ name: 'Artists' });
75 | albumList.setData(this.originalData);
76 | 
77 |
78 |
79 | -------------------------------------------------------------------------------- /packages/demo/src/getting-started.ts: -------------------------------------------------------------------------------- 1 | export default class Example {} 2 | -------------------------------------------------------------------------------- /packages/demo/src/images/strawberry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghiscoding/excel-builder-vanilla/6f1a1b2e655e6637dc6ff88867ea704a3bfc2072/packages/demo/src/images/strawberry.jpg -------------------------------------------------------------------------------- /packages/demo/src/images/strawberry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghiscoding/excel-builder-vanilla/6f1a1b2e655e6637dc6ff88867ea704a3bfc2072/packages/demo/src/images/strawberry.png -------------------------------------------------------------------------------- /packages/demo/src/index.d.ts: -------------------------------------------------------------------------------- 1 | export type {}; 2 | 3 | /** Vite image path import, i.e.: `import from "image-path?base64"` */ 4 | declare global { 5 | declare module '*?base64' { 6 | const value: string; 7 | export = value; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/demo/src/main.html: -------------------------------------------------------------------------------- 1 | 29 | 30 |
31 |
32 |
33 | 34 |
35 | 36 |
37 |
38 |
39 | -------------------------------------------------------------------------------- /packages/demo/src/style.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:math'; 2 | 3 | $navbar-height: 56px; 4 | $side-menu-width: 250px; 5 | 6 | :root { 7 | --bs-nav-link-font-size: 14px; 8 | } 9 | 10 | .cloak { 11 | visibility: 'hidden'; 12 | } 13 | h2 .links { 14 | font-size: 18px; 15 | } 16 | 17 | .template-body { 18 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 19 | 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 20 | } 21 | 22 | .bd-title { 23 | font-size: 3rem; 24 | margin-top: 0.5rem; 25 | margin-bottom: 0.5rem; 26 | font-weight: 300; 27 | } 28 | 29 | .full-width { 30 | width: 100%; 31 | } 32 | 33 | .demo-subtitle { 34 | font-size: 1em; 35 | color: #757575; 36 | margin-bottom: 1.2em; 37 | } 38 | 39 | .bold { 40 | font-weight: bold; 41 | } 42 | .italic { 43 | font-style: italic; 44 | } 45 | .faded { 46 | opacity: 0.2; 47 | } 48 | .faded:hover { 49 | opacity: 0.5; 50 | } 51 | .font18 { 52 | font-size: 18px; 53 | } 54 | .btn { 55 | padding: 5px; 56 | } 57 | .btn-group-xs > .btn, 58 | .btn-xs { 59 | padding: 6px 2px; 60 | font-size: 0.875rem; 61 | line-height: 0.5; 62 | border-radius: 0.2rem; 63 | margin: 0 2px; 64 | font-size: 12px; 65 | } 66 | .body-content { 67 | margin-top: $navbar-height; 68 | } 69 | .subtitle { 70 | font-size: 0.875em; 71 | font-style: italic; 72 | color: grey; 73 | margin-bottom: 10px; 74 | } 75 | .faded { 76 | opacity: 0.2; 77 | } 78 | .faded:hover { 79 | opacity: 0.5; 80 | } 81 | 82 | .content-text section { 83 | margin-bottom: 30px; 84 | } 85 | 86 | /** Sidebar (left) and Content (right) */ 87 | @media (min-width: 1200px) { 88 | .panel-wm-content .container { 89 | width: calc(1170px - #{$side-menu-width}); 90 | } 91 | } 92 | 93 | .nav-item a { 94 | cursor: pointer; 95 | } 96 | .panel-wm { 97 | padding-top: $navbar-height; 98 | 99 | .nav-stacked { 100 | padding-bottom: 30px; 101 | 102 | .nav-item { 103 | width: 100%; 104 | } 105 | } 106 | 107 | .nav > li > a { 108 | cursor: pointer; 109 | padding: 8px; 110 | padding-left: 16px; 111 | border-radius: 0; 112 | } 113 | 114 | .navbar-vertical-label { 115 | font-size: 1.1rem; 116 | font-weight: 500; 117 | margin-left: 2px; 118 | } 119 | 120 | .panel-wm-content { 121 | margin-left: $side-menu-width; 122 | padding: 0 1rem; 123 | min-height: calc(100vh - #{$navbar-height}); 124 | } 125 | 126 | .panel-wm-left { 127 | position: fixed; 128 | z-index: 400; 129 | transition: left 0.15s; 130 | top: calc(#{$navbar-height}); 131 | bottom: 0; 132 | left: 0; 133 | background-color: #f5f5f5; 134 | transform: translate3d(0, 0, 0); 135 | border-right: 1px solid #d0d0d0; 136 | overflow-y: auto; 137 | width: $side-menu-width; 138 | } 139 | } 140 | 141 | .github-button-container { 142 | position: relative; 143 | top: -2px; 144 | // margin: 0 5px; 145 | } 146 | 147 | .w-35px { 148 | width: 35px; 149 | } 150 | -------------------------------------------------------------------------------- /packages/demo/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "resolveJsonModule": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "noEmit": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "skipLibCheck": true, 17 | "typeRoots": ["./node_modules/@types"] 18 | }, 19 | "include": ["src/**/*", "index.d.ts"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/demo/vite.config.mts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'node:fs'; 2 | import { defineConfig, type Plugin } from 'vite'; 3 | 4 | const base64Loader: Plugin = { 5 | name: 'base64-loader', 6 | transform(_: any, id: string) { 7 | const [path, query] = id.split('?'); 8 | if (query !== 'base64') return null; 9 | 10 | const data = readFileSync(path); 11 | const base64 = data.toString('base64'); 12 | 13 | return `export default '${base64}';`; 14 | }, 15 | }; 16 | 17 | export default defineConfig({ 18 | base: './', 19 | css: { 20 | preprocessorOptions: { 21 | scss: { 22 | quietDeps: true, 23 | }, 24 | }, 25 | }, 26 | server: { 27 | port: 3000, 28 | cors: true, 29 | open: true, 30 | host: 'localhost', 31 | }, 32 | optimizeDeps: { 33 | exclude: ['excel-builder-vanilla'], 34 | }, 35 | plugins: [base64Loader], 36 | }); 37 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla-types/.gitignore: -------------------------------------------------------------------------------- 1 | !dist -------------------------------------------------------------------------------- /packages/excel-builder-vanilla-types/.npmignore: -------------------------------------------------------------------------------- 1 | tsconfig.json 2 | tsconfig.tsbuildinfo 3 | node_modules 4 | CHANGELOG.md -------------------------------------------------------------------------------- /packages/excel-builder-vanilla-types/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | ## Visit the [Excel-Builder-Vanilla](https://github.com/ghiscoding/excel-builder-vanilla) GitHub project or take a look at the [Live Demo](https://ghiscoding.github.io/excel-builder-vanilla) 3 | 4 | All notable changes to this project will be documented in this file. 5 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 6 | 7 | ## [4.0.1](https://github.com/ghiscoding/excel-builder-vanilla/compare/v4.0.0...v4.0.1) (2025-04-21) 8 | 9 | ### Bug Fixes 10 | 11 | * use correct export entry ([d33190f](https://github.com/ghiscoding/excel-builder-vanilla/commit/d33190f5a72cc9495ea1ec8dcf393fbe2190f5db)) - by @ghiscoding 12 | 13 | ## [4.0.0](https://github.com/ghiscoding/excel-builder-vanilla/compare/v3.1.0...v4.0.0) (2025-04-12) 14 | 15 | ### ⚠ BREAKING CHANGES 16 | 17 | * build as ESM-Only, drop CJS 18 | 19 | ### Features 20 | 21 | * build as ESM-Only, drop CJS ([ee22a7b](https://github.com/ghiscoding/excel-builder-vanilla/commit/ee22a7bfa6f3cec1c324aca85dd97b2fb2aef027)) - by @ghiscoding 22 | 23 | ## [3.1.0](https://github.com/ghiscoding/excel-builder-vanilla/compare/v3.0.14...v3.1.0) (2025-03-05) 24 | 25 | **Note:** Version bump only for package @excel-builder-vanilla/types 26 | 27 | ## [3.0.14](https://github.com/ghiscoding/excel-builder-vanilla/compare/v3.0.13...v3.0.14) (2024-10-13) 28 | 29 | ### Bug Fixes 30 | 31 | * run attw and use correct index file entries ([19c3e99](https://github.com/ghiscoding/excel-builder-vanilla/commit/19c3e99c2963a5dc5849ba2c395ae90f7cf89d03)) - by @ghiscoding 32 | 33 | ## [3.0.13](https://github.com/ghiscoding/excel-builder-vanilla/compare/v3.0.12...v3.0.13) (2024-10-13) 34 | 35 | ### Bug Fixes 36 | 37 | * type package add back type module ([ce77971](https://github.com/ghiscoding/excel-builder-vanilla/commit/ce77971412e8978873d3e3ef19240a74480d9f0a)) - by @ghiscoding 38 | 39 | ## [3.0.12](https://github.com/ghiscoding/excel-builder-vanilla/compare/v3.0.11...v3.0.12) (2024-10-11) 40 | 41 | ### Bug Fixes 42 | 43 | * remove tsup, replace with Vite + dts-bundle-generator ([2f8431f](https://github.com/ghiscoding/excel-builder-vanilla/commit/2f8431f27d18d0eaddbf53bdb16f9b649cb9a414)) - by @ghiscoding 44 | 45 | ## [3.0.11](https://github.com/ghiscoding/excel-builder-vanilla/compare/v3.0.10...v3.0.11) (2024-10-05) 46 | 47 | ### Bug Fixes 48 | 49 | * add missing interfaces export ([fb67ae6](https://github.com/ghiscoding/excel-builder-vanilla/commit/fb67ae6de79ce2f7ae37afcf680b81e195b37eb5)) - by @ghiscoding 50 | 51 | ## [3.0.10](https://github.com/ghiscoding/excel-builder-vanilla/compare/v3.0.9...v3.0.10) (2024-10-05) 52 | 53 | ### Bug Fixes 54 | 55 | * avoid barrel files, keep only 1 entry file ([c9f34a0](https://github.com/ghiscoding/excel-builder-vanilla/commit/c9f34a092dec3db3dc446f5b4a0f0db1dcec9522)) - by @ghiscoding 56 | 57 | ## [3.0.9](https://github.com/ghiscoding/excel-builder-vanilla/compare/v3.0.8...v3.0.9) (2024-10-04) 58 | 59 | ### Bug Fixes 60 | 61 | * full CJS/ESM hybrid support with tsup ([c5e5349](https://github.com/ghiscoding/excel-builder-vanilla/commit/c5e53497c2b268dfedccab1018490f5484b3d335)) - by @ghiscoding 62 | 63 | ## [3.0.8](https://github.com/ghiscoding/excel-builder-vanilla/compare/v3.0.7...v3.0.8) (2024-10-03) 64 | 65 | **Note:** Version bump only for package @excel-builder-vanilla/types 66 | 67 | ## [3.0.7](https://github.com/ghiscoding/excel-builder-vanilla/compare/v3.0.6...v3.0.7) (2024-09-06) 68 | 69 | ### Bug Fixes 70 | 71 | * include all d.ts files for types pkg to detect change ([3031593](https://github.com/ghiscoding/excel-builder-vanilla/commit/3031593f19e5b8d231885029367992551d998479)) - by @ghiscoding 72 | 73 | ## [3.0.4](https://github.com/ghiscoding/excel-builder-vanilla/compare/v3.0.3...v3.0.4) (2024-09-03) 74 | 75 | **Note:** Version bump only for package @excel-builder-vanilla/types 76 | 77 | ## [3.0.3](https://github.com/ghiscoding/excel-builder-vanilla/compare/v3.0.2...v3.0.3) (2024-08-29) 78 | 79 | **Note:** Version bump only for package @excel-builder-vanilla/types 80 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla-types/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009-present Stephen Liberty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla-types/README.md: -------------------------------------------------------------------------------- 1 | # @excel-builder-vanilla/types 2 | 3 | This package contains only the shared types and interfaces of `excel-builder-vanilla`'s package. 4 | 5 | A use-case for `@excel-builder-vanilla/types` is when you want to import `excel-builder-vanilla`'s types in order to create custom interfaces **but** without importing the entire `excel-builder-vanilla` package. This was mainly created so that we can use it in Slickgrid-Universal as potential options in the grid options interface but still keep the Excel Export as an optional install, so importing only the types is much smaller for users who will never install the Excel Export. 6 | 7 | ```ts 8 | import type { ExcelStyleInstruction, Worksheet, Workbook } from '@excel-builder-vanilla/types'; 9 | 10 | export interface ExcelExportOption { 11 | columnHeaderStyle?: ExcelStyleInstruction; 12 | customExcelHeader?: (workbook: Workbook, sheet: Worksheet) => void; 13 | 14 | // .... 15 | } 16 | ``` 17 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla-types/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": false, 3 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", 4 | "linter": { 5 | "rules": { 6 | "correctness": { 7 | "useImportExtensions": "off" 8 | }, 9 | "style": { 10 | "noParameterAssign": "error", 11 | "useAsConstAssertion": "error", 12 | "useDefaultParameterLast": "error", 13 | "useEnumInitializers": "error", 14 | "useSelfClosingElements": "error", 15 | "useSingleVarDeclarator": "error", 16 | "noUnusedTemplateLiteral": "error", 17 | "useNumberNamespace": "error", 18 | "noInferrableTypes": "error", 19 | "noUselessElse": "error" 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla-types/dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; -------------------------------------------------------------------------------- /packages/excel-builder-vanilla-types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@excel-builder-vanilla/types", 3 | "version": "4.0.1", 4 | "description": "excel-builder-vanilla types", 5 | "author": "Stephen Liberty", 6 | "contributors": [ 7 | { 8 | "name": "Ghislain B." 9 | } 10 | ], 11 | "homepage": "https://github.com/ghiscoding/excel-builder-vanilla", 12 | "bugs": { 13 | "url": "https://github.com/ghiscoding/excel-builder-vanilla/issues" 14 | }, 15 | "license": "MIT", 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/ghiscoding/excel-builder-vanilla.git", 19 | "directory": "packages/excel-builder-vanilla-types" 20 | }, 21 | "publishConfig": { 22 | "access": "public" 23 | }, 24 | "type": "module", 25 | "main": "./dist/index.js", 26 | "exports": { 27 | ".": { 28 | "types": "./dist/index.d.ts", 29 | "default": "./dist/index.js" 30 | }, 31 | "./package.json": "./package.json" 32 | }, 33 | "types": "./dist/index.d.ts", 34 | "sideEffects": false, 35 | "scripts": { 36 | "are-type-wrong": "pnpx @arethetypeswrong/cli --pack ." 37 | }, 38 | "funding": { 39 | "type": "ko_fi", 40 | "url": "https://ko-fi.com/ghiscoding" 41 | }, 42 | "dependencies": { 43 | "fflate": "^0.8.2" 44 | } 45 | } -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/.npmignore: -------------------------------------------------------------------------------- 1 | tsconfig.json 2 | tsconfig.tsbuildinfo 3 | node_modules 4 | CHANGELOG.md 5 | vite.config.mts 6 | __tests__ -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009-present Stephen Liberty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/README.md: -------------------------------------------------------------------------------- 1 | # Excel-Builder-Vanilla 2 | 3 | ## Documentation 4 | 5 | 📘 [Documentation](https://ghiscoding.gitbook.io/excel-builder-vanilla/) website powered by GitBook 6 | 7 | ## Live Demo 8 | 9 | Available [**Live demo**](https://ghiscoding.github.io/excel-builder-vanilla/) which displays all available options/methods. 10 | 11 | ## Installation 12 | 13 | ```sh 14 | npm install excel-builder-vanilla 15 | ``` 16 | 17 | The project offers 3 different bundle types, choose the best for your use case 18 | 1. ESM: to `import from` (preferred) 19 | 2. IIFE: standalone script with `ExcelBuilder` available on the `window` object 20 | 21 | ```ts 22 | // ESM - npm install 23 | import { createWorksheet } from 'excel-builder-vanilla'; 24 | 25 | // IIFE - CDN 26 | 27 | 30 | ``` 31 | 32 | ### Basic Usage 33 | 34 | ```ts 35 | import { Workbook, downloadExcelFile } from 'excel-builder-vanilla'; 36 | 37 | const originalData = [ 38 | ['Artist', 'Album', 'Price'], 39 | ['Buckethead', 'Albino Slug', 8.99], 40 | ['Buckethead', 'Electric Tears', 13.99], 41 | ['Buckethead', 'Colma', 11.34], 42 | ]; 43 | const artistWorkbook = new Workbook(); 44 | const albumList = artistWorkbook.createWorksheet({ name: 'Artists' }); 45 | albumList.setData(originalData); 46 | artistWorkbook.addWorksheet(albumList); 47 | 48 | downloadExcelFile(artistWorkbook, 'Artist WB.xlsx'); 49 | ``` 50 | 51 | ## Changelog 52 | 53 | [CHANGELOG](https://github.com/ghiscoding/excel-builder-vanilla/blob/main/packages/excel-builder-vanilla/CHANGELOG.md) 54 | 55 | ## LICENSE 56 | 57 | [MIT License](https://github.com/ghiscoding/excel-builder-vanilla/blob/main/LICENSE.md) 58 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/copy-types.mjs: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from 'node:fs'; 2 | import { copyfiles } from 'native-copyfiles'; 3 | 4 | // copy all types (d.ts) files with same folder structures 5 | const source = 'dist/**/*.d.ts'; 6 | const destination = '../excel-builder-vanilla-types'; 7 | copyfiles([source, destination], { stat: true }, err => { 8 | if (err) { 9 | console.error(err); 10 | } else { 11 | // all good, next step, create JS entry file 12 | writeFileSync(`${destination}/dist/index.js`, `'use strict';`); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/index.d.ts: -------------------------------------------------------------------------------- 1 | export type {}; 2 | 3 | declare global { 4 | interface Window { 5 | ExcelBuilder: any; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "excel-builder-vanilla", 3 | "version": "4.0.1", 4 | "description": "An easy way of building Excel files with javascript", 5 | "keywords": [ 6 | "excel", 7 | "javascript", 8 | "xls", 9 | "xlsx", 10 | "spreadsheet" 11 | ], 12 | "author": "Stephen Liberty", 13 | "contributors": [ 14 | { 15 | "name": "Ghislain B." 16 | } 17 | ], 18 | "homepage": "https://github.com/ghiscoding/excel-builder-vanilla", 19 | "bugs": { 20 | "url": "https://github.com/ghiscoding/excel-builder-vanilla/issues" 21 | }, 22 | "license": "MIT", 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/ghiscoding/excel-builder-vanilla.git", 26 | "directory": "packages/excel-builder-vanilla" 27 | }, 28 | "publishConfig": { 29 | "access": "public" 30 | }, 31 | "type": "module", 32 | "main": "./dist/index.js", 33 | "exports": { 34 | ".": { 35 | "types": "./dist/index.d.ts", 36 | "default": "./dist/index.js" 37 | }, 38 | "./package.json": "./package.json" 39 | }, 40 | "types": "./dist/index.d.ts", 41 | "sideEffects": false, 42 | "funding": { 43 | "type": "ko_fi", 44 | "url": "https://ko-fi.com/ghiscoding" 45 | }, 46 | "scripts": { 47 | "are-type-wrong": "pnpx @arethetypeswrong/cli --pack .", 48 | "clean": "rimraf dist ../excel-builder-vanilla-types/dist", 49 | "dev:init": "vite build", 50 | "dev": "vite build --watch", 51 | "build": "pnpm clean && vite build && pnpm build:dts && pnpm copy:types", 52 | "build:dts": "dts-bundle-generator -o dist/index.d.ts src/index.ts", 53 | "copy:types": "node copy-types.mjs" 54 | }, 55 | "dependencies": { 56 | "fflate": "catalog:" 57 | }, 58 | "devDependencies": { 59 | "dts-bundle-generator": "^9.5.1", 60 | "native-copyfiles": "^1.2.0", 61 | "typescript": "catalog:", 62 | "vite": "catalog:" 63 | } 64 | } -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/Excel/Drawing/AbsoluteAnchor.ts: -------------------------------------------------------------------------------- 1 | import type { AnchorOption } from './Drawing.js'; 2 | import { Util } from '../Util.js'; 3 | import type { XMLDOM } from '../XMLDOM.js'; 4 | 5 | /** 6 | * 7 | * @param {Object} config 8 | * @param {Number} config.x X offset in EMU's 9 | * @param {Number} config.y Y offset in EMU's 10 | * @param {Number} config.width Width in EMU's 11 | * @param {Number} config.height Height in EMU's 12 | * @constructor 13 | */ 14 | export class AbsoluteAnchor { 15 | x: number | null = null; 16 | y: number | null = null; 17 | width: number | null = null; 18 | height: number | null = null; 19 | 20 | constructor(config: AnchorOption) { 21 | if (config) { 22 | this.setPos(config.x, config.y); 23 | this.setDimensions(config.width, config.height); 24 | } 25 | } 26 | 27 | /** 28 | * Sets the X and Y offsets. 29 | * 30 | * @param {Number} x 31 | * @param {Number} y 32 | * @returns {undefined} 33 | */ 34 | setPos(x: number, y: number) { 35 | this.x = x; 36 | this.y = y; 37 | } 38 | 39 | /** 40 | * Sets the width and height of the image. 41 | * 42 | * @param {Number} width 43 | * @param {Number} height 44 | * @returns {undefined} 45 | */ 46 | setDimensions(width: number, height: number) { 47 | this.width = width; 48 | this.height = height; 49 | } 50 | 51 | toXML(xmlDoc: XMLDOM, content: any) { 52 | const root = Util.createElement(xmlDoc, 'xdr:absoluteAnchor'); 53 | const pos = Util.createElement(xmlDoc, 'xdr:pos'); 54 | pos.setAttribute('x', this.x); 55 | pos.setAttribute('y', this.y); 56 | root.appendChild(pos); 57 | 58 | const dimensions = Util.createElement(xmlDoc, 'xdr:ext'); 59 | dimensions.setAttribute('cx', this.width); 60 | dimensions.setAttribute('cy', this.height); 61 | root.appendChild(dimensions); 62 | 63 | root.appendChild(content); 64 | 65 | root.appendChild(Util.createElement(xmlDoc, 'xdr:clientData')); 66 | return root; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/Excel/Drawing/Chart.ts: -------------------------------------------------------------------------------- 1 | export class Chart {} 2 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/Excel/Drawing/Drawing.ts: -------------------------------------------------------------------------------- 1 | import { uniqueId } from '../../utilities/uniqueId.js'; 2 | import { AbsoluteAnchor } from './AbsoluteAnchor.js'; 3 | import { OneCellAnchor } from './OneCellAnchor.js'; 4 | import { TwoCellAnchor } from './TwoCellAnchor.js'; 5 | // import { Picture } from './Picture.js'; 6 | 7 | export interface AnchorOption { 8 | /** X offset in EMU's */ 9 | x: number; 10 | /** Y offset in EMU's */ 11 | y: number; 12 | xOff?: boolean; 13 | yOff?: boolean; 14 | /** Width in EMU's */ 15 | height: number; 16 | /** Height in EMU's */ 17 | width: number; 18 | drawing?: Drawing; 19 | } 20 | 21 | export interface DualAnchorOption { 22 | to: AnchorOption; 23 | from: AnchorOption; 24 | drawing?: Drawing; 25 | } 26 | 27 | /** 28 | * This is mostly a global spot where all of the relationship managers can get and set 29 | * path information from/to. 30 | * @module Excel/Drawing 31 | */ 32 | export class Drawing { 33 | anchor!: AbsoluteAnchor | OneCellAnchor | TwoCellAnchor; 34 | id = uniqueId('Drawing'); 35 | 36 | /** 37 | * 38 | * @param {String} type Can be 'absoluteAnchor', 'oneCellAnchor', or 'twoCellAnchor'. 39 | * @param {Object} config Shorthand - pass the created anchor coords that can normally be used to construct it. 40 | * @returns {Anchor} 41 | */ 42 | // TODO: couldn't get function override working, but hopefully in the future 43 | // createAnchor(type: 'absoluteAnchor', config: AnchorOption): AbsoluteAnchor; 44 | // createAnchor(type: 'oneCellAnchor', config: AnchorOption): OneCellAnchor; 45 | // createAnchor(type: 'twoCellAnchor', config: DualAnchorOption): TwoCellAnchor; 46 | createAnchor(type: 'absoluteAnchor' | 'oneCellAnchor' | 'twoCellAnchor', config: any): AbsoluteAnchor | OneCellAnchor | TwoCellAnchor { 47 | config ??= {} as AnchorOption | DualAnchorOption; 48 | config.drawing = this; 49 | switch (type) { 50 | case 'absoluteAnchor': 51 | this.anchor = new AbsoluteAnchor(config as AnchorOption); 52 | break; 53 | case 'oneCellAnchor': 54 | this.anchor = new OneCellAnchor(config as AnchorOption); 55 | break; 56 | case 'twoCellAnchor': 57 | this.anchor = new TwoCellAnchor(config as DualAnchorOption); 58 | break; 59 | } 60 | return this.anchor; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/Excel/Drawing/OneCellAnchor.ts: -------------------------------------------------------------------------------- 1 | import type { AnchorOption } from './Drawing.js'; 2 | import { Util } from '../Util.js'; 3 | import type { XMLDOM } from '../XMLDOM.js'; 4 | 5 | /** 6 | * 7 | * @param {Object} config 8 | * @param {Number} config.x The cell column number that the top left of the picture will start in 9 | * @param {Number} config.y The cell row number that the top left of the picture will start in 10 | * @param {Number} config.width Width in EMU's 11 | * @param {Number} config.height Height in EMU's 12 | * @constructor 13 | */ 14 | export class OneCellAnchor { 15 | x: number | null = null; 16 | y: number | null = null; 17 | xOff: boolean | null = null; 18 | yOff: boolean | null = null; 19 | width: number | null = null; 20 | height: number | null = null; 21 | 22 | constructor(config: AnchorOption) { 23 | if (config) { 24 | this.setPos(config.x, config.y, config.xOff, config.yOff); 25 | this.setDimensions(config.width, config.height); 26 | } 27 | } 28 | 29 | setPos(x: number, y: number, xOff?: boolean, yOff?: boolean) { 30 | this.x = x; 31 | this.y = y; 32 | if (xOff !== undefined) { 33 | this.xOff = xOff; 34 | } 35 | if (yOff !== undefined) { 36 | this.yOff = yOff; 37 | } 38 | } 39 | 40 | setDimensions(width: number, height: number) { 41 | this.width = width; 42 | this.height = height; 43 | } 44 | 45 | toXML(xmlDoc: XMLDOM, content: any) { 46 | const root = Util.createElement(xmlDoc, 'xdr:oneCellAnchor'); 47 | const from = Util.createElement(xmlDoc, 'xdr:from'); 48 | const fromCol = Util.createElement(xmlDoc, 'xdr:col'); 49 | fromCol.appendChild(xmlDoc.createTextNode(String(this.x))); 50 | const fromColOff = Util.createElement(xmlDoc, 'xdr:colOff'); 51 | fromColOff.appendChild(xmlDoc.createTextNode(String(this.xOff || 0))); 52 | const fromRow = Util.createElement(xmlDoc, 'xdr:row'); 53 | fromRow.appendChild(xmlDoc.createTextNode(String(this.y))); 54 | const fromRowOff = Util.createElement(xmlDoc, 'xdr:rowOff'); 55 | fromRowOff.appendChild(xmlDoc.createTextNode(String(this.yOff || 0))); 56 | from.appendChild(fromCol); 57 | from.appendChild(fromColOff); 58 | from.appendChild(fromRow); 59 | from.appendChild(fromRowOff); 60 | 61 | root.appendChild(from); 62 | 63 | const dimensions = Util.createElement(xmlDoc, 'xdr:ext'); 64 | dimensions.setAttribute('cx', String(this.width)); 65 | dimensions.setAttribute('cy', String(this.height)); 66 | root.appendChild(dimensions); 67 | root.appendChild(content); 68 | 69 | root.appendChild(Util.createElement(xmlDoc, 'xdr:clientData')); 70 | return root; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/Excel/Drawing/Picture.ts: -------------------------------------------------------------------------------- 1 | import { Drawing } from './Drawing.js'; 2 | import { uniqueId } from '../../utilities/uniqueId.js'; 3 | import { Util } from '../Util.js'; 4 | import type { MediaMeta } from '../Workbook.js'; 5 | import type { XMLDOM } from '../XMLDOM.js'; 6 | 7 | export class Picture extends Drawing { 8 | id = uniqueId('Picture'); 9 | pictureId = Util.uniqueId('Picture'); 10 | fill: any = {}; 11 | mediaData: MediaMeta | null = null; 12 | description = ''; 13 | 14 | constructor() { 15 | super(); 16 | // Picture.prototype = new Drawing(); 17 | this.id = uniqueId('Picture'); 18 | this.pictureId = Util.uniqueId('Picture'); 19 | } 20 | 21 | setMedia(mediaRef: MediaMeta) { 22 | this.mediaData = mediaRef; 23 | } 24 | 25 | setDescription(description: string) { 26 | this.description = description; 27 | } 28 | 29 | setFillType(type: string) { 30 | this.fill.type = type; 31 | } 32 | 33 | setFillConfig(config: any) { 34 | Object.assign(this.fill, config); 35 | } 36 | 37 | getMediaType(): keyof typeof Util.schemas { 38 | return 'image'; 39 | } 40 | 41 | getMediaData() { 42 | return this.mediaData as MediaMeta; 43 | } 44 | 45 | setRelationshipId(rId: string) { 46 | this.mediaData!.rId = rId; 47 | } 48 | 49 | toXML(xmlDoc: XMLDOM) { 50 | const pictureNode = Util.createElement(xmlDoc, 'xdr:pic'); 51 | 52 | const nonVisibleProperties = Util.createElement(xmlDoc, 'xdr:nvPicPr'); 53 | 54 | const nameProperties = Util.createElement(xmlDoc, 'xdr:cNvPr', [ 55 | ['id', this.pictureId], 56 | ['name', this.mediaData!.fileName], 57 | ['descr', this.description || ''], 58 | ]); 59 | nonVisibleProperties.appendChild(nameProperties); 60 | const nvPicProperties = Util.createElement(xmlDoc, 'xdr:cNvPicPr'); 61 | nvPicProperties.appendChild( 62 | Util.createElement(xmlDoc, 'a:picLocks', [ 63 | ['noChangeAspect', '1'], 64 | ['noChangeArrowheads', '1'], 65 | ]), 66 | ); 67 | nonVisibleProperties.appendChild(nvPicProperties); 68 | pictureNode.appendChild(nonVisibleProperties); 69 | const pictureFill = Util.createElement(xmlDoc, 'xdr:blipFill'); 70 | pictureFill.appendChild( 71 | Util.createElement(xmlDoc, 'a:blip', [ 72 | ['xmlns:r', Util.schemas.relationships], 73 | ['r:embed', this.mediaData!.rId], 74 | ]), 75 | ); 76 | pictureFill.appendChild(Util.createElement(xmlDoc, 'a:srcRect')); 77 | const stretch = Util.createElement(xmlDoc, 'a:stretch'); 78 | stretch.appendChild(Util.createElement(xmlDoc, 'a:fillRect')); 79 | pictureFill.appendChild(stretch); 80 | pictureNode.appendChild(pictureFill); 81 | 82 | const shapeProperties = Util.createElement(xmlDoc, 'xdr:spPr', [['bwMode', 'auto']]); 83 | 84 | const transform2d = Util.createElement(xmlDoc, 'a:xfrm'); 85 | shapeProperties.appendChild(transform2d); 86 | 87 | const presetGeometry = Util.createElement(xmlDoc, 'a:prstGeom', [['prst', 'rect']]); 88 | shapeProperties.appendChild(presetGeometry); 89 | 90 | pictureNode.appendChild(shapeProperties); 91 | // 92 | // 93 | // 94 | // 95 | // 96 | // 97 | // 98 | // 99 | // 100 | // 101 | // 102 | // 103 | // 104 | // 105 | // 106 | // 107 | // 108 | // 109 | // 110 | // 111 | 112 | // TODO: add back extends Drawing and the following 113 | return this.anchor.toXML(xmlDoc, pictureNode); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/Excel/Drawing/TwoCellAnchor.ts: -------------------------------------------------------------------------------- 1 | import type { DualAnchorOption } from './Drawing.js'; 2 | import { Util } from '../Util.js'; 3 | import type { XMLDOM } from '../XMLDOM.js'; 4 | 5 | export class TwoCellAnchor { 6 | from: any = { xOff: 0, yOff: 0 }; 7 | to: any = { xOff: 0, yOff: 0 }; 8 | 9 | constructor(config: DualAnchorOption) { 10 | if (config) { 11 | this.setFrom(config.from.x, config.from.y, config.to.xOff, config.to.yOff); 12 | this.setTo(config.to.x, config.to.y, config.to.xOff, config.to.yOff); 13 | } 14 | } 15 | 16 | setFrom(x: number, y: number, xOff?: boolean, yOff?: boolean) { 17 | this.from.x = x; 18 | this.from.y = y; 19 | if (xOff !== undefined) { 20 | this.from.xOff = xOff; 21 | } 22 | if (yOff !== undefined) { 23 | this.from.yOff = xOff; 24 | } 25 | } 26 | 27 | setTo(x: number, y: number, xOff?: boolean, yOff?: boolean) { 28 | this.to.x = x; 29 | this.to.y = y; 30 | if (xOff !== undefined) { 31 | this.to.xOff = xOff; 32 | } 33 | if (yOff !== undefined) { 34 | this.to.yOff = xOff; 35 | } 36 | } 37 | 38 | toXML(xmlDoc: XMLDOM, content: any) { 39 | const root = Util.createElement(xmlDoc, 'xdr:twoCellAnchor'); 40 | 41 | const from = Util.createElement(xmlDoc, 'xdr:from'); 42 | const fromCol = Util.createElement(xmlDoc, 'xdr:col'); 43 | fromCol.appendChild(xmlDoc.createTextNode(this.from.x)); 44 | const fromColOff = Util.createElement(xmlDoc, 'xdr:colOff'); 45 | fromColOff.appendChild(xmlDoc.createTextNode(this.from.xOff)); 46 | const fromRow = Util.createElement(xmlDoc, 'xdr:row'); 47 | fromRow.appendChild(xmlDoc.createTextNode(this.from.y)); 48 | const fromRowOff = Util.createElement(xmlDoc, 'xdr:rowOff'); 49 | fromRowOff.appendChild(xmlDoc.createTextNode(this.from.yOff)); 50 | 51 | from.appendChild(fromCol); 52 | from.appendChild(fromColOff); 53 | from.appendChild(fromRow); 54 | from.appendChild(fromRowOff); 55 | 56 | const to = Util.createElement(xmlDoc, 'xdr:to'); 57 | const toCol = Util.createElement(xmlDoc, 'xdr:col'); 58 | toCol.appendChild(xmlDoc.createTextNode(this.to.x)); 59 | const toColOff = Util.createElement(xmlDoc, 'xdr:colOff'); 60 | toColOff.appendChild(xmlDoc.createTextNode(this.from.xOff)); 61 | const toRow = Util.createElement(xmlDoc, 'xdr:row'); 62 | toRow.appendChild(xmlDoc.createTextNode(this.to.y)); 63 | const toRowOff = Util.createElement(xmlDoc, 'xdr:rowOff'); 64 | toRowOff.appendChild(xmlDoc.createTextNode(this.from.yOff)); 65 | 66 | to.appendChild(toCol); 67 | to.appendChild(toColOff); 68 | to.appendChild(toRow); 69 | to.appendChild(toRowOff); 70 | 71 | root.appendChild(from); 72 | root.appendChild(to); 73 | 74 | root.appendChild(content); 75 | 76 | root.appendChild(Util.createElement(xmlDoc, 'xdr:clientData')); 77 | return root; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/Excel/Drawings.ts: -------------------------------------------------------------------------------- 1 | import { uniqueId } from '../utilities/uniqueId.js'; 2 | import type { Drawing } from './Drawing/Drawing.js'; 3 | import type { Picture } from './Drawing/Picture.js'; 4 | import { RelationshipManager } from './RelationshipManager.js'; 5 | import { Util } from './Util.js'; 6 | 7 | /** 8 | * @module Excel/Drawings 9 | */ 10 | 11 | export class Drawings { 12 | drawings: (Drawing | Picture)[] = []; 13 | relations = new RelationshipManager(); 14 | id = uniqueId('Drawings'); 15 | 16 | /** 17 | * Adds a drawing (more likely a subclass of a Drawing) to the 'Drawings' for a particular worksheet. 18 | * 19 | * @param {Drawing} drawing 20 | * @returns {undefined} 21 | */ 22 | addDrawing(drawing: Drawing) { 23 | this.drawings.push(drawing); 24 | } 25 | 26 | getCount() { 27 | return this.drawings.length; 28 | } 29 | 30 | toXML() { 31 | const doc = Util.createXmlDoc(Util.schemas.spreadsheetDrawing, 'xdr:wsDr'); 32 | const drawings = doc.documentElement; 33 | drawings.setAttribute('xmlns:a', Util.schemas.drawing); 34 | drawings.setAttribute('xmlns:r', Util.schemas.relationships); 35 | drawings.setAttribute('xmlns:xdr', Util.schemas.spreadsheetDrawing); 36 | 37 | for (let i = 0, l = this.drawings.length; i < l; i++) { 38 | let rId = this.relations.getRelationshipId((this.drawings[i] as Picture).getMediaData()); 39 | if (!rId) { 40 | rId = this.relations.addRelation((this.drawings[i] as Picture).getMediaData(), (this.drawings[i] as Picture).getMediaType()); //chart 41 | } 42 | (this.drawings[i] as Picture).setRelationshipId(rId); 43 | drawings.appendChild((this.drawings[i] as Picture).toXML(doc)); 44 | } 45 | return doc; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/Excel/Pane.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module Excel/Pane 3 | * 4 | * https://msdn.microsoft.com/en-us/library/documentformat.openxml.spreadsheet.pane%28v=office.14%29.aspx 5 | */ 6 | 7 | import type { XMLDOM } from './XMLDOM.js'; 8 | 9 | export class Pane { 10 | /* 11 | Possible Values: 12 | null 13 | split Split 14 | frozen Frozen 15 | frozenSplit Frozen Split 16 | http://www.datypic.com/sc/ooxml/t-ssml_ST_PaneState.html 17 | */ 18 | state: null | 'split' | 'frozen' | 'frozenSplit' = null; 19 | xSplit: number | null = null; 20 | ySplit: number | null = null; 21 | activePane = 'bottomRight'; 22 | topLeftCell: number | string | null = null; 23 | _freezePane!: { xSplit: number; ySplit: number; cell: string }; 24 | 25 | freezePane(column: number, row: number, cell: string) { 26 | this._freezePane = { xSplit: column, ySplit: row, cell }; 27 | } 28 | 29 | exportXML(doc: XMLDOM) { 30 | const pane = doc.createElement('pane'); 31 | 32 | if (this.state !== null) { 33 | pane.setAttribute('xSplit', this._freezePane.xSplit); 34 | pane.setAttribute('ySplit', this._freezePane.ySplit); 35 | pane.setAttribute('topLeftCell', this._freezePane.cell); 36 | pane.setAttribute('activePane', 'bottomRight'); 37 | pane.setAttribute('state', 'frozen'); 38 | } 39 | return pane; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/Excel/Paths.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is mostly a global spot where all of the relationship managers can get and set 3 | * path information from/to. 4 | * @module Excel/Paths 5 | */ 6 | export const Paths: { 7 | [path: string]: string; 8 | } = {}; 9 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/Excel/Positioning.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts pixel sizes to 'EMU's, which is what Open XML uses. 3 | * 4 | * @todo clean this up. Code borrowed from http://polymathprogrammer.com/2009/10/22/english-metric-units-and-open-xml/, 5 | * but not sure that it's going to be as accurate as it needs to be. 6 | * 7 | * @param int pixels 8 | * @returns int 9 | */ 10 | export class Positioning { 11 | static pixelsToEMUs(pixels: number) { 12 | return Math.round((pixels * 914400) / 96); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/Excel/RelationshipManager.ts: -------------------------------------------------------------------------------- 1 | import { uniqueId } from '../utilities/uniqueId.js'; 2 | import { Paths } from './Paths.js'; 3 | import { Util } from './Util.js'; 4 | 5 | type Relation = { 6 | [id: string]: { 7 | id: string; 8 | schema: string; 9 | object: any; 10 | data?: { 11 | id: number; 12 | schema: string; 13 | object: any; 14 | }; 15 | }; 16 | }; 17 | 18 | /** 19 | * @module Excel/RelationshipManager 20 | */ 21 | export class RelationshipManager { 22 | relations: Relation = {}; 23 | lastId = 1; 24 | 25 | constructor() { 26 | uniqueId('rId'); // priming 27 | } 28 | 29 | importData(data: { relations: Relation; lastId: number }) { 30 | this.relations = data.relations; 31 | this.lastId = data.lastId; 32 | } 33 | 34 | exportData() { 35 | return { 36 | relations: this.relations, 37 | lastId: this.lastId, 38 | }; 39 | } 40 | 41 | addRelation(object: { id: string }, type: keyof typeof Util.schemas) { 42 | this.relations[object.id] = { 43 | id: uniqueId('rId'), 44 | schema: Util.schemas[type], 45 | object, 46 | }; 47 | return this.relations[object.id].id; 48 | } 49 | 50 | getRelationshipId(object: { id: string }) { 51 | return this.relations[object.id] ? this.relations[object.id].id : null; 52 | } 53 | 54 | toXML() { 55 | const doc = Util.createXmlDoc(Util.schemas.relationshipPackage, 'Relationships'); 56 | const relationships = doc.documentElement; 57 | 58 | for (const [id, data] of Object.entries(this.relations)) { 59 | const relationship = Util.createElement(doc, 'Relationship', [ 60 | ['Id', data.id], 61 | ['Type', data.schema], 62 | ['Target', data.object.target || Paths[id]], 63 | ]); 64 | if (data.object.targetMode) { 65 | relationship.setAttribute('TargetMode', data.object.targetMode); 66 | } 67 | relationships.appendChild(relationship); 68 | } 69 | return doc; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/Excel/SharedStrings.ts: -------------------------------------------------------------------------------- 1 | import { uniqueId } from '../utilities/uniqueId.js'; 2 | import { Util } from './Util.js'; 3 | 4 | /** 5 | * @module Excel/SharedStrings 6 | */ 7 | export class SharedStrings { 8 | strings: { [key: string]: number } = {}; 9 | stringArray: string[] = []; 10 | id = uniqueId('SharedStrings'); 11 | 12 | /** 13 | * Adds a string to the shared string file, and returns the ID of the 14 | * string which can be used to reference it in worksheets. 15 | * 16 | * @param str {String} 17 | * @return int 18 | */ 19 | addString(str: string) { 20 | this.strings[str] = this.stringArray.length; 21 | this.stringArray[this.stringArray.length] = str; 22 | return this.strings[str]; 23 | } 24 | 25 | exportData() { 26 | return this.strings; 27 | } 28 | 29 | toXML() { 30 | const doc = Util.createXmlDoc(Util.schemas.spreadsheetml, 'sst'); 31 | const sharedStringTable = doc.documentElement; 32 | this.stringArray.reverse(); 33 | let l = this.stringArray.length; 34 | sharedStringTable.setAttribute('count', l); 35 | sharedStringTable.setAttribute('uniqueCount', l); 36 | 37 | const template = doc.createElement('si'); 38 | const templateValue = doc.createElement('t'); 39 | templateValue.appendChild(doc.createTextNode('--placeholder--')); 40 | template.appendChild(templateValue); 41 | const strings = this.stringArray; 42 | 43 | while (l--) { 44 | const clone = template.cloneNode(true); 45 | if (typeof strings[l] === 'string' && strings[l].match(/\s+/)) { 46 | clone.firstChild!.setAttribute('xml:space', 'preserve'); 47 | } 48 | clone.firstChild!.firstChild!.nodeValue = strings[l]; 49 | sharedStringTable.appendChild(clone); 50 | } 51 | 52 | return doc; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/Excel/SheetView.ts: -------------------------------------------------------------------------------- 1 | import { Pane } from './Pane.js'; 2 | import { Util } from './Util.js'; 3 | import type { XMLDOM } from './XMLDOM.js'; 4 | 5 | interface SheetViewOption { 6 | pane?: Pane; 7 | } 8 | 9 | /** 10 | * @module Excel/SheetView 11 | * https://msdn.microsoft.com/en-us/library/documentformat.openxml.spreadsheet.sheetview%28v=office.14%29.aspx 12 | * 13 | */ 14 | export class SheetView { 15 | pane: Pane; 16 | showZeros: boolean | null = null; // Default 17 | defaultGridColor: string | null = null; 18 | colorId: number | null = null; 19 | rightToLeft: boolean | null = null; 20 | showFormulas: boolean | null = null; 21 | showGridLines: boolean | null = null; 22 | showOutlineSymbols: boolean | null = null; 23 | showRowColHeaders: boolean | null = null; 24 | showRuler: boolean | null = null; 25 | showWhiteSpace: boolean | null = null; 26 | tabSelected: boolean | null = null; 27 | topLeftCell: boolean | null = null; 28 | viewType = null; // http://www.datypic.com/sc/ooxml/t-ssml_ST_SheetViewType.html 29 | windowProtection: boolean | null = null; 30 | zoomScale: boolean | null = null; 31 | zoomScaleNormal: any = null; 32 | zoomScalePageLayoutView: any = null; 33 | zoomScaleSheetLayoutView: any = null; 34 | 35 | constructor(config?: SheetViewOption) { 36 | const conf = config || {}; 37 | this.pane = conf.pane || new Pane(); 38 | } 39 | 40 | /** 41 | * Added froze pane 42 | * @param column - column number: 0, 1, 2 ... 43 | * @param row - row number: 0, 1, 2 ... 44 | * @param cell - 'A1' 45 | * @deprecated 46 | */ 47 | freezePane(column: number, row: number, cell: string) { 48 | this.pane.state = 'frozen'; 49 | this.pane.xSplit = column; 50 | this.pane.ySplit = row; 51 | this.pane.topLeftCell = cell; 52 | } 53 | 54 | exportXML(doc: XMLDOM) { 55 | const sheetViews = doc.createElement('sheetViews'); 56 | const sheetView = doc.createElement('sheetView'); 57 | 58 | Util.setAttributesOnDoc(sheetView, { 59 | //TODO apparent you can add 'book views'.. investigate what these are 60 | workbookViewId: 0, 61 | showZeros: { v: this.showZeros, type: Boolean }, 62 | defaultGridColor: { v: this.defaultGridColor, type: Boolean }, 63 | //TODO: I have no idea what this even is :\ 64 | colorId: this.colorId, 65 | rightToLeft: { v: this.rightToLeft, type: Boolean }, 66 | showFormulas: { v: this.showFormulas, type: Boolean }, 67 | showGridLines: { v: this.showGridLines, type: Boolean }, 68 | showOutlineSymbols: { v: this.showOutlineSymbols, type: Boolean }, 69 | showRowColHeaders: { v: this.showRowColHeaders, type: Boolean }, 70 | showRuler: { v: this.showRuler, type: Boolean }, 71 | showWhiteSpace: { v: this.showWhiteSpace, type: Boolean }, 72 | tabSelected: { v: this.tabSelected, type: Boolean }, 73 | viewType: this.viewType, 74 | windowProtection: { v: this.windowProtection, type: Boolean }, 75 | zoomScale: { v: this.zoomScale, type: Boolean }, 76 | zoomScaleNormal: this.zoomScaleNormal, 77 | zoomScalePageLayoutView: this.zoomScalePageLayoutView, 78 | zoomScaleSheetLayoutView: this.zoomScaleSheetLayoutView, 79 | }); 80 | 81 | sheetView.appendChild(this.pane.exportXML(doc)); 82 | 83 | sheetViews.appendChild(sheetView); 84 | return sheetViews; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/Excel/XMLDOM.ts: -------------------------------------------------------------------------------- 1 | import { htmlEscape } from '../utilities/escape.js'; 2 | 3 | type XMLNodeOption = { 4 | attributes?: { 5 | [key: string]: any; 6 | }; 7 | children?: XMLNode[]; 8 | nodeName: string; 9 | nodeValue?: string; 10 | type?: string; 11 | }; 12 | 13 | export class XMLDOM { 14 | documentElement: XMLNode; 15 | 16 | constructor(ns: string | null, rootNodeName: string) { 17 | this.documentElement = this.createElement(rootNodeName); 18 | this.documentElement.setAttribute('xmlns', ns); 19 | } 20 | 21 | createElement(name: string) { 22 | return new XMLNode({ 23 | nodeName: name, 24 | }); 25 | } 26 | 27 | createTextNode(text: string) { 28 | return new TextNode(text); 29 | } 30 | 31 | toString() { 32 | return this.documentElement.toString(); 33 | } 34 | 35 | static Node = { 36 | Create: (config: any) => { 37 | switch (config.type) { 38 | case 'XML': 39 | return new XMLNode(config); 40 | case 'TEXT': 41 | return new TextNode(config.nodeValue); 42 | default: 43 | return null; 44 | } 45 | }, 46 | }; 47 | } 48 | 49 | class TextNode { 50 | nodeValue: any; 51 | 52 | constructor(text: string) { 53 | this.nodeValue = text; 54 | } 55 | 56 | toJSON() { 57 | return { 58 | nodeValue: this.nodeValue, 59 | type: 'TEXT', 60 | }; 61 | } 62 | 63 | toString() { 64 | return htmlEscape(this.nodeValue); 65 | } 66 | } 67 | 68 | export class XMLNode { 69 | nodeName = ''; 70 | children: XMLNode[]; 71 | nodeValue: string; 72 | attributes: { [key: string]: any }; 73 | firstChild?: XMLNode; 74 | 75 | constructor(config: XMLNodeOption) { 76 | this.nodeName = config.nodeName; 77 | this.children = []; 78 | this.nodeValue = config.nodeValue || ''; 79 | this.attributes = {}; 80 | 81 | if (config.children) { 82 | for (let i = 0, l = config.children.length; i < l; i++) { 83 | this.appendChild(XMLDOM.Node.Create(config.children[i])); 84 | } 85 | } 86 | 87 | if (config.attributes) { 88 | for (const attr in config.attributes) { 89 | if (config.attributes.hasOwnProperty(attr)) { 90 | this.setAttribute(attr, config.attributes[attr]); 91 | } 92 | } 93 | } 94 | } 95 | 96 | toString() { 97 | let string = `<${this.nodeName}`; 98 | for (const attr in this.attributes) { 99 | if (this.attributes.hasOwnProperty(attr)) { 100 | string = `${string} ${attr}=\"${htmlEscape(this.attributes[attr])}\"`; 101 | } 102 | } 103 | 104 | let childContent = ''; 105 | for (let i = 0, l = this.children.length; i < l; i++) { 106 | childContent += this.children[i].toString(); 107 | } 108 | 109 | if (childContent) { 110 | string += `>${childContent}`; 111 | } else { 112 | string += '/>'; 113 | } 114 | 115 | return string; 116 | } 117 | 118 | toJSON() { 119 | const children: any[] = []; 120 | for (let i = 0, l = this.children.length; i < l; i++) { 121 | children.push(this.children[i].toJSON()); 122 | } 123 | return { 124 | nodeName: this.nodeName, 125 | children: children, 126 | nodeValue: this.nodeValue, 127 | attributes: this.attributes, 128 | type: 'XML', 129 | }; 130 | } 131 | 132 | setAttribute(name: string, val: any) { 133 | if (val === null) { 134 | delete this.attributes[name]; 135 | delete (this as any)[name]; 136 | return; 137 | } 138 | this.attributes[name] = val; 139 | (this as any)[name] = val; 140 | } 141 | 142 | appendChild(child: any) { 143 | this.children.push(child); 144 | this.firstChild = this.children[0]; 145 | } 146 | 147 | cloneNode(_deep?: boolean) { 148 | return new XMLNode(this.toJSON()); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/Excel/__tests__/Util.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { Util } from '../Util.js'; 4 | 5 | describe('utility functions', () => { 6 | describe('positionToLetterRef', () => { 7 | it('will give back the appropriate excel cell coordinate on an x/y position', () => { 8 | expect(Util.positionToLetterRef(1, 1)).toEqual('A1'); 9 | expect(Util.positionToLetterRef(5, 50)).toEqual('E50'); 10 | expect(Util.positionToLetterRef(50, 50)).toEqual('AX50'); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/Excel/__tests__/Worksheet.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { Worksheet } from '../Worksheet.js'; 4 | import { XMLDOM, XMLNode } from '../XMLDOM.js'; 5 | 6 | describe('Excel/Worksheet', () => { 7 | describe('compilePageDetailPiece', () => { 8 | it('will give back the appropriate string for an instruction object', () => { 9 | const io = { text: 'Hello there' }; 10 | const text = Worksheet.prototype.compilePageDetailPiece(io); 11 | const expected = '&"-,Regular"Hello there'; 12 | expect(text).toEqual(expected); 13 | }); 14 | 15 | it('will give back a string with underline instructions when an instruction object has underline set', () => { 16 | const io = { text: 'Hello there', underline: true }; 17 | const text = Worksheet.prototype.compilePageDetailPiece(io); 18 | const expected = '&"-,Regular"&UHello there'; 19 | expect(text).toEqual(expected); 20 | }); 21 | 22 | it('will give back a string with bold instructions when an instruction object has bold set', () => { 23 | const io = { text: 'Hello there', bold: true }; 24 | const text = Worksheet.prototype.compilePageDetailPiece(io); 25 | const expected = '&"-,Bold"Hello there'; 26 | expect(text).toEqual(expected); 27 | }); 28 | 29 | it('will give back a string with font instructions when an instruction object has a font set', () => { 30 | const io = { text: 'Hello there', font: 'Arial' }; 31 | const text = Worksheet.prototype.compilePageDetailPiece(io); 32 | const expected = '&"Arial,Regular"Hello there'; 33 | expect(text).toEqual(expected); 34 | }); 35 | 36 | it('will build each piece of an array of instructions and return the end result', () => { 37 | const io = [{ text: 'Hello there', font: 'Arial' }, ' - on ', { text: '5/7/9', underline: true }]; 38 | const text = Worksheet.prototype.compilePageDetailPiece(io); 39 | const expected = '&"Arial,Regular"Hello there&"-,Regular" - on &"-,Regular"&U5/7/9'; 40 | expect(text).toEqual(expected); 41 | }); 42 | }); 43 | 44 | describe('setPageMargin() method', () => { 45 | it('should call exportPageSettings() and expect updated margins', () => { 46 | const ws = new Worksheet({ name: 'worksheet1' }); 47 | 48 | ws.setPageMargin({ bottom: 120, footer: 21, header: 22, left: 0, right: 33, top: 8 }); 49 | 50 | const xmlDom = new XMLDOM('something', 'root'); 51 | const xmlNode = new XMLNode({ nodeName: 'some name' }); 52 | ws.exportPageSettings(xmlDom, xmlNode); 53 | expect(ws._margin).toEqual({ bottom: 120, footer: 21, header: 22, left: 0, right: 33, top: 8 }); 54 | }); 55 | }); 56 | 57 | describe('Orientation', () => { 58 | it('should call setPageOrientation() and expect updated margins', () => { 59 | const ws = new Worksheet({ name: 'worksheet1' }); 60 | 61 | ws.setPageOrientation('landscape'); 62 | 63 | expect(ws._orientation).toBe('landscape'); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/Excel/__tests__/XMLDOM.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { XMLDOM } from '../XMLDOM.js'; 4 | 5 | describe('basic DOM simulator for web workers', () => { 6 | describe('XMLDOM', () => { 7 | const nodeName = 'arbitraryNodeName'; 8 | const ns = 'arbitraryNS'; 9 | it('has a documentElement', () => { 10 | const d = new XMLDOM(ns, nodeName); 11 | expect(d.documentElement).toBeTruthy(); 12 | }); 13 | 14 | it('will have a properly named root node', () => { 15 | const d = new XMLDOM(ns, nodeName); 16 | expect(d.documentElement.nodeName).toEqual(nodeName); 17 | }); 18 | 19 | it('will have the correct namespace', () => { 20 | const d = new XMLDOM(ns, nodeName); 21 | expect((d.documentElement as any).xmlns).toEqual(ns); 22 | }); 23 | 24 | it('will have the appropriate content', () => { 25 | const d = new XMLDOM(ns, nodeName); 26 | 27 | const foo = d.createElement('foo'); 28 | foo.setAttribute('france', 'silly'); 29 | foo.setAttribute('britain', 'port'); 30 | const bar = d.createElement('bar'); 31 | bar.setAttribute('georgia', 'peaches'); 32 | const baz = d.createElement('baz'); 33 | foo.appendChild(bar); 34 | d.documentElement.appendChild(foo); 35 | d.documentElement.appendChild(baz); 36 | 37 | expect(d.toString()).toEqual( 38 | '', 39 | ); 40 | }); 41 | }); 42 | 43 | describe('XMLDOM.XMLNode', () => { 44 | const nodeName = 'arbitraryNodeName'; 45 | const ns = 'arbitraryNS'; 46 | 47 | it('will clone properly', () => { 48 | const d = new XMLDOM(ns, nodeName); 49 | const foo = d.createElement('foo'); 50 | const bar = d.createElement('bar'); 51 | 52 | foo.appendChild(bar); 53 | 54 | const baz = foo.cloneNode(true); 55 | bar.setAttribute('joy', true); 56 | 57 | expect((baz as any).joy).toEqual(undefined); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/index.ts: -------------------------------------------------------------------------------- 1 | export { AbsoluteAnchor } from './Excel/Drawing/AbsoluteAnchor.js'; 2 | export { Chart } from './Excel/Drawing/Chart.js'; 3 | export { Drawing } from './Excel/Drawing/Drawing.js'; 4 | export { OneCellAnchor } from './Excel/Drawing/OneCellAnchor.js'; 5 | export { Picture } from './Excel/Drawing/Picture.js'; 6 | export { TwoCellAnchor } from './Excel/Drawing/TwoCellAnchor.js'; 7 | export { Drawings } from './Excel/Drawings.js'; 8 | export { Pane } from './Excel/Pane.js'; 9 | export { Paths } from './Excel/Paths.js'; 10 | export { Positioning } from './Excel/Positioning.js'; 11 | export { RelationshipManager } from './Excel/RelationshipManager.js'; 12 | export { SharedStrings } from './Excel/SharedStrings.js'; 13 | export { SheetView } from './Excel/SheetView.js'; 14 | export { StyleSheet } from './Excel/StyleSheet.js'; 15 | export { Table } from './Excel/Table.js'; 16 | export { Util } from './Excel/Util.js'; 17 | export { Workbook } from './Excel/Workbook.js'; 18 | export { Worksheet } from './Excel/Worksheet.js'; 19 | export { XMLDOM, XMLNode } from './Excel/XMLDOM.js'; 20 | export { createExcelFile, createWorkbook, downloadExcelFile } from './factory.js'; 21 | export * from './interfaces.js'; 22 | export { htmlEscape } from './utilities/escape.js'; 23 | export { isObject, isPlainObject, isString } from './utilities/isTypeOf.js'; 24 | export { pick } from './utilities/pick.js'; 25 | export { uniqueId } from './utilities/uniqueId.js'; 26 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/utilities/README.md: -------------------------------------------------------------------------------- 1 | ### Lodash Utilities 2 | 3 | This folder contains a bunch of util methods copied from [Lodash](https://github.com/lodash/lodash), they were created when we dropped Lodash but still required some util functions that had no native JS equivalent. 4 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/utilities/__tests__/isTypeOf.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { isObject, isPlainObject, isString } from '../isTypeOf.js'; 4 | 5 | describe('isObject() method', () => { 6 | it('should return truthy when input is a valid object', () => { 7 | const output = isObject({ name: 'John', age: 40 }); 8 | expect(output).toBeTruthy(); 9 | }); 10 | 11 | it('should return truthy when input is a Date object', () => { 12 | const output = isObject(new Date()); 13 | expect(output).toBeTruthy(); 14 | }); 15 | 16 | it('should return falsy when input is null', () => { 17 | const output = isObject(null); 18 | expect(output).toBeFalsy(); 19 | }); 20 | }); 21 | 22 | describe('isPlainObject() method', () => { 23 | it('should return truthy when input is a valid object', () => { 24 | const output = isPlainObject({ name: 'John', age: 40 }); 25 | expect(output).toBeTruthy(); 26 | }); 27 | 28 | it('should return falsy when input is a Date object', () => { 29 | const output = isPlainObject(new Date()); 30 | expect(output).toBeFalsy(); 31 | }); 32 | 33 | it('should return falsy when input is null', () => { 34 | const output = isPlainObject(null); 35 | expect(output).toBeFalsy(); 36 | }); 37 | }); 38 | 39 | describe('isString() method', () => { 40 | it('should return truthy when input is a valid string', () => { 41 | const output = isString('John'); 42 | expect(output).toBeTruthy(); 43 | }); 44 | 45 | it('should return falsy when input is a Date object', () => { 46 | const output = isString(new Date()); 47 | expect(output).toBeFalsy(); 48 | }); 49 | 50 | it('should return falsy when input is null', () => { 51 | const output = isString(null); 52 | expect(output).toBeFalsy(); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/utilities/__tests__/uniqueId.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import { uniqueId } from '../uniqueId.js'; 4 | 5 | describe('uniqueId() method', () => { 6 | it('should return number starting at 1 when no prefix provided', () => { 7 | expect(uniqueId()).toBe('1'); 8 | expect(uniqueId()).toBe('2'); 9 | expect(uniqueId('Pre')).toBe('Pre1'); 10 | expect(uniqueId()).toBe('3'); 11 | }); 12 | 13 | it('should return prefix + number when different prefix are provided', () => { 14 | expect(uniqueId('Workbook')).toBe('Workbook1'); 15 | expect(uniqueId('Workbook')).toBe('Workbook2'); 16 | expect(uniqueId('Worksheet')).toBe('Worksheet1'); 17 | expect(uniqueId('Workbook')).toBe('Workbook3'); 18 | expect(uniqueId('Worksheet')).toBe('Worksheet2'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/utilities/escape.ts: -------------------------------------------------------------------------------- 1 | /** Lodash Util - Used to map characters to HTML entities. */ 2 | const htmlEscapes: { [char: string]: string } = { 3 | '&': '&', 4 | '<': '<', 5 | '>': '>', 6 | '"': '"', 7 | "'": ''', 8 | }; 9 | 10 | /** 11 | * Converts the characters "&", "<", ">", '"', and "'" in `string` to their 12 | * corresponding HTML entities. 13 | * 14 | * **Note:** No other characters are escaped. To escape additional 15 | * characters use a third-party library like [_he_](https://mths.be/he). 16 | * 17 | * Though the ">" character is escaped for symmetry, characters like 18 | * ">" and "/" don't need escaping in HTML and have no special meaning 19 | * unless they're part of a tag or unquoted attribute value. See 20 | * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands) 21 | * (under "semi-related fun fact") for more details. 22 | * 23 | * When working with HTML you should always 24 | * [quote attribute values](http://wonko.com/post/html-escaping) to reduce 25 | * XSS vectors. 26 | * 27 | * @since 0.1.0 28 | * @category String 29 | * @param {string} [str=''] The string to escape. 30 | * @returns {string} Returns the escaped string. 31 | * @see escapeRegExp, unescape 32 | * @example 33 | * 34 | * escape('fred, barney, & pebbles') 35 | * // => 'fred, barney, & pebbles' 36 | */ 37 | export const htmlEscape = (str: string) => { 38 | if (typeof str !== 'string') { 39 | str = `${str}`; 40 | } 41 | return str.replace(/[&<>"']/g, m => htmlEscapes[m]); 42 | }; 43 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/utilities/isTypeOf.ts: -------------------------------------------------------------------------------- 1 | export function isObject(value: unknown): value is object { 2 | const type = typeof value; 3 | return value != null && (type === 'object' || type === 'function'); 4 | } 5 | 6 | export function isPlainObject(value: unknown) { 7 | if (typeof value !== 'object' || value === null) { 8 | return false; 9 | } 10 | 11 | if (Object.prototype.toString.call(value) !== '[object Object]') { 12 | return false; 13 | } 14 | 15 | /* v8 ignore next 4 */ 16 | const proto = Object.getPrototypeOf(value); 17 | if (proto === null) { 18 | return true; 19 | } 20 | 21 | const Ctor = Object.prototype.hasOwnProperty.call(proto, 'constructor') && proto.constructor; 22 | return typeof Ctor === 'function' && Ctor instanceof Ctor && Function.prototype.call(Ctor) === Function.prototype.call(value); 23 | } 24 | 25 | export function isString(value: any): value is string { 26 | if (value != null && typeof value.valueOf() === 'string') { 27 | return true; 28 | } 29 | return false; 30 | } 31 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/utilities/pick.ts: -------------------------------------------------------------------------------- 1 | export function pick(object: any, keys: string[]) { 2 | return keys.reduce((obj: any, key: string) => { 3 | if (object?.hasOwnProperty(key)) { 4 | obj[key] = object[key]; 5 | } 6 | return obj; 7 | }, {}); 8 | } 9 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/utilities/uniqueId.ts: -------------------------------------------------------------------------------- 1 | /** Lodash Util - Used to generate unique IDs. */ 2 | const idCounter: { [prefix: string]: number } = {}; 3 | 4 | /** 5 | * Generates a unique ID. If `prefix` is given, the ID is appended to it. 6 | * 7 | * @since 0.1.0 8 | * @category Util 9 | * @param {string} [prefix=''] The value to prefix the ID with. 10 | * @returns {string} Returns the unique ID. 11 | * @see random 12 | * @example 13 | * 14 | * uniqueId('contact_') 15 | * // => 'contact_104' 16 | * 17 | * uniqueId() 18 | * // => '105' 19 | */ 20 | export function uniqueId(prefix = '$lodash$') { 21 | if (!idCounter[prefix]) { 22 | idCounter[prefix] = 0; 23 | } 24 | 25 | const id = ++idCounter[prefix]; 26 | if (prefix === '$lodash$') { 27 | return `${id}`; 28 | } 29 | 30 | return `${prefix}${id}`; 31 | } 32 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "declaration": true, 5 | "declarationMap": true, 6 | "rootDir": "src", 7 | "outDir": "dist", 8 | "target": "ES2021", 9 | "useDefineForClassFields": true, 10 | "module": "ESNext", 11 | "lib": ["es2021", "DOM"], 12 | "moduleResolution": "Bundler", 13 | "allowSyntheticDefaultImports": true, 14 | "experimentalDecorators": true, 15 | "strict": true, 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "esModuleInterop": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noImplicitReturns": true, 22 | "sourceMap": true 23 | }, 24 | "exclude": ["node_modules", "**/*.spec.ts"], 25 | "include": ["src/**/*", "index.d.ts"] 26 | } -------------------------------------------------------------------------------- /packages/excel-builder-vanilla/vite.config.mts: -------------------------------------------------------------------------------- 1 | import { dirname, resolve } from 'node:path'; 2 | import { fileURLToPath } from 'node:url'; 3 | import { defineConfig } from 'vite'; 4 | 5 | const __dirname = dirname(fileURLToPath(import.meta.url)); 6 | 7 | export default defineConfig({ 8 | build: { 9 | lib: { 10 | entry: resolve(__dirname, 'src/index.ts'), 11 | formats: ['es', 'iife'], 12 | name: 'ExcelBuilder', 13 | fileName: format => { 14 | switch (format) { 15 | case 'es': 16 | return 'index.js'; 17 | default: 18 | return `excel-builder.${format}.js`; 19 | } 20 | }, 21 | }, 22 | emptyOutDir: false, 23 | sourcemap: true, 24 | rollupOptions: { 25 | external: ['fflate'], 26 | output: { 27 | globals: { 28 | fflate: 'fflate', 29 | }, 30 | }, 31 | }, 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/**' 3 | 4 | # Define a catalog of version ranges. 5 | catalog: 6 | fflate: ^0.8.2 7 | sass: ^1.89.1 8 | typescript: ^5.8.3 9 | vite: ^6.3.5 -------------------------------------------------------------------------------- /vitest/vitest-pretest.ts: -------------------------------------------------------------------------------- 1 | // (global as any).Storage = window.localStorage; 2 | (global as any).navigator = { userAgent: 'node.js' }; 3 | -------------------------------------------------------------------------------- /vitest/vitest.config.mts: -------------------------------------------------------------------------------- 1 | import { configDefaults, defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | cache: false, 6 | clearMocks: true, 7 | deps: { 8 | interopDefault: false, 9 | }, 10 | environment: 'happy-dom', 11 | setupFiles: ['./vitest/vitest-pretest.ts'], 12 | watch: false, 13 | coverage: { 14 | include: ['packages/excel-builder-vanilla/**/*.ts'], 15 | exclude: [...configDefaults.exclude, '**/__tests__/**', '**/interfaces/**', '**/interfaces.ts', '**/*.d.ts', '**/index.ts'], 16 | provider: 'v8', 17 | }, 18 | }, 19 | }); 20 | --------------------------------------------------------------------------------