Repository: jspreadsheet/ce Branch: master Commit: ba9a39a673fd Files: 264 Total size: 1.9 MB Directory structure: gitextract_kbw2mvuc/ ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── build.cjs ├── contributing.md ├── dist/ │ ├── index.d.ts │ ├── index.js │ ├── jspreadsheet.css │ └── jspreadsheet.themes.css ├── docs/ │ ├── jspreadsheet/ │ │ ├── contact.md │ │ ├── demo.md │ │ ├── docs/ │ │ │ ├── angular.md │ │ │ ├── cells.md │ │ │ ├── clipboard.md │ │ │ ├── columns.md │ │ │ ├── comments.md │ │ │ ├── comparison.md │ │ │ ├── config.md │ │ │ ├── contextmenu.md │ │ │ ├── custom-formulas.md │ │ │ ├── data.md │ │ │ ├── editors.md │ │ │ ├── events.md │ │ │ ├── examples/ │ │ │ │ ├── column-dragging.md │ │ │ │ ├── create-from-table.md │ │ │ │ ├── jquery.md │ │ │ │ ├── richtext-html-editor.md │ │ │ │ ├── table-overflow.md │ │ │ │ ├── translations.md │ │ │ │ └── web-component.md │ │ │ ├── examples.md │ │ │ ├── filters.md │ │ │ ├── footers.md │ │ │ ├── format.md │ │ │ ├── formulas.md │ │ │ ├── freeze-columns.md │ │ │ ├── getting-started.md │ │ │ ├── headers.md │ │ │ ├── helpers.md │ │ │ ├── history.md │ │ │ ├── images.md │ │ │ ├── javascript-calendar.md │ │ │ ├── javascript-dropdown.md │ │ │ ├── merged-cells.md │ │ │ ├── meta-information.md │ │ │ ├── nested-headers.md │ │ │ ├── pagination.md │ │ │ ├── plugins.md │ │ │ ├── react/ │ │ │ │ └── tests.md │ │ │ ├── react.md │ │ │ ├── readonly.md │ │ │ ├── rows.md │ │ │ ├── search.md │ │ │ ├── selection.md │ │ │ ├── sorting.md │ │ │ ├── style.md │ │ │ ├── tests.md │ │ │ ├── themes.md │ │ │ ├── toolbars.md │ │ │ ├── upgrade-from-v4-to-v5.md │ │ │ ├── vue/ │ │ │ │ └── tests.md │ │ │ ├── vue.md │ │ │ └── worksheets.md │ │ ├── docs.md │ │ ├── download.md │ │ ├── sponsors.md │ │ ├── v2/ │ │ │ ├── docs/ │ │ │ │ ├── events.md │ │ │ │ ├── programmatically-changes.md │ │ │ │ └── quick-reference.md │ │ │ ├── docs.md │ │ │ ├── examples/ │ │ │ │ ├── a-custom-table-design.md │ │ │ │ ├── autocomplete.md │ │ │ │ ├── comments.md │ │ │ │ ├── create-from-a-existing-html-table.md │ │ │ │ ├── creating-a-table-from-an-external-csv-file.md │ │ │ │ ├── currency-and-masking-numbers.md │ │ │ │ ├── getting-data-from-table.md │ │ │ │ ├── headers.md │ │ │ │ ├── images.md │ │ │ │ ├── including-formulas-on-your-spreadsheet.md │ │ │ │ ├── integrating-a-third-party-plugin-into-your-spreadsheet.md │ │ │ │ ├── jquery-table-with-toolbars.md │ │ │ │ ├── meta-information.md │ │ │ │ ├── mobile.md │ │ │ │ ├── multiple-spreadsheets-in-the-same-page.md │ │ │ │ ├── readonly-options.md │ │ │ │ ├── responsive-columns.md │ │ │ │ ├── sorting-data.md │ │ │ │ ├── table-styling.md │ │ │ │ ├── table-with-fixed-headers.md │ │ │ │ ├── text-wrapping.md │ │ │ │ ├── tracking-changes-on-the-spreadsheet.md │ │ │ │ ├── using-a-calendar-column-type.md │ │ │ │ ├── working-with-dropdowns.md │ │ │ │ └── working-with-the-data.md │ │ │ ├── examples.md │ │ │ └── getting-started.md │ │ ├── v3/ │ │ │ ├── docs/ │ │ │ │ ├── events.md │ │ │ │ ├── programmatically-changes.md │ │ │ │ └── quick-reference.md │ │ │ ├── docs.md │ │ │ ├── examples/ │ │ │ │ ├── angular.md │ │ │ │ ├── column-types.md │ │ │ │ ├── comments.md │ │ │ │ ├── contextmenu.md │ │ │ │ ├── custom-table-design.md │ │ │ │ ├── datatables.md │ │ │ │ ├── date-and-datetime-picker.md │ │ │ │ ├── dropdown-and-autocomplete.md │ │ │ │ ├── events.md │ │ │ │ ├── headers.md │ │ │ │ ├── image-upload.md │ │ │ │ ├── import-data.md │ │ │ │ ├── jquery.md │ │ │ │ ├── lazy-loading.md │ │ │ │ ├── merged-cells.md │ │ │ │ ├── meta-information.md │ │ │ │ ├── programmatically-updates.md │ │ │ │ ├── react.md │ │ │ │ ├── readonly.md │ │ │ │ ├── sorting.md │ │ │ │ ├── spreadsheet-formulas.md │ │ │ │ ├── spreadsheet-toolbars.md │ │ │ │ ├── table-overflow.md │ │ │ │ ├── table-scripting.md │ │ │ │ ├── table-style.md │ │ │ │ ├── translations.md │ │ │ │ └── vue.md │ │ │ ├── examples.md │ │ │ └── getting-started.md │ │ ├── v4/ │ │ │ ├── cases/ │ │ │ │ ├── data-persistence.md │ │ │ │ ├── food-store.md │ │ │ │ ├── highcharts.md │ │ │ │ └── project-management.md │ │ │ ├── cases.md │ │ │ ├── docs/ │ │ │ │ ├── events.md │ │ │ │ ├── examples.md │ │ │ │ ├── getting-started.md │ │ │ │ ├── most-common-questions-and-answers.md │ │ │ │ ├── programmatically-changes.md │ │ │ │ ├── quick-reference.md │ │ │ │ └── special-formulas.md │ │ │ ├── docs.md │ │ │ ├── events.md │ │ │ ├── examples/ │ │ │ │ ├── angular.md │ │ │ │ ├── column-dragging.md │ │ │ │ ├── column-filters.md │ │ │ │ ├── column-types.md │ │ │ │ ├── comments.md │ │ │ │ ├── contextmenu.md │ │ │ │ ├── create-from-table.md │ │ │ │ ├── custom-table-design.md │ │ │ │ ├── datatables.md │ │ │ │ ├── date-and-datetime-picker.md │ │ │ │ ├── dropdown-and-autocomplete.md │ │ │ │ ├── events.md │ │ │ │ ├── footers.md │ │ │ │ ├── freeze-columns.md │ │ │ │ ├── headers.md │ │ │ │ ├── image-upload.md │ │ │ │ ├── import-data.md │ │ │ │ ├── jquery.md │ │ │ │ ├── lazy-loading.md │ │ │ │ ├── merged-cells.md │ │ │ │ ├── meta-information.md │ │ │ │ ├── nested-headers.md │ │ │ │ ├── programmatically-changes.md │ │ │ │ ├── programmatically-updates.md │ │ │ │ ├── react.md │ │ │ │ ├── readonly.md │ │ │ │ ├── richtext-html-editor.md │ │ │ │ ├── sorting.md │ │ │ │ ├── spreadsheet-formulas.md │ │ │ │ ├── spreadsheet-toolbars.md │ │ │ │ ├── spreadsheet-webcomponent.md │ │ │ │ ├── table-overflow.md │ │ │ │ ├── table-scripting.md │ │ │ │ ├── table-style.md │ │ │ │ ├── tabs.md │ │ │ │ ├── translations.md │ │ │ │ └── vue.md │ │ │ ├── examples.md │ │ │ ├── getting-started.md │ │ │ ├── most-common-questions-and-answers.md │ │ │ ├── programmatically-changes.md │ │ │ ├── quick-reference.md │ │ │ └── special-formulas.md │ │ └── v4.md │ └── jspreadsheet.md ├── eslint.config.mjs ├── mocha.config.js ├── package.json ├── packages/ │ └── vue/ │ ├── README.md │ ├── dist/ │ │ ├── index.d.ts │ │ └── index.js │ └── package.json ├── public/ │ └── index.html ├── resources/ │ └── lang/ │ ├── de_DE.json │ ├── dk_DA.json │ ├── en_GB.json │ ├── fr_FR.json │ ├── it_IT.json │ ├── pl_PL.json │ ├── pt_BR.json │ ├── vi_VN.json │ └── zh_CN.json ├── src/ │ ├── index.js │ ├── jspreadsheet.css │ ├── jspreadsheet.themes.css │ ├── test.js │ ├── utils/ │ │ ├── cells.js │ │ ├── columns.js │ │ ├── comments.js │ │ ├── config.js │ │ ├── copyPaste.js │ │ ├── data.js │ │ ├── dispatch.js │ │ ├── download.js │ │ ├── editor.js │ │ ├── events.js │ │ ├── factory.js │ │ ├── filter.js │ │ ├── footer.js │ │ ├── freeze.js │ │ ├── headers.js │ │ ├── helpers.js │ │ ├── history.js │ │ ├── internal.js │ │ ├── internalHelpers.js │ │ ├── keys.js │ │ ├── lazyLoading.js │ │ ├── libraryBase.js │ │ ├── merges.js │ │ ├── meta.js │ │ ├── orderBy.js │ │ ├── pagination.js │ │ ├── rows.js │ │ ├── search.js │ │ ├── selection.js │ │ ├── style.js │ │ ├── toolbar.js │ │ ├── version.js │ │ └── worksheets.js │ └── webcomponent.js ├── test/ │ ├── calculations.js │ ├── columns.js │ ├── comments.js │ ├── data.js │ ├── footer.js │ ├── headers.js │ ├── instance.js │ ├── merges.js │ ├── meta.js │ ├── orderBy.js │ ├── pagination.js │ ├── paste.js │ ├── redo.js │ ├── rows.js │ └── search.js └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc.json ================================================ { "extends": "eslint:recommended", "ignorePatterns": ["dist/", "node_modules/"], "rules": { "quotes": ["error", "single"], "semi": ["error", "always"] } } ================================================ FILE: .gitignore ================================================ .idea /package-lock.json node_modules /vue/package-lock.json ================================================ FILE: .npmignore ================================================ .idea .gitignore .gitattributes docs public /react/ src test /vue/ /wrappers/ build.cjs mocha.config.cjs webpack.config.cjs ================================================ FILE: .prettierignore ================================================ dist/ docs/ ================================================ FILE: .prettierrc ================================================ { "tabWidth": 4, "singleQuote": true, "semi": true, "trailingComma": "es5", "printWidth": 160 } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2024 Jspreadsheet Ltd Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Jspreadsheet CE v5: The JavaScript spreadsheet Jexcel CE has been renamed to Jspreadsheet CE

## Jspreadsheet CE Use Cases Jspreadsheet CE is an extensible framework for building sophisticated data-oriented interfaces with Excel-like controls. By bringing familiar spreadsheet features to your application, you can drastically reduce development time while delivering an interface that users already know how to use, leading to faster adoption and increased productivity. You can use Jspreadsheet in many different applications, such as: - An editable data grid-based interface to simplify inventory management and production planning in a manufacturing company's ERP system. - At an educational institution, Jspreadsheet powers grade management systems where teachers can efficiently import and modify student data. - A logistics company uses Jspreadsheet to create dynamic delivery route planning tables with real-time updates. - In a research laboratory, scientists use Jspreadsheet to collect and analyze experimental data with custom validation rules. - At a retail chain, managers use Spreadsheet-based tools to coordinate staff schedules across multiple locations. ### Jspreadsheet Pro - Enterprise Solution - [Jspreadsheet Pro](https://jspreadsheet.com/) - [Real-time React Spreadsheets](https://github.com/jspreadsheet/spreadsheet-react-server) ## Overview ### Why Choose Jspreadsheet CE? - Create rich, interactive data grid interfaces - Handle complex data inputs with Excel-like functionality - Direct Excel compatibility: Copy and paste using standard shortcuts - Proven success across thousands of implementations - Lightweight, fast, and intuitive - Easy integration with third-party plugins - Built for collaboration and sharing ## Screenshot ![The JavaScript spreadsheet](https://bossanova.uk/templates/default/img/jexcel.gif) ## Installation ### NPM `npm install jspreadsheet-ce` ### CDN ```html ``` ### Basic Demo A basic example to integrate the Jspreadsheet in your website to create your first data grid with spreadsheet controls. #### Usage Add jexcel/jspreadsheet and Jsuites to your html file ```html ``` You should initialize your data grid on a div container, such as: ```html
``` To initialize a Jspreadsheet CE table you should run JavaScript, such as: ```javascript jspreadsheet(document.getElementById('spreadsheet'), { worksheets: [ { data: [ ['Jazz', 'Honda', '2019-02-12', '', true, '$ 2.000,00', '#777700'], ['Civic', 'Honda', '2018-07-11', '', true, '$ 4.000,01', '#007777'], ], columns: [ { type: 'text', title: 'Car', width: 120 }, { type: 'dropdown', title: 'Make', width: 200, source: ['Alfa Romeo', 'Audi', 'Bmw', 'Honda'], }, { type: 'calendar', title: 'Available', width: 200 }, { type: 'image', title: 'Photo', width: 120 }, { type: 'checkbox', title: 'Stock', width: 80 }, { type: 'numeric', title: 'Price', width: 100, mask: '$ #.##,00', decimal: ',', }, { type: 'color', width: 100, render: 'square' }, ], }, ], }); ``` Serve your html file and then you will get the rendered table in your browser ![sampleTable](./docs/sampleTable.png)
## Development ### Build your package % npm run build ### Start a web service % npm run start ## Data Grid Examples - https://bossanova.uk/jspreadsheet/docs/examples/create-from-table - https://bossanova.uk/jspreadsheet/docs/examples/translations - https://bossanova.uk/jspreadsheet/docs/examples/table-overflow - https://bossanova.uk/jspreadsheet/docs/examples/richtext-html-editor - https://bossanova.uk/jspreadsheet/docs/examples/column-dragging - https://bossanova.uk/jspreadsheet/docs/examples/web-component - https://bossanova.uk/jspreadsheet/docs/examples/jquery ## Jspreadsheet Changelog ### Custom - Add onupdateresult event after search/filter; ### Jspreadsheet 5.0.0 - Separation of spreadsheet and worksheets; - New worksheet methods and events; - Dedicated wrappers for React and Vue for better framework integration; - Modern development environment powered by Webpack; - Updated architecture aligned with other distributions; [More information](https://bossanova.uk/jspreadsheet/docs/upgrade-from-v4-to-v5) ### Jspreadsheet 4.6.0 - Jexcel renamed to Jspreadsheet. - Integration with Jsuites v4. ### Jspreadsheet 4.2.3 - The spreadsheet plugin is now compatible with Jsuites v3. - New flags and security implementations. - New DOM element references in the toolbar. - Worksheet events are now tabbed. ### Jspreadsheet 4.0.0 Special thanks to [FDL - Fonds de Dotation du Libre](https://www.fdl-lef.org/) for their support and sponsorship, which made the new version possible with many exciting features. - Workbook/tab support for spreadsheets. - Create dynamic spreadsheets from static HTML elements. - Highlight selected cells in the spreadsheet after CTRL+C. - Footer with formula support. - Multiple column resizing. - JSON update support (helpers to update a remote server). - Centralized event dispatch method for all spreadsheet events. - Custom helpers: `=PROGRESS` (progress bar), `=RATING` (5-star rating). - Custom formula helpers: `=COLUMN`, `=ROW`, `=CELL`, `=TABLE`, `=VALUE`. - Dynamic nested header updates. - New HTML editing column type. - New flags: `includeHeadersOnCopy`, `persistence`, `filters`, `autoCasting`, `freezeColumns`. - New events: `onevent`, `onchangepage`, `onbeforesave`, `onsave`. - More examples and documentation. ### Jspreadsheet 3.9.0 - New methods. - General fixes. ### Jspreadsheet 3.6.0 - Improved spreadsheet formula parsing. - New spreadsheet events. - New initialization options. - General fixes. ### Jspreadsheet 3.2.3 - `getMeta`, `setMeta` methods. - NPM package with jSuites. - General fixes. ### Jspreadsheet 3.0.1 Jspreadsheet v3 is a complete rebuild of the JavaScript spreadsheet (previously a jQuery plugin). Due to the changes, full compatibility could not be ensured. If upgrading, your code may require some updates. For more information, refer to the article on upgrading from Jspreadsheet v2 or Handsontable. **New features in Jspreadsheet v3:** - Drag and drop columns. - Resizable rows. - Merge columns. - Search functionality. - Pagination. - Lazy loading. - Full-screen mode. - Image upload. - Native color picker. - Better mobile compatibility. - Enhanced nested headers support. - Advanced keyboard navigation. - Better hidden column management. - Data picker enhancements: dropdown, autocomplete, multiple selection, group options, and icons. - Import from XLSX (experimental). **Major improvements:** - A new formula engine with faster results and no external dependencies. - No use of selectors, leading to faster performance. - New native column types. - No jQuery required. - Examples for React, Vue, and Angular. - XLSX support via a custom SheetJS integration (experimental). ### Jspreadsheet 2.1.0 - Mobile touch improvements. - Paste fixes and a new CSV parser. ### Jspreadsheet 2.0.0 - New radio column type. - New dropdown with autocomplete and multiple selection options. - Header/body separation for better scroll and column resize behavior. - Text-wrap improvements, including Excel-compatible `alt+enter`. - New `set/get` meta information. - New `set/get` configuration parameters. - Programmatic `set/get` cell styles. - `set/get` cell comments. - Custom toolbar for tables. - Responsive calendar picker. ### Jspreadsheet 1.5.7 - Improvements to checkbox column type. - Updates to table destruction in jQuery. ### Jspreadsheet 1.5.1 - Spreadsheet data overflow and fixed headers. See an [example](https://jspreadsheet.com/examples/table-with-fixed-headers). - Navigation improvements. ### Jspreadsheet 1.5.0 - Relative `insertRow`, `deleteRow`, `insertColumn`, `deleteColumn`. See an [example](https://jspreadsheet.com/examples/working-with-the-data). - Redo and undo support for `insertRow`, `deleteRow`, `insertColumn`, `deleteColumn`, `moveRow`. - New formula column recursive chain. - New alternative design option (Bootstrap-like). See an [example](https://jspreadsheet.com/examples/a-custom-table-design). - `updateSettings` improvements. ## Official websites - [Jspreadsheet CE v4 - Javascript Spreadsheet](https://bossanova.uk/jspreadsheet/v4) - [Jspreadsheet CE v3 - Vanilla JavaScript](https://bossanova.uk/jspreadsheet/v3) - [Jspreadsheet CE v2 - jQuery Plugin](https://bossanova.uk/jspreadsheet/v2) - [Jspreadsheet Pro v11 - Javascript Spreadsheet](https://jspreadsheet.com/docs) - [Jspreadsheet Pro v10 - Javascript Spreadsheet](https://jspreadsheet.com/docs/v10) - [Jspreadsheet Pro v9 - Javascript Spreadsheet](https://jspreadsheet.com/docs/v9) - [Jspreadsheet Pro v8 - Javascript Spreadsheet](https://jspreadsheet.com/docs/v8) - [Jspreadsheet Pro v7 - Javascript Spreadsheet](https://jspreadsheet.com/docs/v7) ## Community - [GitHub](https://github.com/jspreadsheet/ce/issues) ## Contributing See [contributing](contributing.md) ## Copyright and license Jspreadsheet CE is released under the [MIT license]. Contact contact@jspreadsheet.com ## Other tools - [LemonadeJS Reactive Library](https://lemonadejs.com) - [jSuites Components](https://jsuites.net) - [CalendarJS](https://calendarjs.com) ================================================ FILE: build.cjs ================================================ module.exports = function (source) { let result = source.replace(/import\s+jSuites\s+from\s+(?:'|")jsuites(?:'|");?/gm, ''); result = result.replace(/import\s+formula\s+from\s+(?:'|")@jspreadsheet\/formula(?:'|");?/gm, ''); return result; }; ================================================ FILE: contributing.md ================================================ # Contributing When contributing to this repository, please follow the following process ## Pull Request Process 1. Create a fork of this repo 2. Create a branch named according to what you are developing (new-feature, fix-issue) 3. Make your changes 4. Create a Pull Request (PR) against origin:dev branch (origin:master) 5. Comment your PR explaining your changes, if it resolves an issue or adds a feature, also modify documentation accordingly if necessary. 6. Wait for yout PR to be reviewed and approved ## Steps to run the project locally after forking the repository. Open [http://localhost:8000](http://localhost:8000/) in your browser, or check the devServer settings in webpack.config.js if the port is different. Alternatively, you can change the port in the webpack.config.js file. # Code of Conduct ### Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ### Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ### Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ### Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ### Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ### Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: dist/index.d.ts ================================================ export = jspreadsheet; export as namespace jspreadsheet; declare const jspreadsheet: jspreadsheet.JSpreadsheet; declare namespace jspreadsheet { type CellValue = string | number | boolean; type DropdownSourceItem = | string | number | { id: string | number; name: string; title?: string; image?: string; group?: string; }; interface CalendarOptions { /** * @default "YYYY-MM-DD" */ format?: string; /** * Open calendar in full screen mode (this is automatic set for screensize < 800). */ fullscreen?: boolean; /** Placeholder. */ placeholder?: string; /** * Allow keyboard date entry. * @default true */ readonly?: boolean; /** * Show the reset button. * @default true */ resetButton?: boolean; /** * Show timepicker. * @default false */ time?: boolean; /** Today is default. */ today?: boolean; } interface CustomEditor { /** * Event responsible for closing the editor of a cell with a custom editor. * @param cell - Td tag whose editor should close. * @param save - If true, the value returned by this event will be the cell's new value. Otherwise, the value returned by this event is ignored. * @param x - Cell column index. * @param y - Cell row index. * @param instance - Worksheet instance. * @param options - Column configuration object. * @returns New cell value. */ closeEditor?: (cell: HTMLTableCellElement, save: boolean, x: number, y: number, instance: WorksheetInstance, options: Column) => CellValue | undefined; /** * Event called when creating new cells. * @param cell - HTML element prepared to be the new cell. * @param value - Cell value. * @param x - Cell column index. * @param y - Cell row index. * @param instance - Worksheet instance. * @param options - Column configuration object. * @returns HTML element that will be the new cell. */ createCell?: (cell: HTMLTableCellElement, value: CellValue, x: number, y: number, instance: WorksheetInstance, options: Column) => HTMLTableCellElement; /** * Event responsible for opening the editor of a cell with a custom editor. * @param cell - Td tag whose editor should open. * @param value - Cell value. * @param x - Cell column index. * @param y - Cell row index. * @param instance - Worksheet instance. * @param options - Column configuration object. * @param e - Event that called this method. */ openEditor?: ( cell: HTMLTableCellElement, value: CellValue, x: number, y: number, instance: WorksheetInstance, options: Column, e: KeyboardEvent | MouseEvent | TouchEvent | undefined ) => void; /** * Event called before changing the value of a cell. * * The returned value will be the cell's new value. * @param cell - Cell whose value has changed. * @param value - New value. * @param x - Cell column index. * @param y - Cell row index. * @param instance - Worksheet instance. * @param options - Column configuration object. */ updateCell?: ( cell: HTMLTableCellElement, value: CellValue | undefined, x: number, y: number, instance: WorksheetInstance, options: Column ) => CellValue | undefined; } type HorizontalAlign = 'center' | 'left' | 'right' | 'justify'; interface BaseColumn { /** Cell alignment. */ align?: HorizontalAlign; decimal?: string; /** Cell mask. */ mask?: string; /** Name used to refer to this column if the data is arranged as an array of objects. */ name?: string; /** * Prevent user from changing cell values. * @default false */ readOnly?: boolean; /** * Defines a modification that must be made to a cell value before it is displayed in the spreadsheet. * @param cell - Cell whose value has changed. * @param value - New value. * @param x - Cell column index. * @param y - Cell row index. * @param instance - Worksheet instance. * @param options - Column configuration object. */ render?: (cell: HTMLTableCellElement, value: CellValue | undefined, x: number, y: number, instance: WorksheetInstance, options: Column) => void; /** Custom column title. */ title?: string; /** * The type of cells in this column. * @default "text" */ type?: 'text' | 'numeric' | 'hidden' | 'dropdown' | 'autocomplete' | 'checkbox' | 'radio' | 'calendar' | 'image' | 'color' | 'html' | CustomEditor; /** Column width. */ width?: string | number; /** * Enable automatic word wrapping in cells in this column. * @default false */ wordWrap?: boolean; } interface DropdownColumn extends BaseColumn { autocomplete?: boolean; /** * Allow selection of more than one item. * @default false */ multiple?: boolean; /** Options available in the dropdown. */ source?: DropdownSourceItem[]; /** Url to fetch options from an external source. */ url?: string; } interface CalendarColumn extends BaseColumn { /** Calendar options. */ options?: CalendarOptions; } interface ColorColumn extends Omit { /** If undefined, the cell shows the hex code of the color, if "square", it shows a square filled with the color. */ render?: 'square'; } type Column = DropdownColumn | CalendarColumn | ColorColumn | BaseColumn; interface Row { /** Row height. */ height?: string | number; /** Text that replaces the row number. */ title?: string; } interface ToolbarItemBase { /** Tooltip shown when hovering over this option. */ tooltip?: string; /** * Method called when the toolbar state should be updated. * @param toolbarElement - Toolbar HTML element. * @param toolbarInstance - Toolbar instance. For more information, read the jSuites toolbar documentation. * @param itemElement - Toolbar item HTML element. * @param worksheetInstance - Worksheet instance. */ updateState?: ( toolbarElement: HTMLDivElement, toolbarInstance: Record, itemElement: HTMLDivElement, worksheetInstance: WorksheetInstance ) => void; [property: string]: any; } interface ToolbarIconItem extends ToolbarItemBase { /** Defines the icon (from material icons). */ content: string; /** * Event fired when clicking on the html item referring to that item. * @param toolbarElement - Toolbar HTML element. * @param toolbarInstance - Toolbar instance. For more information, read the jSuites toolbar documentation. * @param itemElement - Toolbar item HTML element. * @param event - Pointer event that triggered the onclick. */ onclick?: (toolbarElement: HTMLDivElement, toolbarInstance: Record, itemElement: HTMLDivElement, event: PointerEvent) => void; } interface ToolbarSelectItem extends ToolbarItemBase { type: 'select'; /** Defines the icon (from material icons). */ content: string; /** * Event called when the item picker value is changed. * @param itemElement - Toolbar item HTML element. * @param pickerInstance - Picker instance. For more information, read the jSuites picker documentation. * @param value - New picker value. * @param value2 - New picker value. * @param valueIndex - Index of the new picker value. * @param event - Pointer event that triggered this event. */ onchange?: ( itemElement: HTMLDivElement, pickerInstance: Record, value: string, value2: string, valueIndex: string, event: PointerEvent ) => void; /** * Options available in the picker. */ options?: string[]; /** * Creates picker items based on the picker options. * @param option - One of the items in the {@link ToolbarSelectItem.options} array. * @param pickerInstance - Picker instance. For more information, read the jSuites picker documentation. * @returns string representing the HTML of the picker item. */ render?: (option: string, pickerInstance: Record) => string; /** Initial value of the selectbox. */ value?: string; /** Item width. */ width?: string; } interface ToolbarColorItem extends ToolbarItemBase { type: 'color'; /** Defines the icon (from material icons). */ content: string; /** * Style that should be changed when input value changes. If this property is set, the onclick event is overridden. */ k: string; } interface ToolbarDivisorItem { type: 'divisor'; } /** * Item that makes up the toolbar configuration array. This item may have properties not described here. For more information, read the jSuites toolbar documentation. */ type ToolbarItem = ToolbarIconItem | ToolbarSelectItem | ToolbarColorItem | ToolbarDivisorItem; interface NestedHeaderCell { id?: string; colspan?: number; title?: string; align?: string; } interface CellChange { value: CellValue; oldValue: CellValue; x: string; y: string; } interface HistoryRecord { action: string; [key: string]: any; } /** * Item compared in column ordering. * * It is composed respectively by the index of the row it represents and by the value of that row in the sorting column. */ type SortingItem = [number, CellValue]; type MetaInformation = Record; type ContextMenuItem = { type?: 'line' | 'divisor' | 'default'; title: string; icon?: string; id?: string; disabled?: boolean; onclick?: (instance: any, e: MouseEvent) => void; shortcut?: string; tooltip?: string; submenu?: Array; }; type ContextMenuRole = | 'select-all' | 'fill-handle' | 'row' | 'nested' | 'tabs' | 'toolbar' | 'pagination' | 'cell' | 'grid' | 'footer' | 'header' | 'applications'; interface SpreadsheetOptions { /** * Show or not the "about" item in the context menu. * @default true */ about?: boolean; /** * Allow table export as csv. * @default true */ allowExport?: boolean; /** * If true, Jss will try to convert cell contents used in formulas to numbers * @default true */ autoCasting?: boolean; /** * Auto increment actions when using the dragging corner. * @default true */ autoIncrement?: boolean; /** * Creates context menu when user clicks with secondary mouse button. * @param instance - Instance of the worksheet on which the click was made. * @param colIndex - Horizontal index of the element that was clicked. The meaning of this value depends on the {@link role} argument. * @param rowIndex - Vertical index of the element that was clicked. The meaning of this value depends on the {@link role} argument. * @param event - pointer event that triggered this event. * @param items - jss default context menu. * @param role - indicates in which part of the spreadsheet the click occurred. * @param x - Horizontal index of the element that was clicked. The meaning of this value depends on the {@link role} argument. * @param y - Vertical index of the element that was clicked. The meaning of this value depends on the {@link role} argument. * @returns Context menu configuration that should be created. */ contextMenu?: ( instance: WorksheetInstance, colIndex: string | number | null, rowIndex: string | number | null, event: PointerEvent, items: ContextMenuItem[], role: ContextMenuRole, x: string | number | null, y: string | number | null ) => ContextMenuItem[] | null | undefined; /** * Enable the formula debug notices. * @default false */ debugFormulas?: boolean; /** * Fullscreen mode. * @default false */ fullscreen?: boolean; /** * Include header titles on download. * @default false */ includeHeadersOnDownload?: boolean; /** Spreadsheet namespace */ namespace?: string; /** * Occurs after all changes are applied in the tables. * @param instance - Instance of the worksheet where the change occurred. * @param changes - list of changes. */ onafterchanges?: (instance: WorksheetInstance, changes: CellChange[]) => void; /** * Occurs before a column value is changed. If any value is returned, it will be the cell's new value. * @param instance - Instance of the worksheet where the changes will occur. * @param cell - HTML element that represents the cell being changed. * @param colIndex - Cell column index being changed. * @param rowIndex - Cell row index being changed. * @param newValue - Value being applied to the cell. */ onbeforechange?: ( instance: WorksheetInstance, cell: HTMLTableCellElement, colIndex: string | number, rowIndex: string | number, newValue: CellValue ) => undefined | CellValue; /** * Occurs before a column is excluded. If this method returns false, the removal will be canceled. * @param instance - Instance of the worksheet where columns will be removed. * @param removedColumns - Indexes of the columns to be removed. */ onbeforedeletecolumn?: (instance: WorksheetInstance, removedColumns: number[]) => undefined | boolean; /** * Occurs before a row is deleted. If this method returns false, the removal will be canceled. * @param instance - Instance of the worksheet where rows will be removed. * @param removedRows - Indexes of the rows to be removed. */ onbeforedeleterow?: (instance: WorksheetInstance, removedRows: number[]) => undefined | boolean; /** * Intercept and parse a formula just before the execution. * @param instance - Instance of the worksheet. * @param expression - Formula that triggered the event. * @param x - Column index of the cell whose formula triggered the event. * @param y - Row index of the cell whose formula triggered the event */ onbeforeformula?: (instance: WorksheetInstance, expression: string, x?: number, y?: number) => false | string | undefined; /** * Occurs before a new column is inserted. If this method returns false, the insertion will be canceled. * @param instance - Instance of the worksheet where columns will be added. * @param columns - Settings for columns to be added. */ onbeforeinsertcolumn?: ( instance: WorksheetInstance, columns: { column: number; options: Column; data?: CellValue[]; }[] ) => undefined | boolean; /** * Occurs before a new row is inserted. If this method returns false, the insertion will be canceled. * @param instance - Instance of the worksheet where rows will be added. * @param rows - Settings for rows to be added. */ onbeforeinsertrow?: ( instance: WorksheetInstance, rows: { row: number; data: CellValue[]; }[] ) => undefined | boolean; /** * Occurs before the paste action is performed. * * If it returns false, the jss cancels the paste. * If it returns a string, it will be the content pasted into the worksheet. * * @param instance - Instance of the worksheet where data will be pasted. * @param copiedText - Text being pasted to the spreadsheet. * @param colIndex - Column index where it will start the paste. * @param rowIndex - Row index where it will start the paste. */ onbeforepaste?: ( instance: WorksheetInstance, copiedText: { value: CellValue }[][], colIndex: number | string, rowIndex: number | string ) => undefined | boolean | string; /** * Occurs before persisting any changes to the server. * * This event is only called when the spreadsheet has the {@link WorksheetOptions.persistence} property set. * * If this event returns false, the change is not persisted on the server. * If it returns a truthy value, that value is persisted instead of the initial value. * @param spreadsheetInstance - Spreadsheet instance. * @param worksheetInstance - Instance of the worksheet to be saved. * @param data - Changed data. */ onbeforesave?: ( spreadsheetInstance: SpreadsheetInstance, worksheetInstance: WorksheetInstance, data: { row: number; data: Record }[] ) => any; /** * Occurs before the selection is changed. * @param instance - Worksheet instance where the selection will occur. * @param borderLeftIndex - Index of the first column contained by the selection. * @param borderTopIndex - Index of the first row contained by the selection. * @param borderRightIndex - Index of the last column contained by the selection. * @param borderBottomIndex - Index of the last row contained by the selection. * @param origin - Javascript event that triggered this jss event. */ onbeforeselection?: ( instance: WorksheetInstance, borderLeftIndex: number, borderTopIndex: number, borderRightIndex: number, borderBottomIndex: number, origin: Event | undefined ) => false | undefined; /** * Occurs when the table is blurred. * @param instance - Instance of the worksheet that was blurred. */ onblur?: (instance: WorksheetInstance) => void; /** * Occurs after a column value is changed. * @param instance - Instance of the worksheet where the change occurred. * @param cell - HTML element that represents the cell being changed. * @param colIndex - Cell column index being changed. * @param rowIndex - Cell row index being changed. * @param newValue - New cell value. * @param oldValue - Old cell value. */ onchange?: ( instance: WorksheetInstance, cell: HTMLTableCellElement, colIndex: string | number, rowIndex: string | number, newValue: CellValue, oldValue: CellValue ) => void; /** * Occurs when a column heading is changed. * @param instance - Instance of the worksheet where the change occurred. * @param colIndex - Index of the column that was renamed. * @param newValue - New column title. * @param oldValue - Old column title. */ onchangeheader?: (instance: WorksheetInstance, colIndex: number, newValue: string, oldValue: string) => void; /** * Occurs when a "setMeta" is called. * * @param instance - Instance of the worksheet where the change occurred. * @param cellName - An object with the metadata changes. */ onchangemeta?: (instance: WorksheetInstance, cellName: Record) => void; /** * Occurs when the page is changed. * @param instance - Instance of the worksheet where the change occurred. * @param newPageNumber - Page the worksheet is on. * @param oldPageNumber - Page the worksheet was on. * @param quantityPerPage - Maximum number of lines on pages. */ onchangepage?: (instance: WorksheetInstance, newPageNumber: number, oldPageNumber: number, quantityPerPage: number) => void; /** * Occurs when a "setStyle" is called. * * @param instance - Instance of the worksheet where the change occurred. * @param cellName - An object with the changes. */ onchangestyle?: (instance: WorksheetInstance, changes: Record) => void; /** * Occurs when a comment is changed. * @param instance - Instance of the worksheet where the change occurred. * @param newComments - New comments. * @param oldComments - Old comments. */ oncomments?: (instance: WorksheetInstance, newComments: Record, oldComments: Record) => void; /** * Occurs when the contents of one or more cells are copied. * @param instance - Instance of the worksheet where the change occurred. * @param selectedRange - Copied cell range. * @param copiedData - Data from copied cell range. * @param cut - If true, the action was cut. Otherwise, the action was copy. */ oncopy?: ( instance: WorksheetInstance, selectedRange: [number, number, number, number], copiedData: string, cut: boolean | undefined ) => string | false | undefined; /** * Occurs when a cell is created. * @param instance - Instance of the worksheet where the cell was created. * @param cell - Cell HTML element. * @param colIndex - Cell column index. * @param rowIndex - Cell row index. * @param newValue - Cell value. */ oncreatecell?: (instance: WorksheetInstance, cell: HTMLTableCellElement, colIndex: number, rowIndex: number, newValue: CellValue) => void; /** * Occurs when an editor is opened. * @param instance - Instance of the worksheet where the change occurred. * @param td - Td tag of the cell whose editor was opened. * @param colIndex - Column index of the cell whose editor was opened. * @param rowIndex - Row index of the cell whose editor was opened. * @param input - Input of the editor that was opened. * @param options - Column settings. */ oncreateeditor?: (instance: WorksheetInstance, td: HTMLTableCellElement, colIndex: number, rowIndex: number, input: null, options: Column) => void; /** * Occurs after a column is excluded. * @param instance - Instance of the worksheet where the change occurred. * @param removedColumns - Indexes of the columns that were removed. */ ondeletecolumn?: (instance: WorksheetInstance, removedColumns: number[]) => void; /** * Occurs after a row is excluded. * @param instance - Instance of the worksheet where the change occurred. * @param removedRows - Indexes of the rows that were removed. */ ondeleterow?: (instance: WorksheetInstance, removedRows: number[]) => void; /** * Occurs when a worksheet is created. * @param worksheet - Instance of the new worksheet. * @param worksheetOptions - Options of the new worksheet. * @param index - Index of the new worksheet. */ oncreateworksheet?: (worksheet: WorksheetInstance, worksheetOptions: WorksheetOptions, index: number) => void; /** * Occurs when a worksheet is deleted. * @param worksheet - Instance of the deleted worksheet. * @param index - Index of the deleted worksheet. */ ondeleteworksheet?: (worksheet: WorksheetInstance, index: number) => void; /** * Occurs when a closeEditor is called. * @param instance - Instance of the worksheet where the change occurred. * @param td - Td tag of the cell whose editor was opened. * @param colIndex - Column index of the cell whose editor was opened. * @param rowIndex - Row index of the cell whose editor was opened. * @param editorValue - Value that was in the editor. * @param wasSaved - Whether the value which was in the editor was saved in the cell or not. */ oneditionend?: ( instance: WorksheetInstance, td: HTMLTableCellElement, colIndex: number, rowIndex: number, editorValue: CellValue, wasSaved: boolean ) => void; /** * Occurs when a openEditor is called. * @param instance - Instance of the worksheet where the change occurred. * @param td - Td tag of the cell whose editor was opened. * @param colIndex - Column index of the cell whose editor was opened. * @param rowIndex - Row index of the cell whose editor was opened. */ oneditionstart?: (instance: WorksheetInstance, td: HTMLTableCellElement, colIndex: number, rowIndex: number) => void; /** * Event fired when any other event fires. It runs before the event that was called. * * If the called event has not been defined, the jss considers the value returned by onevent as the value returned by the called event. * @param event - Name of the event that was called. * @param rest - Arguments of the event that was called. */ onevent?: (event: string, ...rest: any[]) => any; /** * Occurs when the table is focused. * @param instance - Instance of the worksheet that was focused on. */ onfocus?: (instance: WorksheetInstance) => void; /** * Occurs after a new column is inserted. * @param instance - Instance of the worksheet where the change occurred. * @param columns - Columns that have been added. */ oninsertcolumn?: ( instance: WorksheetInstance, columns: { column: number; options: Column; data?: CellValue[]; }[] ) => void; /** * Occurs after a new row is inserted. * @param instance - Instance of the worksheet where the change occurred. * @param rows - Rows that have been added. */ oninsertrow?: ( instance: WorksheetInstance, rows: { row: number; data: CellValue[]; }[] ) => void; /** * Event fired when a spreadsheet is created. * @param instance - Jspreadsheet instance. */ onload?: (instance: SpreadsheetInstance) => void; /** * Occurs when a group of cells is merged. * @param instance - Instance of the worksheet where the change occurred. * @param merges - Merges that were created. */ onmerge?: (instance: WorksheetInstance, merges: Record) => void; /** * Occurs when a merge is removed. * @param instance - Instance of the worksheet where the change occurred. * @param cellName - Address of the cell that merge was removed. * @param beforeMerges - Merges that were before the merge was removed. */ onunmerge?: (instance: WorksheetInstance, cellName: string, beforeMerges: Record) => void; /** * Occurs after a column is moved to a new position. * @param instance - Instance of the worksheet where the change occurred. * @param oldPosition - Column index before movement. * @param newPosition - Column index after movement. * @param quantity - Number of columns that were moved. */ onmovecolumn?: (instance: WorksheetInstance, oldPosition: number, newPosition: number, quantity: number) => void; /** * Occurs after a row is moved to a new position. * @param instance - Instance of the worksheet where the change occurred. * @param oldPosition - Row index before movement. * @param newPosition - Row index after movement. * @param quantity - Number of rows that were moved. */ onmoverow?: (instance: WorksheetInstance, oldPosition: number, newPosition: number, quantity: number) => void; /** * Occurs after a paste action is performed in the javascript table. * @param instance - Instance of the worksheet where the change occurred. * @param pastedInfo - Information that was pasted into the worksheet. */ onpaste?: ( instance: WorksheetInstance, pastedInfo: { x: number; y: number; value: CellValue; }[][] ) => void; /** * Occurs when a change is redone. * @param instance - Instance of the worksheet where the change occurred. * @param historyRecord - History item that was redone. If there are no more actions to redo, it takes the value undefined. */ onredo?: (instance: WorksheetInstance, historyRecord: HistoryRecord | undefined) => void; /** * Occurs after a change in column width. * @param instance - Instance of the worksheet where the change occurred. * @param colIndex - Index of columns that were resized. * @param newWidth - New column widths. * @param oldWidth - Old column widths. */ onresizecolumn?: (instance: WorksheetInstance, colIndex: number | number[], newWidth: number | number[], oldWidth: number | number[]) => void; /** * Occurs after a change in row height. * @param instance - Instance of the worksheet where the change occurred. * @param rowIndex - Index of row being resized. * @param newHeight - New row height. * @param oldHeight - Old row height. */ onresizerow?: (instance: WorksheetInstance, rowIndex: number, newHeight: number, oldHeight: number) => void; /** * Occurs when persistence on the server succeeds. * @param spreadsheetInstance - Spreadsheet instance. * @param worksheetInstance - Instance of the worksheet to be saved. * @param data - Data that has been sent to the server. */ onsave?: (spreadsheetInstance: SpreadsheetInstance, worksheetInstance: WorksheetInstance, data: any) => void; /** * Occurs when selection is changed. * @param instance - Instance of the worksheet where the change occurred. * @param borderLeftIndex - Index of the first column contained by the selection. * @param borderTopIndex - Index of the first row contained by the selection. * @param borderRightIndex - Index of the last column contained by the selection. * @param borderBottomIndex - Index of the last row contained by the selection. * @param origin - Javascript event that triggered this jss event. */ onselection?: ( instance: WorksheetInstance, borderLeftIndex: number, borderTopIndex: number, borderRightIndex: number, borderBottomIndex: number, origin: Event | undefined ) => void; /** * Occurs after a colum is sorted. * @param instance - Instance of the worksheet where the change occurred. * @param colIndex - Index of the column that was sorted. * @param order - Sorting direction. 0 for ascending and 1 for descending. * @param newOrderValues - The new order of the rows. */ onsort?: (instance: WorksheetInstance, colIndex: number, order: 0 | 1, newOrderValues: number[]) => void; /** * Occurs when a change is undone. * @param instance - Instance of the worksheet where the change occurred. * @param historyRecord - History item that was undone. If there are no more actions to undo, it takes the value undefined. */ onundo?: (instance: WorksheetInstance, historyRecord: HistoryRecord | undefined) => void; /** * Enable execution of formulas inside the table. * @default true */ parseFormulas?: boolean; /** * Function used in sorting columns. If not specified, the default function will be used. * @param order - Sorting direction. 0 for ascending and 1 for descending. */ sorting?: (order: 0 | 1) => (itemA: SortingItem, itemB: SortingItem) => number; /** * If false, HTML inside cell values will be treated as regular text. If true, the HTML will be treated as HTML. * @default false */ parseHTML?: boolean; /** * If true, the button to create new worksheets is shown. * @default false */ tabs?: boolean; /** Add custom toolbars. */ toolbar?: boolean | ToolbarItem[] | ((defaultToolbar: ToolbarItem[]) => ToolbarItem[]) | Record; /** * Worksheet settings. */ worksheets: WorksheetOptions[]; } interface WorksheetOptions { /** * Allow comments over the cells. * @default true */ allowComments?: boolean; /** * Allow delete a column. * @default true */ allowDeleteColumn?: boolean; /** * Allow delete a row. * @default true */ allowDeleteRow?: boolean; /** * Allow remove all rows. Otherwise, at least one row will be kept. * @default false */ allowDeletingAllRows?: boolean; /** * Allow insert a new column. * @default true */ allowInsertColumn?: boolean; /** * Allow insert a new row. * @default true */ allowInsertRow?: boolean; /** * Allow user to insert a new column using tab key on last column. * @default true */ allowManualInsertColumn?: boolean; /** * Allow user to insert a new row using space key on last row. * @default true */ allowManualInsertRow?: boolean; /** * Allow rename a column. * @default true */ allowRenameColumn?: boolean; /** * Css classes to apply to cells. Only one class per cell is accepted. * @example * { * A1: "some-class", * B3: "another-class" * } */ classes?: Record; /** * Allow column dragging. * @default true */ columnDrag?: boolean; /** * Allow column resizing. * @default true */ columnResize?: boolean; /** Column settings. */ columns?: Column[]; /** * Allow column sorting. * @default true */ columnSorting?: boolean; /** * Worksheet comments. Each object key is a cell name and its value is its comment. */ comments?: Record; /** * Load a external CSV file from this URL. */ csv?: string; /** * Default delimiter for the CSV file. This value is used for both import and export. * @default "," */ csvDelimiter?: string; /** * Default filename for a download method. * @default "jspreadsheet" */ csvFileName?: string; /** * Load header titles from the CSV file. * @default false */ csvHeaders?: boolean; /** Data loaded into the spreadsheet. */ data?: CellValue[][] | Record[]; /** * Default horizontal alignment used when a column does not have its alignment specified. * @default "center" */ defaultColAlign?: HorizontalAlign; /** * Default width for columns with no specified width. * @default 100 */ defaultColWidth?: number; /** * Default row height. */ defaultRowHeight?: number; /** * Allow table edition. * @default true */ editable?: boolean; /** * Enable column filters. * @default false */ filters?: boolean; /** * Set the initial footer of the spreadsheet */ footers?: string[][]; /** * Number of columns frozen at the top of the spreadsheet. */ freezeColumns?: number; /** Activate the table lazyloading. */ lazyLoading?: boolean; /** Cells to be merged in the table innitialization. */ mergeCells?: Record; /** * Meta information. */ meta?: Record; /** * Minimum table dimensions: [cols, rows]. * @default [0, 0] */ minDimensions?: [number, number]; /** * Minimum number of spare cols. * @default 0 */ minSpareCols?: number; /** * Minimum number of spare rows. * @default 0 */ minSpareRows?: number; /** Define the nested headers. */ nestedHeaders?: NestedHeaderCell[][]; /** Number of rows per page. */ pagination?: number; /** * Values available in the dropdown for choosing the number of rows per page. * * This dropdown is only visible when the {@link WorksheetOptions.search} option is true and the {@link WorksheetOptions.pagination} option is greater than 0. */ paginationOptions?: number[]; /** * Try to identify the column type when the instance is created with a table tag. * @default false */ parseTableAutoCellType?: boolean; /** * If creating the instance with a table tag, if this tag has no header, transform the first line into the header. * @default false */ parseTableFirstRowAsHeader?: boolean; /** * Route where requests for data persistence will be sent. If true, the {@link WorksheetOptions.url} property value will be used instead. */ persistence?: boolean | string; /** * Spreadsheet plugins. */ plugins?: Record Plugin>; /** * DOM element for binding the javascript events. This property is normally used when JSS is running as a web component. */ root?: HTMLElement; /** * Allow row dragging. * @default true */ rowDrag?: boolean; /** * Allow row resizing. * @default true */ rowResize?: boolean; /** Row settings. */ rows?: Row[] | Record; /** * Allow search in the table. * @default false */ search?: boolean; /** * If true, Jss will capitalize characters that are part of formulas. This does not apply to characters enclosed in double quotes, which represent text within formulas. * @default true */ secureFormulas?: boolean; /** * Display the copy icon in the lower right corner of the selection. * @default true */ selectionCopy?: boolean; /** * Cell styles. */ style?: Record; /** * Set the max height of the table. * This property is only used when {@link WorksheetOptions.tableOverflow} is allowed. */ tableHeight?: string | number; /** * Allow table overflow. * @default false */ tableOverflow?: boolean; /** * Set the max width of the table. * This property is only used when {@link WorksheetOptions.tableOverflow} is allowed. */ tableWidth?: string | number; /** * If true, cell contents may overflow over empty cells. * @default false */ textOverflow?: boolean; /** Load a external json file from this URL. */ url?: string; /** * Global text wrapping. * @default false */ wordWrap?: boolean; /** * Worksheet name. */ worksheetName?: string; } interface JspreadsheetInstanceElement extends HTMLDivElement { /** * Jss instance this element belongs to */ spreadsheet: SpreadsheetInstance; } interface JworksheetInstanceElement extends HTMLDivElement { /** * Jss worksheet instance this element belongs to */ jspreadsheet: WorksheetInstance; } interface DragInfo { /** * Index where the row or column will be moved. */ destination: number; } interface DragColumnInfo extends DragInfo { /** * Index of the column being moved. */ column: string; /** * HTML element that corresponds to the column being moved. */ element: HTMLTableCellElement; } interface DragRowInfo extends DragInfo { /** * Index of the row being moved. */ row: string; /** * HTML element that corresponds to the row being moved. */ element: HTMLTableRowElement; } interface ResizeInfo { /** * Mouse position when resizing started. This position refers to the vertical axis, when resizing a row, or the horizontal axis, when resizing a column. */ mousePosition: number; } interface ResizeRowInfo extends ResizeInfo { /** * HTML element that represents the row. */ element: HTMLTableRowElement; /** * Old row height. */ height: number; /** * Index of the row being resized. */ row: string; } interface ResizeColumnInfo extends ResizeInfo { /** * Index of the column being resized. */ column: string; /** * Old column width. */ width: number; } interface Plugin { /** * This method is called before a worksheet is created. * @param instance - New worksheet instance. */ beforeinit?: (instance: WorksheetInstance) => void; /** * Get spreadsheet config information. */ getConfig: () => SpreadsheetOptions; /** * This method is called when a worksheet is created. * @param instance - New worksheet instance. */ init?: (instance: WorksheetInstance) => void; /** * Event fired when any other event fires. * @param event - Name of the event that was called. * @param rest - Arguments of the event that was called. */ onevent?: (event: string, ...rest: any[]) => void; /** * This method is called before the spreadsheet sends data to the server. * @param instance - Worksheet instance. * @param method - Name of the method whose execution needs to be saved on the server. * @param data - Arguments of the method whose execution needs to be saved on the server. */ persistence?: (instance: WorksheetInstance, method: string, data: any) => void; /** * Method called before the context menu is displayed. If this method returns anything other than a falsy value, that value overrides the context menu settings. * @param instance - Instance of the worksheet on which the click was made. * @param colIndex - Horizontal index of the element that was clicked. The meaning of this value depends on the {@link role} argument. * @param rowIndex - Vertical index of the element that was clicked. The meaning of this value depends on the {@link role} argument. * @param event - pointer event that triggered this method. * @param items - jss default context menu. * @param role - indicates in which part of the spreadsheet the click occurred. * @param x - Horizontal index of the element that was clicked. The meaning of this value depends on the {@link role} argument. * @param y - Vertical index of the element that was clicked. The meaning of this value depends on the {@link role} argument. * @returns Context menu configuration that should be created. */ contextMenu?: ( instance: WorksheetInstance, colIndex: number | null, rowIndex: number | null, event: PointerEvent, items: ContextMenuItem[], role: ContextMenuRole, x: number | null, y: number | null ) => ContextMenuItem[] | null | undefined; /** * Method called before the toolbar is displayed. If this method returns anything other than a falsy value, that value overrides the toolbar settings. * @param defaultToolbar */ toolbar?: (defaultToolbar: ToolbarItem[]) => ToolbarItem[] | null | undefined; } interface SpreadsheetInstance { /** * Spreadsheet settings. */ config: SpreadsheetOptions; /** * Jsuites contextmenu of this jss instance */ contextMenu: HTMLDivElement; /** * Root HTML element of this jss instance. */ el: JspreadsheetInstanceElement; /** * Alias for el. */ element: JspreadsheetInstanceElement; /** * Toogle table fullscreen mode. * @param activate - Desired mode. Default: The opposite of the current mode. */ fullscreen: (activate?: boolean) => void; /** * Get the index of the currently active worksheet */ getWorksheetActive: () => number; /** * Hide the toolbar. */ hideToolbar: () => void; /** * If true, the spreadsheet does not emit events. */ ignoreEvents?: boolean; /** * Spreadsheet plugins. */ plugins: Record; /** * Change the spreadsheet settings. * @param config - New settings. */ setConfig: (config: SpreadsheetOptions) => void; /** * Add new plugins to the spreadsheet. * @param plugins - New plugins. */ setPlugins: (plugins: Record Plugin>) => void; /** * Show the toolbar using the current settings. */ showToolbar: () => void; /** * HTML div tag used as the toolbar of this jss instance. */ toolbar: HTMLDivElement; /** * Instances of the worksheets that make up this spreadsheet. */ worksheets: WorksheetInstance[]; } interface WorksheetInstance { ads: HTMLDivElement; /** * Close a cell editor. * @param cell - HTML td tag whose editor must be closed. * @param save - Whether or not to save editor content in cell. */ closeEditor: (cell: HTMLTableCellElement, save: boolean) => void; /** * List of "col" tags for this spreadsheet's table */ cols: { colElement: HTMLTableColElement; x: number; }[]; /** * Colgroup tag for this spreadsheet's table */ colgroupContainer: HTMLElement; content: HTMLDivElement; /** * Copies or cuts the contents of selected cells in the worksheet. * @param cut - If true, the operation is cut, if not, it is copy. */ copy: (cut?: boolean) => void; /** * HTML element that sits in the lower-right corner of selections. */ corner: HTMLDivElement; /** * Create a new worksheet. * @param options - Worksheet options. */ createWorksheet: (options: WorksheetOptions) => void; cursor: null | HTMLElement; /** * Last content copied. */ data: string; /** * Remove columns. * * This method returns false if the {@link SpreadsheetOptions.onbeforedeletecolumn} event returns false or if the "This action will destroy any existing merged cells. Are you sure?" dialog receives a negative response. * @param columnNumber - Column index from which removal starts. * @param numOfColumns - Number of columns to be removed. */ deleteColumn: (columnNumber?: number, numOfColumns?: number) => false | undefined; /** * Remove rows. * * This method returns false if the {@link SpreadsheetOptions.onbeforedeleterow} event returns false or if the dialog cases "This action will destroy any existing merged cells. Are you sure?" or "This action will clear your search results. Are you sure?" receive a negative response. * @param rowNumber - Row index from which removal starts. * @param numOfRows - Number of rows to be removed. */ deleteRow: (rowNumber?: number, numOfRows?: number) => false | undefined; /** * Delete a worksheet. * @param position - Worksheet index. */ deleteWorksheet: (position: number) => void; /** * Remove all merged cells. */ destroyMerge: () => void; /** * Emit an event. * @param event - Event name. * @param args - Arguments that should be passed to the event. */ dispatch: (event: string, ...args: any[]) => any; /** * Simulates the action of the "arrow down" key. * @param shiftKey - If true, the method simulates the action of the "arrow down" key while the Shift key is pressed. * @param ctrlKey - If true, the method simulates the action of the "arrow down" key while the Ctrl key is pressed. */ down: (shiftKey?: boolean, ctrlKey?: boolean) => void; /** * Get the current data as a CSV file. * @param includeHeaders - If true, include the header regardless of the {@link SpreadsheetOptions.includeHeadersOnDownload} property value. * @param processed - If true, the result will contain the displayed cell values. Otherwise, the result will contain the actual cell values. */ download: (includeHeaders?: boolean, processed?: boolean) => void; /** * Stores information about the row or column being moved. */ dragging: null | DragColumnInfo | DragRowInfo; /** * Currently open editor information. Respectively the cell whose editor is open, its initial value, its column index and its row index. */ edition: null | [HTMLTableCellElement, string, string, string]; /** * Root HTML element of this worksheet instance. */ element: JworksheetInstanceElement; /** * Execute a formula. * @param expression - Formula to be executed. * @param x - Column index of the cell where the formula is. * @param y - Row index of the cell where the formula is. */ executeFormula: (expression: string, x?: number, y?: number) => any; /** * Table row containing filter inputs. */ filter: null | HTMLTableRowElement; /** * Active filters. */ filters: (string[] | null)[]; /** * Simulates the action of the Home key. * @param shiftKey - If true, the method simulates the action of the Home key while the Shift key is pressed. * @param ctrlKey - If true, the method simulates the action of the Home key while the Ctrl key is pressed. */ first: (shiftKey?: boolean, ctrlKey?: boolean) => void; /** * List of formulas that are used within other formulas. Each key is the name of a cell containing a formula, and each value is a list of cells whose formulas use the cell specified in the key. */ formula: Record; /** * Get cell DOM element by cell name. * @param cell - Cell name. */ getCell(cell: string): HTMLTableCellElement; /** * Get cell DOM element by cell coords. * @param x - Cell column index. * @param y - Cell row index. */ getCell(x: number, y: number): HTMLTableCellElement; /** * Get cell DOM element by cell coordinates. * @param x - Column index of the cell. * @param y - Row index of the cell. */ getCellFromCoords: (x: number, y: number) => HTMLTableCellElement; /** * Get the data from one column by its index. * @param columnNumber - Column index. * @param processed - If true, the return is constructed using the innerHTML of the cells. Otherwise, it is constructed using the {@link WorksheetOptions.data} property. Default: false. */ getColumnData: (columnNumber: number, processed?: boolean) => CellValue[]; /** * Get comments from one or all cells. * @param cell - Cell name. If it is a falsy value, the comments of all cells are returned. */ getComments: (cell?: string) => Record | string; /** * Get worksheet config information. */ getConfig: () => WorksheetOptions; /** * Get the full or partial table data. * @param highlighted - If true, get only data from highlighted cells. If false, get data from all cells. Default: false. * @param processed - If false, the return is constructed using the innerHTML of the cells. Otherwise, it is constructed using the {@link WorksheetOptions.data} property. Default: false. * @param delimiter - Column delimiter. If this property is specified, the result will be formatted like a csv. * @param asJson - If this property is true, the result will be formatted as json. */ getData: (highlighted?: boolean, processed?: boolean, delimiter?: string, asJson?: boolean) => CellValue[][]; /** * Get data from a range. * @param range - Range of cells whose values ​​are to be returned. * @param processed - If true, the method returns the values ​​of the HTML elements of the cells. Otherwise, the method returns the values ​​of the cells in the options.data array. */ getDataFromRange: (range: string, processed: true) => CellValue[][]; /** * Get the column title. * @param column - Column index. */ getHeader: (column: number) => string; /** * Get all header titles. * @param asArray - If true, returns the items in an array, if false, returns them separated by ";" within a single string. */ getHeaders: (asArray?: boolean) => string | string[]; /** * Get height of all rows. */ getHeight(row?: undefined): string[]; /** * Get height of one row. * @param row - Column index. */ getHeight(row: number): string; /** * Get the coordinates of the highlighted selections. */ getHighlighted: () => [number, number, number, number][]; /** * Get the innerHTML of a cell. * @param cell - Cell name. */ getLabel(cell: string): string; /** * Get the innerHTML of a cell. * @param x - Cell column index. * @param y - Cell row index. */ getLabel(x: number, y: number): string; /** * Get information from one or all merged cells * @param cellName - Cell name. If it is a falsy value, it returns the information of all merges. If the given cell is not the anchor of a merge, it returns null. */ getMerge(cellName?: string): Record | [number, number] | null; /** * Get meta information from one or all cells. * @param cell - Cell name. If it is a falsy value, the metadata of all cells is returned. */ getMeta: (cell?: string) => any; /** * Get the range description of the selected cells. */ getRange: () => string; /** * Get data from a row by its index. * @param rowNumber - Row index. * @param processed - If true, the return is constructed using the innerHTML of the cells. Otherwise, it is constructed using the {@link WorksheetOptions.data} property. Default: false. */ getRowData: (rowNumber: number, processed?: boolean) => CellValue[] | undefined; /** * Get information from selected cells in the worksheet. * @param columnNameOnly - If true, the method returns the names of the selected cells. Otherwise, the method returns the records of the selected cells. */ getSelected: (columnNameOnly?: boolean) => | { element: HTMLTableCellElement[][]; x: number; y: number; }[] | string[]; /** * Get indexes of the columns that have highlighted cells. * @param visibleOnly - If true, the method returns only visible columns. */ getSelectedColumns: (visibleOnly?: boolean) => number[]; /** * Get indexes of the rows that have highlighted cells. * @param visibleOnly - If true, the method returns only visible rows. */ getSelectedRows: (visibleOnly?: boolean) => number[]; /** * Get the coordinates of the range that is selected in the worksheet. */ getSelection: () => [number, number, number, number]; /** * Get styles from one or all cells. * @param cell - Name or coordinate of a cell. If omitted, returns styles for all cells. * @param key - Style property. if specified, returns only that property. Otherwise, it returns all the cell's style properties. */ getStyle: (cell?: string | [number, number], key?: string) => string | Record; /** * Get the value of a cell. * @param cell - Cell name. * @param processedValue - If true, it returns the cell's innerHTML. Otherwise, it returns the value of the cell in the {@link WorksheetOptions.data} property. */ getValue: (cell: string, processedValue?: boolean) => CellValue | null; /** * Get the value of a cell by its coordinates. * @param x - Column index. * @param y - Row index. * @param processedValue - If true, it returns the cell's innerHTML. Otherwise, it returns the value of the cell in the {@link WorksheetOptions.data} property. */ getValueFromCoords: (x: number, y: number, processedValue?: boolean) => CellValue | null; /** * Get the width of one or all columns. * @param column - Index of the column. If omitted, returns the widths of all columns. */ getWidth: (column?: number) => number | (number | string)[]; /** * Get the index of the currently active worksheet */ getWorksheetActive: () => number; /** * @deprecated */ hashString: null | number; /** * HTML element that corresponds to the header row. */ headerContainer: HTMLTableRowElement; /** * List of cells that make up the header. */ headers: HTMLTableCellElement[]; /** * Hide a column. * @param colNumber - Column indexes. */ hideColumn: (colNumber: number | number[]) => void; /** * Hide row count column. */ hideIndex: () => void; /** * Hide Row. * @param rowNumber - Row indexes. */ hideRow: (rowNumber: number | number[]) => void; /** * List of highlighted cells. */ highlighted: { element: HTMLTableCellElement; x: number; y: number; }[]; /** * List of actions performed on the worksheet. */ history: HistoryRecord[]; /** * Current position of the {@link WorksheetInstance.history} property. Used to control movement through history. */ historyIndex: number; /** * If true, the "setHistory" method does not create new records in the history. */ ignoreHistory: boolean; /** * Check if a cell is within the current selection. * @param x - Cell column index. * @param y - Cell row index. */ isSelected: (x: number, y: number) => boolean; /** * Insert one or more columns. * * This method returns false if the {@link SpreadsheetOptions.onbeforeinsertcolumn} event returns false or if the "This action will destroy any existing merged cells. Are you sure?" dialog receives a negative response. * @param mixed - Number of columns to insert. It can also be an array of values, but in this case, only one column is inserted, whose data is based on the array items. Default: 1. * @param columnNumber - Index of the column used as reference for the insertion. Default: last column. * @param insertBefore - Insert new columns before or after the reference column. Default: false. * @param properties - New column properties. */ insertColumn: (mixed?: number | CellValue[], columnNumber?: number, insertBefore?: boolean, properties?: Column[]) => false | undefined; /** * Insert one or more rows. * * This method returns false if the {@link SpreadsheetOptions.onbeforeinsertrow} event returns false or if the "This action will destroy any existing merged cells. Are you sure?" or "This action will clear your search results. Are you sure?" dialogs receive a negative response. * @param mixed - Number of rows to insert. It can also be an array of values, but in this case, only one row is inserted, whose data is based on the array items. Default: 1. * @param rowNumber - Index of the row used as reference for the insertion. Default: last row. * @param insertBefore - Insert new rows before or after the reference row. Default: false. */ insertRow: (mixed?: number | CellValue[], rowNumber?: number, insertBefore?: number) => false | undefined; /** * Check if a cell is read only. * @param x - Cell column index. * @param y - Cell row index. */ isReadOnly(x: number, y: number): boolean; /** * Check if a cell is read only. * @param cellName - Cell name. */ isReadOnly(cellName: string): boolean; /** * Simulates the action of the End key. * @param shiftKey - If true, the method simulates the action of the End key while the Shift key is pressed. * @param ctrlKey - If true, the method simulates the action of the End key while the Ctrl key is pressed. */ last: (shiftKey?: boolean, ctrlKey?: boolean) => void; /** * Simulates the action of the "arrow left" key. * @param shiftKey - If true, the method simulates the action of the "arrow left" key while the Shift key is pressed. * @param ctrlKey - If true, the method simulates the action of the "arrow left" key while the Ctrl key is pressed. */ left: (shiftKey?: boolean, ctrlKey?: boolean) => void; /** * Move a column. * * This method returns false if the "This action will destroy any existing merged cells. Are you sure?" dialog receives a negative response. * @param o - Column index. * @param d - New column index. */ moveColumn: (o: number, d: number) => false | undefined; /** * Move a row. * * This method returns false if the "This action will destroy any existing merged cells. Are you sure?" or "This action will clear your search results. Are you sure?" dialogs receive a negative response. * @param o - Row index. * @param d - New row index. */ moveRow: (o: number, d: number) => false | undefined; /** * Start the edition for one cell. * @param cell - Cell HTML Element. * @param empty - If true, the editor opens without content even if the cell had content. * @param event - Js event that triggered the editor opening. This argument is passed to the "openEditor" method of custom editors. */ openEditor: (cell: HTMLTableCellElement, empty?: boolean, event?: KeyboardEvent | MouseEvent | TouchEvent) => void; /** * Open the column filter. * * This method only runs if the {@link WorksheetOptions.filters} property is true. * @param columnId - Column index. */ openFilter: (columnId: number) => void; /** * Open the worksheet by index. * @param position - Worksheet index. */ openWorksheet: (position: number) => void; /** * Spreadsheet settings. */ options: WorksheetOptions; /** * Reorder rows based on values in a column. * * This method returns false if the "This action will destroy any existing merged cells. Are you sure?" dialog receives a negative response, or returns true if the sort is successful. * @param column - Column index. If the value of this parameter is less than 0, the method returns false and does not perform sorting. * @param order - Sorting direction. 0 for ascending and 1 for descending. */ orderBy: (column: number, order: 0 | 1) => boolean | undefined; /** * Go to page. Valid only when {@link WorksheetOptions.pagination} is true. * @param pageNumber - Page number (starting at 0). */ page: (pageNumber: number) => void; /** * Current spreadsheet page. */ pageNumber: undefined | number; /** * Div with pagination controls. */ pagination: HTMLDivElement; /** * Spreadsheet of which this worksheet is part. */ parent: SpreadsheetInstance; /** * Pastes content into one or more cells. * @param x - Column index of the cell from which the content will be pasted. * @param y - Row index of the cell from which the content will be pasted. * @param data - Content to be pasted. */ paste: (x: number, y: number, data: string) => false | undefined; /** * Get the number of pages of the worksheet. */ quantiyOfPages: () => number; /** * List of HTML elements representing table cells. */ records: { element: HTMLTableCellElement; x: number; y: number; }[][]; /** * Redo previously undone action */ redo: () => void; /** * Remove a merge. * @param cellName - Merge anchor cell. * @param data - Data to be placed in cells released from the merge. */ removeMerge: (cellName: string, data?: CellValue[]) => void; /** * Reset all filters. */ resetFilters: () => void; /** * Reset search */ resetSearch: () => void; /** * Reset highlighted cell selection. * @returns If there were highlighted cells, it returns 1, otherwise it returns 0. */ resetSelection: () => 0 | 1; /** * Reset styles of one or more cells. * @param o - Object whose keys are the names of the cells that must have their styles reset. * @param ignoreHistoryAndEvents - If true, do not add this action to history. */ resetStyle: (o: Record, ignoreHistoryAndEvents?: boolean) => void; /** * Information about the row or column currently being resized. */ resizing: undefined | null | ResizeRowInfo | ResizeColumnInfo; /** * Indices of the rows that include the searched text. */ results: null | number[]; /** * Simulates the action of the "arrow right" key. * @param shiftKey - If true, the method simulates the action of the "arrow right" key while the Shift key is pressed. * @param ctrlKey - If true, the method simulates the action of the "arrow right" key while the Ctrl key is pressed. */ right: (shiftKey?: boolean, ctrlKey?: boolean) => void; /** * List of rows that make up the table. */ rows: { element: HTMLTableRowElement; y: number; }[]; /** * Search for some text. * @param query - Text to be searched. */ search: (query: string) => void; /** * Text field used to perform searches. */ searchInput: HTMLInputElement; /** * Select all table cells. */ selectAll: () => void; /** * Current selection coordinates. * * The array is composed respectively by the indices of the leftmost column of the selection [0], the topmost row of the selection [1], the rightmost column of the selection [2] and the bottommost row selection [3]. */ selectedCell: undefined | null | [number, number, number, number] | [string, string, string, string]; selectedContainer: undefined | null | [number, number, number, number]; /** * Cells that currently have "autocomplete selection". */ selection: HTMLTableCellElement[]; /** * Set the data from one column by index. * @param colNumber - Column index. * @param data - New data. Positions with the null value are not changed in the table. * @param force - If true, the method also changes the contents of readonly columns. */ setColumnData: (colNumber: number, data: (CellValue | null)[], force?: boolean) => void; /** * Set or remove a comment. * @param cellId - Name of the cell. * @param comments - New comment. If it is a falsy value, the method just uncomments the cell. */ setComments(cellId: string, comments: string): void; /** * Set or remove comments. * @param cellId - Object whose keys are cell names and values ​​are cell comments. If the value of a key is a falsy value, the cell comment is removed. */ setComments(cellId: Record): void; /** * Change the worksheet or spreadsheet settings. * @param config - New settings. * @param spreadsheetLevel - If true, the settings are applied to the spreadsheet. If not, they are applied to the worksheet. */ setConfig: (config: SpreadsheetOptions, spreadsheetLevel?: boolean) => void; /** * Set data. * @param data - New data. It can be an array of cell values or an array of objects whose values are cell values. */ setData: (data?: CellValue[][] | Record[]) => void; /** * Set a column title. * @param column - Column index. * @param newValue - New title. Empty string or undefined to reset the header title. */ setHeader: (column: number, newValue?: string) => void; /** * Change row height. * @param row - Row index. * @param height - New height. An integer greater than zero. */ setHeight: (row: number, height: number) => void; /** * Merge cells. * @param cellName - Name of a cell. If it is a falsy value, this method merges the selected cells in the table and ignores all parameters of this method. * @param colspan - Number of columns this merge occupies. * @param rowspan - Number of rows this merge occupies. * @returns If the "cellName" parameter is a falsy value, and there are no cells selected in the table, this method returns null. */ setMerge: (cellName?: string, colspan?: number, rowspan?: number) => null | undefined; /** * Set a property on a cell's meta information. * @param o - Cell name. * @param k - Property name. * @param v - Property value. */ setMeta(o: string, k: string, v: string): void; /** * Remove current and define new meta information for one or more cells. * @param o - Object with the new meta information. */ setMeta(o: Record>): void; /** * Change the read only state of a cell. * @param cell - Cell HTML element or its name. * @param state - New read only state. */ setReadOnly: (cell: string | HTMLTableCellElement, state: boolean) => void; /** * Set a row data by index. * @param rowNumber - Row index. * @param data - New data. Positions with the null value are not changed in the table. * @param force - If true, the method also changes the contents of readonly columns. */ setRowData: (rowNumber: number, data: (CellValue | null)[], force?: boolean) => void; /** * Change a single style of one or more cells. * @param o - Name of a cell. * @param k - property to be changed. * @param v - New property value. If equal to the property's current value and the "force" parameter is false, removes that property from the style. * @param force - If true, changes the value of the property even if the cell is read-only. Also, if true, even if the new value of the property is the same as the current one, the property is not removed. */ setStyle(o: string, k: string, v: string, force?: boolean): void; /** * Change cell styles. * @param o - Object where each key is the name of a cell and each value is the style changes for that cell. Each value can be a string with css styles separated by semicolons or an array where each item is a string with a css style. * @param k - It is not used. * @param v - It is not used. * @param force - If true, changes the value of the property even if the cell is read-only. Also, if true, even if the new value of the property is the same as the current one, the property is not removed. */ setStyle(o: Record, k?: null | undefined, v?: null | undefined, force?: boolean): void; /** * Change the value of one or more cells. * @param cell - Name of a cell, HTML element that represents a cell or an array whose items can be any of the previous alternatives or objects. When an array item is an object, it must have the cell coordinates ("x" and "y") and can have the cell's new value ("value"), but if does not have it, the "value" parameter is used instead. * @param value - New cell value. * @param force - If true, changes the value of even read-only cells. */ setValue: ( cell: string | HTMLTableCellElement | (string | { x: number; y: number; value?: CellValue } | HTMLTableCellElement)[], value?: CellValue, force?: boolean ) => void; /** * Set a cell value based on its coordinates. * @param x - Cell column index. * @param y - Cell row index. * @param value - New value. * @param force - If true, changes the value of even read-only cells. */ setValueFromCoords: (x: number, y: number, value: CellValue, force?: boolean) => void; /** * Set the width of a column. * @param column - Column index. * @param width - New width. */ setWidth(column: number, width: number): void; /** * Set the width of one or more columns. * @param column - Column indexes. * @param width - New widths. */ setWidth(column: number[], width: number | number[]): void; /** * Show hidden column. * @param colNumber - Column index. */ showColumn: (colNumber: number | number[]) => void; /** * Show row count column. */ showIndex: () => void; /** * Show hidden row. * @param rowNumber - Row index. */ showRow: (rowNumber: number | number[]) => void; /** * Styles of the cells that were copied. */ style: string[]; /** * HTML table tag of this jss instance. */ table: HTMLTableElement; /** * HTML tbody tag of this jss instance. */ tbody: HTMLTableSectionElement; /** * HTML textarea tag used internally when copying cells. */ textarea: HTMLTextAreaElement; /** * HTML thead tag of this jss instance. */ thead: HTMLTableSectionElement; /** * Undo last action. */ undo: () => void; /** * Simulates the action of the "arrow up" key. * @param shiftKey - If true, the method simulates the action of the "arrow up" key while the Shift key is pressed. * @param ctrlKey - If true, the method simulates the action of the "arrow up" key while the Ctrl key is pressed. */ up: (shiftKey?: boolean, ctrlKey?: boolean) => void; /** * Select cells. * @param x1 - Column index of the first cell of the selection. If omitted or null, rows "y1" through "y2" are selected. * @param y1 - Row index of the first cell of the selection. If omitted or null, columns "x1" through "x2" are selected. * @param x2 - Column index of the last cell of the selection. Default: Parameter "x1". * @param y2 - Row index of the last cell of the selection. Default: Parameter "y1". */ updateSelectionFromCoords: (x1: number | null, y1: number | null, x2?: number | null, y2?: number | null) => false | undefined; /** * Get the page index of a row. * @param cell - Row index. */ whichPage: (cell: number) => number; } type Version = () => { host: string; license: string; print: () => [string]; type: string; version: string; }; interface JssHelpers { /** * Extract the configuration to create a new spreadsheet from a static HTML element. * @param element - Table element. * @param options - Worksheet options. */ createFromTable: (element: HTMLTableElement, options: WorksheetOptions) => WorksheetOptions; /** * Internal method. */ getCaretIndex: (e: any) => number; /** * Get the column letter(s) based on its index. * @param i - Column index. */ getColumnName: (i: number) => string; /** * Get "A1" style coordinates based on column and row indices. * @param x - Column index. * @param y - Row index. */ getCellNameFromCoords: (x: number, y: number) => string; /** * Get column and row indices based on coordinate in "A1" style. * @param columnName - Coordinate in "A1" style. */ getCoordsFromCellName: (columnName: string) => [number, number | null] | undefined; /** * Get coordinates from a range. * @param range - Range in "A1:B2" style. * @returns Array filled with the x and y coordinates of the first and last cells in the range. */ getCoordsFromRange: (range: string) => [number, number, number, number]; /** * Internal method. */ invert: (o: object) => any[] & Record; /** * Parse CSV string to JS array. * @param str - Text in csv format. * @param delimiter - Csv delimiter. */ parseCSV: (str: string, delimiter?: string) => string[][]; } interface JSpreadsheet { (element: HTMLDivElement | HTMLTableElement, options: SpreadsheetOptions): WorksheetInstance[]; /** * Current instance of jss. */ current: null | WorksheetInstance; /** * Destroy an instance of jss. * @param element - Root element of jss instance. * @param destroyEventHandlers - Remove event listeners. Default: false. */ destroy: (element: JspreadsheetInstanceElement, destroyEventHandlers?: boolean) => void; /** * Destroy all instances of jss. */ destroyAll: () => void; /** * Get a worksheet instance by name and namespace. * @param worksheetName - Name of the searched worksheet. If null or undefined, the method returns the found namespace. * @param namespace - Namespace name. */ getWorksheetInstanceByName: (worksheetName: string | null | undefined, namespace: string) => WorksheetInstance | Record; helpers: JssHelpers; /** * Internal method. */ isMouseAction: boolean; /** * Defines translations. * @param o - Translations. */ setDictionary: (o: Record) => void; spreadsheet: SpreadsheetInstance[]; /** * Internal method. */ timeControl: null | number; /** * Internal method. */ timeControlLoading: null | number; /** * Basic version information. */ version: Version; [key: string]: any; } } ================================================ FILE: dist/index.js ================================================ if (! jSuites && typeof(require) === 'function') { var jSuites = require('jsuites'); } if (! formula && typeof(require) === 'function') { var formula = require('@jspreadsheet/formula'); } ;(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : global.jspreadsheet = factory(); }(this, (function () { var jspreadsheet;(function(){"use strict";var __webpack_modules__={805:function(e,t){const s=function(e){const t=this,s=[];for(let n=0;n0)for(let s=0;s0;)t=(s-1)%26,n=String.fromCharCode(65+t).toString()+n,s=parseInt((s-t)/26);return n},i=function(e,t){return l(parseInt(e))+(parseInt(t)+1)},a=function(e){const t=/^[a-zA-Z]+/.exec(e);if(t){let s=0;for(let e=0;e0&&n--,[s,n]}},c=function(e){const[t,s]=e.split(":");return[...a(t),...a(s)]},d=function(e,t){t=t||",",e=e.replace(/\r?\n$|\r$|\n$/g,"");const s=[];let n=!1,o=0,r=0,l=0;for(let i=0;i{for(let t=e.length;t<=o;t++)e.push("")})),s},u=function(e,t){if("TABLE"==e.tagName){t||(t={}),t.columns=[],t.data=[];const s=e.querySelectorAll("colgroup > col");if(s.length)for(let e=0;e50?n.width:50;t.columns[s]||(t.columns[s]={}),e.getAttribute("data-celltype")?t.columns[s].type=e.getAttribute("data-celltype"):t.columns[s].type="text",t.columns[s].width=o+"px",t.columns[s].title=e.innerHTML,e.style.textAlign&&(t.columns[s].align=e.style.textAlign),(n=e.getAttribute("name"))&&(t.columns[s].name=n),(n=e.getAttribute("id"))&&(t.columns[s].id=n),(n=e.getAttribute("data-mask"))&&(t.columns[s].mask=n)},r=[];let l=e.querySelectorAll(":scope > thead > tr");if(l.length){for(let e=0;e tr, :scope > tbody > tr");for(let e=0;e0&&(t.nestedHeaders=r),Object.keys(d).length>0&&(t.style=d),Object.keys(a).length>0&&(t.mergeCells=a),Object.keys(c).length>0&&(t.rows=c),Object.keys(u).length>0&&(t.classes=u),p=e.querySelectorAll("tfoot tr"),p.length){const e=[];for(let t=0;t0&&(t.footers=e)}if(1==t.parseTableAutoCellType){const e=[];for(let s=0;s25&&(n=!1),10==l.length&&"-"==l.substr(4,1)&&"-"==l.substr(7,1)||(o=!1)}const r=Object.keys(e[s]).length;o?t.columns[s].type="calendar":1==n&&r>1&&r<=parseInt(.1*t.data.length)&&(t.columns[s].type="dropdown",t.columns[s].source=Object.keys(e[s]))}}return t}console.log("Element is not a table")}},911:function(e,t,s){s.d(t,{Dh:function(){return c},ZS:function(){return h},tN:function(){return p}});var n=s(805),o=s(689),r=s(530),l=s(910),i=s(94),a=s(657);const c=function(e){const t=this;if(1!=t.ignoreHistory){const s=++t.historyIndex;t.history=t.history=t.history.slice(0,s+1),t.history[s]=e}},d=function(e,t){const s=this,n=t.insertBefore?+t.rowNumber:t.rowNumber+1;if(1==s.options.search&&s.results&&s.results.length!=s.rows.length&&s.resetSearch(),1==e){const e=t.numOfRows;for(let t=n;t[...e]));s.records=(0,o.Hh)(s.records,n,e);const r=t.rowData.map((e=>[...e]));s.options.data=(0,o.Hh)(s.options.data,n,r),s.rows=(0,o.Hh)(s.rows,n,t.rowNode);let l=0;for(let e=n;e0&&s.page(s.pageNumber),r.o8.call(s)},u=function(e,t){const s=this,n=t.insertBefore?t.columnNumber:t.columnNumber+1;if(1==e){const e=t.numOfColumns;s.options.columns.splice(n,e);for(let t=n;t0&&s.options.nestedHeaders[0]&&s.options.nestedHeaders[0][0])for(let n=0;n=0)if(r=e.history[e.historyIndex--],"insertRow"==r.action)d.call(e,1,r);else if("deleteRow"==r.action)d.call(e,0,r);else if("insertColumn"==r.action)u.call(e,1,r);else if("deleteColumn"==r.action)u.call(e,0,r);else if("moveRow"==r.action)e.moveRow(r.newValue,r.oldValue);else if("moveColumn"==r.action)e.moveColumn(r.newValue,r.oldValue);else if("setMerge"==r.action)e.removeMerge(r.column,r.data);else if("setStyle"==r.action)e.setStyle(r.oldValue,null,null,1);else if("setWidth"==r.action)e.setWidth(r.column,r.oldValue);else if("setHeight"==r.action)e.setHeight(r.row,r.oldValue);else if("setHeader"==r.action)e.setHeader(r.column,r.oldValue);else if("setComments"==r.action)e.setComments(r.oldValue);else if("orderBy"==r.action){let t=[];for(let e=0;e0){let t=0;for(let s=e.rows.length-1;s>=0;s--){let n=!1;for(let t=0;t0&&e.insertRow(e.options.minSpareRows-t)}if(e.options.minSpareCols>0){let t=0;for(let s=e.headers.length-1;s>=0;s--){let n=!1;for(let t=0;t0&&e.insertColumn(e.options.minSpareCols-t)}e.options.footers&&_footer_js__WEBPACK_IMPORTED_MODULE_0__.e.call(e),e.options.columns.length-1)return console.error("Self Reference detected"),"#ERROR";{const formulaExpressions={};if(tokens)for(let i=0;i0?n.join("; "):""},validDate=function(e){return"-"==(e=""+e).substr(4,1)&&"-"==e.substr(7,1)||4==(e=e.split("-"))[0].length&&e[0]==Number(e[0])&&2==e[1].length&&e[1]==Number(e[1])},stripScript=function(e){const t=new Option;t.innerHTML=e;let s=null;for(e=t.getElementsByTagName("script");s=e[0];)s.parentNode.removeChild(s);return t.innerHTML},createCell=function(e,t,s){const n=this;let o=document.createElement("td");if(o.setAttribute("data-x",e),o.setAttribute("data-y",t),"none"===n.headers[e].style.display&&(o.style.display="none"),"="==(""+s).substr(0,1)&&1==n.options.secureFormulas){const e=secureFormula(s);e!=s&&(s=e)}if(n.options.columns&&n.options.columns[e]&&"object"==typeof n.options.columns[e].type)!0===n.parent.config.parseHTML?o.innerHTML=s:o.textContent=s,"function"==typeof n.options.columns[e].type.createCell&&n.options.columns[e].type.createCell(o,s,parseInt(e),parseInt(t),n,n.options.columns[e]);else if(n.options.columns&&n.options.columns[e]&&"hidden"==n.options.columns[e].type)o.style.display="none",o.textContent=s;else if(n.options.columns&&n.options.columns[e]&&("checkbox"==n.options.columns[e].type||"radio"==n.options.columns[e].type)){const r=document.createElement("input");r.type=n.options.columns[e].type,r.name="c"+e,r.checked=1==s||1==s||"true"==s,r.onclick=function(){n.setValue(o,this.checked)},1!=n.options.columns[e].readOnly&&0!=n.options.editable||r.setAttribute("disabled","disabled"),o.appendChild(r),n.options.data[t][e]=r.checked}else if(n.options.columns&&n.options.columns[e]&&"calendar"==n.options.columns[e].type){let t=null;if(!validDate(s)){const o=jSuites.calendar.extractDateFromString(s,n.options.columns[e].options&&n.options.columns[e].options.format||"YYYY-MM-DD");o&&(t=o)}o.textContent=jSuites.calendar.getDateString(t||s,n.options.columns[e].options&&n.options.columns[e].options.format)}else if(n.options.columns&&n.options.columns[e]&&"dropdown"==n.options.columns[e].type)o.classList.add("jss_dropdown"),o.textContent=getDropDownValue.call(n,e,s);else if(n.options.columns&&n.options.columns[e]&&"color"==n.options.columns[e].type)if("square"==n.options.columns[e].render){const e=document.createElement("div");e.className="color",e.style.backgroundColor=s,o.appendChild(e)}else o.style.color=s,o.textContent=s;else if(n.options.columns&&n.options.columns[e]&&"image"==n.options.columns[e].type){if(s&&"data:image"==s.substr(0,10)){const e=document.createElement("img");e.src=s,o.appendChild(e)}}else n.options.columns&&n.options.columns[e]&&"html"==n.options.columns[e].type||!0===n.parent.config.parseHTML?o.innerHTML=stripScript(parseValue.call(this,e,t,s,o)):o.textContent=parseValue.call(this,e,t,s,o);n.options.columns&&n.options.columns[e]&&1==n.options.columns[e].readOnly&&(o.className="readonly");const r=n.options.columns&&n.options.columns[e]&&n.options.columns[e].align||n.options.defaultColAlign||"center";return o.style.textAlign=r,n.options.columns&&n.options.columns[e]&&0==n.options.columns[e].wordWrap||!(1==n.options.wordWrap||n.options.columns&&n.options.columns[e]&&1==n.options.columns[e].wordWrap||o.innerHTML.length>200)||(o.style.whiteSpace="pre-wrap"),e>0&&1==this.options.textOverflow&&(s||o.innerHTML?n.records[t][e-1].element.style.overflow="hidden":e==n.options.columns.length-1&&(o.style.overflow="hidden")),_dispatch_js__WEBPACK_IMPORTED_MODULE_3__.A.call(n,"oncreatecell",n,o,e,t,s),o},updateCell=function(e,t,s,n){const o=this;let r;if(1!=o.records[t][e].element.classList.contains("readonly")||n){if("="==(""+s).substr(0,1)&&1==o.options.secureFormulas){const e=secureFormula(s);e!=s&&(s=e)}const n=_dispatch_js__WEBPACK_IMPORTED_MODULE_3__.A.call(o,"onbeforechange",o,o.records[t][e].element,e,t,s);if(null!=n&&(s=n),o.options.columns&&o.options.columns[e]&&"object"==typeof o.options.columns[e].type&&"function"==typeof o.options.columns[e].type.updateCell){const n=o.options.columns[e].type.updateCell(o.records[t][e].element,s,parseInt(e),parseInt(t),o,o.options.columns[e]);void 0!==n&&(s=n)}r={x:e,y:t,col:e,row:t,value:s,oldValue:o.options.data[t][e]};let l=o.options.columns&&o.options.columns[e]&&"object"==typeof o.options.columns[e].type?o.options.columns[e].type:null;if(l)o.options.data[t][e]=s,"function"==typeof l.setValue&&l.setValue(o.records[t][e].element,s);else if(o.options.columns&&o.options.columns[e]&&("checkbox"==o.options.columns[e].type||"radio"==o.options.columns[e].type)){if("radio"==o.options.columns[e].type)for(let t=0;t200)?o.records[t][e].element.style.whiteSpace="":o.records[t][e].element.style.whiteSpace="pre-wrap";e>0&&(o.records[t][e-1].element.style.overflow=s?"hidden":""),o.options.columns&&o.options.columns[e]&&"function"==typeof o.options.columns[e].render&&o.options.columns[e].render(o.records[t]&&o.records[t][e]?o.records[t][e].element:null,s,parseInt(e),parseInt(t),o,o.options.columns[e]),_dispatch_js__WEBPACK_IMPORTED_MODULE_3__.A.call(o,"onchange",o,o.records[t]&&o.records[t][e]?o.records[t][e].element:null,e,t,s,r.oldValue)}else r={x:e,y:t,col:e,row:t};return r},isFormula=function(e){const t=(""+e)[0];return"="==t||"#"==t},getMask=function(e){if(e.format||e.mask||e.locale){const t={};return e.mask?t.mask=e.mask:e.format?t.mask=e.format:(t.locale=e.locale,t.options=e.options),e.decimal&&(t.options||(t.options={}),t.options={decimal:e.decimal}),t}return null},secureFormula=function(e){let t="",s=0;for(let n=0;n0)if(chainLoopProtection[o])n.records[t][e].element.innerHTML="#ERROR",n.formula[o]="";else{chainLoopProtection[o]=!0;for(let e=0;et.content.scrollTop+30&&ht.content.scrollLeft+m&&p0?e.options.pagination:e.results?e.results.length:e.rows.length;e.tbody.firstChild;)e.tbody.removeChild(e.tbody.firstChild);for(let n=0;n-1?(s0&&_pagination_js__WEBPACK_IMPORTED_MODULE_7__.IV.call(e),_selection_js__WEBPACK_IMPORTED_MODULE_1__.Aq.call(e),_dispatch_js__WEBPACK_IMPORTED_MODULE_3__.A.call(e,"onupdateresult",e,e.results),t},getCell=function(e,t){if("string"==typeof e){const s=(0,_internalHelpers_js__WEBPACK_IMPORTED_MODULE_2__.vu)(e,!0);e=s[0],t=s[1]}return this.records[t][e].element},getCellFromCoords=function(e,t){return this.records[t][e].element},getLabel=function(e,t){if("string"==typeof e){const s=(0,_internalHelpers_js__WEBPACK_IMPORTED_MODULE_2__.vu)(e,!0);e=s[0],t=s[1]}return this.records[t][e].element.innerHTML},fullscreen=function(e){const t=this;null==e&&(e=!t.config.fullscreen),t.config.fullscreen!=e&&(t.config.fullscreen=e,1==e?t.element.classList.add("fullscreen"):t.element.classList.remove("fullscreen"))},showIndex=function(){this.table.classList.remove("jss_hidden_index")},hideIndex=function(){this.table.classList.add("jss_hidden_index")},createNestedHeader=function(e){const t=this,s=document.createElement("tr");s.classList.add("jss_nested");const n=document.createElement("td");n.classList.add("jss_selectall"),s.appendChild(n),e.element=s;let o=0;for(let n=0;nn.length;)n.push(void 0);return n.concat(s)},r=function(e,t){const s=/^[a-zA-Z]+/.exec(e);if(s){let n=0;for(let e=0;e0&&o--,e=1==t?[n,o]:n+"-"+o}return e},l=function(e){return Array.isArray(e)||(e=e.split("-")),(0,n.getColumnName)(parseInt(e[0]))+(parseInt(e[1])+1)}},497:function(e,t,s){s.d(t,{AG:function(){return o},G_:function(){return r},p6:function(){return l},wu:function(){return n}});const n=function(e){const t=this;let s;s=1!=t.options.search&&1!=t.options.filters||!t.results?t.rows:t.results;const n=100;null!=e&&-1!=e||(e=Math.ceil(s.length/n)-1);let o=e*n,r=e*n+n;r>s.length&&(r=s.length),o=r-100,o<0&&(o=0);for(let e=o;en&&t.tbody.removeChild(t.tbody.firstChild)},o=function(){const e=this;if(e.selectedCell){const t=parseInt(e.tbody.firstChild.getAttribute("data-y"))/100,s=parseInt(e.selectedCell[3]/100),n=parseInt(e.rows.length/100);if(t!=s&&s<=n&&!Array.prototype.indexOf.call(e.tbody.children,e.rows[e.selectedCell[3]].element))return e.loadPage(s),!0}return!1},r=function(){const e=this;let t;t=1!=e.options.search&&1!=e.options.filters||!e.results?e.rows:e.results;let s=0;if(t.length>100){let n=parseInt(e.tbody.firstChild.getAttribute("data-y"));if(1!=e.options.search&&1!=e.options.filters||!e.results||(n=t.indexOf(n)),n>0)for(let o=0;o<30;o++)n-=1,n>-1&&(1!=e.options.search&&1!=e.options.filters||!e.results?e.tbody.insertBefore(e.rows[n].element,e.tbody.firstChild):e.tbody.insertBefore(e.rows[t[n]].element,e.tbody.firstChild),e.tbody.children.length>100&&(e.tbody.removeChild(e.tbody.lastChild),s=1))}return s},l=function(){const e=this;let t;t=1!=e.options.search&&1!=e.options.filters||!e.results?e.rows:e.results;let s=0;if(t.length>100){let n=parseInt(e.tbody.lastChild.getAttribute("data-y"));if(1!=e.options.search&&1!=e.options.filters||!e.results||(n=t.indexOf(n)),n100&&(e.tbody.removeChild(e.tbody.firstChild),s=1)),n+=1}return s}},910:function(e,t,s){s.d(t,{D0:function(){return c},FU:function(){return u},Lt:function(){return a},VP:function(){return h},Zp:function(){return p},fd:function(){return d}});var n=s(689),o=s(530),r=s(911),l=s(805),i=s(657);const a=function(e,t){const s=this,o=[];if(s.options.mergeCells){const r=Object.keys(s.options.mergeCells);for(let l=0;l1?a-1:0);null==t?c<=e&&d>=e&&o.push(r[l]):t?c=e&&o.push(r[l]):c<=e&&d>e&&o.push(r[l])}}return o},c=function(e,t){const s=this,o=[];if(s.options.mergeCells){const r=Object.keys(s.options.mergeCells);for(let l=0;l1?a-1:0);null==t?c<=e&&d>=e&&o.push(r[l]):t?c=e&&o.push(r[l]):c<=e&&d>e&&o.push(r[l])}}return o},d=function(e){const t=this;let s={};if(e)s=t.options.mergeCells&&t.options.mergeCells[e]?[t.options.mergeCells[e][0],t.options.mergeCells[e][1]]:null;else if(t.options.mergeCells){t.options.mergeCells;const e=Object.keys(t.options.mergeCells);for(let n=0;n1?c.records[u[1]][u[0]].element.setAttribute("colspan",t):t=1,s>1?c.records[u[1]][u[0]].element.setAttribute("rowspan",s):s=1,c.options.mergeCells||(c.options.mergeCells={}),c.options.mergeCells[e]=[t,s,[]],c.records[u[1]][u[0]].element.setAttribute("data-merged","true"),c.records[u[1]][u[0]].element.style.overflow="hidden";const n=[];for(let r=u[1];r0||p>0)&&(r.records[c[1]+u][c[0]+p].element=d[2][h],r.records[c[1]+u][c[0]+p].element.style.display="",t&&t[h]&&o.k9.call(r,c[0]+p,c[1]+u,t[h]),h++);i.c6.call(r,r.records[c[1]][c[0]].element,r.records[c[1]+u-1][c[0]+p-1].element),s||delete r.options.mergeCells[e],l.A.call(r,"onunmerge",r,e,a)}},h=function(e){const t=this;if(t.options.mergeCells){t.options.mergeCells;const s=Object.keys(t.options.mergeCells);for(let n=0;n0)t.page(0);else if(1==t.options.lazyLoading)l.wu.call(t,0);else for(let e=0;e=0){if(s.options.mergeCells&&Object.keys(s.options.mergeCells).length>0){if(!confirm(jSuites.translate("This action will destroy any existing merged cells. Are you sure?")))return!1;s.destroyMerge()}t=null==t?s.headers[e].classList.contains("arrow-down")?1:0:t?1:0;let r=[];if(s.options.columns&&s.options.columns[e]&&("number"==s.options.columns[e].type||"numeric"==s.options.columns[e].type||"percentage"==s.options.columns[e].type||"autonumber"==s.options.columns[e].type||"color"==s.options.columns[e].type))for(let t=0;to?-1:no?1:ne))),!0}}},167:function(e,t,s){s.d(t,{$f:function(){return a},IV:function(){return l},MY:function(){return i},ho:function(){return r}});var n=s(805),o=s(657);const r=function(e){const t=this;return 1!=t.options.search&&1!=t.options.filters||!t.results||(e=t.results.indexOf(e)),Math.ceil((parseInt(e)+1)/parseInt(t.options.pagination))-1},l=function(){const e=this;if(e.pagination.children[0].innerHTML="",e.pagination.children[1].innerHTML="",e.options.pagination){let t;if(t=1!=e.options.search&&1!=e.options.filters||!e.results?e.rows.length:e.results.length,t){const s=Math.ceil(t/e.options.pagination);let n,o;if(e.pageNumber<6?(n=1,o=s<10?s:10):s-e.pageNumber<5?(n=s-9,o=s,n<1&&(n=1)):(n=e.pageNumber-4,o=e.pageNumber+5),n>1){const t=document.createElement("div");t.className="jss_page",t.innerHTML="<",t.title=1,e.pagination.children[1].appendChild(t)}for(let t=n;t<=o;t++){const s=document.createElement("div");s.className="jss_page",s.innerHTML=t,e.pagination.children[1].appendChild(s),e.pageNumber==t-1&&s.classList.add("jss_page_selected")}if(o",t.title=s,e.pagination.children[1].appendChild(t)}const r=function(e){const t=Array.prototype.slice.call(arguments,1);return e.replace(/{(\d+)}/g,(function(e,s){return void 0!==t[s]?t[s]:e}))};e.pagination.children[0].innerHTML=r(jSuites.translate("Showing page {0} of {1} entries"),e.pageNumber+1,s)}else e.pagination.children[0].innerHTML=jSuites.translate("No records found")}},i=function(e){const t=this,s=t.pageNumber;let r;r=1!=t.options.search&&1!=t.options.filters||!t.results?t.rows:t.results;const i=parseInt(t.options.pagination);null!=e&&-1!=e||(e=Math.ceil(r.length/i)-1),t.pageNumber=e;let a=e*i,c=e*i+i;for(c>r.length&&(c=r.length),a<0&&(a=0);t.tbody.firstChild;)t.tbody.removeChild(t.tbody.firstChild);for(let e=a;e0&&l.call(t),o.Aq.call(t),n.A.call(t,"onchangepage",t,e,s,t.options.pagination)},a=function(){const e=this;let t;return t=1!=e.options.search&&1!=e.options.filters||!e.results?e.rows.length:e.results.length,Math.ceil(t/e.options.pagination)}},657:function(e,t,s){s.d(t,{AH:function(){return m},Aq:function(){return d},G9:function(){return g},Jg:function(){return f},Lo:function(){return v},R5:function(){return _},Ub:function(){return B},at:function(){return w},c6:function(){return p},eO:function(){return x},ef:function(){return A},gE:function(){return u},gG:function(){return y},kA:function(){return h},kF:function(){return C},kV:function(){return L},sp:function(){return E},tW:function(){return j}});var n=s(805),o=s(296),r=s(978),l=s(911),i=s(530),a=s(689),c=s(392);const d=function(){const e=this;if(e.highlighted&&e.highlighted.length){const t=e.highlighted[e.highlighted.length-1].element,s=t.getAttribute("data-x"),n=e.content.getBoundingClientRect(),r=n.left,l=n.top,i=t.getBoundingClientRect(),a=i.left,c=i.top,d=i.width,u=i.height,p=a-r+e.content.scrollLeft+d-4,h=c-l+e.content.scrollTop+u-4;if(e.corner.style.top=h+"px",e.corner.style.left=p+"px",e.options.freezeColumns){const t=o.w.call(e);s>e.options.freezeColumns-1&&a-r+d0?s+(l-1):s,r=i>0?n+(i-1):n}else o=s,r=n;for(let e=s;e<=o;e++)t.headers[e]&&t.headers[e].classList.remove("selected");for(let e=n;e<=r;e++)t.rows[e]&&t.rows[e].element.classList.remove("selected")}}else s=0;return t.highlighted=[],t.selectedCell=null,t.corner.style.top="-2000px",t.corner.style.left="-2000px",1==e&&1==s&&n.A.call(t,"onblur",t),s},p=function(e,t,s){const n=e.getAttribute("data-x"),o=e.getAttribute("data-y");let r,l;t?(r=t.getAttribute("data-x"),l=t.getAttribute("data-y")):(r=n,l=o),m.call(this,n,o,r,l,s)},h=function(){const e=document.querySelectorAll(".jss_worksheet .copying");for(let t=0;t=l.headers.length&&(e=l.headers.length-1),t>=l.rows.length&&(t=l.rows.length-1),s>=l.headers.length&&(s=l.headers.length-1),o>=l.rows.length&&(o=l.rows.length-1);let i,a,c,u,p=null,m=null,f=null,g=null;parseInt(e)1&&(sa&&(a=s+o-1)),r&&(nu&&(u=n+r-1))}for(let e=c;e<=u;e++)"none"!=l.rows[e].element.style.display&&(null==f&&(f=e),g=e);for(let e=i;e<=a;e++)for(let t=c;t<=u;t++)l.options.columns&&l.options.columns[e]&&"hidden"==l.options.columns[e].type||(null==p&&(p=e),m=e);if(p||(p=0),m||(m=0),!1===n.A.call(l,"onbeforeselection",l,p,f,m,g,r))return!1;const y=l.resetSelection();l.selectedCell=[e,t,s,o],l.records[t][e]&&l.records[t][e].element.classList.add("highlight-selected");for(let e=i;e<=a;e++)for(let t=c;t<=u;t++)"none"!=l.rows[t].element.style.display&&"none"!=l.records[t][e].element.style.display&&(l.records[t][e].element.classList.add("highlight"),l.highlighted.push(l.records[t][e]));for(let e=p;e<=m;e++)l.options.columns&&l.options.columns[e]&&"hidden"==l.options.columns[e].type||!l.cols[e].colElement.style||"none"==l.cols[e].colElement.style.display||(l.records[f]&&l.records[f][e]&&l.records[f][e].element.classList.add("highlight-top"),l.records[g]&&l.records[g][e]&&l.records[g][e].element.classList.add("highlight-bottom"),l.headers[e].classList.add("selected"));for(let e=f;e<=g;e++)l.rows[e]&&"none"!=l.rows[e].element.style.display&&(l.records[e][p].element.classList.add("highlight-left"),l.records[e][m].element.classList.add("highlight-right"),l.rows[e].element.classList.add("selected"));l.selectedContainer=[p,f,m,g],0==y&&(n.A.call(l,"onfocus",l),h()),n.A.call(l,"onselection",l,p,f,m,g,r),d.call(l)},f=function(e){const t=this;if(!t.selectedCell)return[];const s=[];for(let n=Math.min(t.selectedCell[0],t.selectedCell[2]);n<=Math.max(t.selectedCell[0],t.selectedCell[2]);n++)e&&"none"==t.headers[n].style.display||s.push(n);return s},g=function(){const e=this;e.selectedCell&&e.updateSelectionFromCoords(e.selectedCell[0],e.selectedCell[1],e.selectedCell[2],e.selectedCell[3])},y=function(){const e=this;for(let t=0;t=n.selectedCell[1]&&t<=n.selectedCell[3]||s>=n.selectedCell[1]&&s<=n.selectedCell[3]))return void n.resetSelection()}else if(n.selectedCell&&(t>=n.selectedCell[0]&&t<=n.selectedCell[2]||s>=n.selectedCell[0]&&s<=n.selectedCell[2]))return void n.resetSelection()},_=function(e){const t=this;if(!t.selectedCell)return[];const s=[];for(let n=Math.min(t.selectedCell[1],t.selectedCell[3]);n<=Math.max(t.selectedCell[1],t.selectedCell[3]);n++)e&&"none"==t.rows[n].element.style.display||s.push(n);return s},B=function(){const e=this;e.selectedCell||(e.selectedCell=[]),e.selectedCell[0]=0,e.selectedCell[1]=0,e.selectedCell[2]=e.headers.length-1,e.selectedCell[3]=e.records.length-1,e.updateSelectionFromCoords(e.selectedCell[0],e.selectedCell[1],e.selectedCell[2],e.selectedCell[3])},v=function(){const e=this;return e.selectedCell?[Math.min(e.selectedCell[0],e.selectedCell[2]),Math.min(e.selectedCell[1],e.selectedCell[3]),Math.max(e.selectedCell[0],e.selectedCell[2]),Math.max(e.selectedCell[1],e.selectedCell[3])]:null},A=function(e){const t=this,s=v.call(t);if(!s)return[];const n=[];for(let o=s[1];o<=s[3];o++)for(let l=s[0];l<=s[2];l++)e?n.push((0,r.getCellNameFromCoords)(l,o)):n.push(t.records[o][l]);return n},x=function(){const e=this,t=v.call(e);if(!t)return"";const s=(0,r.getCellNameFromCoords)(t[0],t[1]),n=(0,r.getCellNameFromCoords)(t[2],t[3]);return s===n?e.options.worksheetName+"!"+s:e.options.worksheetName+"!"+s+":"+n},E=function(e,t){const s=v.call(this);return e>=s[0]&&e<=s[2]&&t>=s[1]&&t<=s[3]},L=function(){const e=v.call(this);return e?[e]:[]}},392:function(e,t,s){s.d(t,{Ar:function(){return u},ll:function(){return d},nK:function(){return c}});var n=s(978),o=s(530);const r=function(e,t){0!=t.options.editable?e.classList.remove("jtoolbar-disabled"):e.classList.add("jtoolbar-disabled")},l=function(){const e=[],t=this,s=function(){return o.eN.call(t)};e.push({content:"undo",onclick:function(){s().undo()}}),e.push({content:"redo",onclick:function(){s().redo()}}),e.push({content:"save",onclick:function(){const e=s();e&&e.download()}}),e.push({type:"divisor"}),e.push({type:"select",width:"120px",options:["Default","Verdana","Arial","Courier New"],render:function(e){return''+e+""},onchange:function(e,t,n,o,r){const l=s();let i=l.getSelected(!0);if(i){let e=r?o:"";l.setStyle(Object.fromEntries(i.map((function(t){return[t,"font-family: "+e]}))))}},updateState:function(e,t,n){r(n,s())}}),e.push({type:"select",width:"48px",content:"format_size",options:["x-small","small","medium","large","x-large"],render:function(e){return''+e+""},onchange:function(e,t,n,o){const r=s();let l=r.getSelected(!0);l&&r.setStyle(Object.fromEntries(l.map((function(e){return[e,"font-size: "+o]}))))},updateState:function(e,t,n){r(n,s())}}),e.push({type:"select",options:["left","center","right","justify"],render:function(e){return'format_align_'+e+""},onchange:function(e,t,n,o){const r=s();let l=r.getSelected(!0);l&&r.setStyle(Object.fromEntries(l.map((function(e){return[e,"text-align: "+o]}))))},updateState:function(e,t,n){r(n,s())}}),e.push({content:"format_bold",onclick:function(){const e=s();let t=e.getSelected(!0);t&&e.setStyle(Object.fromEntries(t.map((function(e){return[e,"font-weight:bold"]}))))},updateState:function(e,t,n){r(n,s())}}),e.push({type:"color",content:"format_color_text",k:"color",updateState:function(e,t,n){r(n,s())}}),e.push({type:"color",content:"format_color_fill",k:"background-color",updateState:function(e,t,n,o){r(n,s())}});let l=["top","middle","bottom"];return e.push({type:"select",options:["vertical_align_top","vertical_align_center","vertical_align_bottom"],render:function(e){return''+e+""},value:1,onchange:function(e,t,n,o,r){const i=s();let a=i.getSelected(!0);a&&i.setStyle(Object.fromEntries(a.map((function(e){return[e,"vertical-align: "+l[r]]}))))},updateState:function(e,t,n){r(n,s())}}),e.push({content:"web",tooltip:jSuites.translate("Merge the selected cells"),onclick:function(){const e=s();if(e.selectedCell&&confirm(jSuites.translate("The merged cells will retain the value of the top-left cell only. Are you sure?"))){const t=[Math.min(e.selectedCell[0],e.selectedCell[2]),Math.min(e.selectedCell[1],e.selectedCell[3]),Math.max(e.selectedCell[0],e.selectedCell[2]),Math.max(e.selectedCell[1],e.selectedCell[3])];let s=(0,n.getCellNameFromCoords)(t[0],t[1]);if(e.records[t[1]][t[0]].element.getAttribute("data-merged"))e.removeMerge(s);else{let n=t[2]-t[0]+1,o=t[3]-t[1]+1;1===n&&1===o||e.setMerge(s,n,o)}}},updateState:function(e,t,n){r(n,s())}}),e.push({type:"select",options:["border_all","border_outer","border_inner","border_horizontal","border_vertical","border_left","border_top","border_right","border_bottom","border_clear"],columns:5,render:function(e){return''+e+""},right:!0,onchange:function(e,t,o,r){const l=s();if(l.selectedCell){const e=[Math.min(l.selectedCell[0],l.selectedCell[2]),Math.min(l.selectedCell[1],l.selectedCell[3]),Math.max(l.selectedCell[0],l.selectedCell[2]),Math.max(l.selectedCell[1],l.selectedCell[3])];let s=r;if(e){let o=t.thickness||1,r=t.color||"black";const i=t.style||"solid";"double"===i&&(o+=2);let a={},c=e[0],d=e[1],u=e[2],p=e[3];const h=function(e,t,n){let l=["","","",""];l[0]=("border_top"===s||"border_outer"===s)&&n===d||("border_inner"===s||"border_horizontal"===s)&&n>d||"border_all"===s?"border-top: "+o+"px "+i+" "+r:"border-top: ",l[1]="border_all"!==s&&"border_right"!==s&&"border_outer"!==s||t!==u?"border-right: ":"border-right: "+o+"px "+i+" "+r,l[2]="border_all"!==s&&"border_bottom"!==s&&"border_outer"!==s||n!==p?"border-bottom: ":"border-bottom: "+o+"px "+i+" "+r,l[3]=("border_left"===s||"border_outer"===s)&&t===c||("border_inner"===s||"border_vertical"===s)&&t>c||"border_all"===s?"border-left: "+o+"px "+i+" "+r:"border-left: ",a[e]=l.join(";")};for(let t=e[1];t<=e[3];t++)for(let s=e[0];s<=e[2];s++)h((0,n.getCellNameFromCoords)(s,t),s,t),l.records[t][s].element.getAttribute("data-merged")&&h((0,n.getCellNameFromCoords)(e[0],e[1]),s,t);Object.keys(a)&&l.setStyle(a)}}},onload:function(e,t){let s=document.createElement("div"),n=document.createElement("div");s.appendChild(n);let o=jSuites.color(n,{closeOnChange:!1,onchange:function(e,s){e.parentNode.children[1].style.color=s,t.color=s}}),r=document.createElement("i");r.classList.add("material-icons"),r.innerHTML="color_lens",r.onclick=function(){o.open()},s.appendChild(r),e.children[1].appendChild(s),n=document.createElement("div"),jSuites.picker(n,{type:"select",data:[1,2,3,4,5],render:function(e){return'
'},onchange:function(e,s,n,o){t.thickness=o},width:"50px"}),e.children[1].appendChild(n);const l=document.createElement("div");jSuites.picker(l,{type:"select",data:["solid","dotted","dashed","double"],render:function(e){return"double"===e?'
':'
'},onchange:function(e,s,n,o){t.style=o},width:"50px"}),e.children[1].appendChild(l),n=document.createElement("div"),n.style.flex="1",e.children[1].appendChild(n)},updateState:function(e,t,n){r(n,s())}}),e.push({type:"divisor"}),e.push({content:"fullscreen",tooltip:"Toggle Fullscreen",onclick:function(e,s,n){"fullscreen"===n.children[0].textContent?(t.fullscreen(!0),n.children[0].textContent="fullscreen_exit"):(t.fullscreen(!1),n.children[0].textContent="fullscreen")},updateState:function(e,t,s,n){!0===n.parent.config.fullscreen?s.children[0].textContent="fullscreen_exit":s.children[0].textContent="fullscreen"}}),e},i=function(e){const t=this,s=e.items;for(let e=0;e0&&(n.records[o][r-1].element.style.overflow="hidden");const l=function(t){const s=e.getBoundingClientRect(),n=document.createElement(t);return n.style.width=s.width+"px",n.style.height=s.height-2+"px",n.style.minHeight=s.height-2+"px",e.classList.add("editor"),e.innerHTML="",e.appendChild(n),n};if(1==e.classList.contains("readonly"));else if(n.edition=[n.records[o][r].element,n.records[o][r].element.innerHTML,r,o],n.options.columns&&n.options.columns[r]&&"object"==typeof n.options.columns[r].type)n.options.columns[r].type.openEditor(e,n.options.data[o][r],parseInt(r),parseInt(o),n,n.options.columns[r],s),dispatch.A.call(n,"oncreateeditor",n,e,parseInt(r),parseInt(o),null,n.options.columns[r]);else if(n.options.columns&&n.options.columns[r]&&"hidden"==n.options.columns[r].type);else if(n.options.columns&&n.options.columns[r]&&("checkbox"==n.options.columns[r].type||"radio"==n.options.columns[r].type)){const t=!e.children[0].checked;n.setValue(e,t),n.edition=null}else if(n.options.columns&&n.options.columns[r]&&"dropdown"==n.options.columns[r].type){let t,s=n.options.data[o][r];n.options.columns[r].multiple&&!Array.isArray(s)&&(s=s.split(";")),t="function"==typeof n.options.columns[r].filter?n.options.columns[r].filter(n.element,e,r,o,n.options.columns[r].source):n.options.columns[r].source;const i=[];if(t)for(let e=0;e=0;n--)if("none"!=s.records[n][e].element.style.display&&"none"!=s.rows[n].element.style.display){if(s.records[n][e].element.getAttribute("data-merged")&&s.records[n][e].element==s.records[t][e].element)continue;t=n;break}return t},upVisible=function(e,t){const s=this;let n,o;if(0==e?(n=parseInt(s.selectedCell[0]),o=parseInt(s.selectedCell[1])):(n=parseInt(s.selectedCell[2]),o=parseInt(s.selectedCell[3])),0==t){for(let e=0;e0&&upVisible.call(s,1,t?0:1):(s.selectedCell[1]>0&&upVisible.call(s,0,t?0:1),s.selectedCell[2]=s.selectedCell[0],s.selectedCell[3]=s.selectedCell[1]),s.updateSelectionFromCoords(s.selectedCell[0],s.selectedCell[1],s.selectedCell[2],s.selectedCell[3]),1==s.options.lazyLoading)if(0==s.selectedCell[1]||0==s.selectedCell[3])lazyLoading.wu.call(s,0),s.updateSelectionFromCoords(s.selectedCell[0],s.selectedCell[1],s.selectedCell[2],s.selectedCell[3]);else if(lazyLoading.AG.call(s))s.updateSelectionFromCoords(s.selectedCell[0],s.selectedCell[1],s.selectedCell[2],s.selectedCell[3]);else{const e=parseInt(s.tbody.firstChild.getAttribute("data-y"));s.selectedCell[1]-e<30&&(lazyLoading.G_.call(s),s.updateSelectionFromCoords(s.selectedCell[0],s.selectedCell[1],s.selectedCell[2],s.selectedCell[3]))}else if(s.options.pagination>0){const e=s.whichPage(s.selectedCell[3]);e!=s.pageNumber&&s.page(e)}internal.Rs.call(s,1)},rightGet=function(e,t){const s=this;e=parseInt(e),t=parseInt(t);for(let n=e+1;nn;e--)if("none"!=s.records[o][e].element.style.display){n=e;break}}else n=rightGet.call(s,n,o);0==e?(s.selectedCell[0]=n,s.selectedCell[1]=o):(s.selectedCell[2]=n,s.selectedCell[3]=o)},right=function(e,t){const s=this;e?s.selectedCell[2]o;e--)if("none"!=s.records[e][n].element.style.display&&"none"!=s.rows[e].element.style.display){o=e;break}}else o=downGet.call(s,n,o);0==e?(s.selectedCell[0]=n,s.selectedCell[1]=o):(s.selectedCell[2]=n,s.selectedCell[3]=o)},down=function(e,t){const s=this;if(e?s.selectedCell[3]0){const e=s.whichPage(s.selectedCell[3]);e!=s.pageNumber&&s.page(e)}internal.Rs.call(s,3)},leftGet=function(e,t){const s=this;e=parseInt(e),t=parseInt(t);for(let n=e-1;n>=0;n--)if("none"!=s.records[t][n].element.style.display){if(s.records[t][n].element.getAttribute("data-merged")&&s.records[t][n].element==s.records[t][e].element)continue;e=n;break}return e},leftVisible=function(e,t){const s=this;let n,o;if(0==e?(n=parseInt(s.selectedCell[0]),o=parseInt(s.selectedCell[1])):(n=parseInt(s.selectedCell[2]),o=parseInt(s.selectedCell[3])),0==t){for(let e=0;e0&&leftVisible.call(s,1,t?0:1):(s.selectedCell[0]>0&&leftVisible.call(s,0,t?0:1),s.selectedCell[2]=s.selectedCell[0],s.selectedCell[3]=s.selectedCell[1]),s.updateSelectionFromCoords(s.selectedCell[0],s.selectedCell[1],s.selectedCell[2],s.selectedCell[3]),internal.Rs.call(s,0)},first=function(e,t){const s=this;if(e?t?s.selectedCell[3]=0:leftVisible.call(s,1,0):(t?s.selectedCell[1]=0:leftVisible.call(s,0,0),s.selectedCell[2]=s.selectedCell[0],s.selectedCell[3]=s.selectedCell[1]),1!=s.options.lazyLoading||0!=s.selectedCell[1]&&0!=s.selectedCell[3]){if(s.options.pagination>0){const e=s.whichPage(s.selectedCell[3]);e!=s.pageNumber&&s.page(e)}}else lazyLoading.wu.call(s,0);s.updateSelectionFromCoords(s.selectedCell[0],s.selectedCell[1],s.selectedCell[2],s.selectedCell[3]),internal.Rs.call(s,1)},last=function(e,t){const s=this;if(e?t?s.selectedCell[3]=s.records.length-1:rightVisible.call(s,1,0):(t?s.selectedCell[1]=s.records.length-1:rightVisible.call(s,0,0),s.selectedCell[2]=s.selectedCell[0],s.selectedCell[3]=s.selectedCell[1]),1!=s.options.lazyLoading||s.selectedCell[1]!=s.records.length-1&&s.selectedCell[3]!=s.records.length-1){if(s.options.pagination>0){const e=s.whichPage(s.selectedCell[3]);e!=s.pageNumber&&s.page(e)}}else lazyLoading.wu.call(s,-1);s.updateSelectionFromCoords(s.selectedCell[0],s.selectedCell[1],s.selectedCell[2],s.selectedCell[3]),internal.Rs.call(s,3)};var merges=__webpack_require__(910),selection=__webpack_require__(657),helpers=__webpack_require__(978),internalHelpers=__webpack_require__(689);const copy=function(e,t,s,n,o,r,l){const i=this;t||(t="\t");const a=new RegExp(t,"ig"),c=[];let d=[],u=[];const p=[],h=[],m=i.options.data[0].length,f=i.options.data.length;let g="",y=!1,b="",C="",j=0,w=0,_=0,B=0,v=!0;for(let t=0;t0){g=i.options.nestedHeaders;for(let e=0;e1&Number.isInteger(l/a)){const e=l/a;if(r){const t=[];for(let s=0;s1&Number.isInteger(i/c)){const e=i/c;if(r){const t=[];for(let s=0;s"none"===e.style.display)).length,f=u+m+s[0].length,g=n.headers.length;f>g&&(n.skipUpdateTableReferences=!0,n.insertColumn(f-g));const y=n.rows.slice(p).filter((e=>"none"===e.element.style.display)).length,b=p+y+s.length,C=n.rows.length;for(b>C&&(n.skipUpdateTableReferences=!0,n.insertRow(b-C)),n.skipUpdateTableReferences&&(n.skipUpdateTableReferences=!1,internal.o8.call(n));h=s[l];){for(o=0,u=parseInt(e);null!=h[o];){let e=h[o];n.options.columns&&n.options.columns[o]&&"calendar"==n.options.columns[o].type&&(e=jSuites.calendar.extractDateFromString(e,n.options.columns[o].options&&n.options.columns[o].options.format||"YYYY-MM-DD"));const t=internal.k9.call(n,u,p,e);if(i.push(t),internal.xF.call(n,u,p,i),r&&r[d]){const e=(0,internalHelpers.t3)([u,p]);a[e]=r[d],c[e]=n.getStyle(e),n.records[p][u].element.setAttribute("style",r[d]),d++}if(o++,null!=h[o]){if(u>=n.headers.length-1){if(0==n.options.allowInsertColumn)break;n.insertColumn()}u=rightGet.call(n,u,p)}}if(l++,s[l]){if(p>=n.rows.length-1){if(0==n.options.allowInsertRow)break;n.insertRow()}p=downGet.call(n,e,p)}}selection.AH.call(n,e,t,u,p),utils_history.Dh.call(n,{action:"setValue",records:i,selection:n.selectedCell,newStyle:a,oldStyle:c}),internal.am.call(n);const j=[];for(let n=0;nt&&(t=s.length)}return e.options.minDimensions&&e.options.minDimensions[0]>t&&(t=e.options.minDimensions[0]),t},createCellHeader=function(e){const t=this,s=t.options.columns&&t.options.columns[e]&&t.options.columns[e].width||t.options.defaultColWidth||100,n=t.options.columns&&t.options.columns[e]&&t.options.columns[e].align||t.options.defaultColAlign||"center";t.headers[e]=document.createElement("td"),t.headers[e].textContent=t.options.columns&&t.options.columns[e]&&t.options.columns[e].title||(0,helpers.getColumnName)(e),t.headers[e].setAttribute("data-x",e),t.headers[e].style.textAlign=n,t.options.columns&&t.options.columns[e]&&t.options.columns[e].title&&t.headers[e].setAttribute("title",t.headers[e].innerText),t.options.columns&&t.options.columns[e]&&t.options.columns[e].id&&t.headers[e].setAttribute("id",t.options.columns[e].id);const o=document.createElement("col");o.setAttribute("width",s),t.cols[e]={colElement:o,x:e},t.options.columns&&t.options.columns[e]&&"hidden"==t.options.columns[e].type&&(t.headers[e].style.display="none",o.style.display="none")},insertColumn=function(e,t,s,n){const o=this;if(0!=o.options.allowInsertColumn){let r,l=[];Array.isArray(e)?(r=1,e&&(l=e)):r="number"==typeof e?e:1,s=!!s;const i=Math.max(o.options.columns.length,...o.options.data.map((function(e){return e.length})))-1;(null==t||t>=parseInt(i)||t<0)&&(t=i),n||(n=[]);for(let e=0;e0&&merges.Lt.call(o,t,s).length){if(!confirm(jSuites.translate("This action will destroy any existing merged cells. Are you sure?")))return!1;o.destroyMerge()}const c=s?t:t+1;o.options.columns=(0,internalHelpers.Hh)(o.options.columns,c,n);const d=o.headers.splice(c),u=o.cols.splice(c),p=[],h=[],m=[],f=[],g=[];for(let e=c;e0&&o.options.nestedHeaders[0]&&o.options.nestedHeaders[0][0])for(let e=0;e0){let n;if(n=e>t?1:0,merges.Lt.call(s,e).length||merges.Lt.call(s,t,n).length){if(!confirm(jSuites.translate("This action will destroy any existing merged cells. Are you sure?")))return!1;s.destroyMerge()}}if((e=parseInt(e))>(t=parseInt(t))){s.headerContainer.insertBefore(s.headers[e],s.headers[t]),s.colgroupContainer.insertBefore(s.cols[e].colElement,s.cols[t].colElement);for(let n=0;n1){if(null==e){const n=s.getSelectedColumns(!0);n.length?(e=parseInt(n[0]),t=parseInt(n.length)):(e=s.headers.length-1,t=1)}const n=s.options.data[0].length-1;(null==e||e>n||e<0)&&(e=n),t||(t=1),t>s.options.data[0].length-e&&(t=s.options.data[0].length-e);const o=[];for(let s=0;s-1){let n=!1;if(s.options.mergeCells&&Object.keys(s.options.mergeCells).length>0)for(let o=e;o0&&s.options.nestedHeaders[0]&&s.options.nestedHeaders[0][0])for(let e=0;en+1&&(t.filter.children[n+1].style.display="");for(let e=0;en+1&&(t.filter.children[n+1].style.display="none");for(let e=0;e=parseInt(l)||t<0)&&(t=l);const i=[];for(let e=0;e0&&merges.D0.call(n,t,s).length){if(!confirm(jSuites.translate("This action will destroy any existing merged cells. Are you sure?")))return!1;n.destroyMerge()}if(1==n.options.search){if(n.results&&n.results.length!=n.rows.length){if(!confirm(jSuites.translate("This action will clear your search results. Are you sure?")))return!1;n.resetSearch()}n.results=null}const a=s?t:t+1,c=n.records.splice(a),d=n.options.data.splice(a),u=n.rows.splice(a),p=[],h=[],m=[];for(let e=a;e=0&&n.tbody.insertBefore(s.element,u[0].element):Array.prototype.indexOf.call(n.tbody.children,n.rows[t].element)>=0&&n.tbody.appendChild(s.element),p.push([...n.records[e]]),h.push([...n.options.data[e]]),m.push(s)}Array.prototype.push.apply(n.records,c),Array.prototype.push.apply(n.options.data,d),Array.prototype.push.apply(n.rows,u);for(let e=a;e0&&n.page(n.pageNumber),utils_history.Dh.call(n,{action:"insertRow",rowNumber:t,numOfRows:o,insertBefore:s,rowRecords:p,rowData:h,rowNode:m}),internal.o8.call(n),dispatch.A.call(n,"oninsertrow",n,i)}},moveRow=function(e,t,s){const n=this;if(n.options.mergeCells&&Object.keys(n.options.mergeCells).length>0){let s;if(s=e>t?1:0,merges.D0.call(n,e).length||merges.D0.call(n,t,s).length){if(!confirm(jSuites.translate("This action will destroy any existing merged cells. Are you sure?")))return!1;n.destroyMerge()}}if(1==n.options.search){if(n.results&&n.results.length!=n.rows.length){if(!confirm(jSuites.translate("This action will clear your search results. Are you sure?")))return!1;n.resetSearch()}n.results=null}s||(Array.prototype.indexOf.call(n.tbody.children,n.rows[t].element)>=0?e>t?n.tbody.insertBefore(n.rows[e].element,n.rows[t].element):n.tbody.insertBefore(n.rows[e].element,n.rows[t].element.nextSibling):n.tbody.removeChild(n.rows[e].element)),n.rows.splice(t,0,n.rows.splice(e,1)[0]),n.records.splice(t,0,n.records.splice(e,1)[0]),n.options.data.splice(t,0,n.options.data.splice(e,1)[0]);const o=Math.min(e,t),r=Math.max(e,t);for(let e=o;e<=r;e++)n.rows[e].y=e;for(let e=o;e<=r;e++)for(let t=0;t0&&n.tbody.children.length!=n.options.pagination&&n.page(n.pageNumber),utils_history.Dh.call(n,{action:"moveRow",oldValue:e,newValue:t}),internal.o8.call(n),dispatch.A.call(n,"onmoverow",n,parseInt(e),parseInt(t),1)},deleteRow=function(e,t){const s=this;if(0!=s.options.allowDeleteRow)if(1==s.options.allowDeletingAllRows||s.options.data.length>1){if(null==e){const n=selection.R5.call(s);0===n.length?(e=s.options.data.length-1,t=1):(e=n[0],t=n.length)}let n=s.options.data.length-1;(null==e||e>n||e<0)&&(e=n),t||(t=1),e+t>=s.options.data.length&&(t=s.options.data.length-e);const o=[];for(let s=0;s-1){let r=!1;if(s.options.mergeCells&&Object.keys(s.options.mergeCells).length>0)for(let n=e;n=0&&(s.rows[n].element.className="",s.rows[n].element.parentNode.removeChild(s.rows[n].element));const l=s.records.splice(e,t),i=s.options.data.splice(e,t),a=s.rows.splice(e,t);for(let t=e;t0&&s.tbody.children.length!=s.options.pagination&&s.page(s.pageNumber),selection.at.call(s,1,e,e+t-1),utils_history.Dh.call(s,{action:"deleteRow",rowNumber:e,numOfRows:t,insertBefore:1,rowRecords:l,rowData:i,rowNode:a}),internal.o8.call(s),dispatch.A.call(s,"ondeleterow",s,o)}}else console.error("Jspreadsheet: It is not possible to delete the last row")},getHeight=function(e){const t=this;let s;if(void 0===e){s=[];for(let e=0;e0&&(s||(s=n.rows[e].element.getAttribute("height"))||(s=n.rows[e].element.getBoundingClientRect().height),t=parseInt(t),n.rows[e].element.style.height=t+"px",n.options.rows||(n.options.rows=[]),n.options.rows[e]||(n.options.rows[e]={}),n.options.rows[e].height=t,utils_history.Dh.call(n,{action:"setHeight",row:e,oldValue:s,newValue:t}),dispatch.A.call(n,"onresizerow",n,e,t,s),selection.Aq.call(n))},showRow=function(e){const t=this;Array.isArray(e)||(e=[e]),e.forEach((function(e){t.rows[e].element.style.display=""}))},hideRow=function(e){const t=this;Array.isArray(e)||(e=[e]),e.forEach((function(e){t.rows[e].element.style.display="none"}))},getRowData=function(e,t){return t?this.records[e].map((function(e){return e.element.innerHTML})):this.options.data[e]},setRowData=function(e,t,s){const n=this;for(let o=0;o .jtabs-content > .jtabs-selected"))),"THEAD"==n.tagName?t=1:"TBODY"==n.tagName&&(t=2),n.parentNode&&(s||e(n.parentNode))}(e),[s,t]},mouseUpControls=function(e){if(libraryBase.jspreadsheet.current)if(libraryBase.jspreadsheet.current.resizing){if(libraryBase.jspreadsheet.current.resizing.column){const e=parseInt(libraryBase.jspreadsheet.current.cols[libraryBase.jspreadsheet.current.resizing.column].colElement.getAttribute("width")),t=libraryBase.jspreadsheet.current.getSelectedColumns();if(t.length>1){const s=[];for(let e=0;e0&&(selection.kF.call(libraryBase.jspreadsheet.current,libraryBase.jspreadsheet.current.selection[0],libraryBase.jspreadsheet.current.selection[libraryBase.jspreadsheet.current.selection.length-1]),selection.gG.call(libraryBase.jspreadsheet.current)));libraryBase.jspreadsheet.timeControl&&(clearTimeout(libraryBase.jspreadsheet.timeControl),libraryBase.jspreadsheet.timeControl=null),libraryBase.jspreadsheet.isMouseAction=!1},mouseDownControls=function(e){let t;t=(e=e||window.event).buttons?e.buttons:e.button?e.button:e.which;const s=getElement(e.target);if(s[0]?libraryBase.jspreadsheet.current!=s[0].jssWorksheet&&(libraryBase.jspreadsheet.current&&(libraryBase.jspreadsheet.current.edition&&closeEditor.call(libraryBase.jspreadsheet.current,libraryBase.jspreadsheet.current.edition[0],!0),libraryBase.jspreadsheet.current.resetSelection()),libraryBase.jspreadsheet.current=s[0].jssWorksheet):libraryBase.jspreadsheet.current&&(libraryBase.jspreadsheet.current.edition&&closeEditor.call(libraryBase.jspreadsheet.current,libraryBase.jspreadsheet.current.edition[0],!0),e.target.classList.contains("jss_object")||(selection.gE.call(libraryBase.jspreadsheet.current,!0),libraryBase.jspreadsheet.current=null)),libraryBase.jspreadsheet.current&&1==t){if(e.target.classList.contains("jss_selectall"))libraryBase.jspreadsheet.current&&selection.Ub.call(libraryBase.jspreadsheet.current);else if(e.target.classList.contains("jss_corner"))0!=libraryBase.jspreadsheet.current.options.editable&&(libraryBase.jspreadsheet.current.selectedCorner=!0);else{if(1==s[1]){const t=e.target.getAttribute("data-x");if(t){const s=e.target.getBoundingClientRect();if(0!=libraryBase.jspreadsheet.current.options.columnResize&&s.width-e.offsetX<6){libraryBase.jspreadsheet.current.resizing={mousePosition:e.pageX,column:t,width:s.width},libraryBase.jspreadsheet.current.headers[t].classList.add("resizing");for(let e=0;e"==e.target.textContent?libraryBase.jspreadsheet.current.page(e.target.getAttribute("title")-1):libraryBase.jspreadsheet.current.page(e.target.textContent-1))}libraryBase.jspreadsheet.current.edition?libraryBase.jspreadsheet.isMouseAction=!1:libraryBase.jspreadsheet.isMouseAction=!0}else libraryBase.jspreadsheet.isMouseAction=!1},mouseMoveControls=function(e){let t;if(t=(e=e||window.event).buttons?e.buttons:e.button?e.button:e.which,t||(libraryBase.jspreadsheet.isMouseAction=!1),libraryBase.jspreadsheet.current)if(1==libraryBase.jspreadsheet.isMouseAction){if(libraryBase.jspreadsheet.current.resizing)if(libraryBase.jspreadsheet.current.resizing.column){const t=e.pageX-libraryBase.jspreadsheet.current.resizing.mousePosition;if(libraryBase.jspreadsheet.current.resizing.width+t>0){const e=libraryBase.jspreadsheet.current.resizing.width+t;libraryBase.jspreadsheet.current.cols[libraryBase.jspreadsheet.current.resizing.column].colElement.setAttribute("width",e),selection.Aq.call(libraryBase.jspreadsheet.current)}}else{const t=e.pageY-libraryBase.jspreadsheet.current.resizing.mousePosition;if(libraryBase.jspreadsheet.current.resizing.height+t>0){const e=libraryBase.jspreadsheet.current.resizing.height+t;libraryBase.jspreadsheet.current.rows[libraryBase.jspreadsheet.current.resizing.row].element.setAttribute("height",e),selection.Aq.call(libraryBase.jspreadsheet.current)}}else if(libraryBase.jspreadsheet.current.dragging)if(libraryBase.jspreadsheet.current.dragging.column){const t=e.target.getAttribute("data-x");if(t)if(merges.Lt.call(libraryBase.jspreadsheet.current,t).length)console.error("Jspreadsheet: This column is part of a merged cell.");else{for(let e=0;ee.offsetX?(libraryBase.jspreadsheet.current.dragging.columne.offsetY?e.target.parentNode.nextSibling:e.target.parentNode;libraryBase.jspreadsheet.current.dragging.element!=t&&(e.target.parentNode.parentNode.insertBefore(libraryBase.jspreadsheet.current.dragging.element,t),libraryBase.jspreadsheet.current.dragging.destination=Array.prototype.indexOf.call(libraryBase.jspreadsheet.current.dragging.element.parentNode.children,libraryBase.jspreadsheet.current.dragging.element))}}}else{const t=e.target.getAttribute("data-x"),s=e.target.getAttribute("data-y"),n=e.target.getBoundingClientRect();libraryBase.jspreadsheet.current.cursor&&(libraryBase.jspreadsheet.current.cursor.style.cursor="",libraryBase.jspreadsheet.current.cursor=null),e.target.parentNode.parentNode&&e.target.parentNode.parentNode.className&&(e.target.parentNode.parentNode.classList.contains("resizable")&&(e.target&&t&&!s&&n.width-(e.clientX-n.left)<6?(libraryBase.jspreadsheet.current.cursor=e.target,libraryBase.jspreadsheet.current.cursor.style.cursor="col-resize"):e.target&&!t&&s&&n.height-(e.clientY-n.top)<6&&(libraryBase.jspreadsheet.current.cursor=e.target,libraryBase.jspreadsheet.current.cursor.style.cursor="row-resize")),e.target.parentNode.parentNode.classList.contains("draggable")&&(e.target&&!t&&s&&n.width-(e.clientX-n.left)<6||e.target&&t&&!s&&n.height-(e.clientY-n.top)<6)&&(libraryBase.jspreadsheet.current.cursor=e.target,libraryBase.jspreadsheet.current.cursor.style.cursor="move"))}},updateCopySelection=function(e,t){const s=this;selection.gG.call(s);const n=s.selectedContainer[0],o=s.selectedContainer[1],r=s.selectedContainer[2],l=s.selectedContainer[3];if(null!=e&&null!=t){let i,a,c,d;e-r>0?(i=parseInt(r)+1,a=parseInt(e)):(i=parseInt(e),a=parseInt(n)-1),t-l>0?(c=parseInt(l)+1,d=parseInt(t)):(c=parseInt(t),d=parseInt(o)-1),a-i<=d-c?(i=parseInt(n),a=parseInt(r)):(c=parseInt(o),d=parseInt(l));for(let e=c;e<=d;e++)for(let t=i;t<=a;t++)s.records[e][t]&&"none"!=s.rows[e].element.style.display&&"none"!=s.records[e][t].element.style.display&&(s.records[e][t].element.classList.add("selection"),s.records[c][t].element.classList.add("selection-top"),s.records[d][t].element.classList.add("selection-bottom"),s.records[e][i].element.classList.add("selection-left"),s.records[e][a].element.classList.add("selection-right"),s.selection.push(s.records[e][t].element))}},mouseOverControls=function(e){let t;if(t=(e=e||window.event).buttons?e.buttons:e.button?e.button:e.which,t||(libraryBase.jspreadsheet.isMouseAction=!1),libraryBase.jspreadsheet.current&&1==libraryBase.jspreadsheet.isMouseAction){const t=getElement(e.target);if(t[0]){if(libraryBase.jspreadsheet.current!=t[0].jssWorksheet&&libraryBase.jspreadsheet.current)return!1;let s=e.target.getAttribute("data-x");const n=e.target.getAttribute("data-y");if(libraryBase.jspreadsheet.current.resizing||libraryBase.jspreadsheet.current.dragging);else{if(1==t[1]&&libraryBase.jspreadsheet.current.selectedHeader){s=e.target.getAttribute("data-x");const t=libraryBase.jspreadsheet.current.selectedHeader,n=s;selection.AH.call(libraryBase.jspreadsheet.current,t,0,n,libraryBase.jspreadsheet.current.options.data.length-1,e)}if(2==t[1])if(e.target.classList.contains("jss_row")){if(null!=libraryBase.jspreadsheet.current.selectedRow){const t=libraryBase.jspreadsheet.current.selectedRow,s=n;selection.AH.call(libraryBase.jspreadsheet.current,0,t,libraryBase.jspreadsheet.current.options.data[0].length-1,s,e)}}else libraryBase.jspreadsheet.current.edition||s&&n&&(libraryBase.jspreadsheet.current.selectedCorner?updateCopySelection.call(libraryBase.jspreadsheet.current,s,n):libraryBase.jspreadsheet.current.selectedCell&&selection.AH.call(libraryBase.jspreadsheet.current,libraryBase.jspreadsheet.current.selectedCell[0],libraryBase.jspreadsheet.current.selectedCell[1],s,n,e))}}}libraryBase.jspreadsheet.timeControl&&(clearTimeout(libraryBase.jspreadsheet.timeControl),libraryBase.jspreadsheet.timeControl=null)},doubleClickControls=function(e){if(libraryBase.jspreadsheet.current)if(e.target.classList.contains("jss_corner")){if(libraryBase.jspreadsheet.current.highlighted.length>0){const e=libraryBase.jspreadsheet.current.highlighted[0].element.getAttribute("data-x"),t=parseInt(libraryBase.jspreadsheet.current.highlighted[libraryBase.jspreadsheet.current.highlighted.length-1].element.getAttribute("data-y"))+1,s=libraryBase.jspreadsheet.current.highlighted[libraryBase.jspreadsheet.current.highlighted.length-1].element.getAttribute("data-x"),n=libraryBase.jspreadsheet.current.records.length-1;selection.kF.call(libraryBase.jspreadsheet.current,libraryBase.jspreadsheet.current.records[t][e].element,libraryBase.jspreadsheet.current.records[n][s].element)}}else if(e.target.classList.contains("jss_column_filter")){const t=e.target.getAttribute("data-x");filter.N$.call(libraryBase.jspreadsheet.current,t)}else{const t=getElement(e.target);if(1==t[1]&&0!=libraryBase.jspreadsheet.current.options.columnSorting){const t=e.target.getAttribute("data-x");t&&libraryBase.jspreadsheet.current.orderBy(parseInt(t))}if(2==t[1]&&0!=libraryBase.jspreadsheet.current.options.editable&&!libraryBase.jspreadsheet.current.edition){const t=function(e){if(e.parentNode){const s=e.getAttribute("data-x"),n=e.getAttribute("data-y");return s&&n?e:t(e.parentNode)}},s=t(e.target);s&&s.classList.contains("highlight")&&openEditor.call(libraryBase.jspreadsheet.current,s,void 0,e)}}},pasteControls=function(e){libraryBase.jspreadsheet.current&&libraryBase.jspreadsheet.current.selectedCell&&(libraryBase.jspreadsheet.current.edition||0!=libraryBase.jspreadsheet.current.options.editable&&(e&&e.clipboardData?(paste.call(libraryBase.jspreadsheet.current,libraryBase.jspreadsheet.current.selectedCell[0],libraryBase.jspreadsheet.current.selectedCell[1],e.clipboardData.getData("text")),e.preventDefault()):window.clipboardData&&paste.call(libraryBase.jspreadsheet.current,libraryBase.jspreadsheet.current.selectedCell[0],libraryBase.jspreadsheet.current.selectedCell[1],window.clipboardData.getData("text"))))},getRole=function(e){if(e.classList.contains("jss_selectall"))return"select-all";if(e.classList.contains("jss_corner"))return"fill-handle";let t=e;for(;!t.classList.contains("jss_spreadsheet");){if(t.classList.contains("jss_row"))return"row";if(t.classList.contains("jss_nested"))return"nested";if(t.classList.contains("jtabs-headers"))return"tabs";if(t.classList.contains("jtoolbar"))return"toolbar";if(t.classList.contains("jss_pagination"))return"pagination";if("TBODY"===t.tagName)return"cell";if("TFOOT"===t.tagName)return 0===getElementIndex(e)?"grid":"footer";if("THEAD"===t.tagName)return"header";t=t.parentElement}return"applications"},defaultContextMenu=function(e,t,s,n){const o=[];if("header"===n&&(0!=e.options.allowInsertColumn&&o.push({title:jSuites.translate("Insert a new column before"),onclick:function(){e.insertColumn(1,parseInt(t),1)}}),0!=e.options.allowInsertColumn&&o.push({title:jSuites.translate("Insert a new column after"),onclick:function(){e.insertColumn(1,parseInt(t),0)}}),0!=e.options.allowDeleteColumn&&o.push({title:jSuites.translate("Delete selected columns"),onclick:function(){e.deleteColumn(e.getSelectedColumns().length?void 0:parseInt(t))}}),0!=e.options.allowRenameColumn&&o.push({title:jSuites.translate("Rename this column"),onclick:function(){const s=e.getHeader(t),n=prompt(jSuites.translate("Column name"),s);e.setHeader(t,n)}}),0!=e.options.columnSorting&&(o.push({type:"line"}),o.push({title:jSuites.translate("Order ascending"),onclick:function(){e.orderBy(t,0)}}),o.push({title:jSuites.translate("Order descending"),onclick:function(){e.orderBy(t,1)}}))),"row"!==n&&"cell"!==n||(0!=e.options.allowInsertRow&&(o.push({title:jSuites.translate("Insert a new row before"),onclick:function(){e.insertRow(1,parseInt(s),1)}}),o.push({title:jSuites.translate("Insert a new row after"),onclick:function(){e.insertRow(1,parseInt(s))}})),0!=e.options.allowDeleteRow&&o.push({title:jSuites.translate("Delete selected rows"),onclick:function(){e.deleteRow(e.getSelectedRows().length?void 0:parseInt(s))}})),"cell"===n&&0!=e.options.allowComments){o.push({type:"line"});const n=e.records[s][t].element.getAttribute("title")||"";o.push({title:jSuites.translate(n?"Edit comments":"Add comments"),onclick:function(){const o=prompt(jSuites.translate("Comments"),n);o&&e.setComments((0,helpers.getCellNameFromCoords)(t,s),o)}}),n&&o.push({title:jSuites.translate("Clear comments"),onclick:function(){e.setComments((0,helpers.getCellNameFromCoords)(t,s),"")}})}return 0!==o.length&&o.push({type:"line"}),"header"!==n&&"row"!==n&&"cell"!==n||(o.push({title:jSuites.translate("Copy")+"...",shortcut:"Ctrl + C",onclick:function(){copy.call(e,!0)}}),navigator&&navigator.clipboard&&o.push({title:jSuites.translate("Paste")+"...",shortcut:"Ctrl + V",onclick:function(){e.selectedCell&&navigator.clipboard.readText().then((function(t){t&&paste.call(e,e.selectedCell[0],e.selectedCell[1],t)}))}})),0!=e.parent.config.allowExport&&o.push({title:jSuites.translate("Save as")+"...",shortcut:"Ctrl + S",onclick:function(){e.download()}}),0!=e.parent.config.about&&o.push({title:jSuites.translate("About"),onclick:function(){void 0===e.parent.config.about||!0===e.parent.config.about?alert(version.print()):alert(e.parent.config.about)}}),o},getElementIndex=function(e){const t=e.parentElement.children;for(let s=0;sparseInt(libraryBase.jspreadsheet.current.selectedCell[2])||oparseInt(libraryBase.jspreadsheet.current.selectedCell[3]))&&selection.AH.call(libraryBase.jspreadsheet.current,n,o,n,o,e)}else if("row"===s||"header"===s)"row"===s?o=e.target.getAttribute("data-y"):n=e.target.getAttribute("data-x"),(!libraryBase.jspreadsheet.current.selectedCell||nparseInt(libraryBase.jspreadsheet.current.selectedCell[2])||oparseInt(libraryBase.jspreadsheet.current.selectedCell[3]))&&selection.AH.call(libraryBase.jspreadsheet.current,n,o,n,o,e);else if("nested"===s){const t=e.target.getAttribute("data-column").split(",");n=getElementIndex(e.target)-1,o=getElementIndex(e.target.parentElement),libraryBase.jspreadsheet.current.selectedCell&&t[0]==parseInt(libraryBase.jspreadsheet.current.selectedCell[0])&&t[t.length-1]==parseInt(libraryBase.jspreadsheet.current.selectedCell[2])&&null==libraryBase.jspreadsheet.current.selectedCell[1]&&null==libraryBase.jspreadsheet.current.selectedCell[3]||selection.AH.call(libraryBase.jspreadsheet.current,t[0],null,t[t.length-1],null,e)}else"select-all"===s?selection.Ub.call(libraryBase.jspreadsheet.current):"tabs"===s?n=getElementIndex(e.target):"footer"===s&&(n=getElementIndex(e.target)-1,o=getElementIndex(e.target.parentElement));let r=defaultContextMenu(libraryBase.jspreadsheet.current,parseInt(n),parseInt(o),s);if("function"==typeof t.config.contextMenu){const l=t.config.contextMenu(libraryBase.jspreadsheet.current,n,o,e,r,s,n,o);if(l)r=l;else if(!1===l)return}"object"==typeof t.plugins&&Object.entries(t.plugins).forEach((function([,t]){if("function"==typeof t.contextMenu){const l=t.contextMenu(libraryBase.jspreadsheet.current,null!==n?parseInt(n):null,null!==o?parseInt(o):null,e,r,s,null!==n?parseInt(n):null,null!==o?parseInt(o):null);l&&(r=l)}})),t.contextMenu.contextmenu.open(e,r),e.preventDefault()}}},touchStartControls=function(e){const t=getElement(e.target);if(t[0]?libraryBase.jspreadsheet.current!=t[0].jssWorksheet&&(libraryBase.jspreadsheet.current&&libraryBase.jspreadsheet.current.resetSelection(),libraryBase.jspreadsheet.current=t[0].jssWorksheet):libraryBase.jspreadsheet.current&&(libraryBase.jspreadsheet.current.resetSelection(),libraryBase.jspreadsheet.current=null),libraryBase.jspreadsheet.current&&!libraryBase.jspreadsheet.current.edition){const t=e.target.getAttribute("data-x"),s=e.target.getAttribute("data-y");t&&s&&(selection.AH.call(libraryBase.jspreadsheet.current,t,s,void 0,void 0,e),libraryBase.jspreadsheet.timeControl=setTimeout((function(){"color"==libraryBase.jspreadsheet.current.options.columns[t].type?libraryBase.jspreadsheet.tmpElement=null:libraryBase.jspreadsheet.tmpElement=e.target,openEditor.call(libraryBase.jspreadsheet.current,e.target,!1,e)}),500))}},touchEndControls=function(e){libraryBase.jspreadsheet.timeControl&&(clearTimeout(libraryBase.jspreadsheet.timeControl),libraryBase.jspreadsheet.timeControl=null,libraryBase.jspreadsheet.tmpElement&&"INPUT"==libraryBase.jspreadsheet.tmpElement.children[0].tagName&&libraryBase.jspreadsheet.tmpElement.children[0].focus(),libraryBase.jspreadsheet.tmpElement=null)},cutControls=function(e){libraryBase.jspreadsheet.current&&(libraryBase.jspreadsheet.current.edition||(copy.call(libraryBase.jspreadsheet.current,!0,void 0,void 0,void 0,void 0,!0),0!=libraryBase.jspreadsheet.current.options.editable&&libraryBase.jspreadsheet.current.setValue(libraryBase.jspreadsheet.current.highlighted.map((function(e){return e.element})),"")))},copyControls=function(e){libraryBase.jspreadsheet.current&©Controls.enabled&&(libraryBase.jspreadsheet.current.edition||copy.call(libraryBase.jspreadsheet.current,!0))},isMac=function(){return navigator.platform.toUpperCase().indexOf("MAC")>=0},isCtrl=function(e){return isMac()?e.metaKey:e.ctrlKey},keyDownControls=function(e){if(libraryBase.jspreadsheet.current){if(libraryBase.jspreadsheet.current.edition)if(27==e.which)libraryBase.jspreadsheet.current.edition&&closeEditor.call(libraryBase.jspreadsheet.current,libraryBase.jspreadsheet.current.edition[0],!1),e.preventDefault();else if(13==e.which)if(libraryBase.jspreadsheet.current.options.columns&&libraryBase.jspreadsheet.current.options.columns[libraryBase.jspreadsheet.current.edition[2]]&&"calendar"==libraryBase.jspreadsheet.current.options.columns[libraryBase.jspreadsheet.current.edition[2]].type)closeEditor.call(libraryBase.jspreadsheet.current,libraryBase.jspreadsheet.current.edition[0],!0);else if(libraryBase.jspreadsheet.current.options.columns&&libraryBase.jspreadsheet.current.options.columns[libraryBase.jspreadsheet.current.edition[2]]&&"dropdown"==libraryBase.jspreadsheet.current.options.columns[libraryBase.jspreadsheet.current.edition[2]].type);else if((1==libraryBase.jspreadsheet.current.options.wordWrap||libraryBase.jspreadsheet.current.options.columns&&libraryBase.jspreadsheet.current.options.columns[libraryBase.jspreadsheet.current.edition[2]]&&1==libraryBase.jspreadsheet.current.options.columns[libraryBase.jspreadsheet.current.edition[2]].wordWrap||libraryBase.jspreadsheet.current.options.data[libraryBase.jspreadsheet.current.edition[3]][libraryBase.jspreadsheet.current.edition[2]]&&libraryBase.jspreadsheet.current.options.data[libraryBase.jspreadsheet.current.edition[3]][libraryBase.jspreadsheet.current.edition[2]].length>200)&&e.altKey){const e=libraryBase.jspreadsheet.current.edition[0].children[0];let t=libraryBase.jspreadsheet.current.edition[0].children[0].value;const s=e.selectionStart;t=t.slice(0,s)+"\n"+t.slice(s),e.value=t,e.focus(),e.selectionStart=s+1,e.selectionEnd=s+1}else libraryBase.jspreadsheet.current.edition[0].children[0].blur();else 9==e.which&&(libraryBase.jspreadsheet.current.options.columns&&libraryBase.jspreadsheet.current.options.columns[libraryBase.jspreadsheet.current.edition[2]]&&["calendar","html"].includes(libraryBase.jspreadsheet.current.options.columns[libraryBase.jspreadsheet.current.edition[2]].type)?closeEditor.call(libraryBase.jspreadsheet.current,libraryBase.jspreadsheet.current.edition[0],!0):libraryBase.jspreadsheet.current.edition[0].children[0].blur());if(!libraryBase.jspreadsheet.current.edition&&libraryBase.jspreadsheet.current.selectedCell)if(37==e.which)left.call(libraryBase.jspreadsheet.current,e.shiftKey,e.ctrlKey),e.preventDefault();else if(39==e.which)right.call(libraryBase.jspreadsheet.current,e.shiftKey,e.ctrlKey),e.preventDefault();else if(38==e.which)up.call(libraryBase.jspreadsheet.current,e.shiftKey,e.ctrlKey),e.preventDefault();else if(40==e.which)down.call(libraryBase.jspreadsheet.current,e.shiftKey,e.ctrlKey),e.preventDefault();else if(36==e.which)first.call(libraryBase.jspreadsheet.current,e.shiftKey,e.ctrlKey),e.preventDefault();else if(35==e.which)last.call(libraryBase.jspreadsheet.current,e.shiftKey,e.ctrlKey),e.preventDefault();else if(46==e.which||8==e.which)0!=libraryBase.jspreadsheet.current.options.editable&&(null!=libraryBase.jspreadsheet.current.selectedRow?0!=libraryBase.jspreadsheet.current.options.allowDeleteRow&&confirm(jSuites.translate("Are you sure to delete the selected rows?"))&&libraryBase.jspreadsheet.current.deleteRow():libraryBase.jspreadsheet.current.selectedHeader?0!=libraryBase.jspreadsheet.current.options.allowDeleteColumn&&confirm(jSuites.translate("Are you sure to delete the selected columns?"))&&libraryBase.jspreadsheet.current.deleteColumn():libraryBase.jspreadsheet.current.setValue(libraryBase.jspreadsheet.current.highlighted.map((function(e){return e.element})),""));else if(13==e.which)e.shiftKey?up.call(libraryBase.jspreadsheet.current):(0!=libraryBase.jspreadsheet.current.options.allowInsertRow&&0!=libraryBase.jspreadsheet.current.options.allowManualInsertRow&&libraryBase.jspreadsheet.current.selectedCell[1]==libraryBase.jspreadsheet.current.options.data.length-1&&libraryBase.jspreadsheet.current.insertRow(),down.call(libraryBase.jspreadsheet.current)),e.preventDefault();else if(9==e.which)e.shiftKey?left.call(libraryBase.jspreadsheet.current):(0!=libraryBase.jspreadsheet.current.options.allowInsertColumn&&0!=libraryBase.jspreadsheet.current.options.allowManualInsertColumn&&libraryBase.jspreadsheet.current.selectedCell[0]==libraryBase.jspreadsheet.current.options.data[0].length-1&&libraryBase.jspreadsheet.current.insertColumn(),right.call(libraryBase.jspreadsheet.current)),e.preventDefault();else if(!e.ctrlKey&&!e.metaKey||e.shiftKey){if(libraryBase.jspreadsheet.current.selectedCell&&0!=libraryBase.jspreadsheet.current.options.editable){const t=libraryBase.jspreadsheet.current.selectedCell[1],s=libraryBase.jspreadsheet.current.selectedCell[0];32==e.keyCode?(e.preventDefault(),"checkbox"==libraryBase.jspreadsheet.current.options.columns[s].type||"radio"==libraryBase.jspreadsheet.current.options.columns[s].type?setCheckRadioValue.call(libraryBase.jspreadsheet.current):openEditor.call(libraryBase.jspreadsheet.current,libraryBase.jspreadsheet.current.records[t][s].element,!0,e)):113==e.keyCode?openEditor.call(libraryBase.jspreadsheet.current,libraryBase.jspreadsheet.current.records[t][s].element,!1,e):1!==e.key.length&&"Process"!==e.key||e.altKey||isCtrl(e)||(openEditor.call(libraryBase.jspreadsheet.current,libraryBase.jspreadsheet.current.records[t][s].element,!0,e),libraryBase.jspreadsheet.current.options.columns&&libraryBase.jspreadsheet.current.options.columns[s]&&"calendar"==libraryBase.jspreadsheet.current.options.columns[s].type&&e.preventDefault())}}else 65==e.which?(selection.Ub.call(libraryBase.jspreadsheet.current),e.preventDefault()):83==e.which?(libraryBase.jspreadsheet.current.download(),e.preventDefault()):89==e.which?(libraryBase.jspreadsheet.current.redo(),e.preventDefault()):90==e.which?(libraryBase.jspreadsheet.current.undo(),e.preventDefault()):67==e.which?(copy.call(libraryBase.jspreadsheet.current,!0),e.preventDefault()):88==e.which?(0!=libraryBase.jspreadsheet.current.options.editable?cutControls():copyControls(),e.preventDefault()):86==e.which&&pasteControls();else e.target.classList.contains("jss_search")&&(libraryBase.jspreadsheet.timeControl&&clearTimeout(libraryBase.jspreadsheet.timeControl),libraryBase.jspreadsheet.timeControl=setTimeout((function(){libraryBase.jspreadsheet.current.search(e.target.value)}),200))}},wheelControls=function(e){const t=this;1==t.options.lazyLoading&&null==libraryBase.jspreadsheet.timeControlLoading&&(libraryBase.jspreadsheet.timeControlLoading=setTimeout((function(){t.content.scrollTop+t.content.clientHeight>=t.content.scrollHeight-10?lazyLoading.p6.call(t)&&(t.content.scrollTop+t.content.clientHeight>t.content.scrollHeight-10&&(t.content.scrollTop=t.content.scrollTop-t.content.clientHeight),selection.Aq.call(t)):t.content.scrollTop<=t.content.clientHeight&&lazyLoading.G_.call(t)&&(t.content.scrollTop<10&&(t.content.scrollTop=t.content.scrollTop+t.content.clientHeight),selection.Aq.call(t)),libraryBase.jspreadsheet.timeControlLoading=null}),100))};let scrollLeft=0;const updateFreezePosition=function(){const e=this;scrollLeft=e.content.scrollLeft;let t=0;if(scrollLeft>50)for(let s=0;s0&&(!e.options.columns||!e.options.columns[s-1]||"hidden"!==e.options.columns[s-1].type)){let n;n=e.options.columns&&e.options.columns[s-1]&&void 0!==e.options.columns[s-1].width?parseInt(e.options.columns[s-1].width):void 0!==e.options.defaultColWidth?parseInt(e.options.defaultColWidth):100,t+=parseInt(n)}e.headers[s].classList.add("jss_freezed"),e.headers[s].style.left=t+"px";for(let t=0;t0?e.records[t][s-1].element.style.width:0)-51+"px";e.records[t][s].element.classList.add("jss_freezed"),e.records[t][s].element.style.left=n}}else for(let t=0;t0&&t.content.scrollLeft!=scrollLeft&&updateFreezePosition.call(t),1!=t.options.lazyLoading&&1!=t.options.tableOverflow||t.edition&&"jdropdown"!=e.target.className.substr(0,9)&&closeEditor.call(t,t.edition[0],!0)},setEvents=function(e){destroyEvents(e),e.addEventListener("mouseup",mouseUpControls),e.addEventListener("mousedown",mouseDownControls),e.addEventListener("mousemove",mouseMoveControls),e.addEventListener("mouseover",mouseOverControls),e.addEventListener("dblclick",doubleClickControls),e.addEventListener("paste",pasteControls),e.addEventListener("contextmenu",contextMenuControls),e.addEventListener("touchstart",touchStartControls),e.addEventListener("touchend",touchEndControls),e.addEventListener("touchcancel",touchEndControls),e.addEventListener("touchmove",touchEndControls),document.addEventListener("keydown",keyDownControls)},destroyEvents=function(e){e.removeEventListener("mouseup",mouseUpControls),e.removeEventListener("mousedown",mouseDownControls),e.removeEventListener("mousemove",mouseMoveControls),e.removeEventListener("mouseover",mouseOverControls),e.removeEventListener("dblclick",doubleClickControls),e.removeEventListener("paste",pasteControls),e.removeEventListener("contextmenu",contextMenuControls),e.removeEventListener("touchstart",touchStartControls),e.removeEventListener("touchend",touchEndControls),e.removeEventListener("touchcancel",touchEndControls),document.removeEventListener("keydown",keyDownControls)};var toolbar=__webpack_require__(392),pagination=__webpack_require__(167);const setData=function(e){const t=this;if(e&&(t.options.data=e),t.options.data||(t.options.data=[]),t.options.data&&t.options.data[0]&&!Array.isArray(t.options.data[0])){e=[];for(let s=0;so?l:o,c=i>r?i:r;for(s=0;s=d&&s0)for(let l=0;l0&&i++}return s?r.map((function(e){return e.join(s)})).join("\r\n")+"\r\n":n?r.map((function(e){const t={};return e.forEach((function(e,s){t[s]=e})),t})):r},getDataFromRange=function(e,t){const s=this,n=(0,helpers.getCoordsFromRange)(e),o=[];for(let e=n[1];e<=n[3];e++){o.push([]);for(let r=n[0];r<=n[2];r++)t?o[o.length-1].push(s.records[e][r].element.innerHTML):o[o.length-1].push(s.options.data[e][r])}return o},search=function(e){const t=this;if(t.options.filters&&filter.dr.call(t),t.resetSelection(),t.pageNumber=0,t.results=[],e){t.searchInput.value!==e&&(t.searchInput.value=e);const s=function(e,s,n){for(let o=0;o=0||(""+t.records[n][o].element.innerHTML).toLowerCase().search(s)>=0)return!0;return!1},n=function(e){-1==t.results.indexOf(e)&&t.results.push(e)};let o=e.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&");o=new RegExp(o,"i"),t.options.data.forEach((function(e,r){if(s(e,o,r)){const e=merges.D0.call(t,r);if(e.length)for(let s=0;s0&&e.options.paginationOptions&&e.options.paginationOptions.length>0){e.paginationDropdown=document.createElement("select"),e.paginationDropdown.classList.add("jss_pagination_dropdown"),e.paginationDropdown.onchange=function(){e.options.pagination=parseInt(this.value),e.page(0)};for(let t=0;t0&&e.options.nestedHeaders[0]&&e.options.nestedHeaders[0][0])for(let t=0;ts&&(s=t)}else t=Object.keys(e.options.data[0]),t.length>s&&(s=t.length);e.options.minDimensions||(e.options.minDimensions=[0,0]),e.options.minDimensions[0]>s&&(s=e.options.minDimensions[0]);const n=[];for(let o=0;o{jSuites.ajax({url:e.options.csv,method:"GET",dataType:"text",success:function(s){const n=(0,helpers.parseCSV)(s,e.options.csvDelimiter);if(1==e.options.csvHeaders&&n.length>0){const t=n.shift();if(t.length>0){e.options.columns||(e.options.columns=[]);for(let s=0;s{jSuites.ajax({url:e.options.url,method:"GET",dataType:"json",success:function(s){e.options.data=s.data?s.data:s,prepareTable.call(e),t()}})}));s.push(t)}else prepareTable.call(e);await Promise.all(s),"object"==typeof t.plugins&&Object.entries(t.plugins).forEach((function([,t]){"function"==typeof t.init&&t.init(e)}))},createWorksheetObj=function(e){const t=this.parent;e.worksheetName||(e.worksheetName=getNextDefaultWorksheetName(this.parent));const s={parent:t,options:e,filters:[],formula:[],history:[],selection:[],historyIndex:-1};return t.config.worksheets.push(s.options),t.worksheets.push(s),s},createWorksheet=function(e){const t=this.parent;t.creationThroughJss=!0,createWorksheetObj.call(this,e),t.element.tabs.create(e.worksheetName)},openWorksheet=function(e){this.parent.element.tabs.open(e)},deleteWorksheet=function(e){const t=this;t.parent.element.tabs.remove(e);const s=t.parent.worksheets.splice(e,1)[0];dispatch.A.call(t.parent,"ondeleteworksheet",s,e)},worksheetPublicMethods=[["selectAll",selection.Ub],["updateSelectionFromCoords",function(e,t,s,n){return selection.AH.call(this,e,t,s,n)}],["resetSelection",function(){return selection.gE.call(this)}],["getSelection",selection.Lo],["getSelected",selection.ef],["getSelectedColumns",selection.Jg],["getSelectedRows",selection.R5],["getData",getData],["setData",setData],["getValue",getValue],["getValueFromCoords",getValueFromCoords],["setValue",setValue],["setValueFromCoords",setValueFromCoords],["getWidth",getWidth],["setWidth",function(e,t){return setWidth.call(this,e,t)}],["insertRow",insertRow],["moveRow",function(e,t){return moveRow.call(this,e,t)}],["deleteRow",deleteRow],["hideRow",hideRow],["showRow",showRow],["getRowData",getRowData],["setRowData",setRowData],["getHeight",getHeight],["setHeight",function(e,t){return setHeight.call(this,e,t)}],["getMerge",merges.fd],["setMerge",function(e,t,s){return merges.FU.call(this,e,t,s)}],["destroyMerge",function(){return merges.VP.call(this)}],["removeMerge",function(e,t){return merges.Zp.call(this,e,t)}],["search",search],["resetSearch",resetSearch],["getHeader",getHeader],["getHeaders",getHeaders],["setHeader",setHeader],["getStyle",getStyle],["setStyle",function(e,t,s,n){return setStyle.call(this,e,t,s,n)}],["resetStyle",resetStyle],["insertColumn",insertColumn],["moveColumn",moveColumn],["deleteColumn",deleteColumn],["getColumnData",getColumnData],["setColumnData",setColumnData],["whichPage",pagination.ho],["page",pagination.MY],["download",download],["getComments",getComments],["setComments",setComments],["orderBy",orderBy.My],["undo",utils_history.tN],["redo",utils_history.ZS],["getCell",internal.tT],["getCellFromCoords",internal.Xr],["getLabel",internal.p9],["getConfig",getWorksheetConfig],["setConfig",setConfig],["getMeta",function(e){return meta.IQ.call(this,e)}],["setMeta",meta.iZ],["showColumn",showColumn],["hideColumn",hideColumn],["showIndex",internal.C6],["hideIndex",internal.TI],["getWorksheetActive",internal.$O],["openEditor",openEditor],["closeEditor",closeEditor],["createWorksheet",createWorksheet],["openWorksheet",openWorksheet],["deleteWorksheet",deleteWorksheet],["copy",function(e){e?cutControls():copy.call(this,!0)}],["paste",paste],["executeFormula",internal.Em],["getDataFromRange",getDataFromRange],["quantiyOfPages",pagination.$f],["getRange",selection.eO],["isSelected",selection.sp],["setReadOnly",setReadOnly],["isReadOnly",isReadOnly],["getHighlighted",selection.kV],["dispatch",dispatch.A],["down",down],["first",first],["last",last],["left",left],["right",right],["up",up],["openFilter",filter.N$],["resetFilters",filter.dr]],worksheetPublicMethodsLength=worksheetPublicMethods.length,factory=function(){},createWorksheets=async function(e,t,s){let n=t.worksheets;if(!n)throw new Error("JSS: worksheets are not defined");{let o={animation:!0,onbeforecreate:function(t,s){return s||getNextDefaultWorksheetName(e)},oncreate:function(s,n){if(e.creationThroughJss)e.creationThroughJss=!1;else{const t=s.tabs.headers.children[s.tabs.headers.children.length-2].innerHTML;createWorksheetObj.call(e.worksheets[0],{minDimensions:[10,15],worksheetName:t})}const o=e.worksheets[e.worksheets.length-1];o.element=n,buildWorksheet.call(o).then((function(){(0,toolbar.nK)(o),dispatch.A.call(o,"oncreateworksheet",o,t,e.worksheets.length-1)}))},onchange:function(t,s,n){0!=e.worksheets.length&&e.worksheets[n]&&(0,toolbar.nK)(e.worksheets[n])}};1==t.tabs?o.allowCreate=!0:o.hideHeaders=!0,o.data=[];let r=1;for(let e=0;e{libraryBase.jspreadsheet.spreadsheet.push(e),dispatch.A.call(e,"onload",e)})),s}catch(e){console.error(e)}},libraryBase.jspreadsheet.getWorksheetInstanceByName=function(e,t){const s=libraryBase.jspreadsheet.spreadsheet.find((e=>e.config.namespace===t));if(s)return{};if(null==e){const e=s.worksheets.map((e=>[e.options.worksheetName,e]));return Object.fromEntries(e)}return s.worksheets.find((t=>t.options.worksheetName===e))},libraryBase.jspreadsheet.setDictionary=function(e){jSuites.setDictionary(e)},libraryBase.jspreadsheet.destroy=function(e,t){if(e.spreadsheet){const s=libraryBase.jspreadsheet.spreadsheet.indexOf(e.spreadsheet);libraryBase.jspreadsheet.spreadsheet.splice(s,1);const n=e.spreadsheet.config.root||document;e.spreadsheet=null,e.innerHTML="",t&&destroyEvents(n)}},libraryBase.jspreadsheet.destroyAll=function(){for(let e=0;e{libraryBase.jspreadsheet.helpers[e]=t}));var src=libraryBase.jspreadsheet;jspreadsheet=__webpack_exports__.default})(); return jspreadsheet; }))); ================================================ FILE: dist/jspreadsheet.css ================================================ :root { --jss-border-color: #000; } .jss_spreadsheet { outline: none; } .jss_container { display: inline-block; padding-right: 2px; box-sizing: border-box; overscroll-behavior: contain; outline: none; } .fullscreen { position: fixed !important; top: 0px; left: 0px; width: 100%; height: 100%; z-index: 21; display: flex; flex-direction: column; background-color: #ffffff; } .fullscreen .jtabs-content { flex: 1; overflow: hidden; } .fullscreen .jss_content { overflow: auto; width: 100% !important; height: 100%; max-height: 100% !important; } .fullscreen .jss_container { height: 100%; } .jss_content { display: inline-block; box-sizing: border-box; padding-right: 3px; padding-bottom: 3px; position: relative; scrollbar-width: thin; scrollbar-color: #666 transparent; } @supports (-moz-appearance: none) { .jss_content { padding-right: 10px; } } .jss_content::-webkit-scrollbar { width: 8px; height: 8px; } .jss_content::-webkit-scrollbar-track { background: #eee; } .jss_content::-webkit-scrollbar-thumb { background: #666; } .jss_worksheet { border-collapse: separate; table-layout: fixed; white-space: nowrap; empty-cells: show; border: 0px; background-color: #fff; width: 0; border-top: 1px solid transparent; border-left: 1px solid transparent; border-right: 1px solid #ccc; border-bottom: 1px solid #ccc; } .jss_worksheet > thead > tr > td { border-top: 1px solid #ccc; border-left: 1px solid #ccc; border-right: 1px solid transparent; border-bottom: 1px solid transparent; background-color: #f3f3f3; padding: 2px; cursor: pointer; box-sizing: border-box; overflow: hidden; position: -webkit-sticky; position: sticky; top: 0; z-index: 2; } .jss_worksheet > thead > tr > td.dragging { opacity: 0.5; } .jss_worksheet > thead > tr > td.selected { background-color: #dcdcdc; } .jss_worksheet > thead > tr > td.arrow-up { background-repeat: no-repeat; background-position: center right 5px; background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 14l5-5 5 5H7z' fill='gray'/%3E%3C/svg%3E"); text-decoration: underline; } .jss_worksheet > thead > tr > td.arrow-down { background-repeat: no-repeat; background-position: center right 5px; background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='gray'/%3E%3C/svg%3E"); text-decoration: underline; } .jss_worksheet > tbody > tr > td:first-child { position: relative; background-color: #f3f3f3; text-align: center; } .jss_worksheet > tbody.resizable > tr > td:first-child::before { content: '\00a0'; width: 100%; height: 3px; position: absolute; bottom: 0px; left: 0px; cursor: row-resize; } .jss_worksheet > tbody.draggable > tr > td:first-child::after { content: '\00a0'; width: 3px; height: 100%; position: absolute; top: 0px; right: 0px; cursor: move; } .jss_worksheet > tbody > tr.dragging > td { background-color: #eee; opacity: 0.5; } .jss_worksheet > tbody > tr > td { border-top: 1px solid #ccc; border-left: 1px solid #ccc; border-right: 1px solid transparent; border-bottom: 1px solid transparent; padding: 4px; white-space: nowrap; box-sizing: border-box; line-height: 1em; } .jss_overflow > tbody > tr > td { overflow: hidden; } .jss_worksheet > tbody > tr > td:last-child { overflow: hidden; } .jss_worksheet > tbody > tr > td > img { display: inline-block; max-width: 100px; } .jss_worksheet > tbody > tr > td.readonly { color: rgba(0, 0, 0, 0.3); } .jss_worksheet > tbody > tr.selected > td:first-child { background-color: #dcdcdc; } .jss_worksheet > tbody > tr > td > select, .jss_worksheet > tbody > tr > td > input, .jss_worksheet > tbody > tr > td > textarea { border: 0px; border-radius: 0px; outline: 0px; width: 100%; margin: 0px; padding: 0px; padding-right: 2px; background-color: transparent; box-sizing: border-box; } .jss_worksheet > tbody > tr > td > textarea { resize: none; padding-top: 6px !important; } .jss_worksheet > tbody > tr > td > input[type='checkbox'] { width: 12px; margin-top: 2px; } .jss_worksheet > tbody > tr > td > input[type='radio'] { width: 12px; margin-top: 2px; } .jss_worksheet > tbody > tr > td > select { -webkit-appearance: none; -moz-appearance: none; appearance: none; background-repeat: no-repeat; background-position-x: 100%; background-position-y: 40%; background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSdibGFjaycgaGVpZ2h0PScyNCcgdmlld0JveD0nMCAwIDI0IDI0JyB3aWR0aD0nMjQnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2Zyc+PHBhdGggZD0nTTcgMTBsNSA1IDUtNXonLz48cGF0aCBkPSdNMCAwaDI0djI0SDB6JyBmaWxsPSdub25lJy8+PC9zdmc+); } .jss_worksheet > tbody > tr > td.jss_dropdown { background-repeat: no-repeat; background-position: top 50% right 5px; background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='lightgray'/%3E%3C/svg%3E"); text-overflow: ellipsis; overflow-x: hidden; } .jss_worksheet > tbody > tr > td.jss_dropdown.jss_comments { background: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='lightgray'/%3E%3C/svg%3E") top 50% right 5px no-repeat, url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFuGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDphMTlhZDJmOC1kMDI2LTI1NDItODhjOS1iZTRkYjkyMmQ0MmQiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkOGI5NDUyMS00ZjEwLWQ5NDktYjUwNC0wZmU1N2I3Nzk1MDEiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIHN0RXZ0OndoZW49IjIwMTktMDEtMzFUMTg6NTU6MDhaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmExOWFkMmY4LWQwMjYtMjU0Mi04OGM5LWJlNGRiOTIyZDQyZCIgc3RFdnQ6d2hlbj0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4En6MDAAAAX0lEQVQYlX3KOw6AIBBAwS32RpJADXfx0pTET+ERZJ8F8RODFtONsG0QAoh0CSDM82dqodaBdQXnfoLZQM7gPai+wjNNE8R4pTuAYNZSKZASqL7CMy0LxNgJp30fKYUDi3+vIqb/+rUAAAAASUVORK5CYII=') top right no-repeat; } .jss_worksheet > tbody > tr > td > .color { width: 90%; height: 10px; margin: auto; } .jss_worksheet > tbody > tr > td > a { text-decoration: underline; } .jss_worksheet > tbody > tr > td.highlight > a { color: blue; cursor: pointer; } .jss_worksheet > tfoot > tr > td { border-top: 1px solid #ccc; border-left: 1px solid #ccc; border-right: 1px solid transparent; border-bottom: 1px solid transparent; background-color: #f3f3f3; padding: 2px; cursor: pointer; box-sizing: border-box; overflow: hidden; } .jss_worksheet .highlight { background-color: rgba(0, 0, 0, 0.05); } .jss_worksheet .highlight-top { border-top: 1px solid #000; box-shadow: 0px -1px #ccc; } .jss_worksheet .highlight-left { border-left: 1px solid #000; box-shadow: -1px 0px #ccc; } .jss_worksheet .highlight-right { border-right: 1px solid #000; } .jss_worksheet .highlight-bottom { border-bottom: 1px solid #000; } .jss_worksheet .highlight-top.highlight-left { box-shadow: -1px -1px #ccc; -webkit-box-shadow: -1px -1px #ccc; -moz-box-shadow: -1px -1px #ccc; } .jss_worksheet .highlight-selected { background-color: rgba(0, 0, 0, 0); } .jss_worksheet .selection { background-color: rgba(0, 0, 0, 0.05); } .jss_worksheet .selection-left { border-left: 1px dotted #000; } .jss_worksheet .selection-right { border-right: 1px dotted #000; } .jss_worksheet .selection-top { border-top: 1px dotted #000; } .jss_worksheet .selection-bottom { border-bottom: 1px dotted #000; } .jss_corner { position: absolute; background-color: rgb(0, 0, 0); height: 1px; width: 1px; border: 1px solid rgb(255, 255, 255); top: -2000px; left: -2000px; cursor: crosshair; box-sizing: initial; z-index: 20; padding: 2px; } .jss_worksheet .editor { outline: 0px solid transparent; overflow: visible; white-space: nowrap; text-align: left; padding: 0px; box-sizing: border-box; overflow: visible !important; } .jss_worksheet .editor > input { padding-left: 4px; } .jss_worksheet .editor .jupload { position: fixed; top: 100%; z-index: 40; user-select: none; -webkit-font-smoothing: antialiased; font-size: 0.875rem; letter-spacing: 0.2px; -webkit-border-radius: 4px; border-radius: 4px; -webkit-box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); padding: 10px; background-color: #fff; width: 300px; min-height: 225px; margin-top: 2px; } .jss_worksheet .editor .jupload img { width: 100%; height: auto; } .jss_worksheet .editor .jss_richtext { position: fixed; top: 100%; z-index: 40; user-select: none; -webkit-font-smoothing: antialiased; font-size: 0.875rem; letter-spacing: 0.2px; -webkit-box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); padding: 10px; background-color: #fff; width: 358px; margin-top: 2px; text-align: left; white-space: initial; } .jss_worksheet .editor .jclose:after { position: absolute; top: 0; right: 0; margin: 10px; content: 'close'; font-family: 'Material icons'; font-size: 24px; width: 24px; height: 24px; line-height: 24px; cursor: pointer; text-shadow: 0px 0px 5px #fff; } .jss_worksheet, .jss_worksheet td, .jss_corner { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -webkit-user-drag: none; -khtml-user-drag: none; -moz-user-drag: none; -o-user-drag: none; user-drag: none; } .jss_textarea { position: absolute; top: -999px; left: -999px; width: 1px; height: 1px; } .jss_worksheet .dragline { position: absolute; } .jss_worksheet .dragline div { position: relative; top: -6px; height: 5px; width: 22px; } .jss_worksheet .dragline div:hover { cursor: move; } .jss_worksheet .onDrag { background-color: rgba(0, 0, 0, 0.6); } .jss_worksheet .error { border: 1px solid red; } .jss_worksheet thead td.resizing { border-right-style: dotted !important; border-right-color: red !important; } .jss_worksheet tbody tr.resizing > td { border-bottom-style: dotted !important; border-bottom-color: red !important; } .jss_worksheet tbody td.resizing { border-right-style: dotted !important; border-right-color: red !important; } .jss_worksheet .jdropdown-header { border: 0px !important; outline: none !important; width: 100% !important; height: 100% !important; padding: 0px !important; padding-left: 8px !important; } .jss_worksheet .jdropdown-container { margin-top: 1px; } .jss_worksheet .jdropdown-container-header { padding: 0px; margin: 0px; height: inherit; } .jss_worksheet .jdropdown-picker { border: 0px !important; padding: 0px !important; width: inherit; height: inherit; } .jss_worksheet .jss_comments { background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFuGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDphMTlhZDJmOC1kMDI2LTI1NDItODhjOS1iZTRkYjkyMmQ0MmQiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkOGI5NDUyMS00ZjEwLWQ5NDktYjUwNC0wZmU1N2I3Nzk1MDEiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIHN0RXZ0OndoZW49IjIwMTktMDEtMzFUMTg6NTU6MDhaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmExOWFkMmY4LWQwMjYtMjU0Mi04OGM5LWJlNGRiOTIyZDQyZCIgc3RFdnQ6d2hlbj0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4En6MDAAAAX0lEQVQYlX3KOw6AIBBAwS32RpJADXfx0pTET+ERZJ8F8RODFtONsG0QAoh0CSDM82dqodaBdQXnfoLZQM7gPai+wjNNE8R4pTuAYNZSKZASqL7CMy0LxNgJp30fKYUDi3+vIqb/+rUAAAAASUVORK5CYII='); background-repeat: no-repeat; background-position: top right; } .jss_worksheet .sp-replacer { margin: 2px; border: 0px; } .jss_worksheet > thead > tr.jss_filter > td > input { border: 0px; width: 100%; outline: none; } .jss_about { float: right; font-size: 0.7em; padding: 2px; text-transform: uppercase; letter-spacing: 1px; display: none; } .jss_about a { color: #ccc; text-decoration: none; } .jss_about img { display: none; } .jss_filter { display: flex; justify-content: space-between; margin-bottom: 4px; } .jss_filter > div { padding: 8px; align-items: center; } .jss_pagination { display: flex; justify-content: space-between; align-items: center; } .jss_pagination > div { display: flex; padding: 10px; } .jss_pagination > div:last-child { padding-right: 10px; padding-top: 10px; } .jss_pagination > div > div { text-align: center; width: 36px; height: 36px; line-height: 34px; border: 1px solid #ccc; box-sizing: border-box; margin-left: 2px; cursor: pointer; } .jss_page { font-size: 0.8em; } .jss_page_selected { font-weight: bold; background-color: #f3f3f3; } .jss_toolbar { display: flex; background-color: #f3f3f3; border: 1px solid #ccc; padding: 4px; margin: 0px 2px 4px 1px; } .jss_toolbar:empty { display: none; } .jss_worksheet .dragging-left { background-repeat: no-repeat; background-position: top 50% left 0px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M14 7l-5 5 5 5V7z'/%3E%3Cpath fill='none' d='M24 0v24H0V0h24z'/%3E%3C/svg%3E"); } .jss_worksheet .dragging-right { background-repeat: no-repeat; background-position: top 50% right 0px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M10 17l5-5-5-5v10z'/%3E%3Cpath fill='none' d='M0 24V0h24v24H0z'/%3E%3C/svg%3E"); } .jss_hidden_index > tbody > tr > td:first-child, .jss_hidden_index > thead > tr > td:first-child, .jss_hidden_index > tfoot > tr > td:first-child, .jss_hidden_index > colgroup > col:first-child { display: none; } .jss_worksheet .jrating { display: inline-flex; } .jss_worksheet .jrating > div { zoom: 0.55; } .jss_worksheet .copying-top { border-top: 1px dashed #000; } .jss_worksheet .copying-left { border-left: 1px dashed #000; } .jss_worksheet .copying-right { border-right: 1px dashed #000; } .jss_worksheet .copying-bottom { border-bottom: 1px dashed #000; } .jss_worksheet .jss_column_filter { background-repeat: no-repeat; background-position: top 50% right 5px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='gray' width='18px' height='18px'%3E%3Cpath d='M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); text-overflow: ellipsis; overflow: hidden; padding: 0px; padding-left: 6px; padding-right: 20px; } .jss_worksheet thead .jss_freezed, .jss_worksheet tfoot .jss_freezed { left: 0px; z-index: 3 !important; box-shadow: 2px 0px 2px 0.2px #ccc !important; -webkit-box-shadow: 2px 0px 2px 0.2px #ccc !important; -moz-box-shadow: 2px 0px 2px 0.2px #ccc !important; } .jss_worksheet tbody .jss_freezed { position: relative; background-color: #fff; box-shadow: 1px 1px 1px 1px #ccc !important; -webkit-box-shadow: 2px 4px 4px 0.1px #ccc !important; -moz-box-shadow: 2px 4px 4px 0.1px #ccc !important; } .red { color: red; } .jss_worksheet > tbody > tr > td.readonly > input[type='checkbox'], .jss_worksheet > tbody > tr > td.readonly > input[type='radio'] { pointer-events: none; opacity: 0.5; } ================================================ FILE: dist/jspreadsheet.themes.css ================================================ .jss_worksheet > thead > tr > td { border-top: 1px solid var(--border_color, #ccc); border-left: 1px solid var(--border_color, #ccc); background-color: var(--header_background, #f3f3f3); color: var(--header_color, #000); } .jss_worksheet > thead > tr > td.selected { background-color: var(--header_background_highlighted, #dcdcdc); color: var(--header_color_highlighted, #000); } .jss_worksheet > tbody > tr > td:first-child { background-color: var(--header_background, #f3f3f3); color: var(--header_color, #000); } .jss_worksheet > tbody > tr > td { background-color: var(--content_background, #fff); color: var(--content_color, #000); border-top: 1px solid var(--border_color, #ccc); border-left: 1px solid var(--border_color, #ccc); } .jss_worksheet > tbody > tr.selected > td:first-child { background-color: var(--header_background_highlighted, #dcdcdc); color: var(--header_color_highlighted, #000); } .jss_worksheet .highlight { background-color: var(--selection, rgba(0, 0, 0, 0.05)); } .jss_worksheet .highlight-top { border-top: 1px solid var(--border_color_highlighted, #000); } .jss_worksheet .highlight-left { border-left: 1px solid var(--border_color_highlighted, #000); } .jss_worksheet .highlight-right { border-right: 1px solid var(--border_color_highlighted, #000); } .jss_worksheet .highlight-bottom { border-bottom: 1px solid var(--border_color_highlighted, #000); } .jss_worksheet .highlight-selected { background-color: var(--cursor, #eee); } .jss_pagination > div > div { color: var(--header_color, #000); background: var(--header_background, #f3f3f3); border: 1px solid var(--border_color, #ccc); } .jss_toolbar { background-color: var(--header_background, #f3f3f3); color: var(--header_color, #000); border: 1px solid var(--border_color, #ccc); } .jss_toolbar .jtoolbar-item i { color: var(--content_color, #000); } .jss_toolbar .jtoolbar-item:not(.jtoolbar-divisor):hover, .jss_toolbar .jtoolbar-item.jpicker:hover > .jpicker-header { background-color: var(--content_background_highlighted, #f3f3f3); color: var(--content_color_highlighted, #000); } .jss_toolbar .jtoolbar-divisor { background: var(--header_color, #ddd); } .jss_contextmenu { border: 1px solid var(--border_color, #ccc); background: var(--menu_background, #fff); color: var(--menu_color, #555); box-shadow: var(--menu_box_shadow, 2px 2px 2px 0px rgba(143, 144, 145, 1)); -webkit-box-shadow: var(--menu_box_shadow, 2px 2px 2px 0px rgba(143, 144, 145, 1)); -moz-box-shadow: var(--menu_box_shadow, 2px 2px 2px 0px rgba(143, 144, 145, 1)); } .jss_contextmenu > div a { color: var(--menu_color, #555); } .jss_contextmenu > div:not(.contextmenu-line):hover a { color: var(--menu_color_highlighted, #555); } .jss_contextmenu > div:not(.contextmenu-line):hover { background: var(--menu_background_highlighted, #ebebeb); } .jss_container input { color: var(--header_color, #000); background: var(--header_background, #f3f3f3); } ================================================ FILE: docs/jspreadsheet/contact.md ================================================ title: Jspreadsheet - Contact Us keywords: Contact Jspreadsheet, Get in Touch, Technical Support, Sales Inquiries description: Reach out to us for any technical support or sales inquiries related to Jspreadsheet. We're here to help with your questions and needs. canonical: https://bossanova.uk/jspreadsheet/contact
{data-text="Jspreadsheet" .center} # Contact Us

#### Upgrades Looking for more information? Email: [contact@jspreadsheet.com](mailto:contact@jspreadsheet.com) [Contact Us](mailto:contact@jspreadsheet.com){.button .dark target="_blank"}
#### Technical Support Professional Technical Support Email: [support@jspreadsheet.com](mailto:support@jspreadsheet.com) [Talk to us](mailto:support@jspreadsheet.com){.button .dark target="_blank"}
#### Github GitHub repository: [https://github.com/jspreadsheet/ce](https://github.com/jspreadsheet/ce) [![GitHub](img/github-logo.svg)](https://github.com/jspreadsheet/ce){target="_blank"}
================================================ FILE: docs/jspreadsheet/demo.md ================================================ title: Jspreadsheet Demo Page keywords: Jspreadsheet, JavaScript data grid, spreadsheet controls, interactive data grid, web-based applications, lightweight, responsive controls, agnostic platform, Angular, React, Vue, spreadsheet-like plugin, documentation, examples, license description: Explore working examples of Jspreadsheet in action. See how it delivers interactive, customizable data grid solutions for web-based applications. canonical: https://bossanova.uk/jspreadsheet/demo
{data-text="Demo"} # Data Grid Demo Explore the Jspreadsheet data grid through these interactive demos. Learn how its versatile spreadsheet controls integrate seamlessly with Angular, React, Vue, and other platforms. See practical use cases for building responsive, lightweight, and powerful web-based applications.
================================================ FILE: docs/jspreadsheet/docs/angular.md ================================================ title: Angular Spreadsheet keywords: Angular, Jspreadsheet, data grid, spreadsheet controls, TypeScript, Angular integration, JavaScript grid, Angular application, interactive spreadsheets, data grid functionality description: Create advanced data grids in Angular applications using Jspreadsheet. Integrate spreadsheet-like controls with TypeScript support for enhanced interactivity and data management. # Angular Spreadsheet ## Overview Create advanced JavaScript Data Grids with Angular using Jspreadsheet, featuring spreadsheet-like controls optimized for TypeScript. This guide provides integration steps and practical examples tailored for Angular projects. ## Documentation ### Installation Install Jspreadsheet for Angular using npm: ```bash npm install jspreadsheet-ce@5.0.0-beta.3 ``` ### Importing Stylesheets Include the necessary styles for Jspreadsheet and Jsuites in your Angular project. Add the following to the styles array in your angular.json file: ```json "styles": [ "jsuites/dist/jsuites.css", "jspreadsheet-ce/dist/jspreadsheet.css" ] ``` ## Examples ### Basic Angular Spreadsheet Here's a simple example of integrating Jspreadsheet in an Angular component. The spreadsheet allows dropdowns and colour pickers within cells, demonstrating adding comments and using basic formulas. ```typescript import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; @Component({ selector: "app-root", templateUrl: "./app.component.html", styleUrls: ["./app.component.css"] }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; ngAfterViewInit() { // Create the spreadsheet jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [ { data: [ ['Yes', '#ff0000'], ['No', '#00ff00'] ], minDimensions: [6, 4], columns: [ { type: "dropdown", width: 100, source: ["Yes", "No"] }, { type: "colour", width: 100, render: "square" } ], allowComments: true, comments: { A1: "Select Yes or No", B1: "Choose a colour." } } ] }); } } ``` ### Data from External Sources Use Angular's HttpClient to load or save data dynamically from an API and pass it to Jspreadsheet. {.ignore} ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import { HttpClient } from '@angular/common/http'; import jspreadsheet from "jspreadsheet-ce"; @Component({ selector: "app-root", templateUrl: "./app.component.html", styleUrls: ["./app.component.css"] }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; constructor(private http: HttpClient) {} ngAfterViewInit() { // Fetch data from an external API and initialize the spreadsheet this.http.get('https://api.example.com/spreadsheet-data') .subscribe((data: any[][]) => { jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [ { data: data, columns: [ { type: "dropdown", width: 100, source: ["Yes", "No"] }, { type: "checkbox", width: 50 } ], minDimensions: [10, 5] } ] }); }); } } ``` ### Online Spreadsheet with Angular A basic Angular spreadsheet with export to XLSX. - [Angular Online Spreadsheet](https://codesandbox.io/s/angular-spreadsheet-lbtcwf) - [Angular Data Grid with Spreadsheet Controls](https://stackblitz.com/edit/data-grid-with-spreadsheet-controls) - [Dynamic Spreadsheets](https://stackblitz.com/edit/online-spreadsheets) ### Online XLSX Editor with Angular Import XLSX files and edit them online using Jspreadsheet Pro. - [Angular Spreadsheet XLSX Editor](https://codesandbox.io/s/online-angular-excel-spreadsheet-lk5bnc) ### More Spreadsheet Angular Examples - [Data Grid Custom Cell Editor](https://stackblitz.com/~/github.com/nicolasjesse/jss-custom-column-ng) - [Angular Spreadsheet with Data Validation](https://stackblitz.com/edit/angular-data-validation-example) {.pro} > **Jspreadsheet Pro for Angular - Professional Spreadsheet Components** > > While Jspreadsheet CE provides core spreadsheet functionality for Angular, **Jspreadsheet Pro** offers enhanced Angular integration and enterprise features: > > **Enhanced Angular Integration:** > - **Full TypeScript Support:** Complete type definitions for type-safe Angular development > - **Angular Modules:** NgModule structure for easy integration into Angular apps > - **Standalone Components:** Support for Angular standalone components (Angular 14+) > - **Dependency Injection:** Full DI support for spreadsheet services > - **RxJS Integration:** Observable streams for reactive data handling > - **Angular Signals:** Support for Angular Signals (Angular 16+) > > **Professional Components:** > - **Advanced Editors:** Conditional dropdowns, rich text, HTML editors with Angular forms > - **Formula System:** 500+ Excel functions with Angular change detection > - **Conditional Formatting:** Visual rules, data bars, color scales, icon sets > - **Data Validation:** Real-time validation with Angular form validators > - **Import/Export:** Full Excel (.xlsx) import/export with formatting preservation > - **Charts & Graphs:** Built-in Angular chart components for data visualization > > **Angular Forms Integration:** > - **Reactive Forms:** Full integration with Angular Reactive Forms > - **Template-Driven Forms:** Support for template-driven form bindings > - **Form Validators:** Built-in and custom Angular validators > - **Form Control:** Spreadsheet as FormControl with validation > - **Two-Way Binding:** [(ngModel)] support for spreadsheet data > - **Form States:** Track pristine, dirty, touched, valid states > > **Performance & Scale:** > - **Virtual Scrolling:** Handle 100K+ rows with smooth scrolling > - **Lazy Loading:** Load data on-demand for optimal performance > - **Zone.js Optimized:** Efficient change detection with Angular zones > - **OnPush Strategy:** Support for OnPush change detection strategy > - **Web Workers:** Background processing for heavy calculations > - **AOT Compatible:** Fully compatible with Ahead-of-Time compilation > > **Developer Experience:** > - **Angular-Specific Documentation:** Examples with TypeScript and Angular patterns > - **Professional Support:** Priority support for Angular integration issues > - **Regular Updates:** Continuous Angular version compatibility (14, 15, 16, 17+) > - **Migration Tools:** Easy migration from CE to Pro with Angular examples > > **Angular-Specific Pro Features:** > - **Custom Angular Components:** Use Angular components as cell editors > - **Pipes Support:** Use Angular pipes for cell value transformation > - **Directives:** Custom directives for cell customization > - **Services:** Spreadsheet services for shared state management > - **Router Integration:** Deep linking and navigation integration > - **Testing Support:** Jasmine/Karma and Jest integration examples > > Perfect for Angular applications requiring enterprise-grade spreadsheet functionality with professional support. > > **[Explore Jspreadsheet Pro Angular →](https://jspreadsheet.com/docs/angular)** | **[Compare CE vs Pro →](https://jspreadsheet.com/docs/getting-started)** | **[View Pricing →](https://jspreadsheet.com/pricing)** ================================================ FILE: docs/jspreadsheet/docs/cells.md ================================================ title: Spreadsheet Cells keywords: JavaScript, Jspreadsheet, Jexcel, Data Grid, Spreadsheet Features, Excel-like Functionality, Table Management, Cell Settings, Cell Configuration description: Detailed documentation on Jspreadsheet cell configuration, covering events, methods, and settings for advanced customization. # Spreadsheet Cells This guide provides an in-depth overview of data grid cells in Jspreadsheet, including key methods, configuration options, and a basic implementation example. {.pro} > #### Differences in the Pro Version > > The Jspreadsheet Pro enables cell-level editor customization and allows dynamic programmatic changes to cell types.\ > \ > [Learn more](https://jspreadsheet.com/docs/cells){.button} ## Documentation ### Methods This section outlines methods to facilitate interaction with cells and their attributes within the data grid. | Method | Description | |------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| | `getCell` | Get cell DOM element by cell name.
@param `cell` - Cell name.
`worksheetInstance.getCell(cell: string): HTMLTableCellElement;`

Get cell DOM element by cell coords.
@param `x` - Cell column index.
@param `y` - Cell row index.
`worksheetInstance.getCell(x: number, y: number): HTMLTableCellElement;` | | `getCellFromCoords`{.nowrap} | Get cell DOM element by cell coordinates.
@param `x` - Column index of the cell.
@param `y` - Row index of the cell.
`worksheetInstance.getCellFromCoords(x: number, y: number): HTMLTableCellElement;` | ### Example The following example is a car loan calculation spreadsheet showcasing styles, merged cells, and row properties. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ ['Car loan', ''], ['Purchase price', '19700'], ['Down payment', '1000'], ['Trade-in value', '500'], ['Interest rate', '0.0305'], ['Length of loan (in months)', '60'], ['', ''], ['Monthly payment', '=PMT(B5/12,B6,B2-(B3+B4))'], ['Total cost', '=-(B8*B6)+(B3+B4)'], ]; // Columns const columns = [ { width:'300px' }, { width:'200px' }, ]; // Merge cells const mergeCells = { A1: [2, 1], } // Rows properties const rows = { 0: { height:'200px' } } const style = { A1: 'font-weight: bold' } const applyStyle = function() { if (spreadsheet.current) { spreadsheet.current[0].getCell('A1').style.backgroundColor = 'orange'; } } // Render component return ( <> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create the data grid component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [ ['Car Loan', ''], ['Purchase price', '19700'], ['Down payment', '1000'], ['Trade-in value', '500'], ['Interest rate', '0.0305'], ['Length of loan (in months)', '60'], ['', ''], ['Monthly payment', '=PMT(B5/12,B6,B2-(B3+B4))'], ['Total cost', '=-(B8*B6)+(B3+B4)'], ], columns: [ { width:'300px' }, { width:'200px' }, ], mergeCells: { A1: [2, 1] }, rows: { 0: { height:'200px' } }, style: { 'A1': 'font-weight: bold' } }] }); } applyStyle() { if (this.worksheets) { this.worksheets[0].getCell('A1').style.backgroundColor = 'orange'; } } } ``` {.pro} > **Upgrade to Jspreadsheet Pro for Advanced Cell Features** > > Jspreadsheet CE provides basic cell operations and styling. **Jspreadsheet Pro** adds powerful cell-level capabilities: > > **Cell-Level Validation:** > - **Required Cells:** Mark specific cells as required with custom error messages > - **Custom Validators:** Define validation functions per cell (not just column-level) > - **Cross-Cell Validation:** Validate based on values in other cells > - **Real-Time Validation:** Instant feedback with visual indicators as users type > - **Validation Rules:** Min/max values, regex patterns, custom logic per cell > - **Error Highlighting:** Automatic visual highlighting of invalid cells > > **Advanced Cell Formatting:** > - **Conditional Cell Formatting:** Auto-apply colors, styles based on cell values > - **Data Bars:** In-cell bar charts showing relative values > - **Color Scales:** Gradient colors based on value ranges > - **Icon Sets:** Visual icons (arrows, flags, traffic lights) based on values > - **Rich Text in Cells:** Bold, italic, underline, colors within single cell > - **Cell-Specific Masks:** Different format masks per cell (not just column) > > **Cell Protection & Security:** > - **Cell-Level Protection:** Lock specific cells while allowing others to edit > - **Conditional Protection:** Protect/unprotect cells based on user roles or values > - **Password Protection:** Protect cells with passwords > - **Read-Only Cells:** Programmatically set individual cells as read-only > > **Enhanced Cell Features:** > - **Cell Comments/Notes:** Advanced commenting system with threads and mentions > - **Cell Metadata:** Store custom business data per cell > - **Cell History:** Track changes per cell with audit trail > - **Cell Dependencies:** Track which cells depend on each other > - **Cell Types:** Dynamic cell type changes at runtime > - **Custom Renderers:** Per-cell custom rendering functions > > Perfect for data entry applications requiring strict validation and conditional formatting. > > **[Explore Pro Cell Features →](https://jspreadsheet.com/docs/cells)** | **[Compare Editions →](https://jspreadsheet.com/docs/getting-started)** | **[View Pricing →](https://jspreadsheet.com/pricing)** ================================================ FILE: docs/jspreadsheet/docs/clipboard.md ================================================ title: Jspreadsheet Clipboard keywords: Jspreadsheet, Jexcel, data grid, JavaScript, Excel-like formulas, spreadsheet data, worksheet data, manage spreadsheet data, change cell data, update worksheet data description: Explore Jspreadsheet’s clipboard methods and events, enabling seamless copy-paste operations for managing and updating data within your spreadsheets. # Data Grid Clipboard ## Overview The clipboard in Jspreadsheet offers a comprehensive set of features that facilitate copying and pasting data between Jspreadsheet instances and other spreadsheet software, such as Excel or Google Sheets. These features include automatic formula updates, data overwriting during paste actions, and custom event handling. This section details the methods and events you can use to control clipboard behaviour, customize paste actions, and ensure smooth interoperability between different data grids. ### Key Features - **Automatic Formula Updates**: When pasting data containing formulas, Jspreadsheet automatically updates the formulas to match the new data context; - **Data Overwriting**: Jspreadsheet supports data overwriting during paste actions, giving you fine-grained control over pasted data handling; - **Custom Events**: You can hook into specific clipboard-related events to customize the behaviour of copy-paste operations, including validation, transformation, and data filtering; {.pro} > #### What you can find on the Pro Version > > The **Pro version** of Jspreadsheet includes additional clipboard capabilities, enhancing your experience with formatting, styles, and formula reference updates: > - **Copy and Paste Formatting**: When using the Pro version, not only data but also cell formats (like font styles, borders, and colours) and styles are transferred during copy-paste actions. > - **Formula Reference Updates**: The Pro version ensures that pasted formulas automatically adjust their references according to the new locations, maintaining the integrity of your spreadsheet formulas.\ > >\ > [Learn more](https://jspreadsheet.com/docs/clipboard){.button} ## Documentation ### Clipboard Management Methods Jspreadsheet provides methods to control clipboard operations, allowing smooth data transfers between spreadsheets or external tools like Excel. | Method | Description | |---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `copy` | Copies or cuts the contents of selected cells in the worksheet.
@param `cut` - If true, the operation is cut, if not, it is copy.
`worksheetInstance.copy(cut?: boolean): void;`
**Example**: `worksheetInstance.copy(true)` to cut the data. | | `paste` | Paste data at a coordinate.Pastes content into one or more cells.
@param `x` - Column index of the cell from which the content will be pasted.
@param `y` - Row index of the cell from which the content will be pasted.
@param `data` - Content to be pasted.
`worksheetInstance.paste(x: number, y: number, data: string): false \| undefined;` | ### Clipboard Operation Events These events let you hook into clipboard operations like copy and paste, providing greater control over how data is transferred or manipulated. | Event | Description | |--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `oncopy` | Fires when data is copied to the clipboard. Use this to inspect or modify the copied data before it goes to the clipboard.
`oncopy(instance: WorksheetInstance, selectedRange: [number, number, number, number], copiedData: string, cut: boolean \| undefined): string \| false \| undefined;` | | `onbeforepaste`{.nowrap} | Fires before pasting occurs, allowing you to validate or transform the incoming data. You can also cancel the paste if needed.
`onbeforepaste(instance: WorksheetInstance, copiedText: { value: CellValue }[][], colIndex: number \| string, rowIndex: number \| string): undefined \| boolean \| string;` | | `onpaste` | Fires after the paste action is completed. Useful for post-processing or triggering additional actions after the paste.
`onpaste(instance: WorksheetInstance, pastedInfo: { x: number, y: number, value: CellValue }[][]): void;` | > **Note** > In Jspreadsheet CE, it is not possible to cancel or modify the clipboard value during the oncopy event. ## Examples ### Intercept Pasted Data You can intercept and cancel the paste event if needed. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ ['Mazda', 2001, 2000], ['Peugeot', 2010, 5000], ['Honda Fit', 2009, 3000], ['Honda CRV', 2010, 6000], ]; const onbeforepaste = function() { alert('Not allowed to paste'); return false; } // Render data grid component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create the data grid component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ minDimensions: [6,6], }], onbeforepaste: function() { alert('Not allowed to paste'); return false; } }); } } ``` ================================================ FILE: docs/jspreadsheet/docs/columns.md ================================================ title: Spreadsheet Columns keywords: Jspreadsheet, column management, JavaScript spreadsheets, Excel-like columns, data grid customization, column settings, spreadsheet methods, column events description: Dive into the comprehensive guide on managing spreadsheet columns in Jspreadsheet, covering column settings, programmable methods, and associated events for complete control and customization. # Spreadsheet Columns The Jspreadsheet column settings control all the cell attributes within a column, including data types, read-only status, data masks, and rendering options. This section covers settings, events, and methods in Jspreadsheet to customize spreadsheet functionality for your application needs. ## Documentation ### Methods The following methods allow for programmatic interaction with spreadsheet columns in Jspreadsheet. | Method | Description | |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| | `getWidth` | Get the width of one or all columns.
@param `column` - Index of the column. If omitted, returns the widths of all columns.
`worksheetInstance.getWidth(column?: number): number \| (number \| string)[];` | | `setWidth` | Set the width of a column.
@param `column` - Column index.
@param `width` - New width.
`worksheetInstance.setWidth(column: number, width: number): void;`

Set the width of one or more columns.
@param `column` - Column indexes.
@param `width` - New widths.
`worksheetInstance.setWidth(column: number[], width: number \| number[]): void;` | | `getColumn` | Retrieves settings for a specified column.
`worksheetInstance.getColumn(colNumber: Number): Object` | | `moveColumn` | Move a column. This method returns false if the "This action will destroy any existing merged cells. Are you sure?" dialog receives a negative response.
@param o - Column index.
@param `d` - New column index.
`worksheetInstance.moveColumn(columnNumber: number, newPositionNumber: number): false \| undefined;` | | `insertColumn` | Insert one or more columns. This method returns false if the onbeforeinsertcolumn event returns false or if the "This action will destroy any existing merged cells. Are you sure?" dialog receives a negative response.
@param `mixed` - Number of columns to insert. It can also be an array of values, but in this case, only one column is inserted, whose data is based on the array items. Default: 1.
@param `columnNumber` - Index of the column used as reference for the insertion. Default: last column.
@param `insertBefore` - Insert new columns before or after the reference column. Default: false.
@param `properties` - New column properties.
`worksheetInstance.insertColumn(mixed?: number \| CellValue[], columnNumber?: number, insertBefore?: boolean, properties?: Column[]): false \| undefined;` | | `deleteColumn`{.nowrap} | Remove columns. This method returns false if the onbeforedeletecolumn event returns false or if the "This action will destroy any existing merged cells. Are you sure?" dialog receives a negative response.
@param `columnNumber` - Column index from which removal starts.
@param `numOfColumns` - Number of columns to be removed.
`worksheetInstance.deleteColumn(columnNumber?: number, numOfColumns?: number): false \| undefined;` | ### Events Several events are available for handling column actions in your spreadsheet, including `onbefore` events that let you intercept, validate, or cancel user actions. | Event | Description | |---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| | `onbeforeinsertcolumn`{.nowrap} | Triggered before a new column is inserted. Return `false` to cancel the action.
`onbeforeinsertcolumn(instance: WorksheetInstance, columns: { column: number, options: Column, data?: CellValue[] }[]): undefined \| boolean;` | | `oninsertcolumn` | Triggered after a new column is inserted.
`oninsertcolumn(instance: WorksheetInstance, columns: { column: number, options: Column, data?: CellValue[] }[]): void;` | | `onbeforedeletecolumn` | Triggered before a column is deleted. Return `false` to cancel the action.
`onbeforedeletecolumn(instance: WorksheetInstance, removedColumns: number[]): undefined \| boolean;` | | `ondeletecolumn` | Triggered after a column is deleted.
`ondeletecolumn(instance: WorksheetInstance, removedColumns: number[]): void;` | | `onmovecolumn` | Triggered after a column is moved to a new position.
`ondeletecolumn(instance: WorksheetInstance, removedColumns: number[]): void;` | ### Initial Settings The following column-related properties are configurable during the initialization of the online spreadsheet: | Property | Description | |---------------------------------------------|--------------------------------------------------------------------------------------------------------| | `allowInsertColumn: boolean` | Enables users to add new columns. `Default: true` | | `allowManualInsertColumn: boolean`{.nowrap} | Adds a new column when the user presses the Tab key in the last column. `Default: true` | | `allowDeleteColumn: boolean` | Allows users to delete columns. `Default: true` | | `allowRenameColumn: boolean` | Allows users to rename columns. `Default: true` | | `columnDrag: boolean` | Enables drag-and-drop for changing column positions. `Default: true` | | `columnSorting: boolean` | Allows users to sort columns. `Default: true` | | `columnResize: boolean` | Allows users to resize columns. `Default: true` | | `defaultColWidth: number` | Sets the default column width. `Default: 100px` | | `defaultColAlign: string` | Sets the default column text alignment. `Default: center` | | `minSpareCols: number` | Number of blank columns at the spreadsheet's end. `Default: none` | ### Available Properties Each column type in Jspreadsheet can hold specific properties. Below are some of the most commonly available options: | Property | Description | |----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------| | `type` | Defines the editor type for the column. It can be a string for a native editor or a method for a custom editor plugin. | | `title` | Title of the column. | | `name` | Property name or path when using JSON data. | | `width` | Width of the column. | | `align` | Alignment of the column content. `Default: center.` | | `url` | URL to load items for dropdowns in this column. | | `source` | Items for dropdown or autocomplete fields. | | `autocomplete` | Enables autocomplete for the column. | | `multiple` | Allows multiple selections in dropdown or autocomplete fields. | | `mask` | Input mask applied to data cells. | | `decimal` | Character used as the decimal separator. | | `disabledMaskOnEdition` | Disables the mask when editing. | | `render` | Renderer method or rule for cell content. | | `format` | Date or number format in the cell. `Default for the calendar: "DD/MM/YYYY".` | | `options` | Extended configuration for the column. | #### Adding a New Column To insert a new column into the data grid, you can pass an array of objects, each object containing three properties outlined below. This approach allows for the creation of multiple columns in a single operation. | Method | Description | | ----------------|--------------------------------------------------| | data: any[] | Array with the column data | | column: number | The index where the new column will be inserted. | | options: object | An object specifying the column's attributes. | ##### Example To add a new column at the beginning of the grid: {.ignore} ```javascript worksheet.insertColumn([ { data: [1,2,3], column: 0, options: { type: 'calendar', title: 'My new column' } } ]); ``` ## Examples ### Render Method The render method modifies visible data before inserting it into a grid cell. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jspreadsheet-ce/dist/jspreadsheet.css"; // Adding an arbitrary number leading zeros. const pad = (cell, value, x, y, instance, options) => { if (value !== '') { let size = options.digits||0; value = value.toString(); while (value.length < size) { value = "0" + value; } cell.innerText = value; } } export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [[1]]; // Columns const columns = [ { render: pad, digits: 6 } ]; // Render component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Adding an arbitrary number leading zeros. let pad = function(cell, value, x, y, instance, options) { if (value || Number(value)) { let size = options.digits||0; value = value.toString(); while (value.length < size) { value = "0" + value; } cell.innerText = value; } } // Create component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { tabs: true, toolbar: true, worksheets: [{ data: [[1]], minDimensions: [6,6], columns: [{ render: pad, digits: 6 }] }], }); } } ``` ### Programmatic Methods A basic spreadsheet example demonstrating various programmatic methods. ```html














``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(null); // Data const data = [ ['US', 'Cheese', 1000 ], ['CA', 'Apples', 1200 ], ['CA', 'Carrots', 2000 ], ['BR', 'Oranges', 3800 ], ]; // Render component return ( <>




); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
  • Click to insert a new blank column at the end of the table
  • Click to insert two new blank columns at the beginning of the table
  • Click to delete the last column
  • Click to move the first column to the third position
  • Hide the first column
  • Show the first column
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { tabs: true, toolbar: true, worksheets: [{ data: [ ['US', 'Cheese', 1000 ], ['CA', 'Apples', 1200 ], ['CA', 'Carrots', 2000 ], ['BR', 'Oranges', 3800 ], ] }], }); } } ``` {.pro} > **Upgrade to Jspreadsheet Pro for Advanced Column Features** > > Jspreadsheet CE provides core column functionality including types, widths, and basic validation. **Jspreadsheet Pro** adds powerful column management capabilities: > > **Advanced Validation:** > - **Column-Level Validation Rules:** Required fields, min/max values, regex patterns, custom validators > - **Data Type Validation:** Strict validation for numbers, dates, emails, URLs, and custom types > - **Cross-Column Validation:** Validation rules based on values in other columns > - **Custom Error Messages:** User-friendly validation messages per column > - **Real-Time Validation:** Instant feedback as users type > > **Column Dependencies:** > - **Conditional Logic:** Show/hide columns based on other column values > - **Dynamic Dropdowns:** Dropdown options that change based on other selections > - **Calculated Columns:** Auto-calculate column values from formulas > - **Cascading Updates:** Automatic updates when dependent columns change > > **Enhanced Column Features:** > - **Nested Headers:** Multi-level column headers for complex layouts > - **Column Grouping:** Group related columns with expand/collapse > - **Fixed Columns:** Freeze columns while scrolling horizontally > - **Column Templates:** Reusable column configurations > - **Advanced Sorting:** Multi-column sorting with custom sort logic > - **Column Filters:** Built-in filter dropdowns per column > > Perfect for complex data entry forms and enterprise applications requiring sophisticated column management. > > **[Explore Pro Column Features →](https://jspreadsheet.com/docs/columns)** | **[Compare Editions →](https://jspreadsheet.com/docs/getting-started)** | **[View Pricing →](https://jspreadsheet.com/pricing)** ================================================ FILE: docs/jspreadsheet/docs/comments.md ================================================ title: Data Grid Cell Comments keywords: Jspreadsheet, Jexcel, Data Grid, JavaScript, Excel-like features, Spreadsheet, Cell comments, Cell comments methods, Cell comments events, Cell comments settings description: Learn how to add, manage, and customize comments in Jspreadsheet data grid cells using built-in methods, events, and settings. # Data Grid Cell Comments ## Overview This guide explains how to manage and add comments to data grid cells in Jspreadsheet, similar to the features found in Excel or Google Sheets. You can add comments programmatically or through the context menu, enabling annotations and additional information for specific cell contents. ## Documentation ### Methods The following methods allow you to get or set comments on one or multiple data grid cells. | Method | Description | |---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `getComments` | Retrieve comments from a specific cell or the entire data grid.
@param `cell` - Cell name. If null or undefined, returns comments for all cells.
`worksheetInstance.getComments(cell?: string): Record \| string;` | | `setComments` | Add, update, or remove a comment.
@param `cellId` - Cell name.
@param `comments` - New comment. If null, removes the comment from the cell.
`worksheetInstance.setComments(cellId: string, comments: string): void;`

Handle multiple cells:
@param `cellId` - Object where keys are cell names and values are cell comments. Null values remove the comments.
`worksheetInstance.setComments(cellId: Record): void;` | ### Events The `onbeforecomments` event allows you to intercept, modify, or cancel the action of adding a new comment. | Event | Description | |-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `oncomments`{.nowrap} | Triggered when a comment is modified.
@param `instance` - The worksheet instance where the change occurred.
@param `newComments` - Object containing updated comments.
@param `oldComments` - Object containing previous comments.
`oncomments(instance: WorksheetInstance, newComments: Record, oldComments: Record): void;` | ### Initial Settings These properties can be configured during the initialization of the spreadsheet. | Property | Description | |--------------------------|----------------------------------------------------------------------------------------| | `allowComments: boolean` | Enable or disable the ability for users to add new comments to cells. | | `comments: object` | Object containing the initial comments. Example: `{ A1: 'test', B1: 'another test' }`. | ## Examples ### Data Grid with Comments Initialize a Jspreadsheet data grid with predefined comments and interact with them programmatically using `setComments` and `getComments`. ```html

``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { const spreadsheet = useRef(); const data = [ ['US', 'Cheese', '2019-02-12'], ['CA', 'Apples', '2019-03-01'], ['CA', 'Carrots', '2018-11-10'], ['BR', 'Oranges', '2019-01-12'], ]; const columns = [ { width: '300px' }, { width: '200px' }, { width: '200px' }, ]; const comments = { B1: 'Initial comments on B1', C1: 'Initial comments on C1' }; const oncomments = () => { console.log(arguments); } return ( <> spreadsheet.current[0].setComments('A1', 'Test')} /> alert(spreadsheet.current[0].getComments('A1'))} /> spreadsheet.current[0].setComments('A1', '')} /> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; @Component({ standalone: true, selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; worksheets: jspreadsheet.worksheetInstance[]; ngAfterViewInit() { this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [ { data: [ ['US', 'Cheese', '2019-02-12'], ['CA', 'Apples', '2019-03-01'], ['CA', 'Carrots', '2018-11-10'], ['BR', 'Oranges', '2019-01-12'], ], columns: [ { width: '300px' }, { width: '200px' }, { width: '200px' }, ], allowComments: true, comments: { B1: 'Initial comments on B1', C1: 'Initial comments on C1' }, } ], oncomments: function () { console.log(arguments); } }); } setA1Comments() { // Set a comment on cell A1 this.worksheets[0].setComments('A1', 'This is a comment for A1'); } getA1Comments() { // Get the comment from cell A1 const comment = this.worksheets[0].getComments('A1'); console.log('Comment on A1:', comment); } resetA1Comments() { // Reset the comment on cell A1 this.worksheets[0].setComments('A1', ''); } } ``` ### Batch Update for Multiple Cells The `setComments` method supports adding comments to multiple cells simultaneously by passing an object, optimizing performance and avoiding unnecessary history entries. {.ignore} ```javascript // Create the spreadsheet let worksheets = jspreadsheet(document.getElementById('spreadsheet'), { worksheets: [{ minDimensions: [10,10], allowComments: true, }], }); worksheets[0].setComments({ A1: 'Comment on A1', B1: 'Comments on B1' }); ``` ## Related Tools - [Jspreadsheet Pro Comments](https://jspreadsheet.com/docs/comments) - Advanced comment features with formatting and attachments (Pro) - [Advanced Comments Extension](https://jspreadsheet.com/products/comments) - Multiple comments per cell (Enterprise) ================================================ FILE: docs/jspreadsheet/docs/comparison.md ================================================ title: Jspreadsheet Distributions Comparison keywords: Jspreadsheet, Jexcel, data grid, JavaScript, Excel-like, spreadsheet, table, documentation, performance, version differences, distribution comparison description: Explore a detailed comparison highlighting the key differences between various Jspreadsheet distributions. # Comparison Table The main differences between the JSS distributions: | Package | CE distribution | Pro distribution | Pro distribution | |-------------------------------------------------|:---------------------:|:-------------------:|:-------------------:| | Distribution | **CE** | **Base** | **Premium** | | License | Free (MIT) | Required | Required | | Spreadsheet-like formulas | Yes | Advanced | Advanced | | Copy and paste features | Yes | Advanced | Advanced | | Viewport | Lazy loading | Smart loading* | Smart loading* | | Plugins support | Yes | Yes | Yes | | Advance filtering and multiple column filtering | No | Yes | Yes | | Worksheets management | Limited | Yes | Yes | | Native persistence features | Limited | Yes | Yes | | Automatic formula update on copy and paste | No | Yes | Yes | | Automatic formula update on corner copy | No | Yes | Yes | | Formula returning DOM objects to cells. | No | Yes | Yes | | Cell renderer | No | Yes | Yes | | Editor definitions at a cell level | No | Yes | Yes | | Cross worksheet calculations | No | No | Yes | | Native validations | No | No | Yes | | Column and row grouping | No | No | Yes | | Defined names (Range and variables) | No | No | Yes | | Extensions | No | No | Yes | | Formula Picker | No | No | Yes | ## More Details ### Worksheet Management Pro includes dragging-and-drop, deleting or renaming worksheets, adding a new worksheet button, and configuring the context menu. ### Persistence Support Pro offers native persistence features such as internal row IDs and sequences, updated row IDs, saving methods, and integration with persistence plugins. ### Advanced Lazy Loading Provides efficient DOM management with pagination and lazy loading, creating and displaying DOM elements only when required. From version 8, lazy loading leverages the viewport and extends virtual DOM management to columns. ### Cell-Level Definitions The Pro distribution enables developers to configure properties like masking, format, type, and other attributes at the cell level. ================================================ FILE: docs/jspreadsheet/docs/config.md ================================================ title: Data Grid Settings keywords: Jspreadsheet, data grid customization, React, Vue, JavaScript spreadsheet, Excel-like functionality, configuration settings, spreadsheet customization, data grid options description: Explore the configuration settings in Jspreadsheet for customizing data grids. Learn how to enable, disable, and adjust features to enhance flexibility for both spreadsheets and individual worksheets. # Data Grid Settings This section provides an overview of the parameters that define the behaviour and functionality of spreadsheets and worksheets. Configuration options are categorized into two levels: - Spreadsheet-level settings for overarching configurations. - Worksheet-level settings for individual worksheet customizations. ## Documentation ### Methods The following methods allow programmatic manipulation of configuration settings: | Method | Description | |-------------|----------------------------------------------------------------------------------------------------| | `getConfig` | Retrieves the configuration of a worksheet.
`getConfig() => Object` | | `setConfig` | Updates the configuration for a worksheet.
`setConfig(options: Object, level?: Boolean) => void` | ## Examples ### Data Grid Cloning This example demonstrates how to copy a data grid configuration and use it to initialize a new spreadsheet. [See this example on JSFiddle](https://jsfiddle.net/spreadsheet/xz5pfgq7/) ```html


``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); const console = useRef(); const copy = useRef(); // Method to clone the data grid const clone = function() { // Get the data grid configuration let config = JSON.stringify(spreadsheet.current[0].parent.getConfig()); // Show on the textarea console.current.value = config; // Destroy any existing spreadsheet jspreadsheet.destroy(copy.current); // Parse config = JSON.parse(config); // Create a new spreadsheet jspreadsheet(copy.current, config); } return ( <>


clone()} /> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; // Create component @Component({ standalone: true, selector: "app-root", template: `


`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; @ViewChild("console") console: ElementRef; @ViewChild("copy") copy: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ minDimensions: [4,4], }], contextmenu: function() { return false; } }); } // Clone the data grid clone() { // Get the data grid configuration let config = JSON.stringify(this.worksheets[0].parent.getConfig()); // Show on the textarea this.console.nativeElement.value = config; // Destroy any existing spreadsheet jspreadsheet.destroy(this.copy.nativeElement); // Parse config = JSON.parse(config); // Create a new spreadsheet jspreadsheet(this.copy.nativeElement, config); } } ``` ================================================ FILE: docs/jspreadsheet/docs/contextmenu.md ================================================ title: Data Grid Context Menu Customization keywords: Jspreadsheet, Jexcel, Angular data grid, JavaScript, Excel-like spreadsheet, data tables, data grid context menu, context customization, customize context menu, interactive data grid. description: Define and customize the context menu in Jspreadsheet. Manage default items, section info, and cell coordinates while adding custom menu entries based on application-specific rules. # Context Menu Customization Jspreadsheet lets you customize the context menu through a context menu method, which provides default items, section info, coordinates, and context, which allows you to add or modify menu options based on dynamic application rules. ## Documentation ### Item Properties The `contextmenu` handler method might return an array of menu items, each represented as an object with the following properties: | Property | Description | |------------|--------------------------------------------------------------------| | `type` | Type of menu item: `"line"`, `"divisor"`, or `"default"`. | | `title` | The title of the menu item. | | `icon` | Icon key for the item (uses Material icon key for identification). | | `id` | `id` attribute for the HTML element. | | `disabled` | If `true`, the item is disabled. | | `onclick` | `onclick` event handler function for the item. | | `shortcut` | Short description or shortcut instruction. | | `tooltip` | Tooltip text displayed on hover. | | `submenu` | Array of submenu items. | ### Translation You can define translations for default menu items in Jspreadsheet by using `jspreadsheet.setDictionary` as shown below: {.ignore} ```javascript jspreadsheet.setDictionary({ 'Rename this worksheet': 'Renomear worksheet', 'Delete this worksheet': 'Apagar worksheet', 'Are you sure?': 'Tem certeza?', 'Rename this cell': 'Renomear essa celula', 'Cut': 'Cortar', 'Copy': 'Copy', 'Paste': 'Colar', 'Insert a new column before': 'Inserir uma coluna antes', 'Insert a new column after': 'Inserior uma coluna depois', 'Insert a new column after': 'Inserior uma coluna depois', 'Delete selected columns': 'Apagar colunas selecionadas', 'Rename this column': 'Renomar essa coluna', 'Create a new row': 'Criar uma nova linha', 'Order ascending': 'Ordenar asc', 'Order descending': 'Ordenar desc', 'Insert a new row before': 'Inserir uma linha antes', 'Insert a new row after': 'Inserir uma nova linha depois', 'Delete selected rows': 'Apagar linhas selecionadas', 'Edit notes': 'Editar notas', 'Add notes': 'Adicionar notas', 'Notes': 'Notas', 'Clear notes': 'Apagar notas', 'Save as': 'Salvar como', 'About': 'Sobre', }); ``` Jspreadsheet uses the jSuites [contextmenu plugin](https://jsuites.net/docs/contextmenu). For further details and examples, refer to the documentation. ## Examples ### Customize the contextmenu Customize the contextmenu with a few basic options depending on the section clicked. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import jSuites from "jsuites"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; // Intercept the context menu const contextMenu = (o, x, y, e, items, section) => { // Reset all items items = []; // If the click was in the headers if (section == 'header') { // Items to the header only items.push({ title: jSuites.translate('Execute one action'), onclick: function() { alert('test') } }); // Add a line items.push({ type: 'line' }); } // Save items.push({ title: jSuites.translate('Save as'), shortcut: 'Ctrl + S', icon: 'save', onclick: function () { o.download(); } }); // About items.push({ title: jSuites.translate('About'), onclick: function() { alert('https://jspreadsheet.com'); } }); return items; } export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ ["Apples", "Produce", 1.5, 10, "=C1*(1-(D1/100))"], ["Bread", "Bakery", 3.0, 20, "=C2*(1-(D2/100))"], ["Cheese", "Dairy", 5.0, 15, "=C3*(1-(D3/100))"], ["Eggs", "Dairy", 2.5, 0, "=C4*(1-(D4/100))"], ]; // Columns const columns = [ { title: 'Food', width: '140px' }, { title: 'Category' }, { title: 'Unit Price' }, { title: 'Discount' }, { title: 'Total ' }, ]; return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import jSuites from "jsuites" import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [ ["Apples", "Produce", 1.5, 10, "=C1*(1-(D1/100))"], ["Bread", "Bakery", 3.0, 20, "=C2*(1-(D2/100))"], ["Cheese", "Dairy", 5.0, 15, "=C3*(1-(D3/100))"], ["Eggs", "Dairy", 2.5, 0, "=C4*(1-(D4/100))"], ], columns: [ { title: 'Food', width: '140px' }, { title: 'Category' }, { title: 'Unit Price' }, { title: 'Discount' }, { title: 'Total ' }, ], allowComments: true, }], contextMenu: function(o, x, y, e, items, section) { // Reset all items items = []; // If the click was in the headers if (section == 'header') { // Items to the header only items.push({ title: jSuites.translate('Execute one action'), onclick: function() { alert('test') } }); // Add a line items.push({ type: 'line' }); } // Save items.push({ title: jSuites.translate('Save as'), shortcut: 'Ctrl + S', icon: 'save', onclick: function () { o.download(); } }); // About items.push({ title: jSuites.translate('About'), onclick: function() { alert('https://jspreadsheet.com'); } }); return items; } }); } } ``` ### Disable the Context Menu To disable the context menu in Jspreadsheet, define a contextMenu method that returns false. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Disable the context menu const contextMenu = () => { return false; }; return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ minDimensions: [4,4], }], // Disable the context menu contextMenu: function() { return false; } }); } } ``` ## Related Tools - [JavaScript Context Menu](https://jsuites.net/docs/contextmenu) - Standalone context menu component - [LemonadeJS Context Menu](https://lemonadejs.com/docs/plugins/context-menu) - Reactive context menu component - [Jspreadsheet Pro Context Menu](https://jspreadsheet.com/docs/contextmenu) - Advanced spreadsheet context menu customization (Pro) ================================================ FILE: docs/jspreadsheet/docs/custom-formulas.md ================================================ title: Custom Excel-Like Formulas keywords: Jspreadsheet, Jexcel, data grid, JavaScript, Excel-like formulas, formulas, calculations, spreadsheet calculations, spreadsheet formulas, spreadsheet-like formulas, Excel formulas, Google Sheet formulas, event handling, customizations, internationalization description: Learn how to create custom Excel-like formulas using Jspreadsheet, with examples for implementing advanced calculations and formula features. # Custom Formulas ## Overview Jspreadsheet allows developers to create custom Excel-like formulas and methods. These formulas can interact with APIs, return specific outputs, and render dynamic content or DOM elements directly within cells. This feature enables the development of highly interactive and dynamic spreadsheet applications. {.green} > **IMPORTANT:** All custom method names must be capitalized. ### Example ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; import formula from "@jspreadsheet/formula"; // Create a custom javascript method (capital case) const NEGATIVE = function(v) { return -1 * v; } // Send custom formula to the correct scope formula.setFormula({ NEGATIVE }) // Create the component export default function App() { // Array with all the data grids const spreadsheet = useRef(); // Data const data = [ [ '1000', '=NEGATIVE(A1)' ], [ '2000', '=NEGATIVE(A2)' ], [ '3000', '=NEGATIVE(A3)' ], ] // Render data grid component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create a custom javascript method (capital case) const NEGATIVE = function(v) { return -1 * v; } // Send the custom formula to the correct scope formula.setFormula({ NEGATIVE }) // Create the data grid component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [ [ '1000', '=NEGATIVE(A1)' ], [ '2000', '=NEGATIVE(A2)' ], [ '3000', '=NEGATIVE(A3)' ], ] }] }); } } ``` {.pro} > #### Formula Pro > > The Formula Pro extension enhances custom formula creation by providing execution context, including the origin cell (x, y), the worksheet, and the ability for developers to return DOM elements directly to the cell via the formula. >

> [Learn more](https://jspreadsheet.com/products/formulas){.button} ================================================ FILE: docs/jspreadsheet/docs/data.md ================================================ title: Spreadsheet Data Operations keywords: Jspreadsheet, Jexcel, data grid, JavaScript, Excel-like formulas, spreadsheet data, worksheet data, data management, update cell data, manage worksheet data, programmatically update data description: Learn how to load, manage, and manipulate worksheet data in Jspreadsheet. Explore methods for programmatic data updates and integrate spreadsheet functionality with your web applications using related events. # Data Management This section details the methods, events, and settings for loading, updating, and managing data in Jspreadsheet data grids. ## Loading the Data ### Formats You can create spreadsheets using various data formats, including: - Existing HTML tables - CSV files - JSON objects - JavaScript arrays ## Documentation ### Methods These methods assist in managing data in your grid or spreadsheet. #### Read Methods | Method | Description | |-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `getValue` | Get the value of a cell.
@param `cell` - Cell name.
@param `processedValue` - If true, it returns the cell's innerHTML. Otherwise, it returns the value of the cell in the data property.
`worksheetInstance.getValue(cell: string, processedValue?: boolean): CellValue \| null;` | | `getValueFromCoords`{.nowrap} | Get the value of a cell by its coordinates.
@param `x` - Column index.
@param `y` - Row index.
@param `processedValue` - If true, it returns the cell's innerHTML. Otherwise, it returns the value of the cell in the data property.
`worksheetInstance.getValueFromCoords(x: number, y: number, processedValue?: boolean): CellValue \| null;` | | `getData` | Get the full or partial table data.
@param `highlighted` - If true, get only data from highlighted cells. If false, get data from all cells. Default: false.
@param `processed` - If false, the return is constructed using the innerHTML of the cells. Otherwise, it is constructed using the {@link WorksheetOptions.data} property. Default: false.
@param `delimiter` - Column delimiter. If this property is specified, the result will be formatted like a csv.
@param `asJson` - If this property is true, the result will be formatted as json.
`getData(highlighted?: boolean, processed?: boolean, delimiter?: string, asJson?: boolean): CellValue[][];` | | `getRowData` | Get data from a row by its index.
@param `rowNumber` - Row index.
@param `processed` - If true, the return is constructed using the innerHTML of the cells. Otherwise, it is constructed using the data property. Default: false.
`worksheetInstance.getRowData(rowNumber: number, processed?: boolean): CellValue[] \| undefined;` | | `getColumnData` | Get the data from one column by its index.
@param `columnNumber` - Column index.
@param `processed` - If true, the return is constructed using the innerHTML of the cells. Otherwise, it is constructed using the data property. Default: false.
`worksheetInstance.getColumnData(columnNumber: number, processed?: boolean): CellValue[];` | | `download` | Get the current data as a CSV file.
@param `includeHeaders` - If true, include the header regardless of the {@link SpreadsheetOptions.includeHeadersOnDownload} property value.
@param `processed` - If true, the result will contain the displayed cell values. Otherwise, the result will contain the actual cell values.
`worksheetInstance.download(includeHeaders?: boolean, processed?: boolean): void;` | #### Write Methods | Method | Description | |-------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------| | `setValue` | Change the value of one or more cells.
@param `cell` - Name of a cell, HTML element that represents a cell or an array whose items can be any of the previous alternatives or objects. When an array item is an object, it must have the cell coordinates ("x" and "y") and can have the cell's new value ("value"), but if does not have it, the "value" parameter is used instead.
@param `value` - New cell value.
@param `force` - If true, changes the value of even read-only cells.
`worksheetInstance.setValue(cell: string \| HTMLTableCellElement \| (string \| { x: number; y: number; value?: CellValue } \| HTMLTableCellElement)[], value?: CellValue, force?: boolean): void;` | | `setValueFromCoords`{.nowrap} | Set a cell value based on its coordinates.
@param `x` - Cell column index.
@param `y` - Cell row index.
@param `value` - New value.
@param `force` - If true, changes the value of even read-only cells.
`worksheetInstance.setValueFromCoords(x: number, y: number, value: CellValue, force?: boolean): void;` | | `setData` | Set data.
@param `data` - New data. It can be an array of cell values or an array of objects whose values are cell values.
`worksheetInstance.setData(data?: CellValue[][] \| Record[]): void;` | | `setRowData` | Set a row data by index.
@param `rowNumber` - Row index.
@param `data` - New data. Positions with the null value are not changed in the table.
@param `force` - If true, the method also changes the contents of readonly columns.
`worksheetInstance.setRowData(rowNumber: number, data: (CellValue \| null)[], force?: boolean): void;` | | `setColumnData` | Set the data from one column by index.
@param `colNumber` - Column index.
@param `data` - New data. Positions with the null value are not changed in the table.
@param `force` - If true, the method also changes the contents of readonly columns.
`worksheetInstance.setColumnData(colNumber: number, data: (CellValue \| null)[], force?: boolean): void;` | ### Events You can find a list of javascript events related to the data operations. | Event | Description | |---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------| | `onbeforechange`{.nowrap} | Occurs before a column value is changed. If any value is returned, it will be the cell's new value.
@param `instance` - Instance of the worksheet where the changes will occur.
@param `cell` - HTML element that represents the cell being changed.
@param `colIndex` - Cell column index being changed.
@param `rowIndex` - Cell row index being changed.
@param `newValue` - Value being applied to the cell
`onbeforechange(instance: WorksheetInstance, cell: HTMLTableCellElement, colIndex: string \| number, rowIndex: string \| number, newValue: CellValue): undefined \| CellValue;` | | `onchange` | Occurs after a column value is changed.
@param `instance` - Instance of the worksheet where the change occurred.
@param `cell` - HTML element that represents the cell being changed.
@param `colIndex` - Cell column index being changed.
@param `rowIndex` - Cell row index being changed.
@param `newValue` - New cell value.
@param `oldValue` - Old cell value.
`onchange(instance: WorksheetInstance, cell: HTMLTableCellElement, colIndex: string \| number, rowIndex: string \| number, newValue: CellValue, oldValue: CellValue): void;` | | `onafterchanges` | Occurs after all changes are applied in the tables.
@param `instance` - Instance of the worksheet where the change occurred.
@param `changes` - list of changes.
`onafterchanges(instance: WorksheetInstance, changes: CellChange[]): void;` | ### Initial Settings A list of settings that can be utilized when initializing the data grid. | Property | Description | |-------------------------|---------------------------------------------------| | `data: Array \| Object` | Defines initial data from a JSON or array. | | `url: String` | Loads data from an external file. | | `csv: String` | Loads data from an external CSV file. | | `csvHeaders: Boolean` | The first row of the CSV file is used as headers. | | `csvDelimiter: String` | Sets the CSV delimiter (default: ','). | ## Examples ### Create a data grid from an 2D array. Create a spreadsheet from a JavaScript array ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ ['Mazda', 2001, 2000], ['Peugeot', 2010, 5000], ['Honda Fit', 2009, 3000], ['Honda CRV', 2010, 6000], ]; // Columns const columns = [ { title:'Model', width:'300px' }, { title:'Price', width:'80px' }, { title:'Model', width:'100px' } ]; // Render data grid component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [ ['Mazda', 2001, 2000], ['Peugeot', 2010, 5000], ['Honda Fit', 2009, 3000], ['Honda CRV', 2010, 6000], ], columns: [ { title:'Model', width:'300px' }, { title:'Price', width:'80px' }, { title:'Model', width:'100px' } ] }] }); } } ``` ### Create a data grid from a CSV file How to create a data grid from a remote CSV file ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Render data grid component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ selector: "app-root", template: `
`; }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ // Point to your file csv: '/jspreadsheet/demo.csv', // First line will define the header titles csvHeaders: true, columns: [ { width: '200px' }, { width: '100px' }, { width: '100px' }, ] }] }); } } ``` ### Create a data grid from a HTML table How to create data grid from an existing HTML table element ```html
POS TITLE ARTIST PEAK
1 DIVINELY UNINSPIRED TO A HELLISH EXTENT LEWIS CAPALDI 1
2 NO 6 COLLABORATIONS PROJECT ED SHEERAN 1
3 THE GREATEST SHOWMAN MOTION PICTURE CAST RECORDING 1

``` ```jsx import React, { useRef, useEffect } from "react"; import { jspreadsheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); useEffect(() => { if (spreadsheet.current) { jspreadsheet(spreadsheet.current); } }, []) // Render component return ( <>
POS TITLE ARTIST PEAK
1 DIVINELY UNINSPIRED TO A HELLISH EXTENT LEWIS CAPALDI 1
2 NO 6 COLLABORATIONS PROJECT ED SHEERAN 1
3 THE GREATEST SHOWMAN MOTION PICTURE CAST RECORDING 1
); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
POS TITLE ARTIST PEAK
1 DIVINELY UNINSPIRED TO A HELLISH EXTENT LEWIS CAPALDI 1
2 NO 6 COLLABORATIONS PROJECT ED SHEERAN 1
3 THE GREATEST SHOWMAN MOTION PICTURE CAST RECORDING 1
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement); } } ``` ### Batch update How to update multiple cells with a single call ```html

``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Update multiple cells const update = () => { let records = [ { x: 0, y: 0, value: 'update A1', }, { x: 10, y: 10, value: 'Another cell', }, // (...) ]; spreadsheet.current[0].setValue(records); } // Render data grid component return ( <> update()} /> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ selector: "app-root", template: `
`; }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ minDimensions: [10,10], }] }); } update() { let records = [ { x: 0, y: 0, value: 'update A1', }, { x: 10, y: 10, value: 'Another cell', }, // (...) ]; this.worksheets[0].setValue(records); } } ``` {.pro} > **Upgrade to Jspreadsheet Pro for Advanced Data Operations** > > Jspreadsheet CE supports basic data loading from arrays, CSV, JSON, and HTML tables. **Jspreadsheet Pro** adds enterprise-grade data management capabilities: > > **Excel Import/Export:** > - **Full Excel Compatibility:** Import/export native .xlsx and .xls files with formulas, formatting, and styles preserved > - **Multiple Worksheet Support:** Import/export workbooks with multiple sheets > - **Formula Translation:** Excel formulas automatically converted to Jspreadsheet format > - **Style Preservation:** Cell colors, fonts, borders, and formatting maintained on import/export > - **Chart Import:** Import Excel charts and graphs > - **Named Ranges:** Support for Excel named ranges and defined names > > **Advanced Data Loading:** > - **Database Integration:** Direct integration with MySQL, PostgreSQL, SQL Server, MongoDB > - **API Integration:** RESTful API support with authentication and pagination > - **Real-Time Data:** WebSocket support for live data updates > - **Lazy Loading:** Load large datasets incrementally for better performance > - **Background Processing:** Async data loading with progress indicators > > **Data Export Options:** > - **PDF Export:** Export spreadsheets to formatted PDF documents > - **XML Export:** Export data in XML format > - **Custom Formats:** Build custom export formats with templates > - **Batch Export:** Export multiple worksheets at once > - **Server-Side Export:** Generate exports on the server for large datasets > > **Data Validation & Quality:** > - **Data Validation Rules:** Enforce data integrity with validation rules > - **Duplicate Detection:** Find and merge duplicate records > - **Data Cleansing:** Auto-trim, case normalization, format correction > - **Import Validation:** Validate data during import with error reporting > > Perfect for business applications requiring Excel compatibility and enterprise data management. > > **[Explore Pro Data Features →](https://jspreadsheet.com/docs/import-and-export)** | **[Compare Editions →](https://jspreadsheet.com/docs/getting-started)** | **[View Pricing →](https://jspreadsheet.com/pricing)** ================================================ FILE: docs/jspreadsheet/docs/editors.md ================================================ title: Spreadsheet Inline Cell Editors keywords: Jspreadsheet, data grid, JavaScript, Excel-like controls, spreadsheet controls, data input editors, custom cell editors, input components, editor methods, editor settings, editor events, interactive data grid, custom data input description: Jspreadsheet cell editors support integrating third-party JavaScript components, enabling advanced, customizable inputs for dynamic data entry. # Spreadsheet Cell Editors ## Overview Jspreadsheet editors assist users with cell input during editing. Jspreadsheet includes built-in components, from basic text fields to interactive widgets. The API also allows developers to create custom components, integrating various JavaScript input methods for enhanced data interaction. ### Available Editors * text * numeric * hidden * dropdown * autocomplete * checkbox * radio * calendar * image * color * html {.pro} > #### Differences in the Pro Version > The Pro version offers 17 native editors and allows defining cell-level editors and programmatically changing cell types.\ >\ > [Learn more](https://jspreadsheet.com/docs/editors){.button} ## Documentation ### Methods Methods related to editors and data grid cell editing: | Method | Description | |---------------|-----------------------------------------------------------------------------------------------------| | `getEditor` | Retrieve the editor instance and options for a column (`x`) or cell (`x, y`). | | `openEditor` | Start editing a cell.
`openEditor(cell: HTMLElement, empty: boolean, e: MouseEvent): void` | | `closeEditor` | Close the editor.
`closeEditor(cell: HTMLElement, save: boolean): void` | ### Events Events related to editors and data grid editing: | Event | Description | |---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `oneditionstart` | Triggered when cell editing starts.
`oneditionstart(worksheet: Object, cell: HTMLElement, x: number, y: number): void \| boolean` | | `oneditionend` | Triggered when cell editing ends.
`oneditionend(worksheet: Object, cell: HTMLElement, x: number, y: number, v: any, save: boolean): void` | | `oncreateeditor`{.nowrap} | Triggered when an editor is created.
`oncreateeditor(worksheet: Object, cell: HTMLElement, x: number, y: number, input: HTMLElement, options: object): void` |
### Declaring the Editors In CE, the `type` property declares the editor at the column level. The Pro version allows defining editors at the cell level. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Columns const columns = [ { type: 'text' }, { type: 'dropdown', source: ['Male','Female'] } ]; // Render data grid component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; @Component({ standalone: true, selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ minDimensions: [5,5], columns: [ { type: 'text' }, { type: 'dropdown', source: ['Male','Female'] } ], }] }); } } ``` ### Custom Editors Custom editors allow enhanced user interaction and data collection in your data grid. A custom editor is defined as a JavaScript object with the following methods: | Method | Description | |---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `createCell` | Triggered when a new cell is created.
`createCell(cell: Object, value: Any, x: Number, y: Number, instance: Object, options: Object): void` | | `updateCell` | Triggered when the cell value changes.
`updateCell(cell: Object, value: Any, x: Number, y: Number, instance: Object, options: Object): void` | | `openEditor` | Triggered when the user starts editing a cell.
`openEditor(cell: Object, value: Any, x: Number, y: Number, instance: Object, options: Object): void` | | `closeEditor` | Triggered when the user finalizes the cell edit.
`closeEditor(cell: Object, confirmChanges: Boolean, x: Number, y: Number, instance: Object, options: Object): void` | | `destroyCell` | Triggered when a cell is destroyed.
`destroyCell(cell: Object, x: Number, y: Number, instance: Object): void` | | `get` | Converts raw data into processed data, such as displaying text instead of an ID in a dropdown type.
`get(options: Object, value: Any): Any` | ## Examples ### Native Editors A basic example showcasing the use of multiple native editors. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ ['Jazz', 'Honda', '2019-02-12', '/templates/default/img/nophoto.jpg', true, 2000.00, '#777700'], ['Civic', 'Honda', '2018-07-11', '/templates/default/img/nophoto.jpg', true, 4000.01, '#007777'], ]; // Columns const columns = [ { type:'text', title:'Car', width:120 }, { type: 'dropdown', title:'Make', width:180, source:[ "Alfa Romeo", "Audi", "Bmw", "Chevrolet", // (...) ] }, { type: 'calendar', title:'Available', width:120, options:{ format:'DD/MM/YYYY' } }, { type: 'image', title:'Photo', width:120 }, { type: 'checkbox', title:'Stock', width:80 }, { type: 'text', title:'Price', mask:'$ #.##0,00', width:100, decimal:',', disabledMaskOnEdition: true }, { type: 'color', width:100, render:'square', }, ]; // Render data grid component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; @Component({ standalone: true, selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [ ['Jazz', 'Honda', '2019-02-12', '/templates/default/img/nophoto.jpg', true, 2000.00, '#777700'], ['Civic', 'Honda', '2018-07-11', '/templates/default/img/nophoto.jpg', true, 4000.01, '#007777'], ], columns: [ { type:'text', title:'Car', width:120 }, { type: 'dropdown', title:'Make', width:180, source:[ "Alfa Romeo", "Audi", "Bmw", "Chevrolet", "Chrysler", "Dodge", "Ferrari", "Fiat", "Ford", "Honda", "Hyundai", "Jaguar", "Jeep", "Kia", "Mazda", "Mercedes-Benz", "Mitsubishi", "Nissan", "Peugeot", "Porsche", "Subaru", "Suzuki", "Toyota", "Volkswagen" ] }, { type: 'calendar', title:'Available', width:120, options:{ format:'DD/MM/YYYY' } }, { type: 'image', title:'Photo', width:120 }, { type: 'checkbox', title:'Stock', width:80 }, { type: 'text', title:'Price', mask:'$ #.##0,00', width:100, decimal:',', disabledMaskOnEdition: true }, { type: 'color', width:100, render:'square', }, ] }] }); } } ``` ### Custom Editor Create an edit button within a cell. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; const action = function() { let methods = {}; methods.createCell = (cell, value, x, y, instance, options) => { let input = document.createElement('i'); input.className = 'material-icons'; input.style.cursor = 'pointer'; input.style.fontSize = '22px'; input.innerHTML = "search"; input.onclick = function() { let id = instance.getValueFromCoords(0,y); // Do some action alert(id); } cell.appendChild(input); } return methods; }(); export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ [1, 'Google'], [2, 'Bing'], [3, 'Yahoo'], [4, 'Duckduckgo'], ] // Columns const columns = [ { type: 'text', width:'100px' }, { type: 'rating', width:'100px' }, { type: action, width:'100px', readOnly: true }, ]; // Render data grid component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; const Action = function() { let methods = {}; methods.createCell = (cell, value, x, y, instance, options) => { let input = document.createElement('i'); input.className = 'material-icons'; input.style.cursor = 'pointer'; input.style.fontSize = '22px'; input.innerHTML = "search"; input.onclick = function() { let id = instance.getValueFromCoords(0,y); alert(id); } cell.appendChild(input); } return methods; }(); @Component({ standalone: true, selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [ [1, 'Google'], [2, 'Bing'], [3, 'Yahoo'], [4, 'Duckduckgo'], ], columns: [ { type: 'text', width:'100px' }, { type: 'rating', width:'100px' }, { type: action, width:'100px', readOnly: true }, ] }] }); } } ``` ## More Resources ### Editors Source Code The source code for editors is available for reference. Use these examples as a foundation to create your custom editors.\ \ [https://github.com/jspreadsheet/editors](https://github.com/jspreadsheet/editors){.button} {.pro} > **Upgrade to Jspreadsheet Pro for Advanced Cell Editors** > > Jspreadsheet CE provides 12 basic editors. **Jspreadsheet Pro** extends this with 17 professional editors and advanced capabilities: > > **Advanced Editor Types:** > - **Conditional Dropdowns:** Options dynamically change based on other cell values > - **Rich Text Editor:** Full HTML formatting with bold, italic, lists, and styling > - **HTML Editor:** Embed custom HTML content directly in cells > - **Advanced Date/Time:** Enhanced calendar with time selection and custom formats > - **Rating Editor:** Star ratings and custom rating systems > - **Tags Editor:** Multi-select tags with autocomplete > - **Progress Bars:** Visual progress indicators in cells > - **Custom Editors:** Build your own cell editors with full API access > > **Pro-Only Editor Features:** > - **Cell-Level Editors:** Define different editors for individual cells (not just columns) > - **Programmatic Type Changes:** Dynamically change cell types at runtime > - **Enhanced Validation:** Advanced validation rules with custom messages > - **Editor Events:** Extended event system for editor lifecycle > > Perfect for data-intensive applications requiring sophisticated user input controls. > > **[Explore Pro Editors →](https://jspreadsheet.com/docs/editors)** | **[Compare Editions →](https://jspreadsheet.com/docs/getting-started)** | **[View Pricing →](https://jspreadsheet.com/pricing)** ## Related Tools - [JavaScript HTML Editor](https://jsuites.net/docs/javascript-html-editor) - Standalone rich text editor component - [Jspreadsheet Pro Editors](https://jspreadsheet.com/docs/editors) - Advanced spreadsheet cell editors (Pro) - [Custom Cell Editors](https://jspreadsheet.com/docs/editors/custom) - Build your own custom editors (Pro) - [HTML Editor](https://jspreadsheet.com/docs/editors/html) - Rich text editing in cells (Pro) ================================================ FILE: docs/jspreadsheet/docs/events.md ================================================ title: Spreadsheet Events keywords: Jspreadsheet, JavaScript data grid, spreadsheet events, event handling, interactive spreadsheets, Excel-like features, JavaScript integration, feature customization, event-driven programming, data grid events description: Discover Jspreadsheet's robust event system, enabling seamless integration, advanced customization, and dynamic user interaction. canonical: https://bossanova.uk/jspreadsheet/docs/events # Spreadsheet Events Jspreadsheet provides a comprehensive event system designed to enhance integration and customization. These events allow developers to create dynamic, interactive spreadsheets by responding to user actions and modifying spreadsheet behaviour in real-time. ## Events | Event | Description | |---------------------------|-------------------------------------------------------------------------------------------------------| | `onload` | Triggered when a spreadsheet is loaded. | | `onbeforechange` | Triggered before a cell value is changed. | | `onchange` | Triggered after a cell value is changed. | | `onafterchanges` | Triggered after all pending changes are applied to the table. | | `onpaste` | Triggered after a paste action in the table. | | `onbeforepaste` | Triggered before a paste action is performed. Useful for parsing input data. It returns the parsed data. | | `oninsertrow` | Triggered after a new row is inserted. | | `onbeforeinsertrow` | Triggered before a new row is inserted. Cancel the insert by returning `false`. | | `ondeleterow` | Triggered after a row is deleted. | | `onbeforedeleterow` | Triggered before a row is deleted. Cancel the delete by returning `false`. | | `oninsertcolumn` | Triggered after a new column is inserted. | | `onbeforeinsertcolumn` | Triggered before a new column is inserted. Cancel the insert by returning `false`. | | `ondeletecolumn` | Triggered after a column is deleted. | | `onbeforedeletecolumn` | Triggered before a column is deleted. Cancel the delete by returning `false`. | | `onmoverow` | Triggered after a row is moved. | | `onmovecolumn` | Triggered after a column is moved. | | `onresizerow` | Triggered after a row height change. | | `onresizecolumn` | Triggered after a column width change. | | `onselection` | Triggered when selection changes. | | `onsort` | Triggered after a column is sorted. | | `onfocus` | Triggered when the table gains focus. | | `onblur` | Triggered when the table loses focus. | | `onmerge` | Triggered when cells are merged. | | `onunmerge` | Triggered when merge is removed | | `onchangeheader` | Triggered when a header is changed. | | `onundo` | Triggered when an undo action is applied. | | `onredo` | Triggered when a redo action is applied. | | `oneditionstart` | Triggered when `openEditor` is called. | | `oneditionend` | Triggered when `closeEditor` is called. | | `onchangestyle` | Triggered when `setStyle` is called. | | `onchangemeta` | Triggered when `setMeta` is called. | ## Examples ### Update External Components The following example demonstrates integrating Jspreadsheet CE data with an external chart component using `onchange` events. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; import HighchartsReact from "highcharts-react-official"; import Highcharts from "highcharts"; const chartOptions = { title: { text: "Monthly Average Temperature", x: -20 //center }, subtitle: { text: "Source: WorldClimate.com", x: -20 }, xAxis: { categories: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] }, yAxis: { title: { text: "Temperature (°C)" }, plotLines: [{ value: 0, width: 1, color: "#808080" }] }, tooltip: { valueSuffix: "°C" }, legend: { layout: "vertical", align: "right", verticalAlign: "middle", borderWidth: 0 }, series: [{ name: "Tokyo", data: [7.0, 6.9, 9.5, 14.5, 18.2, 21.5, 25.2, 26.5, 23.3, 18.3, 13.9, 9.6] }, { name: "New York", data: [-0.2, 0.8, 5.7, 11.3, 17.0, 22.0, 24.8, 24.1, 20.1, 14.1, 8.6, 2.5] }, { name: "Berlin", data: [-0.9, 0.6, 3.5, 8.4, 13.5, 17.0, 18.6, 17.9, 14.3, 9.0, 3.9, 1.0] }, { name: "London", data: [3.9, 4.2, 5.7, 8.5, 11.9, 15.2, 17.0, 16.6, 14.2, 10.3, 6.6, 4.8] } ] }; export default function App() { // Spreadsheet array of worksheets const chart = useRef(); const spreadsheet = useRef(); // Data const data = [ [chartOptions.series[0].name, ...chartOptions.series[0].data], [chartOptions.series[1].name, ...chartOptions.series[1].data], [chartOptions.series[2].name, ...chartOptions.series[2].data], [chartOptions.series[3].name, ...chartOptions.series[3].data] ]; // Columns const columns = [{ type: "text", width: "200" }]; // Updates const updates = (instance, cell, x, y, value) => { const component = chart.current.chart; // If the related series does not exists create a new one if (!component.series[y]) { // Create a new series row let row = []; for (let i = 1; i < data[y].length; i++) { // @ts-ignore row.push(parseFloat(data[y][i])); } // Append new series to the chart component.addSeries({ name: data[y][0], data: row }); } else { if (x < 1) { // Update legend component.series[y].update({ name: value }); } else { // Update chart data component.series[y].data[x - 1].update({ y: parseFloat(value) }); } } }; // Render data grid component return ( <> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import Highcharts from 'highcharts' import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" import charts from "@jspreadsheet/charts"; const chartOptions = { title: { text: "Monthly Average Temperature", x: -20 //center }, subtitle: { text: "Source: WorldClimate.com", x: -20 }, xAxis: { categories: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] }, yAxis: { title: { text: "Temperature (°C)" }, plotLines: [{ value: 0, width: 1, color: "#808080" }] }, tooltip: { valueSuffix: "°C" }, legend: { layout: "vertical", align: "right", verticalAlign: "middle", borderWidth: 0 }, series: [{ name: "Tokyo", data: [7.0, 6.9, 9.5, 14.5, 18.2, 21.5, 25.2, 26.5, 23.3, 18.3, 13.9, 9.6] }, { name: "New York", data: [-0.2, 0.8, 5.7, 11.3, 17.0, 22.0, 24.8, 24.1, 20.1, 14.1, 8.6, 2.5] }, { name: "Berlin", data: [-0.9, 0.6, 3.5, 8.4, 13.5, 17.0, 18.6, 17.9, 14.3, 9.0, 3.9, 1.0] }, { name: "London", data: [3.9, 4.2, 5.7, 8.5, 11.9, 15.2, 17.0, 16.6, 14.2, 10.3, 6.6, 4.8] } ] }; // Create the data grid component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; @ViewChild("chartContainer") chartContainer: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create summary spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [ [ chartOptions.series[0].name, ...chartOptions.series[0].data ], [ chartOptions.series[1].name, ...chartOptions.series[1].data ], [ chartOptions.series[2].name, ...chartOptions.series[2].data ], [ chartOptions.series[3].name, ...chartOptions.series[3].data ] ], columns: [ { type: 'text', width:'200' }, ], }], onchange: (instance, cell, x, y, value) => { // If the related series does not exists create a new one if (! this.chart.series[y]) { // Create a new series row let row = []; for (i = 1; i < instance.options.data[y].length; i++) { row.push(parseFloat(instance.options.data[y][i])); } // Append new series to the chart chart.addSeries({ name: instance.options.data[y][0], data: row }); } else { if (x == 0) { // Update legend this.chart.series[y].update({ name:value }); } else { // Update chart data this.chart.series[y].data[x-1].update({ y:parseFloat(value) }); } } }, }); // Create external chart component this.chart = Highcharts.chart(this.chartContainer.nativeElement, chartOptions); } } ``` ================================================ FILE: docs/jspreadsheet/docs/examples/column-dragging.md ================================================ title: Column Dragging keywords: Jexcel, spreadsheet, JavaScript, JavaScript table, column dragging, data grid customization, interactive columns description: Learn how to enable and use column dragging functionality in Jspreadsheet CE to create dynamic and customizable data grids. # Column Dragging Column dragging in Jspreadsheet CE is disabled by default. To enable this feature, set the `columnDrag: true` option during initialization, as demonstrated below: ### Source code ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Tabs const data = [ ['BR', 'Cheese', 1], ['CA', 'Apples', 0], ['US', 'Carrots', 1], ['GB', 'Oranges', 0], ]; const columns = [ { type: 'autocomplete', title: 'Country', width: '300', url: '/jspreadsheet/countries.json' }, { type: 'dropdown', title: 'Food', width: '150', source: ['Apples','Bananas','Carrots','Oranges','Cheese'] }, { type: 'checkbox', title: 'Stock', width:'100' }, ]; return ( ); } ``` ```vue ``` ================================================ FILE: docs/jspreadsheet/docs/examples/create-from-table.md ================================================ title: Create from an HTML Table keywords: Jexcel, JavaScript, dynamic spreadsheet, create table, HTML table element, Jspreadsheet table, interactive table description: Learn how to create a dynamic Jspreadsheet table from a static HTML table element with a complete example. # Create from an HTML Table Starting with v4+, Jspreadsheet allows you to create an online spreadsheet dynamically from a static HTML table element. ```html

The Official Top 40 biggest albums of 2019

General
Info Stats
POS TITLE ARTIST PEAK
1 DIVINELY UNINSPIRED TO A HELLISH EXTENT LEWIS CAPALDI 1
2 NO 6 COLLABORATIONS PROJECT ED SHEERAN 1
3 THE GREATEST SHOWMAN MOTION PICTURE CAST RECORDING 1
4 WHEN WE ALL FALL ASLEEP WHERE DO WE GO BILLIE EILISH 1
5 STAYING AT TAMARA'S GEORGE EZRA 1
6 BOHEMIAN RHAPSODY - OST QUEEN 3
7 THANK U NEXT ARIANA GRANDE 1
8 WHAT A TIME TO BE ALIVE TOM WALKER 1
9 A STAR IS BORN MOTION PICTURE CAST RECORDING 1
10 YOU'RE IN MY HEART ROD STEWART 1
=SUM(B1,B2,B3)

``` ```jsx import React, { useRef, useEffect } from "react"; import { jspreadsheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); useEffect(() => { if (spreadsheet.current) { jspreadsheet(spreadsheet.current); } }, []) // Render component return ( <>
General
Info Stats
POS TITLE ARTIST PEAK
1 DIVINELY UNINSPIRED TO A HELLISH EXTENT LEWIS CAPALDI 1
2 NO 6 COLLABORATIONS PROJECT ED SHEERAN 1
3 THE GREATEST SHOWMAN MOTION PICTURE CAST RECORDING 1
4 WHEN WE ALL FALL ASLEEP WHERE DO WE GO BILLIE EILISH 1
5 STAYING AT TAMARA'S GEORGE EZRA 1
6 BOHEMIAN RHAPSODY - OST QUEEN 3
7 THANK U NEXT ARIANA GRANDE 1
8 WHAT A TIME TO BE ALIVE TOM WALKER 1
9 A STAR IS BORN MOTION PICTURE CAST RECORDING 1
10 YOU'RE IN MY HEART ROD STEWART 1
=SUM(B1,B2,B3)
); } ``` ```vue ``` ================================================ FILE: docs/jspreadsheet/docs/examples/jquery.md ================================================ title: jQuery Spreadsheet keywords: Jexcel, JavaScript, Jspreadsheet, jQuery integration, JavaScript spreadsheet, interactive table description: Learn how to integrate Jspreadsheet with jQuery through a complete example. # jQuery Spreadsheet Create fantastic data grids with spreadsheet controls integrating Jspreadsheet with jQuery. ### Source code ```html

``` ================================================ FILE: docs/jspreadsheet/docs/examples/richtext-html-editor.md ================================================ title: HTML Editor keywords: Jexcel, JavaScript, rich text, HTML editor, spreadsheet editor, excel-like features, dynamic columns description: Learn how to create a rich text column with an HTML editor in Jspreadsheet for enhanced data input and formatting. # Rich Text and HTML Editor Column Type Add an HTML Editor input to your spreadsheet to create rich text columns. ### Source code ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; const data = [ [ 'the Sorcerer`s Stone', 'On his 11th birthday, Harry receives a letter inviting him to study magic at the Hogwarts School of Witchcraft and Wizardry. Harry discovers that not only is he a wizard, but he is a famous one. He meets two best friends, Ron Weasley and Hermione Granger, and makes his first enemy, Draco Malfoy.' ] ]; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Columns const columns = [ { type:'text', width:'200px', title:'Title' }, { type:'html', width:'400px', title:'HTML' }, ]; // Render data grid component return ( ); } ``` ```vue ``` ================================================ FILE: docs/jspreadsheet/docs/examples/table-overflow.md ================================================ title: Table Overflow keywords: Jexcel, JavaScript, table overflow, fixed dimensions, JavaScript table, spreadsheet customization, table width and height description: Learn how to define fixed width and height for Jspreadsheet tables to manage table overflow effectively. # Table Overflow Set fixed width and height for your online JavaScript spreadsheet to control table overflow and improve layout management. ### Source code ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Columns const columns = [ { type: "dropdown", source: [{ id: 1, name: "yes" }, { id: 2, name: "no" }] }, { type: "dropdown", source: [{ id: 1, name: "yes" }, { id: 2, name: "no" }] }, { type: "dropdown", source: [{ id: 1, name: "yes" }, { id: 2, name: "no" }] }, { type: "dropdown", source: [{ id: 1, name: "yes" }, { id: 2, name: "no" }] }, { type: "dropdown", source: [{ id: 1, name: "yes" }, { id: 2, name: "no" }] } ]; return ( ); } ``` ```vue ``` ================================================ FILE: docs/jspreadsheet/docs/examples/translations.md ================================================ title: How to Translate the Default Messages in Jspreadsheet keywords: Jexcel, JavaScript, spreadsheet, JavaScript table, translate messages, Jspreadsheet translations, internationalization description: Learn how to translate and customize the default messages in Jspreadsheet for internationalization. # Jspreadsheet Internationalization Learn how to update and customize the default texts in your online spreadsheet for better internationalization. ### Source code ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; jspreadsheet.setDictionary({ 'Search': '検索', 'Show': '表示', 'entries': 'エントリ', 'Insert a new column before': '新しい列を前に挿入', 'Insert a new column after': '新しい列を後に挿入', 'Delete selected columns': '選択した列を削除', 'Rename this column': 'この列の名前を変更', 'Column name': '列名', 'Order ascending': '昇順に並べ替え', 'Order descending': '降順に並べ替え', 'Insert a new row before': '新しい行を前に挿入', 'Insert a new row after': '新しい行を後に挿入', 'Edit comments': 'コメントを編集', 'Add comments': 'コメントを追加', 'Comments': 'コメント', 'Clear comments': 'コメントをクリア', 'Copy': 'コピー', 'Paste': '貼り付け', 'Save as': '名前を付けて保存', 'About': '情報', 'Are you sure to delete the selected rows?': '選択した行を削除してもよろしいですか?', 'Are you sure to delete the selected columns?': '選択した列を削除してもよろしいですか?', 'No cells selected': 'セルが選択されていません', 'No records found': '記録が見つかりません', 'Showing page {0} of {1} entries': '{1}件中{0}ページを表示', 'Merge the selected cells': '選択したセルを結合', 'There is a conflict with another merged cell': '別の結合されたセルとの競合があります', 'Invalid merged properties': '無効な結合プロパティ', 'Cell already merged': 'セルはすでに結合されています', 'This action will destroy any existing merged cells. Are you sure?': 'この操作により、既存の結合セルが削除されます。よろしいですか?', 'The merged cells will retain the value of the top-left cell only. Are you sure?': '結合セルは左上のセルの値のみを保持します。よろしいですか?', 'This action will clear your search results. Are you sure?': 'この操作により検索結果がクリアされます。よろしいですか?' }); export default function App() { const spreadsheet = useRef(); return ( ); }; ``` ```vue ``` ================================================ FILE: docs/jspreadsheet/docs/examples/web-component.md ================================================ title: Spreadsheet Web Component with Jspreadsheet keywords: Jexcel, JavaScript, JavaScript plugin, web component, spreadsheet, data grid, table, Excel-like grid, data tables, data visualization description: Learn how to use Jspreadsheet CE to create a fully functional spreadsheet as a web component for your applications. # Spreadsheet Web Component Easily create a spreadsheet web component using Jspreadsheet CE for seamless integration into your web applications. ```html ``` ================================================ FILE: docs/jspreadsheet/docs/examples.md ================================================ title: Jspreadsheet Examples keywords: Jspreadsheet examples, JavaScript spreadsheet, interactive data grid, spreadsheet columns, spreadsheet integrations, rich text editor, HTML table, internationalization description: Explore Jspreadsheet examples, including creating spreadsheets from HTML, rich text editors, column dragging, and integration with web components and jQuery. # Examples Explore practical examples of Jspreadsheet in action. This page showcases how to create and customize spreadsheets, implement advanced column features, and integrate with popular web technologies like web components and jQuery. ## Spreadsheet - [Create a new Spreadsheet from HTML](https://jspreadsheet.com/docs/examples/create-from-table) - [Data Grid Translations](https://jspreadsheet.com/docs/examples/translations) - [Data Grid Table Ooverflow](https://jspreadsheet.com/docs/examples/table-overflow) ## Columns - [Rich Text Cell Editor](https://jspreadsheet.com/docs/examples/richtext-html-editor) - [Data Grid Column Dragging](https://jspreadsheet.com/docs/examples/column-dragging) ## Integrations - [Spreadsheet Web Component](https://jspreadsheet.com/docs/examples/web-component) - [Jquery Spreadsheet](https://jspreadsheet.com/docs/examples/jquery) ================================================ FILE: docs/jspreadsheet/docs/filters.md ================================================ title: Data Grid Filters keywords: Jspreadsheet, data grid filters, JavaScript, Excel-like functionality, spreadsheet filters, column filters, filter methods, filter events, filter customization description: Explore detailed insights into Jspreadsheet data grid column filters, including settings, methods, and related events. # Spreadsheet Column Filters This section provides details about the row filters in Jspreadsheet CE. {.pro} > #### Differences in the Pro Version > The Pro version provides multi-column filters, support for filtering specific columns or cell ranges, sorting ranges, additional events, and extensive customization options, with excellent compatibility with other spreadsheet software.\ >\ > [Learn more](https://jspreadsheet.com/docs/filters){.button} ## Documentation ### Methods | Method | Description | |-------------------|--------------------------------------------------------------------------------------------| | `openFilter()` | Opens the filter input.
`openFilter(columnNumber: Number, getAsSets?: boolean): void` | | `closeFilter()` | Closes the filter input.
`closeFilter(): void` | | `resetFilters()` | Resets all applied filters.
`resetFilters(): void` | ### Initial Settings | Property | Description | |----------------------------|------------------------------------------------------------------| | `filters: boolean\|string` | Start the spreadsheet with the filters enabled. `Default: false` | #### Enabling Spreadsheet Filters To activate a filter for a specific column, use the filter attribute in the column object during spreadsheet initialization: ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(null); // Data const data = [ ['Jazz', 'Honda'], ['Civic', 'Honda'], ['Civic', 'Honda'], ['Picanto', 'Kia'], ['Optima', 'Kia'], ]; // Render component return ( <> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [ ['Jazz', 'Honda'], ['Civic', 'Honda'], ['Civic', 'Honda'], ['Picanto', 'Kia'], ['Optima', 'Kia'], ], filters: true, }], }); } } ``` {.pro} > **Upgrade to Jspreadsheet Pro for Advanced Filtering** > > Jspreadsheet CE provides basic row filtering. **Jspreadsheet Pro** adds Excel-like filtering capabilities for data analysis: > > **Advanced Filter Types:** > - **Text Filters:** Contains, starts with, ends with, equals, custom text matching > - **Number Filters:** Greater than, less than, between, top 10, above/below average > - **Date Filters:** Today, this week, this month, this year, custom date ranges > - **Boolean Filters:** True/false checkbox filters > - **Color Filters:** Filter by cell background or text color > - **Custom Filters:** Define your own filter logic with JavaScript functions > > **Multi-Column Filtering:** > - **Simultaneous Filters:** Apply filters to multiple columns at once (Excel-like) > - **Filter Combinations:** AND/OR logic between multiple column filters > - **Filter Priority:** Control which filters apply first > - **Dependent Filters:** Filter options change based on other active filters > > **Professional Filter UI:** > - **Dropdown Filter Menus:** Excel-like dropdown on each column header > - **Search in Filters:** Quick search within filter dropdown options > - **Select All/None:** Bulk select/deselect filter values > - **Filter Indicators:** Visual indicators showing which columns are filtered > - **Filter Count:** Show count of visible vs total rows > > **Filter Management:** > - **Save Filter States:** Persist filters across sessions > - **Filter Templates:** Predefined filter configurations for common scenarios > - **Clear All Filters:** One-click to reset all active filters > - **Filter History:** Navigate through previously applied filters > - **Filter Events:** Track filter changes with advanced event system > - **Programmatic API:** Set/get/clear filters via JavaScript API > > **Performance & Compatibility:** > - **Large Dataset Filtering:** Optimized for filtering 100K+ rows > - **Excel Compatibility:** Import/export preserves Excel filters > - **Filter Cell Ranges:** Filter specific ranges, not just entire columns > - **Virtual Scrolling:** Maintain performance with filtered large datasets > > Perfect for business intelligence dashboards and data analysis applications. > > **[Explore Pro Filters →](https://jspreadsheet.com/docs/filters)** | **[Compare Editions →](https://jspreadsheet.com/docs/getting-started)** | **[View Pricing →](https://jspreadsheet.com/pricing)** ## Related Tools - [Data Grid Search](/jspreadsheet/docs/search) - Search and highlight functionality for data grids - [Jspreadsheet Pro Filters](https://jspreadsheet.com/docs/filters) - Advanced filtering with custom rules and date ranges (Pro) - [Jspreadsheet Pro Search](https://jspreadsheet.com/docs/search) - Enhanced search with regex and highlighting (Pro) ================================================ FILE: docs/jspreadsheet/docs/footers.md ================================================ title: Data Grid Footers keywords: Jspreadsheet, Jexcel, data grid, JavaScript, Excel-like footer, spreadsheet footers, table footers, summary calculations, status bar, floating footers, worksheet footers description: Spreadsheet footers let you display information such as formulas and data summaries at the bottom of the grid for easy reference and visibility. # Data Grid Footers Jspreadsheet footers enable you to display information, including calculations or summaries, at the bottom of a data grid. This section explains implementing footers during initialization or dynamically at runtime using available methods and settings. ## Documentation ### Initial Settings The following properties are available during the initialization of the Jspreadsheet data grid: | Property | Description | |-------------------------------|------------------------------------------------------------------| | footers?: string[] | Defines the footers for the grid. | ## Examples ### Programmatic updates How to change the formulas in the footers after the initialization. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import jSuites from "jsuites"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/style.css"; // Create a new data grid export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ ['Cheese', 10, 6.00], ['Apples', 5, 4.00], ['Carrots', 5, 1.00], ['Oranges', 6, 2.00], ]; // Columns const columns = [ { width:'400px' } ]; // Data grid cell definitions const footers = [ [ 'Total', '=SUM(B1:B4)', '=SUM(C1:C4)', ] ]; // Render data grid component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
`; }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [ ['Cheese', 10, 6.00], ['Apples', 5, 4.00], ['Carrots', 5, 1.00], ['Oranges', 6, 2.00], ], footers: [ [ 'Total', '=SUM(B1:B4)', '=SUM(C1:C4)', ] ], columns: [ { width:'400px' } ] }] }); } } ``` ## Related Tools - [Spreadsheet Headers](/jspreadsheet/docs/headers) - Customize column headers - [Nested Headers](/jspreadsheet/docs/nested-headers) - Multi-level column headers - [Jspreadsheet Pro Footers](https://jspreadsheet.com/docs/footers) - Advanced footer features (Pro) ================================================ FILE: docs/jspreadsheet/docs/format.md ================================================ title: Data Grid Cell Masking keywords: Jspreadsheet, Jexcel, data grid, JavaScript, cell formatting, currency format, date format, date mask, number formatting, spreadsheet currency, data formatting, format mask, data display customization, Jspreadsheet formatting description: Learn how to format and customize cell data in Jspreadsheet. Explore techniques for formatting numbers, currency, dates, and more to improve the presentation of your data grid. # Cell Formatting Jspreadsheet offers flexible cell formatting features similar to Excel and Google Sheets, with customizable options for better data presentation. You can format numbers, currencies, dates, and more to suit specific application requirements, enabling enhanced control over how data is displayed and integrated. ## Overview ### Column Level Settings The following properties are applied uniformly to all cells in a column. | Attribute | Description | |-------------------|-----------------------------------------------------------------------------------------------------------------------| | mask?: object | This property force the mask during the edition. | | format?: object | This property will mask the content of a cell after the edition. | | locale? object | This property apply the JavaScript Intl.FormatNumber to the cells. | | render?: function | Intercepts and modifies the text of all cells in a column.
`render(td, value, x, y, worksheet, options) => void` | {.pro} > #### Differences in the Pro Version > > Jspreadsheet Pro allows cell-level format and other advanced format customizations.\ > \ > [Learn more](https://jspreadsheet.com/docs/format){.button} ## Documentation ### Mask and Format Settings In Jspreadsheet CE, the `mask` property enforces a specific input pattern during cell editing using spreadsheet-like tokens. The `format` property applies formatting rules to cell content after editing. Below are examples of tokens that can be used: | Valid examples | |----------------| | 0 | | 0.00 | | 0% | | 0.00% | | #,##0 | | #,##0.00 | | #,##0 | | d-mmm-yy | | d-mmm | | dd/mm/yyyy | | mmm-yy | | h:mm AM/PM | | h:mm:ss AM/PM | | h:mm | | h:mm:ss | | m/d/yy h:mm | | mm:ss | | [h]:mm:ss | #### Example ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const columns = [{ type: 'number', // Excel like token to format the currency input mask: 'U$ #.##0,00' }]; // Render data grid component return ( ); } ``` ```vue ``` ```angularjs @Component({ standalone: true, selector: "app-root", template: `
` }) export class AppComponent { // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ minDimensions: [6,6], columns: [{ type: 'number', // Excel like token to format the currency input mask: 'U$ #.##0,00' }] }] }); } } ``` ### Locale #### Currency Formatting To use the `currency` style in Jspreadsheet, specify the `currency` property. Optionally, you can use the `currencyDisplay` and `currencySign` properties to control the display format of the currency unit. ```html
``` ```jsx // India currency export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Columns const columns = [{ type: 'number', // Locale will enable number formatting locale: 'en-IN', // Options for the number format class. You can find more about he options on the link above options: { style:'currency', currency: 'INR' } }]; // Render data grid component return ( ); } ``` ```vue ``` ```angularjs export class AppComponent { // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ minDimensions: [6,6], columns: [{ type: 'number', // Locale will enable number formatting locale: 'en-IN', // Options for the number format class. You can find more about he options on the link above options: { style:'currency', currency: 'INR' } }] }] }); } } ``` ### Custom Formatting with `render()` Jspreadsheet supports custom masking through the `render` method, allowing you to customize cell content display. {.ignore} ```javascript jspreadsheet(DOMElement, { tabs: true, toolbar: true, worksheets: [{ data: [['2022-01-01 12:14:12'],['=TODAY()']], columns: [{ width: 300, customFormat: 'MMMM Do YYYY, h:mm:ss a', render: function(td, value, x, y, worksheet, options) { if (td && td.innerText && options.customFormat) { td.innerText = moment(td.innerText).format(options.customFormat); } }, align: 'right', }], minDimensions: [4,8], }] }); ``` ## Examples ### Data Grid with Different Currencies The example below demonstrates number formatting using `Intl.NumberFormat` or `mask`. See more examples of the [spreadsheet format](https://jsfiddle.net/spreadsheet/L9jxszf3/) on jsfiddle ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ [1024,1024,0.24,1024,1024,1024], [1000.456,1000.456,0.4155,1000.456,1000.456,1000.456], ['547','547,98','7,98','547.98','547,98','547.98'], ]; // Columns const columns = [ { title:"Currency INR", type: "number", locale: 'en-IN', options: { style:'currency', currency: 'INR' } }, { title: "Currency BRL", type: "number", locale: 'pt-BR', options: { style: 'currency', currency: 'BRL' } }, { title: "Percent US", type: "number", mask: "0.00%" }, { title: "Units Liter US", type: "number", locale: 'en-US', options: { style: 'unit', unit: 'liter', unitDisplay: 'long' } }, { type: "number", format: '#.##0,00' }, { type: "number", mask: '#,##0' } ]; // Render data grid component return ( ); } ``` ```vue ``` ```angularjs @Component({ selector: "app-root", template: `
` }) export class AppComponent { // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { toolbar: true, worksheets: [ { minDimensions:[6, 10], data: [ [1024,1024,0.24,1024,1024,1024], [1000.456,1000.456,0.4155,1000.456,1000.456,1000.456], ['547','547,98','7,98','547.98','547,98','547.98'], ], columns: [ { title:"Currency INR", type: "number", locale: 'en-IN', options: { style:'currency', currency: 'INR' } }, { title: "Currency BRL", type: "number", locale: 'pt-BR', options: { style: 'currency', currency: 'BRL' } }, { title: "Percent US", type: "number", mask: "0.00%" }, { title: "Units Liter US", type: "number", locale: 'en-US', options: { style: 'unit', unit: 'liter', unitDisplay: 'long' } }, { type: "number", format: '#.##0,00' }, { type: "number", mask: '#,##0' }, ], defaultColWidth: '110px', } ] }); } } ``` ### Custom Formatting with MomentJS The example below demonstrates how to apply a mask to a cell using MomentJS. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet-ce/react"; import moment from "momentjs"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; // Create a new spreadsheet const customRender = function(td, value, x, y, instance, options) { if (td && td.innerText && options.customFormat) { td.innerText = moment(td.innerText).format(options.customFormat); } } export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [['2022-01-01 12:14:12'],['=TODAY()']]; // Columns const columns = [{ width: 300, customFormat: 'MMMM Do YYYY, h:mm:ss a', render: customRender, align: 'right', }] // Render data grid component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create a new spreadsheet const customRender = function(td, value, x, y, instance, options) { if (td && td.innerText && options.customFormat) { td.innerText = moment(td.innerText).format(options.customFormat); } } @Component({ selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { tabs: true, toolbar: true, worksheets: [{ data: [['2022-01-01 12:14:12'],['=TODAY()']], columns: [{ width: 300, customFormat: 'MMMM Do YYYY, h:mm:ss a', render: customRender, align: 'right', }], minDimensions: [4,8], }] }); } } ``` {.pro} > **Upgrade to Jspreadsheet Pro for Conditional Formatting** > > Jspreadsheet CE provides column-level formatting with masks and locale settings. **Jspreadsheet Pro** adds powerful visual formatting capabilities: > > **Conditional Formatting Features:** > - **Visual Rules:** Automatically highlight cells based on values (greater than, less than, between, equal to) > - **Color Scales:** Gradient color scales for data visualization (2-color and 3-color scales) > - **Data Bars:** In-cell bar charts showing relative values > - **Icon Sets:** Traffic lights, arrows, flags, and custom icons based on cell values > - **Custom Rules:** Formula-based rules for complex conditional formatting > - **Cell-Level Formatting:** Apply different formats to individual cells, not just columns > - **Style Templates:** Pre-defined formatting styles and themes > > **Advanced Format Options:** > - **Rich Text Formatting:** Bold, italic, underline, strikethrough, superscript, subscript > - **Cell-Specific Masks:** Different formatting masks for individual cells > - **Background Colors & Borders:** Cell-level styling with custom colors and border styles > - **Text Alignment:** Per-cell horizontal and vertical alignment > - **Font Controls:** Font family, size, and color per cell > > Perfect for dashboards, reports, and data analysis applications requiring visual data presentation. > > **[Explore Pro Formatting →](https://jspreadsheet.com/docs/format)** | **[Compare Editions →](https://jspreadsheet.com/docs/getting-started)** | **[View Pricing →](https://jspreadsheet.com/pricing)** ## Related Tools - [JavaScript Input Mask](https://jsuites.net/docs/javascript-mask) - Standalone input masks with Excel-like tokens - [JavaScript Rich Forms](https://jsuites.net/docs/rich-form) - Form validation and change tracking - [LemonadeJS Forms](https://lemonadejs.com/docs/forms) - Reactive form binding - [Jspreadsheet Pro Format](https://jspreadsheet.com/docs/format) - Advanced cell formatting and conditional formatting (Pro) - [Data Validations](https://jspreadsheet.com/docs/validations) - Cell validation rules (Pro) These tools use the same Excel-like mask tokens as Jspreadsheet CE, making it easy to maintain consistent formatting across your application. ================================================ FILE: docs/jspreadsheet/docs/formulas.md ================================================ title: Excel-Like Formulas in Jspreadsheet CE keywords: Jspreadsheet, Jexcel, data grid, JavaScript, Excel-like formulas, spreadsheet formulas, calculations, spreadsheet calculations, custom formulas, web-based spreadsheets, internationalization description: Implement Excel-like formulas in your web app with Jspreadsheet CE for dynamic, browser-based spreadsheets. # Excel-Like Formulas Jspreadsheet supports Excel-like formulas compatible with popular spreadsheet applications such as Excel and Google Sheets. These features enable advanced calculation capabilities, including: - Excel-style Formulas: Customizable formulas resembling Excel. - Formula Auto-update: Adjusts on cell actions such as copy and paste. - Custom Formulas: Create your custom formulas. {.pro} > #### Formula Pro > > Jspreadsheet Pro's Formula Pro Extension provides a suite of advanced features designed to enhance spreadsheet functionality, including: > > - Full-stack applications; > - Secure private scopes; > - Cross-sheet calculations; > - complex matrix operations; > - Advanced operators ('%', '@'); > - Dynamic range calculations (e.g., A:A, 1:1); > >\ > [More about Formula Pro](https://jspreadsheet.com/products/formulas){.button} ## Documentation This section details the settings, methods, and events associated with spreadsheet calculations in Jspreadsheet. All formula names, including custom ones, should be capitalized for security and standardization. ### Settings A summary of configurations related to the use of formulas. | Configuration | Description | |----------------------------------|-------------------------------------------------------------------| | secureFormulas?: boolean | Enable formula security. Default: true | | parseFormulas?: boolean | Enable formula calculations. Default: true | | debugFormulas?: boolean | Enable the formula debug notices. Default: false | | autoIncrement?: boolean | Formula variable increment on cloning or copying. Default: true | ### Events All events related to formulas. | Event | Description | | ---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------| | onbeforeformula?: Function | Intercept and modify a formula before execution.
onbeforeformula(worksheet: Object, expression: String, x: Number, y: Number) => String | ### Methods All methods related to formulas. | Method | Description | | --------------------------|-----------------------------------------------------------------------------------------------------------------| | executeFormula?: Function | Execute a formula.
executeFormula(expression: string, x?: number, y?: number, caching?: boolean) => String | ## Examples ### Basic excel-like formulas usage A basic spreadsheet example using formulas, including currency, percentage and mask. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Worksheet data const data = [ [ 'Crayons Crayola only (No Rose Art)', 2, 5.01, 0.01, '=B1*C1*(1-D1)' ], [ 'Colored Pencils Crayola only', 2, 4.41, 0.02, '=B2*C2*(1-D2)' ], [ 'Expo Dry-erase Markers Wide', 4, 3.00, 0.1, '=B3*C3*(1-D3)' ], [ 'Index Cards Unlined', 3, 6.00, 0.03, '=B4*C4*(1-D4)' ], [ 'Tissues', 10, 1.90, 0.01, '=B5*C5*(1-D5)' ], [ 'Ziploc Sandwich-size Bags', 5, 1.00, 0.01, '=B6*C6*(1-D6)' ], [ 'Thin Markers Crayola only', 2, 3.00, 0.02, '=B7*C7*(1-D7)' ], [ 'Highlighter', 4, 1.20, 0.01, '=B8*C8*(1-D8)' ], [ 'Total', '=SUM(B1:B8)', '=ROUND(SUM(C1:C8), 2)', '', '=SUM(E1:E8)' ], ] // Column definitions const columns = [ { type: 'text', title:'Product', width:'300' }, { type: 'text', title:'Qtd', width:'80', mask:'#.##0' }, { type: 'text', title:'Price', width:'100px', mask:'$ #.##0,00' }, { type: 'text', title:'Discount', mask:'0.00%' }, { type: 'number', title: 'Total', width: '100px', format: 'US #.##0,00', }, ] // Render component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" @Component({ standalone: true, selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { style: ['background-color:orange; font-weight: bold;'], worksheets: [{ data: [ [ 'Crayons Crayola only (No Rose Art)', 2, 5.01, 0.01, '=B1*C1*(1-D1)' ], [ 'Colored Pencils Crayola only', 2, 4.41, 0.02, '=B2*C2*(1-D2)' ], [ 'Expo Dry-erase Markers Wide', 4, 3.00, 0.1, '=B3*C3*(1-D3)' ], [ 'Index Cards Unlined', 3, 6.00, 0.03, '=B4*C4*(1-D4)' ], [ 'Tissues', 10, 1.90, 0.01, '=B5*C5*(1-D5)' ], [ 'Ziploc Sandwich-size Bags', 5, 1.00, 0.01, '=B6*C6*(1-D6)' ], [ 'Thin Markers Crayola only', 2, 3.00, 0.02, '=B7*C7*(1-D7)' ], [ 'Highlighter', 4, 1.20, 0.01, '=B8*C8*(1-D8)' ], [ 'Total', '=SUM(B1:B8)', '=ROUND(SUM(C1:C8), 2)', '', '=SUM(E1:E8)' ], ], columns: [ { type: 'text', title:'Product', width:'300' }, { type: 'text', title:'Qtd', width:'80', mask:'#.##0' }, { type: 'text', title:'Price', width:'100px', mask:'$ #.##0,00' }, { type: 'text', title:'Discount', mask:'0.00%' }, { type: 'number', title: 'Total', width: '100px', format: 'US #.##0,00', }, ] }] }); } } ``` ## More Information Explore additional sections related to spreadsheet calculations. ### Jspreadsheet CE - [Custom Excel-Like Formulas](/jspreadsheet/docs/custom-formulas) ### Jspreadsheet PRO - [Custom Excel-Like Formulas](https://jspreadsheet.com/docs/custom-formulas) - [Formula Selector](https://jspreadsheet.com/docs/formula-picker) - [The Calculation Queue State](https://jspreadsheet.com/docs/calculations) - [Namespaces](https://jspreadsheet.com/docs/namespaces) - [The Formula Pro Extension](https://jspreadsheet.com/products/formulas) - [Defined Names](https://jspreadsheet.com/docs/defined-names) {.pro} > **Upgrade to Jspreadsheet Pro for 500+ Excel-Compatible Functions** > > Jspreadsheet CE provides basic formulas like SUM, AVERAGE, and arithmetic operations. **Jspreadsheet Pro** expands this with full Excel compatibility: > > - **Lookup Functions:** VLOOKUP, HLOOKUP, INDEX, MATCH, XLOOKUP > - **Conditional Formulas:** SUMIF, COUNTIF, AVERAGEIF, SUMIFS, COUNTIFS, IFS > - **Financial Functions:** NPV, IRR, PMT, FV, PV, RATE > - **Date/Time Functions:** Advanced date calculations, NETWORKDAYS, EDATE, EOMONTH > - **Text Functions:** String manipulation, CONCATENATE, TEXTJOIN, REGEX > - **Statistical Functions:** STDEV, VAR, PERCENTILE, QUARTILE > - **Cross-Worksheet Calculations:** Reference cells across multiple worksheets (e.g., Sheet2!A1:B10) > - **Dynamic Ranges:** Full column/row references (e.g., A:A, 1:1) > - **Array Formulas:** Matrix operations and bulk calculations > > Perfect for business applications requiring full Excel compatibility and advanced spreadsheet functionality. > > **[Explore Pro Formula Features →](https://jspreadsheet.com/docs/formulas)** | **[Compare Editions →](https://jspreadsheet.com/docs/getting-started)** | **[View Pricing →](https://jspreadsheet.com/pricing)** ================================================ FILE: docs/jspreadsheet/docs/freeze-columns.md ================================================ title: Jspreadsheet Freeze Columns keywords: Jspreadsheet, freeze columns, data grid customization, JavaScript spreadsheets, Excel-like freeze functionality, floating columns, spreadsheet column freezing description: This section provides comprehensive information on freezing columns in Jspreadsheet, including programmatic settings, methods, and related events. # Freeze Columns Freezing columns in a spreadsheet keeps specific columns visible as you scroll. This section covers the settings, methods, and events for managing frozen columns. {.pro} > #### What you can find on the Pro Version > > The **Pro version** of Jspreadsheet provides programmatic methods, events, and manual controls for `freezing columns`, enabling users to adjust this setting directly through the interface. > \ > [Learn more](https://jspreadsheet.com/docs/freeze-columns){.button} ## Documentation ### Initial Settings Below are the initial settings related to freezing columns. | Property | Description | |----------------------------|---------------------------------------------------| | **freezeColumns**: number | Defines the columns to freeze on initialization. | > #### Limitations > Freeze columns currently have limited functionality when used with filters and footers. These improvements are planned for the next release. ## Examples ### Basic Freeze Columns Example A straightforward example of how to use frozen columns in Jspreadsheet. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/style.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Render component return ( <> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css"; import "jsuites/dist/jsuites.css"; @Component({ standalone: true, selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [ minDimensions: [50,10], tableOverflow: true, tableWidth: '800px', freezeColumns: 1 }] }); } } ``` ## Related Tools - [Jspreadsheet Pro Freeze Columns](https://jspreadsheet.com/docs/freeze-columns) - Advanced freeze with user controls (Pro) - [Jspreadsheet Pro Freeze Rows](https://jspreadsheet.com/docs/freeze-rows) - Freeze header rows with interactive controls (Pro) ================================================ FILE: docs/jspreadsheet/docs/getting-started.md ================================================ title: Getting Started with Jspreadsheet CE keywords: Jspreadsheet CE, Jexcel, JavaScript data grid, spreadsheets, JavaScript tables, Excel-like grids, web-based spreadsheets, data grid controls, interactive spreadsheet features description: Learn how to create interactive data grids with powerful spreadsheet controls using Jspreadsheet CE. # Getting Started with Jspreadsheet CE v5 ## Overview Jspreadsheet CE, formerly jExcel, is a free, lightweight JavaScript spreadsheet library to help developers bring Excel-like data grids and spreadsheet features to their applications. The library enables developers to build robust data management interfaces using React, Angular or pure JavaScript. ### Why Choose Jspreadsheet CE? - Create rich, interactive data grid interfaces - Handle complex data inputs with Excel-like functionality - Direct Excel compatibility: Copy and paste using standard shortcuts - Proven success across thousands of implementations - Lightweight, fast, and intuitive - Easy integration with third-party plugins - Built for collaboration and sharing ## Jspreadsheet Editions ### Jspreadsheet CE - Community Edition This page documents Jspreadsheet CE (v5), the free, open-source spreadsheet library: - Core spreadsheet functionality - Excel-like data grids - MIT license - Community support **Perfect for:** - Open source projects - Learning and prototyping - Basic spreadsheet needs - Budget-conscious projects ### Upgrade to Jspreadsheet Pro [Jspreadsheet Pro](https://jspreadsheet.com/docs/getting-started) offers professional features not available in CE: - **Advanced Editors:** Dropdown with conditional logic, date/time, rich text, HTML, color pickers - **Enhanced Formulas:** 500+ Excel-compatible functions, cross-worksheet calculations - **Conditional Formatting:** Visual rules, data bars, color scales - **Data Validation:** Advanced rules, custom validation - **Multiple Worksheets:** Tabs, cross-sheet references - **Import/Export:** Full Excel compatibility, CSV, JSON - **Performance:** Optimized for large datasets - **Professional Support:** Dedicated support team, priority bug fixes - **Commercial License:** For commercial applications **[Explore Jspreadsheet Pro Features →](https://jspreadsheet.com/docs/getting-started)** | **[Compare Editions →](https://jspreadsheet.com/pricing)** ### LemonadeJS Data Grid - Lightweight Alternative For simple data display without Excel features, [LemonadeJS Data Grid](https://lemonadejs.net/docs/plugins/data-grid) offers: - Lightweight (5KB) - Reactive data binding - Basic search and pagination - Perfect for simple CRUD interfaces **[View LemonadeJS Data Grid →](https://lemonadejs.net/docs/plugins/data-grid)** --- ### Feature Comparison | Feature | CE | Pro | LemonadeJS Grid | |---------|----|----|-----------------| | Formulas | Basic | ✓✓ 500+ functions | ❌ | | Conditional Formatting | ❌ | ✓ | ❌ | | Advanced Editors | Basic | ✓✓ Full suite | ❌ | | Multiple Worksheets | ✓ | ✓ | ❌ | | Excel Import/Export | Basic | ✓✓ Full | ❌ | | Data Validation | Basic | ✓✓ Advanced | ❌ | | License | MIT | Commercial | MIT | | Support | Community | ✓ Dedicated | Community | | Size | Medium | Full-featured | 5KB | --- ## Installation Choose one of the following installation options: ### NPM Install jspreadsheet using NPM: ```bash npm install jspreadsheet-ce@5.0.0-beta.3 ``` ### CDN Include jspreadsheet directly from JSDelivr CDN: {.ignore} ```html ``` ### Download Download and run jspreadsheet on your server or local machine: https://bossanova.uk/jspreadsheet/v5/jspreadsheet.zip ## License This software is distributed under the MIT license. ## Documentation ### Global methods and properties | Method Description | |------------------------------------------------------------------------------------------------------------------------------------------| | Create a new spreadsheet.
`jspreadsheet(element: HTMLDivElement \| HTMLTableElement, options: SpreadsheetOptions): WorksheetInstance[];` | | Destroy a given spreadsheet.
`jspreadsheet.destroy(element: JspreadsheetInstanceElement, destroyEventHandlers?: boolean): void;` | | Destroy all spreadsheets in all namespaces.
`jspreadsheet.destroyAll(): void;` | | Translate Jspreadsheet components and extensions.
`jspreadsheet.setDictionary(translations: Record): void;` | | Get a worksheet instance by name and namespace.
`jspreadsheet.getWorksheetInstanceByName(worksheetName: string \| null \| undefined, namespace: string): WorksheetInstance \| Record;` | ## Examples ### Create a new data grid You can create a new data grid with spreadsheet-like controls from an HTML table element, a JS array, a CSV, a JSON file or an Excel XLSX file. ```html
``` ### Destroying The Data Grid The following example shows how to dynamically destroy and recreate a new data grid. ```html

``` ================================================ FILE: docs/jspreadsheet/docs/headers.md ================================================ title: Spreadsheet Headers keywords: Jspreadsheet, Jexcel, data grids, JavaScript data grid, Excel-like headers, column title, headers, data grid headers, column headers, customize headers, modify headers, spreadsheet customization, column title customization, data grid organization description: Customize and modify column headers in your data grids. Explore programmatic header updates and advanced options for organizing and managing your data grid. # Spreadsheet Headers This section explains how to create and modify custom headers in Jspreadsheet. Users can change header titles via the context menu or by performing a long click on the header. ## Documentation ### Methods You can update the headers programmatically using the following methods. | Method | Description | | ------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | getHeaders | Get all header titles.
@param `asArray` - If true, returns the items in an array, if false, returns them separated by ";" within a single string.
`worksheetInstance.getHeaders(asArray?: boolean): string \| string[];` | | getHeader | Get the column title.
@param `column` - Column index.
`worksheetInstance.getHeader(column: number): string;` | | setHeader | Sets a custom header title for a specified column (starting at zero).
@param `column` - column number starting on zero.
@param `newValue` - New title. Empty string or undefined to reset the header title.
`worksheetInstance.setHeader(column: number, newValue?: string): void;` | ### Initial Settings To customize headers, use the `title` and `tooltip` attributes in the column settings. {.ignore} ```html ``` ### Available Events | Event | Description | | ---------------|------------------------------------------------------------------------------------------------------------------------------------| | onchangeheader | Triggered when the header title is changed.
`onchangeheader(instance: WorksheetInstance, colIndex: number, newValue: string, oldValue: string): void;` | ## Examples ### Updates to the Headers The following example initializes the spreadsheet with basic headers and demonstrates how to update the titles programmatically. ```html
``` ```jsx import React, { useRef, useEffect } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); const columns = [ { title: 'Header 1' }, { title: 'Header 2' }, { title: 'Header 3' } ]; useEffect(() => { if (spreadsheet.current) { spreadsheet.current[0].setHeader(0, 'New Header 1'); spreadsheet.current[0].setHeader(1, 'New Header 2'); spreadsheet.current[0].setHeader(2, 'New Header 3'); } }, []) // Render component return ( <> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { minDimensions: [6,6], columns: [ { title: 'Header 1' }, { title: 'Header 2' }, { title: 'Header 3' } ], }); // Programmatically update the header titles this.worksheets[0].setHeader(0, 'New Header 1'); this.worksheets[0].setHeader(1, 'New Header 2'); this.worksheets[0].setHeader(2, 'New Header 3'); } } ``` ## Related Tools - [Nested Headers](/jspreadsheet/docs/nested-headers) - Create multi-level column headers - [Data Grid Footers](/jspreadsheet/docs/footers) - Add footer rows with formulas - [Jspreadsheet Pro Headers](https://jspreadsheet.com/docs/headers) - Advanced header customization (Pro) ================================================ FILE: docs/jspreadsheet/docs/helpers.md ================================================ title: Common Spreadsheet Helper Methods keywords: Jspreadsheet, Jexcel, data grid, JavaScript, Excel-like features, spreadsheet, data table, documentation, spreadsheet helpers, useful methods description: This section provides helper methods for everyday spreadsheet scripting, including formatting, transformations, and general spreadsheet utilities. # Spreadsheet Helpers This section provides essential methods for common spreadsheet operations, such as handling cell names, ranges, formulas, and data parsing. Each method is documented for easy integration into your applications. ## Documentation ### getColumnName `jspreadsheet.helpers.getColumnName(i: number): string;` **Description:** Returns the column letter corresponding to the given column index. Useful for converting numeric indices (e.g., 0, 1, 2) into Excel-style letters (e.g., A, B, C). **Parameters:** - `columnNumber` (number): Zero-based column index. **Returns:** - `string`: Column letter (e.g., "A", "B", ...). {.break} --- ### getCellNameFromCoords `jspreadsheet.helpers.getCellNameFromCoords(x: Number, y: Number) => String` **Description:** Generates a cell name (e.g., A1) from specified zero-based x (column) and y (row) coordinates. **Parameters:** - `x` (Number): Column index (0-based). - `y` (Number): Row index (0-based). **Returns:** - `String`: Cell name (e.g., "A1", "B2"). {.break} --- ### getCoordsFromCellName `jspreadsheet.helpers.getCoordsFromCellName(cellName: String) => [Number, Number]` **Description:** Converts a cell name (e.g., A1) into zero-based coordinates. **Parameters:** - `cellName` (String): Cell name in Excel-style notation. **Returns:** - `[Number, Number]`: Array containing x (column) and y (row) indices. {.break} --- ### getCoordsFromRange `jspreadsheet.helpers.getCoordsFromRange(range: String, adjust?: boolean) => [Number, Number, Number, Number]` **Description:** Converts a range string into its corresponding zero-based coordinates. **Parameters:** - `range` (String): Range string (e.g., "A1:B2"). - `adjust` (boolean, optional): Whether to normalize coordinates (default: false). **Returns:** - `[Number, Number, Number, Number]`: Array containing x1, y1, x2, y2. {.break} --- ### createFromTable `jspreadsheet.helpers.createFromTable(element: HTMLElement, options: Object) => Object` **Description:** Creates a new spreadsheet configuration based on an HTML table element. **Parameters:** - `element` (HTMLElement): Source HTML table. - `options` (Object): Configuration options. **Returns:** - `Object`: Spreadsheet configuration. {.break} --- ### parseCSV `jspreadsheet.helpers.parseCSV(str: string, delimiter?: string): string[][];` **Description:** Converts a CSV string into a JavaScript array. **Parameters:** - `data` (string): CSV-formatted string. - `delimiter` (string): Delimiter character (e.g., ","). **Returns:** - `string[][]`: Parsed array of rows. {.break} --- ### getTokensFromCoords `jspreadsheet.helpers.getTokensFromCoords(x1: Number, y1: Number, x2: Number, y2: Number, wsName?: String) => Array` **Description:** Generates cell tokens from a range of coordinates. **Parameters:** - `x1, y1, x2, y2` (Number): Range coordinates. - `wsName` (String, optional): Worksheet name. **Returns:** - `Array`: List of tokens (e.g., ["A1", "A2"]). {.break} --- ## Examples ### Data Grid Helpers Example {.ignore} ```javascript // Returns A1 jspreadsheet.helpers.getCellNameFromCoords(0,0); // Returns (4) [1, 0, 2, 3] jspreadsheet.helpers.getCoordsFromRange('B1:C4'); // Also works with the worksheet instance. Returns 1,1 jspreadsheet.helpers.getCoordsFromCellName('B2'); ``` ```html

``` ================================================ FILE: docs/jspreadsheet/docs/history.md ================================================ title: Jspreadsheet: Undo & Redo History keywords: Jspreadsheet, Jexcel, JavaScript data grid, undo, redo, Ctrl+Z, Ctrl+Y, spreadsheet change tracking, action history, data revisions, Jspreadsheet history description: Discover how to manage action history in Jspreadsheet with undo and redo features, allowing easy tracking of data grid changes and control over modifications. # History Tracker The Jspreadsheet History Tracker captures all changes within each spreadsheet, enabling users to undo (CTRL+Z) and redo (CTRL+Y) actions. In Jspreadsheet CE, the history tracker operates independently for each worksheet. {.pro} > #### What you can find on the Pro Version > > In the Pro version of Jspreadsheet, the unified history tracker supports cross-spreadsheet calculations and integration across all spreadsheets displayed on the same screen. > > \ > [Learn more](https://jspreadsheet.com/docs/history){.button} ## Documentation ### Methods The undo and redo methods are normally invoked by the CTRL+Z, CTRL+Y keyboard shortcut. The following methods can be called programmatically, as follows: | Method | Description | |----------|---------------------------------------------------------------------------------| | `undo()` | Undo the last worksheet change.
`worksheet.undo() : void` | | `redo()` | Redo the most recent worksheet change.
`worksheet.redo() : void` | ### Events Events related to the history changes tracker. | Event | Description | |----------|--------------------------------------------------------------------------------------------------------------------------| | `onredo` | `onredo(worksheet: Object, info: Object) : null`
The info array contains all necessary information about the action. | | `onundo` | `onundo(worksheet: Object, info: Object) : null`
The info array contains all necessary information about the action. | ### History Tracker State Use the history tracker with caution. You can turn it off temporarily by setting `worksheet.ignoreHistory` to true. ## Examples ### Controlling the changes programmatically As explained above, the history actions are available on the spreadsheet level. ```html

``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Render component return ( <> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `

`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ minDimensions: [8, 8], }], }); } } ``` ================================================ FILE: docs/jspreadsheet/docs/images.md ================================================ title: Data Grid Cell Images keywords: Jspreadsheet, Jexcel, data grid, JavaScript, spreadsheet images, cell images, floating images, image handling, image customization, image placement, image integration description: Enhance data representation by embedding images directly in your data grid cells. # Spreadsheet Images This section demonstrates how to embed images directly into data grid cells. {.pro} > ### What you can find on the Pro Version > The Pro version of Jspreadsheet supports **floating images** compatible with Excel and Google Sheets, along with programmatic methods and events.\ > \ > [Learn more](https://jspreadsheet.com/docs/media){.button} ## Examples ### Column type image editor Configure an image upload editor to insert an image in every cell across an entire column. {.small} Double-click in the image cell to upload a new image. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { const spreadsheet = useRef(); const worksheets = [{ data: [ ['Test Icon', 'data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgLTk2MCA5NjAgOTYwIiB3aWR0aD0iMjRweCIgZmlsbD0iIzVmNjM2OCI+PHBhdGggZD0iTTI4MC0yODBoMTYwdi0xNjBIMjgwdjE2MFptMjQwIDBoMTYwdi0xNjBINTIwdjE2MFpNMjgwLTUyMGgxNjB2LTE2MEgyODB2MTYwWm0yNDAgMGgxNjB2LTE2MEg1MjB2MTYwWk0yMDAtMTIwcS0zMyAwLTU2LjUtMjMuNVQxMjAtMjAwdi01NjBxMC0zMyAyMy41LTU2LjVUMjAwLTg0MGg1NjBxMzMgMCA1Ni41IDIzLjVUODQwLTc2MHY1NjBxMCAzMy0yMy41IDU2LjVUNzYwLTEyMEgyMDBabTAtODBoNTYwdi01NjBIMjAwdjU2MFptMC01NjB2NTYwLTU2MFoiLz48L3N2Zz4='], ], minDimensions: [2,4], columns: [ { type:'text', width:300, title:'Title' }, { type:'image', width:120, title:'Image' }, ], }] return ( <> ) } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [ ['Test Icon', 'data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgLTk2MCA5NjAgOTYwIiB3aWR0aD0iMjRweCIgZmlsbD0iIzVmNjM2OCI+PHBhdGggZD0iTTI4MC0yODBoMTYwdi0xNjBIMjgwdjE2MFptMjQwIDBoMTYwdi0xNjBINTIwdjE2MFpNMjgwLTUyMGgxNjB2LTE2MEgyODB2MTYwWm0yNDAgMGgxNjB2LTE2MEg1MjB2MTYwWk0yMDAtMTIwcS0zMyAwLTU2LjUtMjMuNVQxMjAtMjAwdi01NjBxMC0zMyAyMy41LTU2LjVUMjAwLTg0MGg1NjBxMzMgMCA1Ni41IDIzLjVUODQwLTc2MHY1NjBxMCAzMy0yMy41IDU2LjVUNzYwLTEyMEgyMDBabTAtODBoNTYwdi01NjBIMjAwdjU2MFptMC01NjB2NTYwLTU2MFoiLz48L3N2Zz4='], ], minDimensions: [2,4], columns: [ { type:'text', width:300, title:'Title' }, { type:'image', width:120, title:'Image' }, ], }], }); } } ``` ## Related Image Tools ### Free Image Components - [JavaScript Image Cropper](https://jsuites.net/docs/image-cropper) - Crop, rotate, and edit images with brightness/contrast controls - [JavaScript Image Slider](https://jsuites.net/docs/image-slider) - Display image galleries and carousels - [LemonadeJS Image Cropper](https://lemonadejs.com/docs/plugins/image-cropper) - Reactive image cropper component These free components complement Jspreadsheet CE for building complete image management solutions. ================================================ FILE: docs/jspreadsheet/docs/javascript-calendar.md ================================================ title: Javascript Calendar and Date Operations keywords: Jspreadsheet, data grid, javascript, excel-like calendar, spreadsheet calendar, javascript calendar, data grid calendar, advanced calendar, calendar events, calendar settings, interactive data grid, customizable calendar, dynamic calendar, event-driven calendar, calendar integration description: Explore the data grid calendar input type, date operations, formatting, validations, events, and other related date operations and calendar features available in Jspreadsheet. # Date operations This section explains how to handle dates in Jspreadsheet, focusing on the calendar input type and various date operations. You can work with dates by applying a mask to text input fields or using the dedicated calendar type. **Key Differences:** - **Text type with date mask**: This option supports date-related calculations, copy-paste functionality, and fill-handle operations similar to Excel. It allows dates to be treated as numerical values, making it ideal for performing operations. - **Calendar Type**: This option provides a calendar picker for selecting dates but stores the value as a string. It’s more focused on user-friendly input rather than complex date calculations. This section covers the following topics: - **Configuring the calendar picker**: How to customize the calendar editor for user-friendly date selection. - **Validating date values**: Set rules to ensure date inputs are valid based on the values in other columns. - **Performing date calculations**: Use formulas to calculate date differences, add days, or perform other date-related operations. - **Formatting dates with new tokens**: Learn how to use tokens to format dates in different styles. - **Localization of date strings**: Translate date formats and related strings to match different locales and languages. ## Documentation ### Calendar editor The [JavaScript calendar](https://jsuites.net/docs/javascript-calendar) from [jsuites.net](https://jsuites.net/docs) is a highly flexible and responsive plugin that offers numerous configurations to adapt to various application needs. For more information, refer to its [documentation](https://jsuites.net/docs/javascript-calendar). | Parameter | Description | |---------------------------------------|---------------------------------------------------------------------------------------------------| | type: `default \| year-month-picker` | Render type. `Default: default` | | validRange: `[String, String]` | Disables the dates out of the defined range. `[Initial date, Final date]` | | startingDay: `Number` | The day of the week the calendar starts on (0 for Sunday - 6 for Saturday). `Default: 0 (Sunday)` | | format: `String` | Date format. `Default: YYYY-MM-DD` | | readonly: `Boolean` | Calendar input is readonly. `Default: false` | | today: `Boolean` | Select today's date automatically when no date value is defined. `Default: true` | | time: `Boolean` | Show hour and minute dropdown. `Default: false` | | resetButton: `Boolean` | Enabled reset button. `Default: true` | | placeholder: `String` | Default place holder for the calendar input. | | fullscreen: `Boolean` | Open in fullscreen mode. | ## Examples ### Cells with Date Format The example below demonstrates applying a date format to the results of a formula. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ [ '=NOW()', ] ] // Data grid cell definitions const cells = { A1: { format: 'dd/mm/yyyy' }, } // Render data grid component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ minDimensions: [4,4], data: [ [ '=NOW()' ] ], cells: { A1: { format: 'dd/mm/yyyy' }, } }] }); } } ``` ### Column Calendar Customization In the example below, we configure the calendar column type as a year-month picker only. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ [ '2021-01-01', '', '', '' ] ]; // Data grid cell definitions const columns = [ { type: 'calendar', options: { type: 'year-month-picker', format: 'Mon/YYYY' } }, ]; // Render data grid component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ minDimensions: [4,4], data: [ [ '2021-01-01', '', '', '' ] ], columns: [ { type: 'calendar', options: { type: 'year-month-picker', format: 'Mon/YYYY' } }, ] }] }); } } ``` ### Calendar Date Validations In the example below, `filterOptions` is used to overwrite the column configuration `validRange` just before the edit. The rule is that the last column cannot have a date after the previous column date. Additionally, the onbeforechange event behavior blocks the user from pasting or programmatically breaking this rule. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; // Filter option will change the column settings just before the edition const filterOptions = (worksheet, cell, x, y, value, config) => { // Get the value of the previous column let previousColumnValue = worksheet.getValueFromCoords(x - 1, y); // Set a valid range to avoid past dates to be selected config.options.validRange = [ previousColumnValue, null ]; // Customized options return config; } export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ ['Roger Taylor', '2019-01-01', '2019-03-01' ], ['Bob Shiran', '2019-04-03', '2019-05-03'], ['Daniel P.', '2018-12-03', '2018-12-03'], ['Karen Roberts', '2018-12-03', '2019-01-03'], ]; // Data grid cell definitions const columns = [ { type:'text', title:'Name', width:'300px', }, { type:'calendar', title:'From', options: { format:'DD/MM/YYYY' }, width:'150px', }, { type:'calendar', title:'To', options: { format:'DD/MM/YYYY' }, filterOptions: filterOptions, width:'150px', }, ]; // Event const onbeforechange = (worksheet, cell, x, y, value) => { // Valid only for second column if (x == 2 && value) { // Get the value of the previous column let previousColumnValue = worksheet.getValueFromCoords(x - 1, y); if (previousColumnValue > value) { cell.style.border = '1px solid red'; // Return nothing return ''; } else { cell.style.border = ''; } } return value; } // Render data grid component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Filter the options in real time before opening the editor const filterOptions = function(worksheet, cell, x, y, value, config) { // Get the value of the previous column let previousColumnValue = worksheet.getValueFromCoords(x - 1, y); // Set a valid range to avoid past dates to be selected config.options.validRange = [ previousColumnValue, null ]; // Customized options return config; } // Create component @Component({ selector: "app-root", template: `
`; }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [ ['Roger Taylor', '2019-01-01', '2019-03-01' ], ['Bob Shiran', '2019-04-03', '2019-05-03'], ['Daniel P.', '2018-12-03', '2018-12-03'], ['Karen Roberts', '2018-12-03', '2019-01-03'], ], columns: [ { type:'text', title:'Name', width:'300px', }, { type:'calendar', title:'From', options: { format:'DD/MM/YYYY' }, width:'150px', }, { type:'calendar', title:'To', options: { format:'DD/MM/YYYY' }, filterOptions: filterOptions, width:'150px', }, ], worksheetName: 'Rules', }], onbeforechange: function(worksheet, cell, x, y, value) { // Valid only for second column if (x == 2 && value) { // Get the value of the previous column let previousColumnValue = worksheet.getValueFromCoords(x - 1, y); if (previousColumnValue > value) { cell.style.border = '1px solid red'; // Return nothing return ''; } else { cell.style.border = ''; } } return value; } }); } } ``` ### International Calendar Configurations To translate the text in the calendar plugin, you can include the `setDictionary` method as below. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; // Send dictionary to the JSS scope jspreadsheet.setDictionary({ // Other entries (...) // Calendar specific entries 'Jan': 'Jan', 'Feb': 'Fev', 'Mar': 'Mar', 'Apr': 'Abr', 'May': 'Mai', 'Jun': 'Jun', 'Jul': 'Jul', 'Aug': 'Ago', 'Sep': 'Set', 'Oct': 'Out', 'Nov': 'Nov', 'Dec': 'Dez', 'January': 'Janeiro', 'February': 'Fevereiro', 'March': 'Março', 'April': 'Abril', 'May': 'Maio', 'June': 'Junho', 'July': 'Julho', 'August': 'Agosto', 'September': 'Setembro', 'October': 'Outubro', 'November': 'Novembro', 'December': 'Dezembro', 'Sunday': 'Domingo', 'Monday': 'Segunda', 'Tuesday': 'Terca', 'Wednesday': 'Quarta', 'Thursday': 'Quinta', 'Friday': 'Sexta', 'Saturday': 'Sabado', 'Done': 'Feito', 'Reset': 'Apagar', 'Update': 'Atualizar', }); export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ { type: 'calendar', options: { startingDay: 1 } }, { type: 'calendar', options: { startingDay: 1 } }, { type: 'calendar', options: { startingDay: 1 } }, { type: 'calendar', options: { startingDay: 1 } }, ] // Render data grid component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" //The dictionary can be defined as below const dictionary = { // Other entries (...) // Calendar specific entries 'Jan': 'Jan', 'Feb': 'Fev', 'Mar': 'Mar', 'Apr': 'Abr', 'May': 'Mai', 'Jun': 'Jun', 'Jul': 'Jul', 'Aug': 'Ago', 'Sep': 'Set', 'Oct': 'Out', 'Nov': 'Nov', 'Dec': 'Dez', 'January': 'Janeiro', 'February': 'Fevereiro', 'March': 'Março', 'April': 'Abril', 'May': 'Maio', 'June': 'Junho', 'July': 'Julho', 'August': 'Agosto', 'September': 'Setembro', 'October': 'Outubro', 'November': 'Novembro', 'December': 'Dezembro', 'Sunday': 'Domingo', 'Monday': 'Segunda', 'Tuesday': 'Terca', 'Wednesday': 'Quarta', 'Thursday': 'Quinta', 'Friday': 'Sexta', 'Saturday': 'Sabado', 'Done': 'Feito', 'Reset': 'Apagar', 'Update': 'Atualizar', } // Send dictionary to the JSS scope jspreadsheet.setDictionary(dictionary); // Create component @Component({ selector: "app-root", template: `
`; }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ minDimensions: [4,4], columns: [ { type: 'calendar', options: { startingDay: 1 } }, { type: 'calendar', options: { startingDay: 1 } }, { type: 'calendar', options: { startingDay: 1 } }, { type: 'calendar', options: { startingDay: 1 } }, ] }] }); } } ``` ## Calendar Component Library ### CalendarJS - Full Calendar & Scheduling Solution [CalendarJS](https://calendarjs.com/docs/calendar) is a complete calendar and scheduling platform (like Google Calendar) for building calendar applications outside the spreadsheet. Perfect for: - **Event scheduling systems** with drag & drop - **Appointment calendars** and booking systems - **Timeline displays** for project management - **Date pickers and filters** for your spreadsheet data - **Resource scheduling** and availability tracking **CalendarJS Includes:** - **Calendar** - Date picker component with range selection (3.18KB) - **Schedule** - Event scheduler with day/week views & drag-drop (4.2KB) - **Timeline** - Chronological event display - Advanced keyboard navigation with ARIA support - Excel-compatible date formats **[Explore CalendarJS →](https://calendarjs.com/docs/calendar)** | **[View Schedule Component →](https://calendarjs.com/docs/schedule)** --- ### LemonadeJS Calendar - Reactive Date Picker For reactive applications, [LemonadeJS Calendar](https://lemonadejs.net/docs/plugins/calendar) provides two-way data binding with your spreadsheet state. **Use Cases:** - Reactive filters that automatically update the spreadsheet - Real-time date synchronization across components - Vue/React-like reactive patterns **[View LemonadeJS Calendar Documentation →](https://lemonadejs.net/docs/plugins/calendar)** --- ### Jspreadsheet Pro - Enhanced Calendar Features Upgrade to [Jspreadsheet Pro](https://jspreadsheet.com/docs/date) for enhanced calendar functionality including: - LemonadeJS Calendar integration - Improved accessibility and ARIA support - Additional date operations and formulas - Advanced customization options **[View Jspreadsheet Pro Date Documentation →](https://jspreadsheet.com/docs/date)** --- ### Comparison: Calendar Options for Jspreadsheet CE | Feature | Jspreadsheet CE Date | CalendarJS | LemonadeJS Calendar | Jspreadsheet Pro | |----------------------------|---------------------|------------|---------------------|------------------| | **In-cell date editing** | ✓ | - | - | ✓ | | **External date picker** | - | ✓ | ✓ | ✓ | | **Reactive binding** | - | - | ✓ | - | | **Excel date format** | ✓ | ✓ | ✓ | ✓ | | **ARIA support** | Basic | ✓✓ | ✓✓ | ✓✓ | | **Range selection** | ✓ | ✓ | ✓ | ✓ | | **Best for** | CE grid cells | Standalone | Reactive apps | Pro grid cells | --- ## Related content **Calendar Components:** - [CalendarJS](https://calendarjs.com/docs/calendar) - Standalone calendar and date picker - [LemonadeJS Calendar](https://lemonadejs.net/docs/plugins/calendar) - Reactive calendar component - [jSuites Calendar](https://jsuites.net/docs/javascript-calendar) - Calendar used by Jspreadsheet CE - [Jspreadsheet Pro Date](https://jspreadsheet.com/docs/date) - Enhanced calendar features **Related Jspreadsheet CE Features:** - [Cell Formatting](./format) - Format dates in cells - [Custom Formulas](./custom-formulas) - Create date calculations - [Headers](./headers) - Column headers and titles ================================================ FILE: docs/jspreadsheet/docs/javascript-dropdown.md ================================================ title: JavaScript Dropdown and Autocomplete Editors keywords: Jspreadsheet, data grid, JavaScript dropdown, autocomplete, Excel-like dropdown, dynamic dropdown, dropdown customization, interactive grid description: Learn how to implement and configure dropdown and autocomplete editors in Jspreadsheet, including dynamic settings and conditional logic for enhanced data input. # JavaScript Dropdown Jspreadsheet CE provides a versatile dropdown column type with features like: - Dropdowns from arrays, JSON, or key-value objects - Multiple selection and searchable options - Custom rendering styles, including icons and grouped options ## Dropdown Component Library ### jSuites Dropdown - CE Component Jspreadsheet CE uses [jSuites Dropdown](https://jsuites.net/docs/dropdown) as the underlying dropdown component: - Lightweight vanilla JavaScript - Simple API - No framework dependencies - Perfect for basic dropdown needs **[View jSuites Dropdown Documentation →](https://jsuites.net/docs/dropdown)** ### Upgrade to Jspreadsheet Pro [Jspreadsheet Pro](https://jspreadsheet.com/docs/dropdown-and-autocomplete) offers advanced dropdown features not available in CE: - **Conditional Dropdowns:** Options change based on other cell values - **Dynamic Ranges:** Link dropdowns to cell ranges (e.g., Sheet1!A1:A4) - **Remote Search:** Autocomplete from backend APIs with JWT support - **Enhanced Performance:** Uses LemonadeJS Dropdown (3x faster than jSuites) - **Better Accessibility:** Improved ARIA support and keyboard navigation - **Professional Support:** Commercial license with dedicated support **[Explore Jspreadsheet Pro Features →](https://jspreadsheet.com/docs/dropdown-and-autocomplete)** ### LemonadeJS Dropdown - Standalone Component For using dropdowns outside spreadsheets, [LemonadeJS Dropdown](https://lemonadejs.net/docs/plugins/dropdown) offers: - High-performance standalone dropdown - Framework integration (Vue, React, Angular) - This is what Jspreadsheet Pro uses internally - Perfect for forms and custom applications **[View LemonadeJS Dropdown →](https://lemonadejs.net/docs/plugins/dropdown)** --- ### Feature Comparison: CE vs Pro | Feature | CE (jSuites) | Pro (LemonadeJS) | |---------|--------------|------------------| | Basic Dropdowns | ✓ | ✓ | | Autocomplete | ✓ | ✓✓ Enhanced | | Multiple Selection | ✓ | ✓ | | Images/Icons | ✓ | ✓ | | **Conditional Dropdowns** | ❌ | ✓ Pro Only | | **Dynamic Ranges** | ❌ | ✓ Pro Only | | **Remote Search** | ❌ | ✓ Pro Only | | Performance | Good | ✓✓ 3x Faster | | Accessibility | Basic | ✓✓ ARIA Enhanced | | License | MIT | Commercial | | Support | Community | ✓ Dedicated | **[Upgrade to Jspreadsheet Pro →](https://jspreadsheet.com/docs/dropdown-and-autocomplete)** --- ## Documentation ### Dropdown Settings The Jspreadsheet CE supports various attributes for the dropdown column type. | Property | Description | |-------------------------|--------------------------------------------------------------------------------| | `source: Items[]` | Array of items to populate the dropdown. | | `url: String` | Fetch dropdown data from a remote URL. | | `multiple: Boolean` | Enable selection of multiple options. | | `delimiter: String` | Define the delimiter for multiple selections. Default: `';'`. | | `autocomplete: Boolean` | Enable autocomplete for the dropdown. | #### Extended Options Extended options can be defined using the `options` property within the column. | Property | Description | |--------------------------|---------------------------------------------------------------------| | `type: String` | Render type: `default` \| `picker` \| `searchbar`. | | `placeholder: String` | Placeholder text for instructions. | ### Properties of an Item An object with the following attributes defines each option in the dropdown: | Property | Description | |---------------------|-----------------------------------------| | `id: mixed` | Key of the item. | | `value: string` | Value of the item. | | `title: string` | Description of the item. | | `image: string` | Icon for the item. | | `group: string` | Name of the group the item belongs to. | | `synonym: array` | Keywords to help find the item. | | `disabled: boolean` | Indicates if the item is disabled. | | `color: number` | Color associated with the item. | | `icon: string` | Material icon keyword. | | `tooltip: string` | Instructions shown on mouse over. | ## Examples ### Autocomplete and Multiple Options The example below demonstrates the first column with autocomplete enabled and multiple options active. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ ['US', 'Wholemeal', 'Yes', '2019-02-12'], ['CA;US;GB', 'Breakfast Cereals', 'Yes', '2019-03-01'], ['CA;BR', 'Grains', 'No', '2018-11-10'], ['BR', 'Pasta', 'Yes', '2019-01-12'], ]; // Columns const columns = [ { type:'dropdown', width:'300px', title: 'Product Origin', url:'/jspreadsheet/countries', autocomplete:true, multiple:true }, { type:'text', width:'200px', title:'Description' }, { type:'dropdown', width:'150px', title:'Stock', source:['No','Yes'] }, { type:'calendar', width:'150px', title:'Best before', format:'DD/MM/YYYY' } ]; // Render data grid component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; @Component({ standalone: true, selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [ ['US', 'Wholemeal', 'Yes', '2019-02-12'], ['CA;US;GB', 'Breakfast Cereals', 'Yes', '2019-03-01'], ['CA;BR', 'Grains', 'No', '2018-11-10'], ['BR', 'Pasta', 'Yes', '2019-01-12'], ], columns: [ { type:'dropdown', width:'300px', title:'Product Origin', url:'/jspreadsheet/countries', autocomplete:true, multiple:true }, { type:'text', width:'200px', title:'Description' }, { type:'dropdown', width:'150px', title:'Stock', source:['No','Yes'] }, { type:'calendar', width:'150px', title:'Best before', format:'DD/MM/YYYY' }, ], }] }); } } ``` ### Group, Images, and Render Options Enhance the user experience with a responsive and visually enriched data picker. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ [1, 'Morning'], [2, 'Morning'], [3, 'Afternoon'], [3, 'Evening'], ]; // Columns const columns = [ { type:'dropdown', title:'Category', width:'300', source:[ { id:'1', name:'Jorge', image:'img/2.jpg', title:'Admin', group:'Secretary' }, { id:'2', name:'Cosme Sergio', image:'img/2.jpg', title:'Teacher', group:'Docent' }, { id:'3', name:'Rose Mary', image:'img/3.png', title:'Teacher', group:'Docent' }, { id:'4', name:'Fernanda', image:'img/3.png', title:'Admin', group:'Secretary' }, { id:'5', name:'Roger', image:'img/3.png', title:'Teacher', group:'Docent' }, ] }, { type:'dropdown', title:'Working hours', width:'200', source:['Morning','Afternoon','Evening'], options: { type:'picker' }, } ]; // Render data grid component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-cw"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-cw/dist/jspreadsheet.css"; @Component({ selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [ [1, 'Morning'], [2, 'Morning'], [3, 'Afternoon'], [3, 'Evening'], ], columns: [ { type:'dropdown', title:'Category', width:'300', source:[ { id:'1', name:'Jorge', image:'img/2.jpg', title:'Admin', group:'Secretary' }, { id:'2', name:'Cosme Sergio', image:'img/2.jpg', title:'Teacher', group:'Docent' }, { id:'3', name:'Rose Mary', image:'img/3.png', title:'Teacher', group:'Docent' }, { id:'4', name:'Fernanda', image:'img/3.png', title:'Admin', group:'Secretary' }, { id:'5', name:'Roger', image:'img/3.png', title:'Teacher', group:'Docent' }, ] }, { type:'dropdown', title:'Working hours', width:'200', source:['Morning','Afternoon','Evening'], options: { type:'picker' }, }, ], }] }); } } ``` ================================================ FILE: docs/jspreadsheet/docs/merged-cells.md ================================================ title: Spreadsheet Merged Cells keywords: Jspreadsheet, Jexcel, data grid, JavaScript, merged cells, react merged cells, excel-like merged cells, spreadsheet merged cells, merge cells functionality, data grid cell merging description: Merged cells in Jspreadsheet allow combining multiple cells into one. This section covers the settings, methods, and events related to merging spreadsheet cells. # Merged cells This section covers how to create and manage merged cells in Jspreadsheet to combine adjacent cells into one. It includes details on settings, methods, and events for merging, unmerging, programmatically handling merged ranges, and managing alignment and formatting. ## Documentation ### Methods The following methods allow for the programmatic management of merged cells. | Method | Description | |-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `setMerge` | Sets merged cells based on a specified number of columns and rows.
@param `cellName` - Name of a cell. If it is a falsy value, this method merges the selected cells in the table and ignores all parameters of this method.
@param `colspan` - Number of columns this merge occupies.
@param `rowspan` - Number of rows this merge occupies.
`worksheetInstance.setMerge(cellName?: string, colspan?: number, rowspan?: number,): null \| undefined;` | | `getMerge` | Get information from one or all merged cells.
@param `cellName` - Cell name. If it is a falsy value, it returns the information of all merges. If the given cell is not the anchor of a merge, it returns null.
`worksheetInstance.getMerge(cellName?: string): Record \| [number, number] \| null;` | | `removeMerge` | Remove a merge.
@param `cellName` - Merge anchor cell.
@param `data` - Data to be placed in cells released from the merge.
`worksheetInstance.removeMerge(cellName: string, data?: CellValue[]): void;`. | | `destroyMerge`{.nowrap} | Removes all merged cells.
`worksheetInstance.destroyMerge(): void;` | ### Events Spreadsheet merge cells related events. | Event | Description | |-----------|-----------------------------------------------------------------------------------------------------------------------------| | `onmerge` | Occurs when a merge is created.
`onmerge(instance: WorksheetInstance, merges: Record): void;` | ### Initial Settings Initial properties for merged cells in the spreadsheet. | Property | Description | |-----------------------------------------------|-------------------------------------------------------------| | `mergeCells: Record;` | Allow the user to define the initial default merged cells. | ## Examples A basic example illustrates how to initialize and programmatically modify merged cell definitions. Open this [merged cells example](https://jsfiddle.net/spreadsheet/gLc0a1x2/) on JSFiddle. ```html

``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { const spreadsheet = useRef(); const log = useRef(); const worksheets = [{ data: [ ['Mazda', 2001, 2000, '2006-01-01 12:00:00'], ['Peugeot', 2010, 5000, '2005-01-01 13:00:00'], ['Honda Fit', 2009, 3000, '2004-01-01 14:01:00'], ['Honda CRV', 2010, 6000, '2003-01-01 23:30:00'], ], columnDrag: true, worksheetName: 'Merged Cells', minDimensions: [50, 50], tableOverflow: true, tableWidth: '800px', tableHeight: '300px', columns: [ { type: 'text', width: '300px', title: 'Model', }, { type: 'text', width: '80px', title: 'Year', }, { type: 'text', width: '100px', title: 'Price', }, { type: 'calendar', width: '150px', title: 'Date', options: { format: 'DD/MM/YYYY HH24:MI', time: 1, } }, ], mergeCells: { A1: [2,2] } }] return ( <>
) } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; @ViewChild("log") log: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { toolbar: true, worksheets: [{ data: [ ['Mazda', 2001, 2000, '2006-01-01 12:00:00'], ['Peugeot', 2010, 5000, '2005-01-01 13:00:00'], ['Honda Fit', 2009, 3000, '2004-01-01 14:01:00'], ['Honda CRV', 2010, 6000, '2003-01-01 23:30:00'], ], columnDrag: true, worksheetName: 'Merged Cells', minDimensions: [50, 5000], tableOverflow: true, tableWidth: '800px', tableHeight: '300px', columns: [ { type: 'text', width: '300px', title: 'Model', }, { type: 'text', width: '80px', title: 'Year', }, { type: 'text', width: '100px', title: 'Price', }, { type: 'calendar', width: '150px', title: 'Date', options: { format: 'DD/MM/YYYY HH24:MI', time: 1, } }, ], mergeCells: { A1: [2, 2] } }] }); } getMerge() { this.log.nativeElement.innerHTML = JSON.stringify(this.worksheets[0].getMerge()); } } ``` ## Related Tools - [Jspreadsheet Pro Merged Cells](https://jspreadsheet.com/docs/merged-cells) - Advanced merge operations with batch methods (Pro) ================================================ FILE: docs/jspreadsheet/docs/meta-information.md ================================================ title: Data Grid Cell Meta Information keywords: Jspreadsheet, Jexcel, data grid, JavaScript, excel-like features, spreadsheet meta information, cell meta information, hidden metadata in cells, hidden data in Jspreadsheet, data grid meta information, cell metadata management, managing hidden metadata, storing additional information in cells, Jspreadsheet cell properties, cell meta information methods, data grid customization description: Jspreadsheet allows you to store hidden metadata in cells, enabling advanced customization and control over data grid behavior. # Data Grid Cell Meta Information The cell meta information feature allows you to store hidden metadata in cells, which is invisible to users. This feature helps track additional information or manage custom states. This guide explains how to set, retrieve, and reset metadata in Jspreadsheet. > Meta information is managed programmatically and does not have a visible interface. ## Documentation ### Methods Here are the main methods for managing cell meta information: | Method | Description | |-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `getMeta` | Get meta information from one or all cells.
@param `cell` - Cell name. If it is a falsy value, the metadata of all cells is returned.
`worksheetInstance.getMeta(cell?: string): any;` | | `setMeta` | Set a property on a cell's meta information.
@param `cellName` - Cell name.
@param `key` - Property name.
@param `value` - Property value.
`worksheetInstance.setMeta(cellName: string, key: string, value: string): void;`

Remove current and define new meta information for one or more cells.
@param `newMeta` - Object with the new meta information.
`worksheetInstance.setMeta(newMeta: Record>): void;` | ### Events Jspreadsheet emits an event when cell meta information is changed. This event allows developers to track or react to metadata changes. | Event | Description | |-----------------|-----------------------------------------------------------------------------------| | `onchangemeta` | `onchangemeta(instance: WorksheetInstance, cellName: Record): void;` | ### Initial Settings You can initialize Jspreadsheet with predefined meta information for cells. The `meta` property allows setting this information during grid initialization. | Property | Description | |---------------------------------------------|---------------------------------------------| | `meta: Record>` | Defines initial meta information for cells. | ### Use Cases for Meta Information - **Tracking User Actions**: Use meta information to store data about user actions, such as edit history or validation status for each cell. - **Custom Data**: Store custom information such as IDs, statuses, or other data that doesn't need to be visible to users but is essential for backend processing. ## Examples Here are examples of how to use the `getMeta`, `setMeta`, and `resetMeta` methods to manage metadata in Jspreadsheet. ### Basic Meta Information Example This example demonstrates how to set and get meta information programmatically: ```html
``` ```jsx import React, { useRef, useEffect } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { const spreadsheet = useRef(); const data = [ ['Apple', 'Banana'], ['Orange', 'Pineapple'] ] const columns = [ { width: 100 }, { width: 100 } ] const meta = { A1: { category: 'Fruit', id: '123' }, B1: { category: 'Fruit', id: '124' } } useEffect(() => { if (spreadsheet.current) { spreadsheet.current[0].setMeta('B2', 'category', 'Citrus'); console.log(spreadsheet.current[0].getMeta('A1')); } }, []) return ( <> ) } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [ ['Apple', 'Banana'], ['Orange', 'Pineapple'] ], columns: [ { width: 100 }, { width: 100 } ], // Initial meta information meta: { A1: { category: 'Fruit', id: '123' }, B1: { category: 'Fruit', id: '124' } } }], }); // Set meta information for B2 this.worksheets[0].setMeta('B2', 'category', 'Citrus'); // Get meta information for A1 console.log(this.worksheets[0].getMeta('A1')); } } ``` ### Interacting with Meta Information Programmatically You can interact with cell meta information at any point during runtime, either to store or retrieve hidden data that can be used for various features: #### Set meta information for multiple cells {.ignore} ```javascript spreadsheet[0].setMeta({ A1: { category: 'Fruit', id: '123' }, B2: { category: 'Citrus', id: '125' } }); ``` #### Get all meta information {.ignore} ```javascript let allMeta = spreadsheet[0].getMeta(null); console.log(allMeta); ``` ### Batch Meta Information Reset You can reset the meta information for multiple cells or for all cells in a spreadsheet. This example demonstrates how to reset metadata for specific cells: {.ignore} ```javascript spreadsheet[0].resetMeta(['A1', 'B2', 'C2']); ``` ### Working Example For a working example of how to interact with meta information in a Jspreadsheet grid, check out this [Data Grid Meta Information](https://jsfiddle.net/spreadsheet/vauo24ws/) example on JSFiddle. ================================================ FILE: docs/jspreadsheet/docs/nested-headers.md ================================================ title: Data Grid Nested Headers keywords: Jspreadsheet, Jexcel, data grid, JavaScript, spreadsheet, data tables, nested headers, hierarchical column headers, nested header customization, header hierarchy description: Learn to enhance Jspreadsheet data grids with hierarchical column headers. # Data Grid Nested Headers This section covers creating spreadsheets with nested headers. ## Documentation ### Initial Settings Learn how to generate a new spreadsheet containing nested headers. | Property | Description | |----------------------------------------------------------------------------------------|--------------------------------------| | `nestedHeaders: { id?: string, colspan?: number; title?: string; align?: string;}[][]` | Worksheet nested header definitions. | ## Examples ### Nested Header Example The example below demonstrates a basic configuration for nested headers in a JSS spreadsheet. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ ['BR', 'Cheese', 1], ['CA', 'Apples', 0], ['US', 'Carrots', 1], ['GB', 'Oranges', 0], ]; // Columns const columns = [ { type: 'autocomplete', title: 'Country', width: '200px' }, { type: 'dropdown', title: 'Food', width: '100px', source: ['Apples','Bananas','Carrots','Oranges','Cheese'] }, { type: 'checkbox', title: 'Stock', width: '100px' }, { type: 'number', title: 'Price', width: '100px' }, ]; // Nested headers const nestedHeaders = [ [ { title: 'Supermarket information', colspan: '8', }, ], [ { title: 'Location', colspan: '1', }, { title: ' Other Information', colspan: '2' }, { title: ' Costs', colspan: '5' } ], ]; // Render component return ( <> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [ ['BR', 'Cheese', 1], ['CA', 'Apples', 0], ['US', 'Carrots', 1], ['GB', 'Oranges', 0], ], columns: [ { type: 'autocomplete', title: 'Country', width: '200px' }, { type: 'dropdown', title: 'Food', width: '100px', source: ['Apples','Bananas','Carrots','Oranges','Cheese'] }, { type: 'checkbox', title: 'Stock', width: '100px' }, { type: 'number', title: 'Price', width: '100px' }, ], minDimensions: [8,4], nestedHeaders:[ [ { title: 'Supermarket information', colspan: '8', }, ], [ { title: 'Location', colspan: '1', }, { title: ' Other Information', colspan: '2' }, { title: ' Costs', colspan: '5' } ], ] }] }); } } ``` ### More Examples Explore a working example of a JSS [spreadsheet with nested headers](https://jsfiddle.net/spreadsheet/0nwh5u71/) that updates programmatically on JSFiddle. ## Related Tools - [Spreadsheet Headers](/jspreadsheet/docs/headers) - Basic column header configuration - [Data Grid Footers](/jspreadsheet/docs/footers) - Add footer rows with calculations - [Jspreadsheet Pro Headers](https://jspreadsheet.com/docs/headers) - Advanced header features with styling (Pro) ================================================ FILE: docs/jspreadsheet/docs/pagination.md ================================================ title: Spreadsheet Pagination keywords: Jspreadsheet, Jexcel, data grid, JavaScript, data grid pagination, large data visualization, efficient data grid, high-performance data grid, paginated data grid, paginated data visualization description: The spreadsheet pagination feature enables efficient handling and visualization of large datasets. # Spreadsheet Pagination The spreadsheet pagination feature can manage large datasets by rendering a specified number of rows per page and offering a navigation index for quick access to different sections. This section details the settings, methods, and events related to pagination. ## Documentation ### Methods The following methods are related to pagination. | Method | Description | |------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------| | `page` | Navigate to the specified page number.
@param `pageNumber` - Page number (starting at 0).
`worksheetInstance.page(pageNumber: number): void;` | | `whichPage` | Get the page index of a row.
@param `cell` - Row index.
`worksheetInstance.whichPage(cell: number): number;` | | `quantiyOfPages` | `worksheet.quantiyOfPages() : number`
Get the total number of pages available. | ### Events This event is triggered when the user changes the page. | Event | Description | |-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `onchangepage`{.nowrap} | After the page has changed.
@param `instance` - Instance of the worksheet where the change occurred.
@param `newPageNumber` - Page the worksheet is on.
@param `oldPageNumber` - Page the worksheet was on.
@param `quantityPerPage` - Maximum number of lines on pages.
`onchangepage(instance: WorksheetInstance, newPageNumber: number, oldPageNumber: number, quantityPerPage: number): void;` | ### Initial Settings Initial configuration related to the pagination of your data grid. | Property | Description | |-------------------------------|--------------------------------------------------------------------| | `pagination: number` | The number of items per page | | `paginationOptions: number[]` | The options for the user to select the number of results per page. | ## Examples ### Data Grid Search and Pagination Enabling search and pagination features during the spreadsheet initialization. ```html

``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Columns const columns = [ { type:'text', width:80 }, { type:'text', width:200 }, { type:'text', width:100 }, { type:'text', width:200 }, { type:'text', width:100 }, ]; // Event const onchangepage = (el, pageNumber, oldPage) => { console.log('New page: ' + pageNumber); } // Render component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" @Component({ standalone: true, selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ csv: '/tests/demo.csv', csvHeaders: true, search: true, pagination: 10, paginationOptions: [10,25,50,100], columns: [ { type:'text', width:80 }, { type:'text', width:200 }, { type:'text', width:100 }, { type:'text', width:200 }, { type:'text', width:100 }, ], }], onchangepage: function(el, pageNumber, oldPage) { console.log('New page: ' + pageNumber); } }); } } ``` ## Related Tools - [Data Grid Search](/jspreadsheet/docs/search) - Search functionality for data grids - [Data Grid Filters](/jspreadsheet/docs/filters) - Filter data before pagination - [Jspreadsheet Pro Pagination](https://jspreadsheet.com/docs/pagination) - Advanced pagination with custom controls (Pro) ================================================ FILE: docs/jspreadsheet/docs/plugins.md ================================================ title: Jspreadsheet CE Plugins keywords: Jspreadsheet, spreadsheets, plugins, add-ons, feature extensions, customization, free plugins, premium plugins, custom plugins description: Develop and distribute plugins for Jspreadsheet CE to encapsulate advanced features, enhance integration, and extend the core functionality of your spreadsheets. # Plugins Plugins help developers integrate multiple components with Jspreadsheet core features, such as the toolbar, context menu, event handling, etc. Their modular design simplifies development, making distribution and reuse more efficient within Jspreadsheet. ## Documentation ### Methods Customize Jspreadsheet by overriding these methods to add or enhance features like the toolbar, context menu, event handling, or server-side data persistence. | Method | Description | |---------------|--------------------------------------------------------------------------------------------------------------------------------------| | `beforeinit` | Before adding a new worksheet.
`beforeinit(worksheet: Object, config: Object): void \| object` | | `init` | When a new worksheet is added.
`init(worksheet: Object): void` | | `onevent` | Called for every spreadsheet event.
`onevent(event: String, a?: any, b?: any, c?: any, d?: any): void` | | `persistence` | Handles server-side data persistence.
`persistence(worksheet: Object, method: String, args: Array): void` | | `contextMenu` | When the context menu opens.
`contextMenu(worksheet: Object, x: Number, y: Number, e: Object, items: [], section: String): void` | | `toolbar` | When the toolbar is created or clicked.
`toolbar(worksheet: Object, items: Array): void` | ### Worksheet Options You can define custom options for each worksheet using the `pluginOptions` property. ### Basic Implementation Below is a basic implementation example that can be used as a reference for defining custom worksheet options. {.ignore} ```javascript const newPlugin = (function() { // Plugin object let plugin = {}; /** * It will be executed for every new worksheet */ plugin.init = function(worksheet) { } /** * Jspreadsheet events */ plugin.onevent = function() { // It would be executed in every single event and can be used to customize actions } /** * It would be call every single time persistence is required * @param {object} worksheet - worksheet * @param {string} method - action executed * @param {object} args - depending on the action. */ plugin.persistence = function(worksheet, method, args) { // Different options are used depending on the action performed. } /** * Run on the context menu * @param instance Jexcel Spreadsheet Instance * @param x coordinates from the clicked cell * @param y coordinates from the clicked cell * @param e click object * @param items current items in the contextMenu */ plugin.contextMenu = function(instance, x, y, e, items) { // Can be used to overwrite the contextMenu return items; } /** * Run on toolbar * @param instance Jexcel Spreadsheet Instance * @param items current items in the toolbar */ plugin.toolbar = function(instance, items) { // Can be used to overwrite the toolbar return items; } // Any startup configuration goes here // (...) // Return the object return plugin; }); ``` ## Examples The following code is a working example of a plugin in action. ### Spreadsheet properties update The properties plugin allow the user to change some of the spreadsheet settings, through a new option included in the context menu. {.small} Right-click in any cell and choose the last option in the context menu. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; // Installation npm install @jspreadsheet/properties import properties from "@jspreadsheet/properties"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Render data grid component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; // Installation: npm install @jspreadsheet/properties import properties from "@jspreadsheet/properties"; // Create component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [ { minDimensions: [6, 6] }, // Worksheet 1 { minDimensions: [6, 6] }, // Worksheet 2 ], plugins: { properties } }); } } ``` ================================================ FILE: docs/jspreadsheet/docs/react/tests.md ================================================ title: Unit Tests for Jspreadsheet in React keywords: Jspreadsheet, Jexcel, JavaScript, Web-based Applications, Web-based Spreadsheet, Unit Tests, React, Next, NextJS, ReactJS description: Enhance your application quality by creating unit tests for Jspreadsheet inside your React application. # Testing Jspreadsheet in React with Jest ## Introduction In modern React applications, unit testing is essential to ensure components work correctly and prevent regressions. Jspreadsheet integrates easily into React, enabling you to add spreadsheet functionality to your web applications. This guide will walk you through setting up and running tests for Jspreadsheet in a React project using Jest as the testing framework. In this section, we’ll guide you through setting up a **React environment** specifically for testing **Jspreadsheet** using **Jest** and **JSDOM**. This will help you create a solid foundation for **running unit tests** for your Jspreadsheet instances. ## Environment Setup We’ll set up a React environment for testing Jspreadsheet using Jest and JSDOM. Follow these steps to prepare your project. ### Step 1: Clone or Create a Project You can either clone an existing project or create a new React project using `create-next-app`: ```bash npx create-next-app jspreadsheet-react-testing cd jspreadsheet-react-testing ``` Alternatively, clone our setup from [GitHub](??). ### Step 2: Install Dependencies Next, install the necessary dependencies, including `jspreadsheet`, Jest, and `jest-environment-jsdom`: ```bash npm install jspreadsheet-ce@5.0.0-beta.3 npm install --save-dev jest@29.7.0 jest-environment-jsdom@29.7.0 ``` ### Step 3: Configure Jest for Jspreadsheet To integrate Jspreadsheet properly in a Jest testing environment, you'll need to set up JSDOM. First, create a `jest.setup.js` file in the root of your project: ```javascript // jest.setup.js const jspreadsheet = require('jspreadsheet-ce'); // Code that runs between each test beforeEach(() => { if (typeof document !== 'undefined') { jspreadsheet.destroyAll(); if (!global.jspreadsheet && !global.root) { global.jspreadsheet = jspreadsheet; global.root = document.createElement('div'); global.root.style.width = '100%'; global.root.style.height = '100%'; document.body.appendChild(global.root); } } }); ``` Next, configure Jest to use this setup by adding the following entry to your `package.json`: ```json { "jest": { "setupFilesAfterEnv": ["/jest.setup.js"], } } ``` This configuration ensures JSDOM will emulate the DOM environment required to run Jspreadsheet within Jest. ### Step 4: Create a Test Create a folder inside your project if it doesn't exist, then inside this folder create a file named `jspreadsheet.test.js`. ```javascript // */tests/jspreadsheet.test.js /** * @jest-environment jsdom */ test("Testing data", () => { let instance = jspreadsheet(root, { worksheets: [ { data: [ ["Mazda", 2001, 2000], ["Peugeot", 2010, 5000], ["Honda Fit", 2009, 3000], ["Honda CRV", 2010, 6000], ], minDimensions: [4, 4], }, ], }); expect(instance[0].getValue("A1", true)).toEqual("Mazda"); expect(instance[0].getValue("A2", true)).toEqual("Peugeot"); expect(instance[0].getValue("B1", true)).toEqual("2001"); }); ``` This test verifies that a basic Jspreadsheet instance is created and that the data values are correctly placed. You can modify it to check whatever you want to test. ## Running the Tests Ensure you add the following line to the `scripts` section of your `package.json`: ```json "test": "jest" ``` After creating your tests and updating `package.json`, you can run them using the following command: ```bash npm test ``` Jest will run all the tests in your project and display the results in the console. If everything is configured correctly, your tests should pass. ================================================ FILE: docs/jspreadsheet/docs/react.md ================================================ title: React Spreadsheet keywords: Jspreadsheet, Jexcel, javascript, React, data grid, spreadsheet-like controls, React data grid, Jspreadsheet integration, React integration, JavaScript data grid, spreadsheet controls in React description: Description: How to integrate spreadsheet-like features into your React applications with Jspreadsheet. # React Spreadsheet Jspreadsheet CE is a lightweight JavaScript data grid library with spreadsheet controls. It integrates seamlessly with React, enabling developers to create highly interactive and customizable spreadsheet-like application components. This guide walks you through the process of integrating Jspreadsheet CE into a React project. ## Install ### Install the Package Install Jspreadsheet React Data Grid wrapper using NPM. ```bash npm install @jspreadsheet-ce/react@5.0.0-beta.3 ``` ### Import Required Styles Import the necessary Spreadsheet CSS style to your project. {.ignore} ```javascript import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; ``` To ensure icons display correctly, include Material Icons in your application. Add the following code to your main HTML file: {.ignore} ```html ``` ## Example ### React Wrapper Create your first React grid with spreadsheet controls using the Jspreadsheet React wrapper. {.ignore} ```jsx import React, { useRef, useEffect } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jspreadsheet-ce/dist/jspreadsheet.css"; import "jsuites/dist/jsuites.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Render component return ( ); } ``` ### React Component You can create your React component using the library directly for better control. {.ignore} ```jsx import React, { useRef, useEffect } from "react"; import jspreadsheet from "@jspreadsheet-ce/react"; import "jspreadsheet-ce/dist/jspreadsheet.css"; import "jsuites/dist/jsuites.css"; export default function App() { const jssRef = useRef(null); useEffect(() => { // Create the spreadsheet only once if (!jssRef.current.jspreadsheet) { jspreadsheet(jssRef.current, { worksheets: [{ minDimensions: [10, 10] }], }); } }, null); return (
); } ``` ## Overview Jspreadsheet operates with simple objects, including big datasets. It manages object references internally to optimize performance, minimize overhead, and maintain efficient data handling. For this reason, proprietary methods and events are provided to interact with its internal states. ### Integration with React #### States Due to its architecture, Jspreadsheet does not work directly with React States. To integrate, you must declare Jspreadsheet events and use them to synchronize with React. #### Events Events can be declared at the spreadsheet level. Refer to the [events documentation](/jspreadsheet/docs/events) for available events and usage examples. {.ignore} ```jsx import React, { useRef, useEffect } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jspreadsheet-ce/dist/jspreadsheet.css"; import "jsuites/dist/jsuites.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); const afterchanges = function() { // There were changes on the data } // Render component return ( ); } ``` ## Jspreadsheet Pro with React ### Real-Time React Spreadsheet Create a Real-time JavaScript Spreadsheet with React and TypeScript using Jspreadsheet. * [Real-Time Collaboration](https://github.com/jspreadsheet/spreadsheet-react-server) #### More React Spreadsheet Examples * [React Spreadsheet](https://codesandbox.io/p/sandbox/react-components-on-jspreadsheet-zx9zxr) Basic react spreadsheet with translations. * [React Spreadsheet Cell Editors](https://codesandbox.io/s/react-spreadsheet-with-a-custom-editor-ic6h3l) How to create a custom data grid cell editor with React. * [Custom React Components](https://codesandbox.io/s/react-components-on-jspreadsheet-k7wc4c) Integrate a custom React component (Recharts) with Jspreadsheet. * [React Spreadsheet as React Classes](https://codesandbox.io/p/sandbox/react-spreadsheet-kkz3s8) Create a basic react spreadsheet using React classes * [React Data Grid Validations](https://codesandbox.io/s/online-spreadsheet-with-validations-with-jspreadsheetxy777) How to crate a data grid with cell validations * [MUI React as a Custom Editor](https://codesandbox.io/p/sandbox/custom-editors-with-react-mui-y4v8lj) React Calendar with Antd * [Antd React Calendar Cell Editor](https://stackblitz.com/edit/vitejs-vite-kwqcwy) React Calendar with MUI * [MUI React Calendar as a Custom Editor](https://codesandbox.io/p/sandbox/custom-editors-with-react-mui-forked-6hw4vz) #### NextJS integration * [Online XLSX NextJS reader](https://codesandbox.io/s/jspreadsheet-and-nextjs-6fhsz) Creating an online XLSX reader with NextJS and Jspreadsheet * [Import a Excel file to NextJS](https://codesandbox.io/s/nextjs-spreadsheet-52mr2z) How to import an Excel file in NextJS using Jspreadsheet. {.pro} > **Jspreadsheet Pro for React - Professional Spreadsheet Components** > > While Jspreadsheet CE provides core spreadsheet functionality for React, **Jspreadsheet Pro** offers enhanced React integration and enterprise features: > > **Enhanced React Integration:** > - **TypeScript Support:** Full TypeScript definitions for type-safe development > - **React Hooks:** Custom hooks for common spreadsheet operations (useSpreadsheet, useWorksheet) > - **State Management:** Built-in Redux/MobX integration for state management > - **React Context:** Context API integration for global spreadsheet state > - **Server-Side Rendering:** Full SSR/SSG support for Next.js and Gatsby > - **React 18 Features:** Concurrent rendering, automatic batching support > > **Professional Components:** > - **Advanced Editors:** Conditional dropdowns, rich text, HTML editors with React integration > - **Formula System:** 500+ Excel functions with React bindings > - **Conditional Formatting:** Visual rules, data bars, color scales, icon sets > - **Data Validation:** Real-time validation with custom error components > - **Import/Export:** Full Excel (.xlsx) import/export with formatting preservation > - **Charts & Graphs:** Built-in charting components for data visualization > > **Performance & Scale:** > - **Virtual Scrolling:** Handle 100K+ rows with smooth scrolling > - **Lazy Loading:** Load data on-demand for optimal performance > - **Optimized Rendering:** React-optimized rendering for large datasets > - **Web Workers:** Background processing for heavy calculations > - **Memory Management:** Efficient memory usage for large spreadsheets > > **Developer Experience:** > - **Comprehensive Documentation:** React-specific examples and best practices > - **Professional Support:** Priority email and chat support for integration issues > - **Regular Updates:** Continuous improvements and React version compatibility > - **Migration Tools:** Easy migration from CE to Pro with code guides > > **React-Specific Pro Features:** > - **Custom React Components:** Use React components as cell editors > - **Event Integration:** Seamless integration with React event system > - **Component Lifecycle:** Hooks into React component lifecycle > - **Prop Validation:** PropTypes and TypeScript validation > - **Testing Support:** Jest/React Testing Library integration examples > > Perfect for React applications requiring enterprise-grade spreadsheet functionality with professional support. > > **[Explore Jspreadsheet Pro React →](https://jspreadsheet.com/docs/react)** | **[Compare CE vs Pro →](https://jspreadsheet.com/docs/getting-started)** | **[View Pricing →](https://jspreadsheet.com/pricing)** ================================================ FILE: docs/jspreadsheet/docs/readonly.md ================================================ title: Data Grid Read-only Cells keywords: Jspreadsheet, JavaScript, plugins, spreadsheet, read-only cells, data grid, data protection, cell permissions, non-editable cells description: Learn to configure read-only columns or cells in Jspreadsheet, protecting data and restricting user edits for specific cells in your data grid. # Read Only Cells ## Documentation ### Methods The following methods allow programmatic updates to read-only states in your spreadsheets. | Method | Description | |----------------------------------------|---------------------------------------------------------------------------------------------------------| | `setReadOnly(object\|string, boolean)` | Sets the read-only state of a cell.
`setReadOnly(ident: HTMLElement\|string, state: boolean): void` | | `isReadOnly(number, number)` | Checks if a cell is read-only.
`isReadOnly(x: number, y: number): boolean` | ## Examples ### Readonly A basic spreadsheet example with a read-only column and an additional read-only cell. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ ['Mazda', 2001, 2000, 1], ['Peugeot', 2010, 5000, 1], ['Honda Fit', 2009, 3000, 1], ['Honda CRV', 2010, 6000, 0], ]; const columns = [ { type: 'text', title:'Description', width:'200px', readOnly:true, }, { type: 'text', title:'Year', width:'200px' }, { type: 'text', title:'Price', width:'100px', mask:'#.##', }, { type: 'checkbox', title:'Automatic', width:'100px' }, ]; const updateTable = function(el, cell, x, y, source, value, id) { if (x == 2 && y == 2) { cell.classList.add('readonly'); } } // Render component return ( <> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
  1. Click to insert a new blank row at the end of the spreadsheet
  2. Click to insert two new blank rows at the beginning of the spreadsheet
  3. Click to delete the last row
  4. Click to move the first row to the third position
  5. Hide the first row
  6. Show the first row
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { data: [ ['Mazda', 2001, 2000, 1], ['GM', 2010, 5000, 1], ['Honda Fit', 2009, 3000, 1], ['Honda CRV', 2010, 6000, 0], ], columns: [ { type: 'text', title:'Description', width:'200px', readOnly: true, }, { type: 'text', title: 'Year', width: '200px' }, { type: 'text', title: 'Price', width: '100px', mask: '#.##', render: function(td, value, x, y) { if (y === 2) { td.classList.add('readonly'); } } }, { type: 'checkbox', title:'Automatic', width:'100px' }, ], updateTable: function(el, cell, x, y, source, value, id) { if (x == 2 && y == 2) { cell.classList.add('readonly'); } } }); } } ``` ================================================ FILE: docs/jspreadsheet/docs/rows.md ================================================ title: Managing the Data Grid Rows keywords: Jspreadsheet, Jexcel, data grid, JavaScript, Excel-like rows, spreadsheet rows, data table rows, row management, add rows, delete rows, move rows, row manipulation, row events, row settings, row documentation description: Learn to manage rows in Jspreadsheet, including adding, deleting, and moving them. Explore methods, events, and settings for customizing row behaviour in dynamic and integrated data grid applications. # Spreadsheet Rows Row settings in Jspreadsheet define behaviours and attributes, such as unique identifiers, row height, styling, and cell properties like read-only status. This section covers the methods, events, and settings for managing rows in a data grid. ## Documentation ### Methods You can manage rows programmatically using one of the following methods. | Method | Description | |-------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `getHeight` | Retrieves the height of all rows.
`worksheetInstance.getHeight(row?: undefined): string[];`

Retrieves the height of a row.
`worksheetInstance.getHeight(row: number): string;` | | `setHeight` | Sets the height of a row.
@param `row` - Row index.
@param `height` - New height. An integer greater than zero.
`worksheetInstance.setHeight(row: number, height: number): void;` | | `moveRow` | Moves a row to a new position.
@param `rowNumber` - Row index.
@param `newPositionNumber` - New row index.
`worksheetInstance.moveRow(rowNumber: number, newPositionNumber: number): false \| undefined;` | | `insertRow` | Inserts one or more new rows.
@param `mixed` - Number of rows to insert. It can also be an array of values, but in this case, only one row is inserted, whose data is based on the array items. Default: 1.
@param `rowNumber` - Index of the row used as reference for the insertion. Default: last row.
@param `insertBefore` - Insert new rows before or after the reference row. Default: false.
`worksheetInstance.insertRow(mixed?: number \| CellValue[],rowNumber?: number,insertBefore?: number): false \| undefined;` | | `deleteRow` | Deletes one or more rows.
@param `rowNumber` - Row index from which removal starts.
@param `numOfRows` - Number of rows to be removed.
`worksheetInstance.deleteRow(rowNumber?: number, numOfRows?: number): false \| undefined;` | ### Events The following events are related to rows in your spreadsheet. | Event | Description | |---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------| | `onbeforeinsertrow` | Triggered before a new row is inserted. Return `false` to cancel the action.
`onbeforeinsertrow(worksheet: Object, rows: Object[]) => Boolean \| Object[] \| void` | | `oninsertrow` | Triggered after a new row is inserted.
`oninsertrow(worksheet: Object, rows: Object[]) => void` | | `onbeforedeleterow` | Triggered before a row is deleted. Return `false` to cancel the action.
`onbeforedeleterow(worksheet: Object, rows: Number[]) => Number[] \| Boolean \| void` | | `ondeleterow` | Triggered after a row is deleted.
`ondeleterow(worksheet: Object, rows: Number[]) => void` | | `onmoverow` | Triggered after a row is moved to a new position.
`onmoverow(worksheet: Object, origin: Number, destination: Number) => void` | | `onresizerow` | Triggered after the height of one or more rows is changed.
`onresizerow(worksheet: Object, row: Mixed, height: Mixed, oldHeight: Mixed) => void` | ### Initial Settings The following row-related properties are available during spreadsheet initialization. | Property | Description | |---------------------------------|----------------------------------------------------------------------------------------------| | `allowInsertRow: boolean` | Enables the user to insert new rows. `Default: true` | | `allowManualInsertRow: boolean` | Automatically inserts a new row when the user presses Enter on the last row. `Default: true` | | `allowDeleteRow: boolean` | Allows the user to delete rows. `Default: true` | | `rowDrag: boolean` | Enables the user to change the position of a row by dragging and dropping. `Default: true` | | `rowResize: boolean` | Allows the user to resize rows. `Default: true` | | `defaultRowHeight: number` | Sets the default row height. | | `minSpareRows: number` | Specifies the number of mandatory blank rows at the end of the spreadsheet. `Default: none.` | ### Available properties You can initialize the spreadsheet with custom `id`, `text`, and `height` using the following properties: | Property | Description | |-------------------|----------------------------------------------------------------------------------------------| | `id: number` | Unique identifier for the row, which can be used to synchronize the content with a database. | | `height: number` | The row height in pixels. | | `title: string` | The title or name of the row. | ## Examples A basic spreadsheet with a few programmatic methods available. ```html














``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ ['US', 'Cheese', 1000 ], ['CA', 'Apples', 1200 ], ['CA', 'Carrots', 2000 ], ['BR', 'Oranges', 3800 ], ]; // Render component return ( <>





); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
  1. Click to insert a new blank row at the end of the spreadsheet
  2. Click to insert two new blank rows at the beginning of the spreadsheet
  3. Click to delete the last row
  4. Click to move the first row to the third position
  5. Hide the first row
  6. Show the first row
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { data: [ ['US', 'Cheese', 1000 ], ['CA', 'Apples', 1200 ], ['CA', 'Carrots', 2000 ], ['BR', 'Oranges', 3800 ], ], worksheetName: 'Row management', }); } } ``` ================================================ FILE: docs/jspreadsheet/docs/search.md ================================================ title: Data Grid Search keywords: Jspreadsheet, data grid search, JavaScript, Excel-like search functionality, spreadsheet search, search customization, search event handling, row filtering description: Explore search functionality in Jspreadsheet, including methods and events to filter rows and customize search behaviour for specific application requirements. # Data Grid Search The Jspreadsheet's search functionality filters rows using keyword matching, offering flexibility to modify or terminate operations based on application needs. This section details the technical methods and events for customizing search behaviour. {.pro} > #### Differences in the Pro Version > Jspreadsheet Pro allows complete customization of search operations, including events to highlight results, customize search criteria, or integrate with the backend.\ > \ > [Learn more](https://jspreadsheet.com/docs/search){.button} ## Documentation ### Methods The following methods are used to implement and manage search functionality in the spreadsheet: | Method | Description | |---------------|---------------------------------------------------------------------------------------------------------------------------------------------------| | `search` | Searches for rows containing the specified terms.
@param `query` - Text to be searched.
`worksheetInstance.search(query: string): void;` | | `resetSearch` | Resets search terms and displays all rows.
`worksheetInstance.resetSearch(): void;` | ### Initial Settings The following property is available to configure the search functionality during spreadsheet initialization: | Property | Description | |-------------------|-----------------------------------------| | `search: boolean` | Enables or disables the search feature. | ## Examples ### Data Grid with Search and Pagination The example below demonstrates a data grid configured with search functionality and pagination support. ```html

``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Columns const columns = [ { type:'text', width:80 }, { type:'text', width:100 }, { type:'text', width:100 }, { type:'text', width:200 }, { type:'text', width:100 }, ] // Render component return ( <> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { csv: '/tests/demo.csv', csvHeaders: true, search: true, pagination: 10, paginationOptions: [10,25,50,100], columns: [ { type:'text', width:80 }, { type:'text', width:100 }, { type:'text', width:100 }, { type:'text', width:200 }, { type:'text', width:100 }, ], }); } } ``` ## Related Tools - [Data Grid Filters](/jspreadsheet/docs/filters) - Filter data grid columns with custom criteria - [Data Grid Pagination](/jspreadsheet/docs/pagination) - Paginate large datasets - [Jspreadsheet Pro Search](https://jspreadsheet.com/docs/search) - Enhanced search with regex and highlighting (Pro) - [Jspreadsheet Pro Filters](https://jspreadsheet.com/docs/filters) - Advanced filtering capabilities (Pro) ================================================ FILE: docs/jspreadsheet/docs/selection.md ================================================ title: Spreadsheet Selection keywords: Jspreadsheet, Jexcel, data grid, JavaScript, excel-like selection, spreadsheet selection, cell selection, data grid selection, selection methods, range selection, multiple cell selection description: This section covers the properties, events, and methods for managing data grid selections, including range and multiple cell selection. # Spreadsheet Selection This section covers the technical aspects of handling spreadsheet selections in Jspreadsheet, detailing the key properties, events, and methods for managing selection behaviour. {.pro} > #### What You Can Find in the Pro Version > The Pro version of Jspreadsheet enhances selection capabilities, offering features commonly found in advanced spreadsheet software: > - **Non-consecutive selection**: Users can hold `Ctrl` to select multiple ranges at once; > - **Custom borders**: Supports different border colours, which are helpful in real-time collaboration for identifying different users or creating custom features;\ > >\ > [Learn more](https://jspreadsheet.com/docs/selection){.button} ## Documentation ### Methods The following methods manage selections in the Jspreadsheet data grid. #### Main Selection | Method | Description | |--------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `getSelection` | Get the coordinates of the main selection.
`getSelection(preserveOrder: Boolean) : Array \| null` | | `getHighlighted` | Get the coordinates of the highlighted selections.
`getHighlighted() : Array \| null` | | `getRange` | Get the range description of the highlighted cells.
`getRange() : String \| null` | | `getSelectedColumns` | Get the selected columns.
@param `visibleOnly` - If true, the method returns only visible columns.
`worksheetInstance.getSelectedColumns(visibleOnly?: boolean): number[];` | | `getSelectedRows` | Get the selected rows.
@param `visibleOnly` - If true, the method returns only visible rows.
`worksheetInstance.getSelectedRows(visibleOnly?: boolean): number[];` | | `getSelected` | Get the worksheet selected cell names or objects.
@param {Boolean?} columnNameOnly: To get only the cell names as string (true). Get the cell coordinates as an object (false). `worksheetInstance.getSelected(columnNameOnly: Boolean) => []` | | `isSelected` | Verify if the coordinates given are included in the current selection.
`isSelected(x: Number, y: Number) : Boolean` | | `selectAll` | Select all cells available in the data grid.
`worksheetInstance.selectAll(): void;` | | `updateSelectionFromCoords`{.nowrap} | Select cells based on the given coordinates.
@param `x1` - Column index of the first cell of the selection. If omitted or null, rows "y1" through "y2" are selected.
@param `y1` - Row index of the first cell of the selection. If omitted or null, columns "x1" through "x2" are selected.
@param `x2` - Column index of the last cell of the selection. Default: Parameter "x1".
@param `y2` - Row index of the last cell of the selection. Default: Parameter "y1".
`worksheetInstance.updateSelectionFromCoords: (x1: number \| null, y1: number \| null, x2?: number \| null, y2?: number \| null): void;` | | `resetSelection` | Remove the selection.
@returns If there were highlighted cells, it returns 1, otherwise it returns 0.
`worksheetInstance.resetSelection(): 0 \| 1;` | ### Selection Events | Event | Description | |------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `onblur` | Occurs when the table is blurred.
`onblur(instance: WorksheetInstance): void;` | | `onfocus` | Occurs when the table is focused.
`onfocus(instance: WorksheetInstance): void;` | | `onbeforeselection`{.nowrap} | `onbeforeselection(worksheet: Object, x1: Number, y1: Number, x2: Number, y2: Number, e: MouseEvent) : void` | | `onselection` | `onselection(instance: WorksheetInstance, borderLeftIndex: number, borderTopIndex: number, borderRightIndex: number, borderBottomIndex: number, origin: Event \| undefined): void;` | ### Initial Settings | Property | Description | |-------------------------|------------------------------| | `selectionCopy: boolean` | Disable the clone selection. | ## Examples ### Programmatically Data Grid Selection Demonstrates how to select all cells within a worksheet in the grid programmatically. ```html

``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Render component return ( <> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" @Component({ standalone: true, selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [ { minDimensions: [6,6], } ] }); } } ``` ================================================ FILE: docs/jspreadsheet/docs/sorting.md ================================================ title: Data Sorting keywords: Jspreadsheet, spreadsheet, javascript, javascript table sorting, spreadsheet sorting, data grid sorting, custom sorting, data organization, sortable data grid, javascript data grid, column sorting, row sorting, excel-like sorting, Jexcel sorting, spreadsheet controls, data grid controls, dynamic data sorting description: Explore comprehensive information on Jspreadsheet data sorting, including sorting events, programmatic sorting, customization methods, and initial settings. # Data Sorting Jspreadsheet CE enables developers to create custom sorting handlers to override the default data grid sorting behaviour. Rows can be sorted through the context menu, double-clicking column headers, or programmatically using the orderBy method. ## Documentation ### Methods The following method can be invoked to execute sorting programmatically: | Method | Description | | --------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | orderBy | Sort the data grid.
@param {number} columnNumber - Sort by column number
@param {boolean} direction: false (asc), true (desc)
`orderBy(columnNumber: Number, direction: Boolean) : void` | ### Events | Event | Description | | -------------|-----------------------------------------------------------------------------------------------| | onsort | `onsort(worksheet: Object, column: Number, direction: Number, newValue: Array) : void` | ### Initial Settings You can define the sorting behaviour of your spreadsheet using the following properties: | Property | Description | |----------------------------------|-----------------------------------------------------------------------------------------------------------------------------------| | sorting: function | Defines a custom sorting handler. Use this to implement your specific sorting logic.
`sorting(direction: Boolean) : function` | | columnSorting: boolean | Enables or disables column-based sorting for the spreadsheet.`Default: true` | ## Examples ### Basic Sorting The example below demonstrates sorting behaviour across various column types. {.small} Double-click on any `data grid` column header below to observe sorting functionality. ```html

``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); const select = useRef(); // Data const data = [ ['Mazda', 2001, 2000, '2006-01-01', '453.00', '2', '=E1*F1'], ['Peugeot', 2010, 5000, '2005-01-01', '23.00', '5', '=E2*F2'], ['Honda Fit', 2009, 3000, '2004-01-01', '214.00', '3', '=E3*F3'], ['Honda CRV', 2010, 6000, '2003-01-01', '56.11', '2', '=E4*F4'], ]; // Render component return ( <> spreadsheet.current[0].orderBy(select.current.value)} /> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" @Component({ standalone: true, selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; @ViewChild("select") select: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [ ['Mazda', 2001, 2000, '2006-01-01', '453.00', '2', '=E1*F1'], ['Peugeot', 2010, 5000, '2005-01-01', '23.00', '5', '=E2*F2'], ['Honda Fit', 2009, 3000, '2004-01-01', '214.00', '3', '=E3*F3'], ['Honda CRV', 2010, 6000, '2003-01-01', '56.11', '2', '=E4*F4'], ] }] }); } } ``` ### Custom Sorting Handler The example below demonstrates how to customize spreadsheet sorting behaviour using the `sorting` property. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ ['Spreadsheets', 1], ['Grids', 2], ['Tables', 3], ['Plugins', 4], ['', ''], ['', ''], ['', ''], ['', ''], ]; // Columns const columns = [ { type: 'text', width:200 }, { type: 'text', width:400 }, ]; // Sorting handler const sorting = (direction, column) => { return (a, b) => { let valueA = a[1]; let valueB = b[1]; // Consider blank rows in the sorting if (! direction) { return (valueA > valueB) ? 1 : (valueA < valueB) ? -1 : 0; } else { return (valueA > valueB) ? -1 : (valueA < valueB) ? 1 : 0; } } } // Render component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" @Component({ standalone: true, selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [ ['Spreadsheets', 1], ['Grids', 2], ['Tables', 3], ['Plugins', 4], ['', ''], ['', ''], ['', ''], ['', ''], ], columns: [ { type: 'text', width:200 }, { type: 'text', width:400 }, ], }], sorting: function(direction, column) { return function(a, b) { let valueA = a[1]; let valueB = b[1]; // Consider blank rows in the sorting if (! direction) { return (valueA > valueB) ? 1 : (valueA < valueB) ? -1 : 0; } else { return (valueA > valueB) ? -1 : (valueA < valueB) ? 1 : 0; } } } }); } } ``` ## Related Tools - [Data Grid Filters](/jspreadsheet/docs/filters) - Filter data before sorting - [Jspreadsheet Pro Sorting](https://jspreadsheet.com/docs/sorting) - Advanced sorting with multi-column support (Pro) ================================================ FILE: docs/jspreadsheet/docs/style.md ================================================ title: Spreadsheet Style keywords: Jspreadsheet, Jexcel, data grid, JavaScript, Excel-like style, spreadsheet cell style, table style, style, themes, style methods, style events, cell formatting, spreadsheet customization, grid style customization, data grid aesthetics, CSS for data grid, style settings, Jspreadsheet design, dynamic styling description: Learn how to apply CSS styles to cells in Jspreadsheet, including settings, events, and programmatic styling methods. # Spreadsheet Style Jspreadsheet allows flexible styling directly on spreadsheet cells using CSS, thanks to its DOM-based structure. While you can apply external CSS styles to customize the appearance, these styles don’t automatically track changes or save to Jspreadsheet’s internal history. For a more dynamic approach, Jspreadsheet provides built-in settings and methods that let you apply, update, and manage cell styles programmatically. {.pro} > #### What You Can Find in the Pro Version > > The **Pro version** of Jspreadsheet offers enhanced performance and includes: > - A global style property at the spreadsheet level, allowing styles to be reused across different worksheets. > - Support row or column styling using Excel-like syntax, such as `A:A` for columns or `1:1` for rows. > > > \ > [Learn more](https://jspreadsheet.com/docs/style){.button} ## Documentation ### Methods You can manage the spreadsheet cell styles using the following methods: | Method | Description | |-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `getStyle` | Get styles from one or all cells.
@param `cell` - Name or coordinate of a cell. If omitted, returns styles for all cells.
@param `key` - Style property. if specified, returns only that property. Otherwise, it returns all the cell's style properties.
`worksheetInstance.getStyle(cell?: string \| [number, number], key?: string): string \| Record;` | | `setStyle` | Change a single style of one or more cells.
@param `cells` - Name of a cell.
@param `k` - property to be changed.
@param `v` - New property value. If equal to the property's current value and the "force" parameter is false, removes that property from the style.
@param `force` - If true, changes the value of the property even if the cell is read-only. Also, if true, even if the new value of the property is the same as the current one, the property is not removed.
`worksheetInstance.setStyle(cells: string, k: string, v: string, force?: boolean): void;`

Change cell styles.
@param `o` - Object where each key is the name of a cell and each value is the style changes for that cell. Each value can be a string with css styles separated by semicolons or an array where each item is a string with a css style.
@param `k` - It is not used.
@param `v` - It is not used.
@param `force` - If true, changes the value of the property even if the cell is read-only. Also, if true, even if the new value of the property is the same as the current one, the property is not removed.
`worksheetInstance.setStyle(cells: Record, k?: null \| undefined, v?: null \| undefined, force?: boolean): void;` | | `resetStyle`{.nowrap} | Reset styles of one or more cells.
@param `cells` - Object whose keys are the names of the cells that must have their styles reset.
@param `ignoreHistoryAndEvents` - If true, do not add this action to history.
`worksheetInstance.resetStyle(cells: Record, ignoreHistoryAndEvents?: boolean): void;` | ### Events Events related to spreadsheet styling. | Event | Description | |------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `onchangestyle` | Triggered when a style change occurs, returning an object with affected cells and properties.
`onchangestyle(instance: WorksheetInstance, changes: Record): void;` | ### Initial Settings The following property is available during the initialization of the online spreadsheet: | Property | Description | |------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------| | `style: Record`{.nowrap} | Defines initial styles for cells. Each object property corresponds to a cell name or range, with values representing a CSS string. | ## Examples ### Style at the Worksheet Level Apply style definitions directly at the worksheet level. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Style const style = { 'E1': 'background-color: #ccffff;', } // Render component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; @Component({ standalone: true, selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [ { minDimensions: [6,4], style: { 'E1': 'background-color: #ccffff;', }, } ] }); } } ``` ### Programmatic Updates Define cell styles during initialization and modify them using `getStyle` and `setStyle` methods after initialization. ```html

``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); const console = useRef(); const data = [ ['US', 'Cheese', 'Yes', '2019-02-12'], ['CA;US;UK', 'Apples', 'Yes', '2019-03-01'], ['CA;BR', 'Carrots', 'No', '2018-11-10'], ['BR', 'Oranges', 'Yes', '2019-01-12'], ]; const columns = [ { type: 'dropdown', title: 'Product Origin', width: 300, url: '/jspreadsheet/countries', // Remote source for your dropdown autocomplete: true, multiple: true }, { type: 'text', title: 'Description', width: 200 }, { type: 'dropdown', title: 'Stock', width: 100 , source: ['No','Yes'] }, { type: 'calendar', title: 'Best before', width: 100 }, ]; const style = { A1:'background-color: orange;', B1:'background-color: orange;', }; // Render component return ( <> spreadsheet.current[0].setStyle('A2', 'background-color', 'yellow')} /> spreadsheet.current[0].setStyle({ A3:'font-weight: bold;', B3:'background-color: yellow;' })} /> console.current.innerHTML = spreadsheet.current[0].getStyle('A1')} /> console.current.innerHTML = JSON.stringify(spreadsheet.current[0].getStyle())} /> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; @Component({ standalone: true, selector: "app-root", template: `
**Upgrade to Jspreadsheet Pro for Advanced Styling** > > Jspreadsheet CE provides basic programmatic cell styling. **Jspreadsheet Pro** adds professional styling capabilities: > > **Enhanced Styling API:** > - **Global Styles:** Define styles at spreadsheet level, reuse across worksheets > - **Row/Column Styling:** Style entire rows/columns with `A:A` or `1:1` syntax > - **Range Styling:** Apply styles to cell ranges (e.g., `A1:D10`) in one command > - **Style Inheritance:** Cascade styles from worksheet → column → cell > - **Style Templates:** Reusable named style templates for consistent branding > - **Batch Styling:** Style thousands of cells efficiently with optimized API > > **Conditional Styling:** > - **Value-Based Styles:** Auto-apply styles based on cell values (>100 = green) > - **Formula-Based Styles:** Apply styles using conditional formulas > - **Data Bars:** In-cell horizontal bars showing relative values > - **Color Scales:** 2-color and 3-color gradients based on value ranges > - **Icon Sets:** Visual icons (arrows, flags, traffic lights) based on values > - **Duplicate Highlighting:** Auto-highlight duplicate values > > **Rich Text & Advanced Formatting:** > - **Rich Text in Cells:** Bold, italic, underline, colors within single cell > - **Cell-Level Fonts:** Different fonts, sizes, colors per cell > - **Advanced Borders:** All Excel border styles (thick, thin, dashed, double, custom colors) > - **Border Styles:** Top, bottom, left, right, diagonal borders per cell > - **Gradient Fills:** Linear and radial gradients for cell backgrounds > - **Pattern Fills:** Dots, stripes, crosshatch patterns > > **Professional Features:** > - **Style Import/Export:** Import/export Excel styles with full fidelity > - **Theme Support:** Pre-built professional themes (Material, Corporate, Dark mode) > - **Style Events:** Track style changes with advanced event system > - **Performance:** Optimized for styling large datasets (100K+ cells) > - **Responsive Styles:** Different styles for mobile/tablet/desktop > - **Print Styles:** Specific styles for printing/PDF export > > Perfect for dashboards, reports, and applications requiring professional visual presentation. > > **[Explore Pro Styling →](https://jspreadsheet.com/docs/style)** | **[Compare Editions →](https://jspreadsheet.com/docs/getting-started)** | **[View Pricing →](https://jspreadsheet.com/pricing)** ## Related Tools - [CE Themes](/jspreadsheet/docs/themes) - Pre-built themes for Jspreadsheet CE - [Jspreadsheet Pro Appearance](https://jspreadsheet.com/docs/appearance) - Visual attributes and theming (Pro) - [Conditional Formatting](https://jspreadsheet.com/docs/conditional-formatting) - Dynamic styling based on values (Pro) ================================================ FILE: docs/jspreadsheet/docs/tests.md ================================================ title: Unit Tests for Jspreadsheet keywords: Jspreadsheet, Jexcel, JavaScript, Web-based Applications, Web-based Spreadsheet, Unit Tests description: Enhance your application quality by creating unit tests for Jspreadsheet. # Testing ## Introduction Unit testing ensures robust Jspreadsheet integrations by validating functionality and catching regressions early. This guide uses Mocha, Chai, and JSDOM to test data grid behaviour in a simulated browser environment. ## Environment Setup Set up a basic environment to implement Jspreadsheet testing. Follow these steps or clone our GitHub example project. ### Step 1: Clone the Project Clone a setup with Jspreadsheet pre-installed and a test file: ```bash git clone https://github.com/jspreadsheet/tests.git cd tests ``` ### Step 2: Install the Dependencies Install required libraries using npm: ```bash npm install jspreadsheet-ce@5.0.0-beta.3 ``` ### Step 3: Configure Mocha Create or edit the `mocha.config.js` file in the project root to configure Mocha and emulate the browser environment using JSDOM. {.ignore} ```javascript #! /usr/bin/env node require('jsdom-global')(undefined, { url: 'https://localhost' }); const jspreadsheet = require('jspreadsheet'); global.jspreadsheet = jspreadsheet; global.root = document.createElement('div'); global.root.style.width = '100%'; global.root.style.height = '100%'; document.body.appendChild(global.root); exports.mochaHooks = { afterEach(done) { jspreadsheet.destroyAll(); done(); }, }; ``` ### Step 4: Create your first test Inside the `test` folder, create a file named `data.js`: {.ignore} ```javascript const { expect } = require('chai'); describe("Data", () => { it("Testing data", () => { let test = jspreadsheet(root, { worksheets: [ { data: [ ["Mazda", 2001, 2000], ["Peugeot", 2010, 5000], ["Honda Fit", 2009, 3000], ["Honda CRV", 2010, 6000], ], minDimensions: [4, 4] }, ], }); expect(test[0].getValue("A1", true)).to.equal("Mazda"); expect(test[0].getValue("A2", true)).to.equal("Peugeot"); expect(test[0].getValue("B1", true)).to.equal("2001"); }); }); ``` ### Step 5: Running the Tests After writing your tests, you can run them with the following command: ```bash npm run test ``` ================================================ FILE: docs/jspreadsheet/docs/themes.md ================================================ title: Jspreadsheet CE Theme Editor - Customize Your Data Grid Styles keywords: Jspreadsheet, data grid customization, JavaScript spreadsheet, Excel-like applications, theme editor, visual styling, colour customization, dynamic themes, interactive spreadsheets, web-based spreadsheet tool description: Customize your Jspreadsheet data grid with the Theme Editor for unique, visually appealing designs, including colour and dynamic theme options that align with your application’s style. canonical: https://bossanova.uk/jspreadsheet/docs/themes # Data Grid Theme Editor The Data Grid Theme Editor is an online tool for customizing spreadsheet colours in Jspreadsheet. To enable this feature, add the CSS file jspreadsheet.themes.css to your project. Then, configure the CSS variables, as shown in the example below, to implement your custom theme. {.green} > To use themes in Jspreadsheet, include `jspreadsheet.themes.css` in your project and add the CSS variables as shown in the example below. {.ignore-execution} ```html
``` ```jsx import React, { useRef, useState } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; import 'jspreadsheet/dist/jspreadsheet.themes.css'; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Render component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" import 'jspreadsheet/dist/jspreadsheet.themes.css'; @Component({ standalone: true, selector: "app-root", template: `
`; }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ minDimensions: [10,10], }] }); } } ``` ## Related Tools - [CE Style Guide](/jspreadsheet/docs/style) - Custom cell styling in Jspreadsheet CE - [Jspreadsheet Pro Appearance](https://jspreadsheet.com/docs/appearance) - Advanced theming and visual customization (Pro) - [Jspreadsheet Pro Themes](https://jspreadsheet.com/docs/themes) - Professional pre-built themes (Pro) ================================================ FILE: docs/jspreadsheet/docs/toolbars.md ================================================ title: Data Grid Toolbars keywords: Jspreadsheet, Jexcel, data grid, javascript, excel-like toolbar, spreadsheet toolbar, table toolbar, toolbars, data grid toolbars, customizable toolbars, toolbar customization, toolbar integration, interactive data grid, JavaScript data grid, dynamic toolbar, toolbar controls. description: Learn how to create and customize toolbar controls in Jspreadsheet. This guide covers adding custom buttons, configuring built-in tools, and extending the toolbar to fit your application's needs. # Data Grid Toolbars This section covers all methods, settings, and events for the data grid toolbar, including advanced customization options, control integration, and key configuration settings. {.green} > **Icons** > > The use of Material icons style sheet is required for utilizing the toolbar. > > `````` ## Documentation ### Methods The following methods manage the visibility state of the toolbar: | Method | Description | |----------------|--------------------------------------------------------------------| | `showToolbar` | Displays the toolbar.
`worksheet.parent.showToolbar() : void` | | `hideToolbar` | Hides the toolbar.
`worksheet.parent.hideToolbar() : void` | ### Settings Customize toolbar items during initialization using the following property: | Property | Description | |-------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------| | `toolbar: boolean \| ToolbarItem[] \| ((defaultToolbar: ToolbarItem[]) => ToolbarItem[])` | Enables toolbar customization. Options include: `true` for the default toolbar, a function for dynamic setup, or an array for specific configurations. | ### Toolbar Object Customize toolbar items during initialization using the properties below. #### Toolbar General Properties | Property | Description | |--------------|-------------------------------------------------| | `container` | Displays a border around the toolbar container. | | `badge` | Adds a badge for each toolbar element. | | `title` | Shows a title below each icon. | | `responsive` | Enables a responsive toolbar. `Default: false` | | `items` | Array of items to display in the toolbar. | #### Toolbar Item Properties | Property | Description | |----------------------|-------------------------------------------------------------------------| | `type: string` | Specifies the element type: `icon` \| `divisor` \| `label` \| `select`. | | `content: string` | Defines the content of the toolbar element. | | `title: boolean` | Sets the tooltip for the toolbar element. | | `width: number` | Width of the toolbar element. | | `active: boolean` | Initial active state for the toolbar element. | | `class: string` | CSS class for each toolbar element. | | `value: number` | Initially selected option for `select` type. | | `render: function` | Render function for dropdown elements when `type: select`. | | `onclick: function` | Callback for when an item is clicked. | | `onchange: function` | Callback for when a new item is selected (`select` type only). | ## Examples ### Toolbar visibility The example below shows how to enable the default data grid toolbar and programmatically control its visibility after initialization. ```html

``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Render component return ( <> spreadsheet.current[0].parent.showToolbar()} /> spreadsheet.current[0].parent.hideToolbar()} /> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; @Component({ standalone: true, selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [ { minDimensions: [6,6] }, ], toolbar: true }); } } ``` ### Toolbar Custom Handler The toolbar property can be a function that allows you to add custom items to the default toolbar without rebuilding it from scratch. See the example below. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Toolbar handler const toolbar = (toolbar) => { // Add a new custom item in the end of my toolbar toolbar.items.push({ tooltip: 'My custom item', content: 'share', onclick: function() { alert('Custom click'); } }); return toolbar; } // Render component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" @Component({ selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { toolbar: function(toolbar) { // Add a new custom item in the end of my toolbar toolbar.items.push({ tooltip: 'My custom item', content: 'share', onclick: function() { alert('Custom click'); } }); return toolbar; }, worksheets: [{ minDimensions: [6,6], }] }); } } ``` ### Custom Toolbar Object The toolbar property allows you to customize the items in the spreadsheet toolbar fully. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Toolbar handler const toolbar = { items: [{ content: 'save', onclick: function () { spreadsheet.current.download(); } }, { type:'divisor', }, { type:'select', width: '160px', options: [ 'Verdana', 'Arial', 'Courier New' ], render: function(e) { return '' + e + ''; }, onchange: function(a,b,c,d) { spreadsheet.current.setStyle(spreadsheet.current.getSelected(), 'font-family', d); } }, { type: 'i', content: 'format_bold', onclick: function(a,b,c) { spreadsheet.current.setStyle(spreadsheet.current.getSelected(), 'font-weight', 'bold'); } }, { type: 'i', content: 'format_italic', onclick: function(a,b,c) { spreadsheet.current.setStyle(spreadsheet.current.getSelected(), 'font-style', 'italic'); } }, { content: 'search', onclick: function(a,b,c) { if (c.children[0].innerText == 'search') { spreadsheet.current.showSearch(); c.children[0].innerText = 'search_off'; } else { spreadsheet.current.hideSearch(); c.children[0].innerText = 'search'; } }, tooltip: 'Toggle Search', updateState: function(a,b,c,worksheet) { // Call this one when the worksheet is opened and on the selection of any cells if (worksheet.options.search == true) { c.children[0].innerText = 'search_off'; } else { c.children[0].innerText = 'search'; } } } ] } // Render component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" @Component({ standalone: true, selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Custom toolbar definitions let customToolbar = { items: [ { content: 'save', onclick: function () { jspreadsheet.current.download(); } }, { type: 'divisor', }, { type: 'select', width: '160px', options: ['Verdana', 'Arial', 'Courier New'], render: function (e) { return '' + e + ''; }, onchange: function (a, b, c, d) { jspreadsheet.current.setStyle(jspreadsheet.current.getSelected(), 'font-family', d); } }, { type: 'i', content: 'format_bold', onclick: function (a, b, c) { jspreadsheet.current.setStyle(jspreadsheet.current.getSelected(), 'font-weight', 'bold'); } }, { type: 'i', content: 'format_italic', onclick: function (a, b, c) { jspreadsheet.current.setStyle(jspreadsheet.current.getSelected(), 'font-style', 'italic'); } }, { content: 'search', onclick: function (a, b, c) { if (c.children[0].innerText == 'search') { jspreadsheet.current.showSearch(); c.children[0].innerText = 'search_off'; } else { jspreadsheet.current.hideSearch(); c.children[0].innerText = 'search'; } }, tooltip: 'Toggle Search', updateState: function (a, b, c, worksheet) { // Call this one when the worksheet is opened and on the selection of any cells if (worksheet.options.search == true) { c.children[0].innerText = 'search_off'; } else { c.children[0].innerText = 'search'; } } } ] } // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ worksheetName: 'Toolbar example', minDimensions: [6, 6], }], toolbar: customToolbar }); } } ``` ## Related Tools - [JavaScript Toolbar](https://jsuites.net/docs/toolbar) - Standalone toolbar component with customizable items - [Jspreadsheet Pro Toolbar](https://jspreadsheet.com/docs/toolbar) - Advanced toolbar customization with plugins (Pro) ================================================ FILE: docs/jspreadsheet/docs/upgrade-from-v4-to-v5.md ================================================ title: Jspreadsheet Upgrade Guide: v4 to v5 keywords: Jspreadsheet upgrade, version 4 to 5, Jspreadsheet CE migration, breaking changes, Jspreadsheet compatibility, JavaScript data grid, spreadsheet customization, migration guide description: Upgrade guide from Jspreadsheet CE v4 to v5. Learn about breaking changes and updated features for a smooth code transition. # Jspreadsheet Upgrade from v4 to v5 ## Overview The latest update to Jspreadsheet CE introduces significant enhancements, simplifying customization and improving compatibility across all distributions. Key updates include structural refinements, increased extensibility, and standardized functionality. Upgrading from version 4 to version 5 includes breaking changes due to updates in properties, methods, and events. This document outlines the critical adjustments required to transition your existing code to the new version. > Be aware that upgrading from version 4 to version 5 introduces breaking changes resulting from updates to properties, methods, and events. ### Spreadsheet vs. Worksheets Jspreadsheet CE introduces two distinct levels: the spreadsheet level and the worksheet level. This separation avoids redundancy by centralizing elements that should not be duplicated across each worksheet, such as toolbars or event declarations. #### Version 4 In version 4, all properties were defined within a single object. {.ignore} ```javascript jspreadsheet(document.getElementById('spreadsheet'), { toolbar: true, minDimensions: [4,4], onchange: function() { // something } }); ``` #### Version 5 In version 5, the worksheets attribute allows you to declare multiple worksheets, each with specific properties, while maintaining centralized common configurations at the spreadsheet level. {.ignore} ```javascript // Create your spreadsheets jspreadsheet(document.getElementById('spreadsheet'), { toolbar: true, worksheets: [ { minDimensions: [4,4], }, // More worksheets ], onchange: function() { // something } }); ``` ### Instances When creating a new **spreadsheet**, Jspreadsheet returns an array of **worksheet** instances. Each worksheet object includes a `parent` property, allowing access to spreadsheet-level features. {.ignore} ```javascript // Create your spreadsheets let worksheets = jspreadsheet(document.getElementById('spreadsheet'), { worksheets: [ { minDimensions: [4,4], }, ], }); // Get the spreadsheet data worksheets[0].getData(); // Show toolbar worksheets[0].parent.showToolbar(); ``` ### Translations From version 5, translations can be managed globally using `jspreadsheet.setDictionary`. This method accepts an object where the keys are the original English texts and the values are their translations. #### Version 4 In version 4, translations were defined directly within the spreadsheet instance: {.ignore} ```javascript jspreadsheet(document.getElementById('spreadsheet'), { minDimensions: [4,4], text:{ noRecordsFound: 'Nenhum registro encontrado', show: 'Show', // many other translations } }); ``` #### Version 5 In version 5, you define translations globally using setDictionary, making it easier to manage and apply them across all instances: {.ignore} ```javascript // Translate all application jspreadsheet.setDictionary({ 'No records found': 'Nenhum registro encontrado', 'Show': 'Exibir', //... }); // Create your spreadsheets jspreadsheet(document.getElementById('spreadsheet'), { worksheets: [{ minDimensions: [4,4], }], }); ``` ### Plugin and Editor Support Jspreadsheet CE now includes plugin support, offering developers enhanced flexibility and the ability to extend functionality. Editors align more closely with the Pro version, ensuring more across the different distributions. {.ignore} ```javascript // Create your spreadsheets jspreadsheet(document.getElementById('spreadsheet'), { worksheets: [{ minDimensions: [4,4], columns: [{ type: myEditor }], // Custom editors }], plugins: { myPlugin }, // Plugin declaration }); ``` ## Library Level ### Library Level Property Updates | Status | Property | Description | |-----------------------------------------|---------------------------------------|--------------------------------------------------------------------------------------------------------------------------| | *New*{.info-label .new} | `destroyAll` | Destroys all spreadsheets across all namespaces. | | *New*{.info-label .new} | `getWorksheetInstanceByName`{.nowrap} | Retrieves a worksheet by its name and namespace. Can also return a namespace depending on the first argument. | | *New*{.info-label .new} | `setDictionary` | Adds a helper for defining new translations. | | *Removed*{.info-label .removed} | `tabs` | Removed as it is no longer necessary since CE always creates a spreadsheet by default. | | *Removed*{.info-label .removed} | `createTabs` | Removed as it is no longer necessary since CE now always creates a spreadsheet by default. | | *Removed*{.info-label .removed .nowrap} | `getColumnName` | Removed because this functionality already exists in the helpers. | ### Helpers | Status | Method | Description | |----------------------------------|---------------------------|-------------------------------------------------------------------------------------------------------| | *New*{.info-label .new} | `getCoordsFromRange` | Retrieves coordinates from a specified range. | | *Updated*{.info-label .updated} | `createFromTable` | Now functions like the previous library-level method with the same name. | | *Updated*{.info-label .updated} | `getColumnNameFromCoords` | Renamed to `getCellNameFromCoords`. | | *Updated*{.info-label .updated} | `getCoordsFromColumnName` | Renamed to `getCoordsFromCellName`. | | *Removed*{.info-label .removed} | `injectArray` | Removed as it was not documented. | ## Spreadsheet Level ### Settings | Status | Property | Description | |-------------------------|-----------------|----------------------------------------------------------------------------------------------------------| | *New*{.info-label .new} | `parseHTML` | Similar functionality to the previous `stripHTML` property. | | *New*{.info-label .new} | `debugFormulas` | Enables formula debugging. Debugging was previously always enabled by default but is now disabled. | | *New*{.info-label .new} | `fullscreen` | Defines a spreadsheet as fullscreen. | ### Toolbars | Status | Method | Description | |--------------------------|---------------|--------------------------------------------------| | *New*{.info-label .new} | `hideToolbar` | Hides the toolbar. | | *New*{.info-label .new} | `showToolbar` | Displays the toolbar using the current settings. | ### Events All events are now defined at the spreadsheet level. Except for the `onload`, `onbeforesave`, and `onsave` events, all other events now receive the worksheet instance as their first argument. Additional changes to method arguments have also been introduced. | Status | Event | Description | |-----------------------------------------|---------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------| | *New*{.info-label .new} | `onbeforeformula` | Intercepts formulas before they are calculated. | | *New*{.info-label .new} | `onbeforeselection` | Intercepts cell selection and cancels it if `false` is returned. | | *New*{.info-label .new} | `oncreatecell` | Triggered when a cell is created. | | *New*{.info-label .new} | `oncreateworksheet` | Triggered when a worksheet is created. | | *New*{.info-label .new} | `ondeleteworksheet` | Triggered when a worksheet is deleted. | | *Updated*{.info-label .updated} | `onafterchanges` | All arguments for this event have been updated. | | *Updated*{.info-label .updated} | `onbeforedeletecolumn`{.nowrap} | All arguments for this event have been updated. | | *Updated*{.info-label .updated} | `onbeforedeleterow` | All arguments for this event have been updated. | | *Updated*{.info-label .updated} | `onbeforeinsertcolumn` | All arguments for this event have been updated. | | *Updated*{.info-label .updated} | `onbeforeinsertrow` | All arguments for this event have been updated. | | *Updated*{.info-label .updated} | `onbeforepaste` | The second argument is a 2D array of objects with a "value" property representing the values to be pasted. | | *Updated*{.info-label .updated} | `onbeforesave` | The first argument is now the spreadsheet instance. | | *Updated*{.info-label .updated} | `onchangeheader` | The third and fourth arguments for this event have been swapped. | | *Updated*{.info-label .updated} | `onchangemeta` | The second argument is now always an object containing the changes. The third and fourth arguments have been removed. | | *Updated*{.info-label .updated} | `onchangepage` | This event now includes a fourth argument for the number of items per page. | | *Updated*{.info-label .updated} | `onchangestyle` | All arguments for this event have been updated. | | *Updated*{.info-label .updated} | `oncopy` | Can cancel the copy action or override the copied value. | | *Updated*{.info-label .updated} | `oncreateeditor` | The fifth argument is always `null`, and a new fifth argument has been added for the column configuration. | | *Updated*{.info-label .updated} | `ondeletecolumn` | All arguments for this event have been updated. | | *Updated*{.info-label .updated} | `ondeleterow` | All arguments for this event have been updated. | | *Updated*{.info-label .updated} | `oninsertcolumn` | All arguments for this event have been updated. | | *Updated*{.info-label .updated} | `oninsertrow` | All arguments for this event have been updated. | | *Updated*{.info-label .updated} | `onload` | The first argument is now the spreadsheet instance. | | *Updated*{.info-label .updated} | `onmerge` | All arguments for this event have been updated. | | *Updated*{.info-label .updated} | `onmovecolumn` | This event now includes a fourth argument indicating the number of columns moved. | | *Updated*{.info-label .updated} | `onmoverow` | This event now includes a fourth argument indicating the number of rows moved. | | *Updated*{.info-label .updated} | `onpaste` | All arguments for this event have been updated. | | *Updated*{.info-label .updated} | `onredo` | The second argument has been updated based on the method the event refers to. | | *Updated*{.info-label .updated} | `onsave` | The first argument is now the spreadsheet instance. | | *Updated*{.info-label .updated} | `onsort` | Now includes a fourth argument, an array representing the new order of rows. | | *Updated*{.info-label .updated .nowrap} | `onundo` | The second argument has been updated based on the method the event refers to. | ## Worksheets Updates ### Attributes | Status | Property | Description | |-----------------------------------------|---------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| | *Updated*{.info-label .updated} | `highlighted` | Now stores records instead of just cell tags. | | *Updated*{.info-label .updated} | `csvHeaders` | The default value for this property is now `false`. | | *Updated*{.info-label .updated} | `nestedHeaders` | Now requires a two-dimensional array. | | *Updated*{.info-label .updated} | `ignoreEvents` | Now operates at the spreadsheet level. | | *Updated*{.info-label .updated} | `options` | Updated to no longer set default values for several properties. | | *Updated*{.info-label .updated} | `records` | Now a 2D array containing objects with cell coordinates and corresponding HTML elements. | | *Updated*{.info-label .updated} | `rows` | Now an array of objects with the HTML element for each row and index. | | *Updated*{.info-label .updated} | `persistance` | Renamed to `persistence`. | | *Updated*{.info-label .updated} | `rowResize` | The default value for this property is now `true`. | | *Updated*{.info-label .updated} | `tableHeight` | Now also accepts a value of type `number`. | | *Updated*{.info-label .updated} | `tableWidth` | Now also accepts a value of type `number`. | | *Updated*{.info-label .updated} | `toolbar` | Now supports being a function, boolean, or a configuration object for the jSuites toolbar. Array configurations were also updated, and it is now spreadsheet-level. | | *Removed*{.info-label .removed} | `contextMenu` | Now operates at the spreadsheet level and includes four new arguments. | | *Removed*{.info-label .removed} | `fullscreen` | Now operates at the spreadsheet level. | | *Removed*{.info-label .removed} | `colgroup` | Replaced by the new `cols` property, which functions similarly. | | *Removed*{.info-label .removed} | `el` | Replaced by the new `element` property. | | *Removed*{.info-label .removed} | `contextMenu` | Now operates at the spreadsheet level. | | *Removed*{.info-label .removed} | `copyCompatibility` | This property has been removed. | | *Removed*{.info-label .removed} | `detachForUpdates` | This property has been removed. | | *Removed*{.info-label .removed} | `imageOptions` | Use the column's configuration object instead. | | *Removed*{.info-label .removed} | `includeHeadersOnCopy`{.nowrap} | This property has been removed. | | *Removed*{.info-label .removed} | `loadingSpin` | This property has been removed. | | *Removed*{.info-label .removed} | `method` | This property has been removed. | | *Removed*{.info-label .removed} | `requestVariables` | This property has been removed. | | *Removed*{.info-label .removed} | `stripHTML` | Use the `parseHTML` property instead. | | *Removed*{.info-label .removed} | `stripHTMLOnCopy` | This property has been removed. | | *Removed*{.info-label .removed} | `text` | Removed as JSS CE now uses `jSuites.translate`. | | *Removed*{.info-label .removed .nowrap} | `updateTable` | This property has been removed. | ### Comments | Status | Property | Description | |-----------------------------------------|--------------------------|---------------------------------------------------------------------------------------------------------------------| | *New*{.info-label .new} | `comments` | Represents comments associated with the worksheet. | | *Updated*{.info-label .updated} | `allowComments`{.nowrap} | The default value of this property is now `true`. | | *Updated*{.info-label .updated} | `getComments` | The undocumented second argument has been removed. Additionally, the first argument can no longer be an array. | | *Updated*{.info-label .updated .nowrap} | `setComments` | The first argument of this method has been updated. Additionally, the undocumented third argument has been removed. | ### Columns | Status | Property | Description | |------------------------------------------|-------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | *New*{.info-label .new} | `columns.render` | Intercepts and modifies visible data before inserting into a grid cell. | | *Updated*{.info-label .updated} | `columnDrag` | The default value for this property is now `true`. | | *Updated*{.info-label .updated} | `columns.type` | Now accepts an object to create custom editors, replacing the old `columns.editor`. Note: "autocomplete" and "readonly" have been removed; use `autocomplete` and `readOnly` properties instead. | | *Removed*{.info-label .removed} | `columns.editor` | This property has been removed. Its functionality for creating custom editors has been moved to the `columns.type` property. | | *Removed*{.info-label .removed} | `columns.stripHTML`{.nowrap} | This property has been removed. | | *Removed*{.info-label .removed} | `colAlignments` | Use the `align` property in the `columns` array items instead. | | *Removed*{.info-label .removed} | `colHeaders` | Use the `title` property in the `columns` array items instead. | | *Removed*{.info-label .removed .nowrap} | `colWidths` | Use the `width` property in the `columns` array items instead. | | *Updated*{.info-label .updated} | `hideColumn` | The first argument for this method can now also be an array. | | *Updated*{.info-label .updated} | `showColumn` | The first argument for this method can now also be an array. | | *Updated*{.info-label .updated} | `setWidth` | Previously undocumented, this method had a third argument, which has now been removed. | ### Data | Status | Method | Description | |-----------------------------------------|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------| | *New*{.info-label .new} | `getDataFromRange`{.nowrap} | Retrieves data from a specified range. | | *Updated*{.info-label .updated} | `getData` | This method now accepts two additional arguments. | | *Updated*{.info-label .updated} | `setData` | No longer emits the `onload` event, and its first argument can no longer be a JSON object. | | *Updated*{.info-label .updated} | `getValue` | The first argument must now be a string. | | *Updated*{.info-label .updated} | `setValue` | The object array in the first argument no longer uses the `newValue` property; use the `value` property instead. | | *Updated*{.info-label .updated} | `getColumnData` | This method now includes a second argument. | | *Updated*{.info-label .updated} | `setColumnData` | This method now includes a third argument. | | *Updated*{.info-label .updated} | `getRowData` | This method now includes a second argument. | | *Updated*{.info-label .updated} | `setRowData` | This method now includes a third argument. | | *Updated*{.info-label .updated} | `download` | This method now accepts a second argument. | | *Updated*{.info-label .updated} | `getLabel` | The first argument can no longer be an array with two numbers. Instead, the method now accepts two numeric arguments. | | *Removed*{.info-label .removed} | `parseCSV` | Use the helper methods instead. | | *Removed*{.info-label .removed .nowrap} | `getJson` | Use the `getData` method instead. | ### Selection | Status | Method | Description | |-----------------------------------------|--------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| | *New*{.info-label .new} | `getHighlighted` | Retrieves the coordinates of the highlighted selections. | | *New*{.info-label .new} | `getSelected` | Retrieves information about selected cells in the worksheet. | | *New*{.info-label .new} | `getSelection` | Retrieves the coordinates of the selected range in the worksheet. | | *New*{.info-label .new} | `isSelected` | Checks if a cell is part of the current selection. | | *Updated*{.info-label .updated} | `updateSelectionFromCoords`{.nowrap} | Previously undocumented, this method had a fifth argument, which has now been removed. It can now return `false` if the selection is cancelled. | | *Updated*{.info-label .updated} | `getSelectedColumns` | This method now accepts an argument. | | *Updated*{.info-label .updated} | `getSelectedRows` | The first argument for this method has been updated. | | *Updated*{.info-label .updated} | `resetSelection` | Previously undocumented, this method had an argument, which has now been removed. | | *Removed*{.info-label .removed .nowrap} | `updateSelection` | Use the `updateSelectionFromCoords` method instead. | ### Editors #### Calendar Editor Type | Status | Property | Description | |------------------------------------|------------------|-------------------------------------------------------------------------------------------| | *Updated*{.info-label .updated} | `format` | The default value for this property is now `"YYYY-MM-DD"`. | | *Removed*{.info-label .removed} | `value` | This property has been removed. | | *Deprecated*{.info-label .removed} | `months` | Still available but should be replaced with the `setDictionary` method. | | *Deprecated*{.info-label .removed} | `weekdays` | Still available but should be replaced with the `setDictionary` method. | | *Deprecated*{.info-label .removed} | `weekdays_short` | Still available but should be replaced with the `setDictionary` method. | ### Removed The following methods and attributes have been removed from the public scope or the library: #### Methods - `col` - `conditionalSelectionUpdate` - `copyData` - `createCell` - `createCellHeader` - `createNestedHeader` - `createRow` - `createTable` - `createToolbar` - `getColumnOptions` - `getDropDownValue` - `getFreezeWidth` - `getJsonRow` - `getLabelFromCoords` - `hash` - `historyProcessColumn` - `historyProcessRow` - `init` - `isColMerged` - `isRowMerged` - `loadDown` - `loadPage` - `loadUp` - `loadValidation` - `onafterchanges` - `parseNumber` - `parseValue` - `prepareJson` - `prepareTable` - `refresh` - `refreshSelection` - `removeCopySelection` - `removeCopyingSelection` - `row` - `save` - `scrollControls` - `setCheckRadioValue` - `setFooter` - `setHistory` - `updateCell` - `updateCopySelection` - `updateCornerPosition` - `updateFormula` - `updateFormulaChain` - `updateFormulas` - `updateFreezePosition` - `updateMeta` - `updateNestedHeader` - `updateOrder` - `updateOrderArrow` - `updatePagination` - `updateResult` - `updateScroll` - `updateTable` - `updateTableReferences` - `wheelControls` #### Attributes - `setExtensions`; - `formula`; - `build`; - `keyDownControls`; - `mouseDownControls`; - `mouseUpControls`; - `mouseMoveControls`; - `mouseOverControls`; - `doubleClickControls`; - `copyControls`; - `cutControls`; - `pasteControls`; - `contextMenuControls`; - `touchStartControls`; - `touchEndControls`; - `fromSpreadsheet`; - `validLetter`; - `injectArray`; - `getIdFromColumnName`; - `getColumnNameFromId`; - `getElement`; - `doubleDigitFormat`; - `createFromTable`; #### Elements Changes in HTML element properties: | Status | Property | Description | |----------------------------------|---------------------------|--------------------------------------------------------------------| | *Removed*{.info-label .removed} | `worksheetElement.jexcel` | Use the `worksheetElement.jspreadsheet` property instead. | ================================================ FILE: docs/jspreadsheet/docs/vue/tests.md ================================================ title: Unit Tests for Jspreadsheet in Vue.js keywords: Jspreadsheet, Jexcel, JavaScript, Web-based Applications, Web-based Spreadsheet, Unit Tests, React, Vue, Vite, Vue.js, VueJS, Vue testing description: Enhance your application quality by creating unit tests for Jspreadsheet inside your Vue.js application. # Testing Jspreadsheet in Vue.js with Jest: A Comprehensive Guide ## Introduction to Vue.js Unit Testing with Jest Unit testing plays a vital role in modern **Vue.js applications** to ensure that components function as expected and to prevent any potential regressions. When working with **Jspreadsheet** and integrating it into **Vue.js**, it's essential to test this functionality thoroughly. This guide will walk you through the process of **setting up unit tests for Jspreadsheet in Vue.js** using **Jest**. By following this guide, you'll learn how to efficiently integrate and test Jspreadsheet in your **Vue.js project**, ensuring the **reliability** and **performance** of your components. ## Vue.js Testing Environment Setup In this section, we’ll guide you through setting up a **Vue.js environment** specifically for testing **Jspreadsheet** using **Jest** and **JSDOM**. This will help you create a solid foundation for **running unit tests** for your Jspreadsheet instances. ### Step 1: Clone or Create a Vue.js Project To begin, you can either clone an existing **Vue.js project** or create a new one using `@vue/cli`. Here's how to get started: ```bash vue create jspreadsheet-vue-testing cd jspreadsheet-vue-testing ``` Alternatively, clone our setup from [GitHub](??). ### Step 2: Install Dependencies Next, install the necessary dependencies, including `jspreadsheet`, Jest, and `jest-environment-jsdom`: ```bash npm install jspreadsheet-ce@5.0.0-beta.3 npm install --save-dev jest@29.7.0 jest-environment-jsdom@29.7.0 ``` ### Step 3: Configure Jest for Jspreadsheet To integrate Jspreadsheet properly in a Jest testing environment, you'll need to set up JSDOM. First, create a `jest.setup.js` file in the root of your project: ```javascript // jest.setup.js const jspreadsheet = require('jspreadsheet-ce'); // Code that runs before each test beforeEach(() => { if (typeof document !== 'undefined') { jspreadsheet.destroyAll(); if (!global.jspreadsheet && !global.root) { global.jspreadsheet = jspreadsheet; global.root = document.createElement('div'); global.root.style.width = '100%'; global.root.style.height = '100%'; document.body.appendChild(global.root); } } }); ``` Next, configure Jest to use this setup by adding the following entry to your `package.json`: ```json { "jest": { "setupFilesAfterEnv": ["/jest.setup.js"], } } ``` This configuration ensures JSDOM will emulate the DOM environment required to run Jspreadsheet within Jest. ### Step 4: Create a Test Create a folder inside your project if it doesn't exist, then inside this folder create a file named `jspreadsheet.test.js`. ```javascript // */tests/jspreadsheet.test.js /** * @jest-environment jsdom */ test("Testing data", () => { let instance = jspreadsheet(root, { worksheets: [ { data: [ ["Mazda", 2001, 2000], ["Peugeot", 2010, 5000], ["Honda Fit", 2009, 3000], ["Honda CRV", 2010, 6000], ], minDimensions: [4, 4], }, ], }); expect(instance[0].getValue("A1", true)).toEqual("Mazda"); expect(instance[0].getValue("A2", true)).toEqual("Peugeot"); expect(instance[0].getValue("B1", true)).toEqual("2001"); }); ``` This test verifies that a basic Jspreadsheet instance is created and that the data values are correctly placed. You can modify it to check for more specific scenarios as needed. ## Running the Tests Ensure you add the following line to the `scripts` section of your `package.json`: ```json "test": "jest" ``` After creating your tests and updating `package.json`, you can run them using the following command: ```bash npm test ``` Jest will run all the tests in your project and display the results in the console. If everything is configured correctly, your tests should pass. ================================================ FILE: docs/jspreadsheet/docs/vue.md ================================================ title: VueJS Spreadsheet keywords: Jspreadsheet, Jexcel, JavaScript, Vue.js, Vue data grid, spreadsheet-like controls, Excel-like data grid, Vue.js spreadsheet, Jspreadsheet Vue wrapper, Vue integration, JavaScript spreadsheet, spreadsheet controls description: Build advanced data grid applications with intuitive spreadsheet-like controls using Jspreadsheet Vue.js wrapper # Vue Spreadsheet Jspreadsheet CE version 5 introduces a VueJS wrapper, enabling developers to create advanced data grid solutions with intuitive spreadsheet-like controls seamlessly integrated with VueJS version 3. ## Documentation ### Using the Vue Data Grid Wrapper To start using the Jspreadsheet Vue wrapper, first install the package: ```bash npm install @jspreadsheet-ce/vue@5.0.0-beta.3 ``` ### Adding CSS to Your Project To apply styles, import the necessary CSS files: {.ignore} ```javascript import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; ``` To ensure icons display correctly, include Material Icons in your application. Add the following code to your main HTML file: {.ignore} ```html ``` #### Creating Your First Jspreadsheet Vue Data Grid Learn how to build a web-based spreadsheet using the Jspreadsheet Vue wrapper, seamlessly integrating advanced data grid functionality into your Vue.js applications. {.ignore} ```vue ``` ## Customizations You can also create your wrapper for Jspreadsheet. This approach offers developers enhanced flexibility and control over the implementation, allowing for tailored integration and custom behaviours. ### Wrapper for VueJS 3 To integrate Jspreadsheet directly with Vue 3, initialize the library within a Vue component using the mounted lifecycle hook. This method allows you to harness the power of Jspreadsheet while maintaining control over its behaviour and styling. {.ignore} ```vue ``` ### Integration with Vue2 {.ignore} ```html

``` ## Jspreadsheet Pro ### More examples If you are interested in using Jspreadsheet Pro, explore the following resources for advanced features and integrations. These examples demonstrate how to leverage Jspreadsheet Pro for creating advanced and feature-rich spreadsheets in Vue.js applications. * [Vue Spreadsheet Pro](https://jspreadsheet.com/docs/vue) * [Vue Spreadsheet with actions](https://codesandbox.io/s/vue3-spreadsheet-with-actions-chx7dw) * [Vue Spreadsheet Editor](https://codesandbox.io/s/vue-spreadsheet-zpmf2) {.pro} > **Jspreadsheet Pro for Vue - Professional Spreadsheet Components** > > While Jspreadsheet CE provides core spreadsheet functionality for Vue, **Jspreadsheet Pro** offers enhanced Vue integration and enterprise features: > > **Enhanced Vue Integration:** > - **TypeScript Support:** Full TypeScript definitions for Vue 3 with type-safe development > - **Composition API:** Native Composition API support with custom composables > - **Vue 3 Optimized:** Built specifically for Vue 3 with full reactivity system support > - **Pinia Integration:** Built-in Pinia store integration for state management > - **Nuxt Support:** Full SSR/SSG support for Nuxt 3 applications > - **Vue DevTools:** Enhanced debugging with Vue DevTools integration > > **Professional Components:** > - **Advanced Editors:** Conditional dropdowns, rich text, HTML editors with Vue reactivity > - **Formula System:** 500+ Excel functions with Vue reactive bindings > - **Conditional Formatting:** Visual rules, data bars, color scales, icon sets > - **Data Validation:** Real-time validation with custom Vue components > - **Import/Export:** Full Excel (.xlsx) import/export with formatting preservation > - **Charts & Graphs:** Built-in Vue chart components for data visualization > > **Vue Reactivity Features:** > - **Reactive Data Binding:** Two-way binding with Vue ref/reactive objects > - **Computed Properties:** Use computed values for cell formulas > - **Watchers:** Automatic updates when source data changes > - **V-Model Support:** Full v-model binding for spreadsheet data > - **Teleport Support:** Use Vue Teleport for dialogs and overlays > - **Suspense Ready:** Async component loading with Suspense > > **Performance & Scale:** > - **Virtual Scrolling:** Handle 100K+ rows with smooth scrolling > - **Lazy Loading:** Load data on-demand for optimal performance > - **Vue 3 Performance:** Optimized for Vue 3's improved rendering engine > - **Web Workers:** Background processing for heavy calculations > - **Memory Efficiency:** Optimized memory usage for large datasets > > **Developer Experience:** > - **Vue-Specific Documentation:** Examples with Options API and Composition API > - **Professional Support:** Priority support for Vue integration issues > - **Regular Updates:** Continuous Vue 3 compatibility improvements > - **Migration Tools:** Easy migration from CE to Pro with Vue examples > > **Vue-Specific Pro Features:** > - **Custom Vue Components:** Use Vue components as cell editors and renderers > - **Scoped Slots:** Advanced customization with scoped slots > - **Event Integration:** Seamless integration with Vue event system > - **Provide/Inject:** Share spreadsheet context across component tree > - **Testing Support:** Vitest/Vue Test Utils integration examples > > Perfect for Vue applications requiring enterprise-grade spreadsheet functionality with professional support. > > **[Explore Jspreadsheet Pro Vue →](https://jspreadsheet.com/docs/vue)** | **[Compare CE vs Pro →](https://jspreadsheet.com/docs/getting-started)** | **[View Pricing →](https://jspreadsheet.com/pricing)** ================================================ FILE: docs/jspreadsheet/docs/worksheets.md ================================================ title: Worksheets Settings, Methods, and Related Events keywords: Jspreadsheet, data grid, javascript, excel-like worksheets, spreadsheet, data tables, grid, worksheet events, worksheet support, Jspreadsheet worksheets, worksheet settings, worksheet methods, worksheet events, create worksheets, customize worksheets, track changes in worksheets, worksheet control, dynamic data grids, JavaScript data tables, worksheet customization, worksheet organization, interactive worksheets, data management description: Learn how to set up and manage worksheets in Jspreadsheet programmatically. Explore various worksheet properties, settings, and efficient handling and customization techniques. # Worksheets Jspreadsheet offers robust tools for managing spreadsheets with multiple worksheets. It enables precise control over user interactions and provides programmatic controls and event listeners to customize the behaviour and monitor changes within the spreadsheet. ## Documentation ### Methods Explore the available methods to programmatically interact with and manage worksheets in Jspreadsheet. | Property | Description | |-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `openWorksheet(number)` | Opens a worksheet by its index.
`openWorksheet(position?: Number) => void` | | `createWorksheet(object, number)` | Adds a new worksheet based on a configuration object, optionally at a specific position.
`createWorksheet(worksheetOptions: Object, position?: Number) => worksheetInstance` | | `deleteWorksheet(number)` | Removes an existing worksheet by its index.
`deleteWorksheet(position?: Number) => void` | ### Events Explore the available events to monitor and respond to worksheet interactions in Jspreadsheet. | Event | Description | |---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `onopenworksheet` | `onopenworksheet(worksheet: Object, worksheetNumber: Number) : void` | | `onbeforecreateworksheet` | Intercept worksheet creation to cancel or configure the new worksheet.
`onbeforecreateworksheet(worksheet: Object, options: Object, worksheetNumber: Number) : mixed` | | `oncreateworksheet` | `oncreateworksheet(worksheet: Object, worksheetOptions: Object, worksheetNumber: Number) : void` | | `ondeleteworksheet` | `ondeleteworksheet(worksheet: Object, oldWorksheetNumber: Number) : void` | ### Configuration You can customize spreadsheet and worksheet behaviour using the following settings. #### Worksheet Settings | Property | Description | |--------------------------|--------------------------------------| | `worksheetId: string` | Unique identifier for the worksheet. | | `worksheetName: string` | Title of the worksheet. | #### Spreadsheet Properties | Property | Description | |----------------------------------|----------------------------------------------------------------------------------------------------| | `tabs: boolean\|object` | Enables tabs for worksheet navigation and allows users to create new worksheets. Default: `false`. | | `allowDeleteWorksheet: boolean` | Enables the delete worksheet option in the context menu. Default: `true`. | | `allowRenameWorksheet: boolean` | Enables the rename worksheet option in the context menu. Default: `true`. | | `allowMoveWorksheet: boolean` | Enables drag-and-drop functionality for rearranging worksheets. Default: `true`. | {.green} > ##### Tabs > The `tabs` object uses the same properties as the `jSuites.tabs` plugin, allowing you to customize the position and behaviour of the worksheet tabs. For more details, visit the: [Tabs Plugin](https://jsuites.net/docs/javascript-tabs) #### Quick example {.ignore-execution} ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Tabs const tabs = { allowCreate: true, allowChangePosition: true, animation: true, position: "bottom", }; // Render component return ( ); } ``` ```vue ``` ```angularjs jspreadsheet(document.getElementById('spreadsheet'), { tabs: { allowCreate: true, allowChangePosition: true, animation: true, position: "bottom", }, worksheets: [{ minDimensions: [8,8], }], }); ``` ## Examples ### Worksheet events Create a new worksheet and explore the related events. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ ["1","DIVINELY UNINSPIRED TO A HELLISH EXTENT","LEWIS CAPALDI"], ["2","NO 6 COLLABORATIONS PROJECT","ED SHEERAN"], ["3","THE GREATEST SHOWMAN","MOTION PICTURE CAST RECORDING"], ["4","WHEN WE ALL FALL ASLEEP WHERE DO WE GO","BILLIE EILISH"] ] // Columns const columns = [ { type: 'autonumber', title: 'Id' }, { type: 'text', width: '350px', title: 'Title' }, { type: 'text', width: '250px', title: 'Artist' }, ] // Intercept the action to define the default configuration for each new worksheet const onbeforecreateworksheet = () => { return { minDimensions: [5,5], } } // When a new worksheet is opened const onopenworksheet = (element, instance, worksheetNumber) => { console.log(element, instance, worksheetNumber); } // Render component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" @Component({ standalone: true, selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { // Allow create a new tab button tabs: true, // Initial worksheet worksheets: [ { data: [ ["1","DIVINELY UNINSPIRED TO A HELLISH EXTENT","LEWIS CAPALDI"], ["2","NO 6 COLLABORATIONS PROJECT","ED SHEERAN"], ["3","THE GREATEST SHOWMAN","MOTION PICTURE CAST RECORDING"], ["4","WHEN WE ALL FALL ASLEEP WHERE DO WE GO","BILLIE EILISH"] ], columns: [ { type: 'autonumber', title: 'Id' }, { type: 'text', width: 350, title: 'Title' }, { type: 'text', width: 250, title: 'Artist' }, ], // Name of the worksheet worksheetName: 'Albums', } ], // Intercept the new worksheet and define the options for the new worksheet onbeforecreateworksheet: function(config, index) { let options = { minDimensions: [5,5], worksheetName: 'Albums ' + index } return options; }, // When you open the worksheet onopenworksheet: function(element, instance, worksheetNumber) { console.log(element, instance, worksheetNumber); }, }); } } ``` ### Programmatic operations Create a new worksheet programmatically. ```html

``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Render component return ( <> spreadsheet.current[0].createWorksheet({ minDimensions: [5,5] })} /> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" @Component({ standalone: true, selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { tabs: true, worksheets: [ { minDimensions: [5,5], worksheetName: 'Example2', } ] }); } } ``` ================================================ FILE: docs/jspreadsheet/docs.md ================================================ title: The JavasScript Spreadsheet Documentation keywords: Jexcel, javascript, excel-like, spreadsheet, table, data grid description: Jspreadsheet CE is an extensible JavaScript component for building sophisticated data grid interfaces with Excel-like controls. canonical: https://bossanova.uk/jspreadsheet/docs # Jspreadsheet v5: The JavaScript Spreadsheet **Jexcel** has been renamed to **Jspreadsheet**. ## Jspreadsheet CE Use Cases Jspreadsheet CE is an extensible framework for building sophisticated data-oriented interfaces with Excel-like controls. By bringing familiar spreadsheet features to your application, you can drastically reduce development time while delivering an interface that users already know how to use, leading to faster adoption and increased productivity. You can use Jspreadsheet in many different applications, such as: - An editable data grid-based interface to simplify inventory management and production planning in a manufacturing company's ERP system. - At an educational institution, Jspreadsheet powers grade management systems where teachers can efficiently import and modify student data. - A logistics company uses Jspreadsheet to create dynamic delivery route planning tables with real-time updates. - In a research laboratory, scientists use Jspreadsheet to collect and analyze experimental data with custom validation rules. - At a retail chain, managers use Spreadsheet-based tools to coordinate staff schedules across multiple locations. ## Installation ### From the NPM ```bash npm install jspreadsheet-ce@5 ``` ### From a CDN {.ignore} ```html ``` ## Data Grid with Spreadsheets Controls How to embed a simple javascript spreadsheet in your application. Find more [examples](/jspreadsheet/docs/examples) here. ```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Render component return ( <> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; @Component({ selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new JavaScript data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { tabs: true, toolbar: true, worksheets: [{ minDimensions: [6,6], }], }); } } ``` ## Copyright and License Jspreadsheet CE is released under the MIT license. ## Jspreadsheet Changelog ### Jspreadsheet 5.0.0 - Separation of spreadsheet and worksheets; - New worksheet methods and events; - Dedicated wrappers for React and Vue for better framework integration; - Modern development environment powered by Webpack; - Updated architecture aligned with other distributions; [More information](/jspreadsheet/docs/upgrade-from-v4-to-v5) ### Jspreadsheet 4.6.0 - Jexcel renamed to Jspreadsheet. - Integration with Jsuites v4. ### Jspreadsheet 4.2.3 - The spreadsheet plugin is now compatible with Jsuites v3. - New flags and security implementations. - New DOM element references in the toolbar. - Worksheet events are now tabbed. ### Jspreadsheet 4.0.0 Special thanks to [FDL - Fonds de Dotation du Libre](https://www.fdl-lef.org/) for their support and sponsorship, which made the new version possible with many exciting features. - Workbook/tab support for spreadsheets. - Create dynamic spreadsheets from static HTML elements. - Highlight selected cells in the spreadsheet after CTRL+C. - Footer with formula support. - Multiple column resizing. - JSON update support (helpers to update a remote server). - Centralized event dispatch method for all spreadsheet events. - Custom helpers: `=PROGRESS` (progress bar), `=RATING` (5-star rating). - Custom formula helpers: `=COLUMN`, `=ROW`, `=CELL`, `=TABLE`, `=VALUE`. - Dynamic nested header updates. - New HTML editing column type. - New flags: `includeHeadersOnCopy`, `persistence`, `filters`, `autoCasting`, `freezeColumns`. - New events: `onevent`, `onchangepage`, `onbeforesave`, `onsave`. - More examples and documentation. ### Jspreadsheet 3.9.0 - New methods. - General fixes. ### Jspreadsheet 3.6.0 - Improved spreadsheet formula parsing. - New spreadsheet events. - New initialization options. - General fixes. ### Jspreadsheet 3.2.3 - `getMeta`, `setMeta` methods. - NPM package with jSuites. - General fixes. ### Jspreadsheet 3.0.1 Jspreadsheet v3 is a complete rebuild of the JavaScript spreadsheet (previously a jQuery plugin). Due to the changes, full compatibility could not be ensured. If upgrading, your code may require some updates. For more information, refer to the article on upgrading from Jspreadsheet v2 or Handsontable. **New features in Jspreadsheet v3:** - Drag and drop columns. - Resizable rows. - Merge columns. - Search functionality. - Pagination. - Lazy loading. - Full-screen mode. - Image upload. - Native color picker. - Better mobile compatibility. - Enhanced nested headers support. - Advanced keyboard navigation. - Better hidden column management. - Data picker enhancements: dropdown, autocomplete, multiple selection, group options, and icons. - Import from XLSX (experimental). **Major improvements:** - A new formula engine with faster results and no external dependencies. - No use of selectors, leading to faster performance. - New native column types. - No jQuery required. - Examples for React, Vue, and Angular. - XLSX support via a custom SheetJS integration (experimental). ### Jspreadsheet 2.1.0 - Mobile touch improvements. - Paste fixes and a new CSV parser. ### Jspreadsheet 2.0.0 - New radio column type. - New dropdown with autocomplete and multiple selection options. - Header/body separation for better scroll and column resize behavior. - Text-wrap improvements, including Excel-compatible `alt+enter`. - New `set/get` meta information. - New `set/get` configuration parameters. - Programmatic `set/get` cell styles. - `set/get` cell comments. - Custom toolbar for tables. - Responsive calendar picker. ### Jspreadsheet 1.5.7 - Improvements to checkbox column type. - Updates to table destruction in jQuery. ### Jspreadsheet 1.5.1 - Spreadsheet data overflow and fixed headers. - Navigation improvements. ### Jspreadsheet 1.5.0 - Relative `insertRow`, `deleteRow`, `insertColumn`, `deleteColumn`. - Redo and undo support for `insertRow`, `deleteRow`, `insertColumn`, `deleteColumn`, `moveRow`. - New formula column recursive chain. - New alternative design option (Bootstrap-like). - `updateSettings` improvements. ================================================ FILE: docs/jspreadsheet/download.md ================================================ title: Download Jspreadsheet keywords: Download Jspreadsheet description: Download Jspreadsheet for your projects. canonical: https://bossanova.uk/jspreadsheet/download # Jspreadsheet Download Jspreadsheet is available through multiple distribution channels, making it easy to integrate into your project regardless of your preferred setup. ## Download Options ### Direct Download [Click here to download](https://bossanova.uk/jspreadsheet/v5/jspreadsheet.zip) ### CDN You can include Jspreadsheet directly via CDN for quick setup: {.ignore} ```html ``` ### NPM For modern web development, install Jspreadsheet via NPM: ```bash npm install jspreadsheet-ce ``` ### GitHub Repository Access the source code and contribute to the development of Jspreadsheet on [GitHub](https://github.com/jspreadsheet/jspreadsheet). ## Documentation For more information on integrating Jspreadsheet into your project, visit the [official documentation](https://bossanova.uk/jspreadsheet/docs). ================================================ FILE: docs/jspreadsheet/sponsors.md ================================================ title: Sponsors keywords: Jspreadsheet sponsors, open-source sponsorship, FDL sponsors, ETH Scientific IT Services, Initiative Tree, Market Control description: Learn about the most recent sponsors supporting Jspreadsheet, including FDL - Libre Endowment Fund, ETH Scientific IT Services, Initiative Tree, and Market Control. canonical: https://bossanova.uk/jspreadsheet/sponsors # Sponsors The most recent sponsors: ![](https://www.fdl-lef.org/FDL-Logo.Colorised.Forest?format=png) **FDL - Fonds de Dotation du Libre - Libre Endowment Fund** FDL - Libre Endowment Fund is a French endowment fund that supports developers and publishers of Free and Open Source software projects. FDL accepts tax-deductible donations to fund specific feature developments for software deemed of general interest. FDL works in synergy with its sister non-profit organization, "AWL" (Action pour un Web Libre). FDL focuses on long-term projects, while AWL sponsors short-term initiatives. Both maintain a repository of Free and Open Source software publishers at [afs.one](https://afs.one). Learn more about FDL and AWL at [www.fdl-lef.org](https://www.fdl-lef.org). **ETH Scientific IT Services** **Initiative Tree** **Market Control** ================================================ FILE: docs/jspreadsheet/v2/docs/events.md ================================================ title: Handling events on Jspreadsheet keywords: Jexcel, javascript, excel-like, spreadsheet, table, grid, events description: Learn how to handle events on Jspreadsheet [Back to Documentation](/jspreadsheet/v2/docs) # Events on your online spreadsheets ## Update Settings on the Go The JavaScript spreadsheet plugin offers a native feature to customize your table on the go. You can define the method updateSettings to create a parser and customize the data should be shown to the user, as the example below. ```javascript // Live update of the settings $('#my').jexcel('updateSettings', { table: function (instance, cell, col, row, val, id) { if (col == 2 || col == 3) { // Get text txt = $(cell).text(); // Format text txt = numeral(txt).format('0,0.00'); // Update cell value $(cell).html(' $ ' + txt); } } }); ``` [Basic example updating the table based on the user entry](/jspreadsheet/v2/examples/currency-and-masking-numbers) ## Basic events The JavaScript spreadsheet basic events available in this current version. | Event| description | | ---|--- | | **onload** | This method is called when the method setData | | **onbeforechange** | Before a column value is changed. | | **onchange** | After a column value is changed. | | **onafterchange** | After all change events is performed. | | **oninsertrow** | After a new row is inserted. | | **ondeleterow** | After a row is excluded. | | **oninsertcolumn** | After a new column is inserted. | | **ondeletecolumn** | After a column is excluded. | | **onselection** | On the selection is changed. | | **onsort** | After a colum is sorted. | | **onresize** | After a colum is resized. | | **onmoverow** | After a row is moved to a new position. | | **onfocus** | On table focus | | **onblur** | On table blur | [Advanced example logging all events in a tableexample](/jspreadsheet/v2/examples/tracking-changes-on-the-spreadsheet) ## Basic Example: {.ignore} ```javascript handler = function(obj, cell, val) { console.log('My table id: ' + $(obj).prop('id')); console.log('Cell changed: ' + $(cell).prop('id')); console.log('Value: ' + val); }; insertrow = function(obj) { alert('new row added on table: ' + $(obj).prop('id')); } deleterow = function(obj) { alert('row excluded on table: ' + $(obj).prop('id')); } data = [ ['Mazda', 2001, 2000, '2006-01-01 00:00:00'], ['Peugeot', 2010, 5000, '2005-01-01 00:00:00'], ['Honda Fit', 2009, 3000, '2004-01-01 00:00:00'], ['Honda CRV', 2010, 6000, '2003-01-01 00:00:00'], ]; $('#my').jexcel({ data:data, colHeaders: ['Model', 'Date', 'Price', 'Date'], colWidths: [ 300, 80, 100, 100 ], onchange:handler, oninsertrow:insertrow, ondeleterow:deleterow, columns: [ { type: 'text' }, { type: 'numeric' }, { type: 'numeric' }, { type: 'calendar', options: { format:'DD/MM/YYYY HH24:MI', time:1 } }, ] }); ``` See this working example on jsfiddle: [Basic example on handling events on jsFiddle](https://jsfiddle.net/spreadsheet/5n21ep6s/) ================================================ FILE: docs/jspreadsheet/v2/docs/programmatically-changes.md ================================================ title: Programmatical Changes keywords: Jspreadsheet CE, Jexcel, JavaScript Data Grid, Spreadsheets, JavaScript tables, Excel-like data grid, web-based spreadsheets, data grid controls, data grid features description: Create data grids with spreadsheet controls with Jexcel. [Back to Documentation](/jspreadsheet/v2/docs) # Programmatically changes available in the plugin Using Jspreadsheet will have a comprehensive range of native commands to interact with the users of your javascript spreadsheet. ## General Methods | Description| Example| | ---|---| | **getData: get the full or partial table data in json**
@Param boolan onlyHighlighedCells - Get only highlighted cells | $('#my').jexcel('getData', false); | | **getRowData: get the data from the a row**
@Param integer rowNumber - Row number starting from zero. | $('#my').jexcel('getRowData', 1); | | **getColumnData: get the data from the a column**
@Param integer columnNumber - Column number starting from zero. | $('#my').jexcel('getColumnData', 2); | | **setData: Update the table data**
@Param json newData - New data, null will reload what is in memory.
@Param boolean ignoreSpare - Ignore configuration of min spare column and rows. | $('#my').jexcel('setData', [data], false); | | **insertColumn: add a new column**
@Param mixed [array or integer] - Integer as the number of columns to be added. Or array to define the data you would like to insert.
@Param json - Properties of the new columns, null to set default.
@Param integer columnNumber - Column reference, null to add the new column after the last column. | $('#my').jexcel('insertColumn', 1, null, 3); | | **deleteColumn: remove column by number**
@Param integer columnNumber - Which column should be excluded starting on zero
@Param integer numOfColumns - How many columns should be deleted | $('#my').jexcel('deleteColumn', 1); | | **insertRow: add a new row**
@Param mixed - Array with the new data or integer with the number of rows should be added
@Param rowNumber - Reference where to add the new row. | $('#my').jexcel('insertRow', 1); | | **deleteRow: remove row by number**
@Param integer rowNumber - Which row should be excluded starting on zero
@Param integer numberOfRows - How many rows should be excluded | $('#my').jexcel('deleteRow', 1); | | **getHeader: get the current header by column number**
@Param integer columnNumber - Column number starting on zero | $('#my').jexcel('getHeader', 2); | | **setHeader: change header by column**
@Param integer columnNumber - column number starting on zero
@Param string columnTitle - New header title | $('#my').jexcel('setHeader', 1, 'Title'); | | **getWidth: get the current column width**
@Param integer columnNumber - column number starting on zero | $('#my').jexcel('getWidth', 2); | | **setWidth: change column width**
@Param integer columnNumber - column number starting on zero
@Param string newColumnWidth - New column width | $('#my').jexcel('setWidth', 1, 100); | | **orderBy: will reorder a column asc or desc**
@Param integer columnNumber - column number starting on zero
@Param smallint sortType - Zero will toggle current option, one for desc, two for asc | $('#my').jexcel('orderBy', 2); | | **getValue: get current cell value**
@Param mixed cellIdent - str compatible with excel, or as object. | $('#my').jexcel('getValue', 'A1'); | | **setValue: change the cell value**
@Param mixed cellIdent - str compatible with excel, or as object.
@Param string Value - new value for the cell | $('#my').jexcel('setValue', 'A1'); | | **updateSelection: select cells**
@Param object startCell - cell object
@Param object endCell - cell object | $('#my').jexcel('updateSelection', [cell], [cell]); | | **download: get the current data as a CSV file.**
@Param none | $('#my').jexcel('download'); | | **destroy: remove the table and all references and events attached.**
@Param none | $('#my').jexcel('destroy'); | | **getCell: get the cell object based on a string**
@Param styring cellIdent - str compatible with excel, or as object. | $('#my').jexcel('getCell', 'B1'); | | **getSelectedCells: get all selected cells**
@Param none | $('#my').jexcel('getSelectedCells'); | | **undo: undo an action**
@Param none | $('#my').jexcel('undo'); | | **redo: redo an action**
@Param none | $('#my').jexcel('redo'); | | **moveRow: move an row to another position**
@Param from - from position y0
@Param to - to position y1 | $('#my').jexcel('moveRow', 1, 2); | | **getStyle: get table or cell style**
@Param mixed - cell identification or null for the whole table. | $('#my').jexcel('getStyle', 'A1'); | | **setStyle: set cell(s) CSS style**
@Param mixed - json with whole table style information or just one cell identification. Ex. A1.
@Param k [optional]- CSS key
@Param v [optional]- CSS value | $('#my').jexcel('setSyle', [ { A1:'background-color:red' }, { B1: 'color:red'} ]); | | **getComments: get cell comments**
@Param mixed - cell identification or null for the whole table. | $('#my').jexcel('getComments', 'A1'); | | **setComments: set cell comments**
@Param cell - cell identification
@Param text - comments | $('#my').jexcel('setComments', 'A1', 'My cell comments!'); | | **getMeta: get the table or cell meta information**
@Param mixed - cell identification or null for the whole table. | $('#my').jexcel('getMeta', 'A1'); | | **setMeta: set the table or cell meta information**
@Param mixed - json with whole table meta information. | $('#my').jexcel('setMeta', [ A1: { info1:'test' }, { B1: { info2:'test2', info3:'test3'} } ]); | ================================================ FILE: docs/jspreadsheet/v2/docs/quick-reference.md ================================================ title: Quick Reference keywords: Jspreadsheet CE, Jexcel, JavaScript Data Grid, Spreadsheets, JavaScript tables, Excel-like data grid, web-based spreadsheets, data grid controls, data grid features description: Quick Reference for Jexcel spreadsheet properties [Back to Documentation](/jspreadsheet/v2/docs "Back to the documentation section") # The javascript spreadsheet quick reference ## Methods | Method | Description | |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------| | **getData: Get the full or partial table data**
@Param boolan onlyHighlighedCells - Get only highlighted cells | $('#my').jexcel('getData', false); | | **setData: Update the table data**
@Param json newData - New json data, null will reload what is in memory.
@Param boolean ignoreSpare - ignore configuration of minimal spareColumn/spareRows | $('#my').jexcel('setData', [json], false); | | **insertColumn: add a new column**
@Param integer numberOfColumns - Number of columns should be added
@Param string headerTitle - Header title | $('#my').jexcel('insertColumn', 1, { header:'Title' }); | | **deleteColumn: remove column by number**
@Param integer columnNumber - Which column should be excluded starting on zero | $('#my').jexcel('deleteColumn', 1); | | **insertRow: add a new row**
@Param integer numberOfRows - Number of rows should be added | $('#my').jexcel('insertRow', 1); | | **deleteRow: remove row by number**
@Param integer rowNumber - Which row should be excluded starting on zero | $('#my').jexcel('deleteRow', 1); | | **getHeader: get the current header by column number**
@Param integer columnNumber - Column number starting on zero | $('#my').jexcel('getHeader', 2); | | **setHeader: change header by column**
@Param integer columnNumber - column number starting on zero
@Param string columnTitle - New header title | $('#my').jexcel('setHeader', 1, 'Title'); | | **getWidth: get the current column width**
@Param integer columnNumber - column number starting on zero | $('#my').jexcel('getWidth', 2); | | **setWidth: change column width**
@Param integer columnNumber - column number starting on zero
@Param string newColumnWidth - New column width | $('#my').jexcel('setWidth', 1, 100); | | **orderBy: will reorder a column asc or desc**
@Param integer columnNumber - column number starting on zero
@Param smallint sortType - Zero will toggle current option, one for desc, two for asc | $('#my').jexcel('orderBy', 2); | | **getValue: get current cell value**
@Param mixed cellIdent - str compatible with excel, or as object. | $('#my').jexcel('getValue', 'A1'); | | **setValue: change the cell value**
@Param mixed cellIdent - str compatible with excel, or as object.
@Param string Value - new value for the cell | $('#my').jexcel('setValue', 'A1'); | | **updateSelection: select cells**
@Param object startCell - cell object
@Param object endCell - cell object
@Param boolean ignoreEvents - ignore onselection event | $('#my').jexcel('updateSelection', [cell], [cell], true); | | **download: get the current data as a CSV file.**
@Param none | $('#my').jexcel('download'); | | **getConfig: get the current value of one configuration by key**
@Param string configuration key | $('#my').jexcel('getConfig', 'allowInsertColumn'); | | **setConfig: set the value of one configuration by key**
@Param string configuration key
@Param mixed configuration value | $('#my').jexcel('setConfig', 'allowInsertColumn', true); | | **download: get the current data as a CSV file.**
@Param none | $('#my').jexcel('download'); | | **getStyle: get table or cell style**
@Param mixed - cell identification or null for the whole table. | $('#my').jexcel('getStyle', 'A1'); | | **setStyle: set cell(s) CSS style**
@Param mixed - json with whole table style information or just one cell identification. Ex. A1.
@Param k [optional]- CSS key
@Param v [optional]- CSS value | $('#my').jexcel('setSyle', [ { A1:'background-color:red' }, { B1: 'color:red'} ]); | | **getComments: get cell comments**
@Param mixed - cell identification or null for the whole table. | $('#my').jexcel('getComments', 'A1'); | | **setComments: set cell comments**
@Param cell - cell identification
@Param text - comments | $('#my').jexcel('setComments', 'A1', 'My cell comments!'); | | **getMeta: get the table or cell meta information**
@Param mixed - cell identification or null for the whole table. | $('#my').jexcel('getMeta', 'A1'); | | **setMeta: set the table or cell meta information**
@Param mixed - json with whole table meta information. | $('#my').jexcel('setMeta', [ A1: { info1:'test' }, { B1: { info2:'test2', info3:'test3'} } ]); | [Working example](/jspreadsheet/v2/docs/programmatically-changes) ## Events | Event| description | | ---|--- | | **onload** | This method is called when the method setData | | **onbeforechange** | Before a column value is changed. | | **onchange** | After a column value is changed. | | **onafterchange** | After all change events is performed. | | **oninsertrow** | After a new row is inserted. | | **ondeleterow** | After a row is excluded. | | **oninsertcolumn** | After a new column is inserted. | | **ondeletecolumn** | After a column is excluded. | | **onselection** | On the selection is changed. | | **onsort** | After a colum is sorted. | | **onresize** | After a colum is resized. | | **onmoverow** | After a row is moved to a new position. | | **onfocus** | On table focus | | **onblur** | On table blur | [Example on handling events on your javascript spreadsheet](/jspreadsheet/v2/examples/tracking-changes-on-the-spreadsheet) ## Initialization parameters | Parameter| description | | ---|--- | | **columns** | Column type, dropdown options, text wrapping,marking, etc. | | **colHeaders** | Column header titles | | **colWidths** | Column widths: width in px. | | **colAlignments** | Column alignments: left, right, center. | | **colHeaderClasses** | Column custom CSS classes | | **defaultColWidth** | Default width for a new column | | **minSpareRows** | Minimum number of spare rows | | **minSpareCols** | Minimum number of spare cols | | **minDimensions** | Minimum table dimensions: [cols,rows] | | **contextMenu** | Context menu content: function() { return customMenu } | | **columnSorting** | Allow column sorting: bool | | **columnResize** | Allow column resizing: bool | | **rowDrag** | Allow row dragging: bool | | **editable** | Allow table edition: bool | | **allowInsertRow** | Allow insert a new row: bool | | **allowManualInsertRow** | Allow user to insert a new row: bool | | **allowInsertColumn** | Allow insert a new column: bool | | **allowManualInsertColumn** | Allow user to create a new column: bool | | **allowDeleteRow** | Allow delete a row: bool | | **allowDeleteColumn** | Allow delete a column: bool | | **wordWrap** | Global text wrapping: bool | | **csvFileName** | CSV default file name: string | | **selectionCopy** | Allow selection copy: bool | | **tableOverflow** | Allow table overflow: bool | | **tableHeight** | Force the max height of the table | | **tableWidth** | Force the max width of the table | | **allowComments** | Allow comments over the cells | | **toolbar** | Add custom toolbars | ================================================ FILE: docs/jspreadsheet/v2/docs.md ================================================ title: Jspreadsheet | Documentation keywords: Jspreadsheet, grid, data, table, datatable, json, excel, excel-like, jquery, javascript, spreadsheet description: Introduction, basic methods, event handles and all information you need about this jquery plugin. ## Column types Jspreadsheet brings some native columns in addition to the default input text. It means you can get alternative ways to entry data in your spreadsheet. From advanced numeric inputs, dropdowns to calendar picks and a very easy way to have your custom integrations, makes the jExcel plugin a very flexible tool to enhance the user experience when using your applications. In the example below, you will have text, numeric inputs and a calendar picker. But, other native options will be available such as: _text, numeric, calendar, checkbox, dropdown, autocomplete._ ```javascript $('#my').jexcel({ data:data, colHeaders: ['Model', 'Date', 'Price', 'Date'], colWidths: [ 300, 80, 100, 100 ], columns: [ { type: 'text' }, { type: 'numeric' }, { type: 'numeric' }, { type: 'calendar', options: { format:'DD/MM/YYYY' } }, ] }); ``` ### Custom columns To make a flexible tools, it is possible to extend the plugin to create your custom entries. The following example will show you how to integrate a third party plugin to create your custom columns. [http://bossanova.uk/jspreadsheet/v2/examles/integrating-a-third-party-plugin-into-your-spreadsheet](/jspreadsheet/v2/examples/integrating-a-third-party-plugin-into-your-spreadsheet) ================================================ FILE: docs/jspreadsheet/v2/examples/a-custom-table-design.md ================================================ title: Jspreadsheet | Examples | Bootstrap and custom table design keywords: Jexcel, jquery, javascript, bootstrap, table design, spreadsheet, CSV, table, grid description: Create a custom table design. For example a bootstrap-like spreadsheet table. canonical: https://bossanova.uk/jspreadsheet/v2/examples/a-custom-table-design [Back to Examples](/jspreadsheet/v2/examples) # Create custom CSS for your javascript spreadsheet The following example shows a CSS addon to change the core layout of your jquery tables. ## Green borders and corners jquery spreadsheet [Bootstrap-like jquery spreadsheet example](/jspreadsheet/v2/examples/a-custom-table-design) ## Bootstrap-like jquery spreadsheet. [Green borders and corners jquery spreadsheet example](/jspreadsheet/v2/examples/a-custom-table-design?layout=green) Your jquery table can be customized by including an additional addon CSS. If you have created a nice design, please share with us. ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v2/examples/autocomplete.md ================================================ title: Autocomplete Column keywords: Jexcel, jquery, javascript, bootstrap, table design, spreadsheet, CSV, table, grid, autocomplete, customization, column description: Customize your column type to autocomplete [Back to Examples](/jspreadsheet/v2/examples) # Autocomplete column It is natively presented as single dropdown with text filter enabled and it is based on a pre-defined options defined in the instance declaration or in a dynamic remote JSON source. [Json source demo](/jspreadsheet/countries) ```html
``` ================================================ FILE: docs/jspreadsheet/v2/examples/comments.md ================================================ title: Jspreadsheet | Examples | Add comments in your jquery table keywords: Jexcel, jquery, javascript, cell comments, jquery table description: Manage a table cell comments [Back to Examples](/jspreadsheet/v2/examples) # Manage cell comments in your jquery table The most recent version the spreadsheet plugin brings the possibility to manage custom comments for each individual cells. By allowComments in the initialization, the user will be able to add comments in the rigth click contextMenu. It is also possible to manage your cell comments programmatically by the use of commands such as setComments or getComments, for example: | Method | Description | |-------------------------------------------------------------------------|--------------------| | $('#my1').jexcel('setComments', 'A1', 'This is the comments from A1'); | Set A1 comments | | $('#my1').jexcel('getComments', 'A1'); | Get A1 comments | | $('#my1').jexcel('setComments', 'A1', ''); | Reset A1 comments | ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v2/examples/create-from-a-existing-html-table.md ================================================ title: Create From HTML Table keywords: Jexcel, jquery, javascript, bootstrap, table design, spreadsheet, CSV, table, grid, table description: Create Spreadsheet based on HTML Table ```html
CreadoCreado porNNombreNombreTipoCallePoblacinNombre SAPCdigo ProductoRespuesta
2019-04-02ocastellanos30616GRUPO UVESCO, S.A. "NETTO"GRANDESupermercado grandeCTRA. S-20. BARRIO MONTE, S/NSANTANDERHAMBURGUESA NATURAL206774NO EST EN SURTIDO
2019-04-02ocastellanos30616GRUPO UVESCO, S.A. "NETTO"GRANDESupermercado grandeCTRA. S-20. BARRIO MONTE, S/NSANTANDERHAMBURGUESA BARBACOA206775NO EST EN SURTIDO
2019-04-02ocastellanos30616GRUPO UVESCO, S.A. "NETTO"GRANDESupermercado grandeCTRA. S-20. BARRIO MONTE, S/NSANTANDERNUGGETS206776ROTURA
2019-04-02ocastellanos30616GRUPO UVESCO, S.A. "NETTO"GRANDESupermercado grandeCTRA. S-20. BARRIO MONTE, S/NSANTANDERESCALOPA206777PRESENTE
2019-04-02ocastellanos30716GRUPO UVESCO, S.A. "BM SUPERMERCADOS"PEQUEOSupermercado medianosJUAN XXIII, 12TORRELAVEGAHAMBURGUESA NATURAL206774NO EST EN SURTIDO
2019-04-02ocastellanos30716GRUPO UVESCO, S.A. "BM SUPERMERCADOS"PEQUEOSupermercado medianosJUAN XXIII, 12TORRELAVEGAESCALOPA206777NO EST EN SURTIDO
2019-04-02ocastellanos30716GRUPO UVESCO, S.A. "BM SUPERMERCADOS"PEQUEOSupermercado medianosJUAN XXIII, 12TORRELAVEGAHAMBURGUESA BARBACOA206775NO EST EN SURTIDO
2019-04-02ocastellanos30716GRUPO UVESCO, S.A. "BM SUPERMERCADOS"PEQUEOSupermercado medianosJUAN XXIII, 12TORRELAVEGANUGGETS206776NO EST EN SURTIDO
2019-04-03ocastellanos46825GRUPO UVESCO, S.A. "BM SUPERMERCADOS"PEQUEOSupermercado medianosBarrio la Cruz S/NLiencresHAMBURGUESA NATURAL206774NO EST EN SURTIDO
2019-04-03ocastellanos46825GRUPO UVESCO, S.A. "BM SUPERMERCADOS"PEQUEOSupermercado medianosBarrio la Cruz S/NLiencresHAMBURGUESA BARBACOA206775NO EST EN SURTIDO
2019-04-03ocastellanos46825GRUPO UVESCO, S.A. "BM SUPERMERCADOS"PEQUEOSupermercado medianosBarrio la Cruz S/NLiencresESCALOPA206777NO EST EN SURTIDO
2019-04-03ocastellanos46825GRUPO UVESCO, S.A. "BM SUPERMERCADOS"PEQUEOSupermercado medianosBarrio la Cruz S/NLiencresNUGGETS206776PRESENTE
2019-04-05ocastellanos30676GRUPO UVESCO, S.A. "NORCASH"PEQUEOSupermercado pequeoP ALISAS, S/NSOLARESNUGGETS206776PRESENTE
2019-04-05ocastellanos30676GRUPO UVESCO, S.A. "NORCASH"PEQUEOSupermercado pequeoP ALISAS, S/NSOLARESHAMBURGUESA BARBACOA206775NO EST EN SURTIDO
2019-04-05ocastellanos30676GRUPO UVESCO, S.A. "NORCASH"PEQUEOSupermercado pequeoP ALISAS, S/NSOLARESHAMBURGUESA NATURAL206774NO EST EN SURTIDO
2019-04-05ocastellanos30676GRUPO UVESCO, S.A. "NORCASH"PEQUEOSupermercado pequeoP ALISAS, S/NSOLARESESCALOPA206777NO EST EN SURTIDO
2019-04-05ocastellanos30296GRUPO UVESCO, S.A. "BM SUPERMERCADOS"1Supermercado grandeLEONARDO RUCABADO, 26CASTRO URDIALESHAMBURGUESA BARBACOA206775NO EST EN SURTIDO
2019-04-05ocastellanos30296GRUPO UVESCO, S.A. "BM SUPERMERCADOS"1Supermercado grandeLEONARDO RUCABADO, 26CASTRO URDIALESHAMBURGUESA NATURAL206774NO EST EN SURTIDO
2019-04-05ocastellanos30296GRUPO UVESCO, S.A. "BM SUPERMERCADOS"1Supermercado grandeLEONARDO RUCABADO, 26CASTRO URDIALESNUGGETS206776PRESENTE
2019-04-05ocastellanos30296GRUPO UVESCO, S.A. "BM SUPERMERCADOS"1Supermercado grandeLEONARDO RUCABADO, 26CASTRO URDIALESESCALOPA206777PRESENTE
2019-04-15ocastellanos30296GRUPO UVESCO, S.A. "BM SUPERMERCADOS"1Supermercado grandeLEONARDO RUCABADO, 26CASTRO URDIALESHAMBURGUESA NATURAL206774NO EST EN SURTIDO
2019-04-15ocastellanos30296GRUPO UVESCO, S.A. "BM SUPERMERCADOS"1Supermercado grandeLEONARDO RUCABADO, 26CASTRO URDIALESHAMBURGUESA BARBACOA206775NO EST EN SURTIDO
2019-04-15ocastellanos30296GRUPO UVESCO, S.A. "BM SUPERMERCADOS"1Supermercado grandeLEONARDO RUCABADO, 26CASTRO URDIALESESCALOPA206777PRESENTE
2019-04-15ocastellanos30296GRUPO UVESCO, S.A. "BM SUPERMERCADOS"1Supermercado grandeLEONARDO RUCABADO, 26CASTRO URDIALESNUGGETS206776PRESENTE
2019-04-12ocastellanos30596GRUPO UVESCO, S.A. "BM SUPERMERCADOS"PEQUEOSupermercado grandeRUBEN DARIO, 2SANTANDERHAMBURGUESA BARBACOA206775NO EST EN SURTIDO
2019-04-12ocastellanos30596GRUPO UVESCO, S.A. "BM SUPERMERCADOS"PEQUEOSupermercado grandeRUBEN DARIO, 2SANTANDERHAMBURGUESA NATURAL206774NO EST EN SURTIDO
2019-04-12ocastellanos30596GRUPO UVESCO, S.A. "BM SUPERMERCADOS"PEQUEOSupermercado grandeRUBEN DARIO, 2SANTANDERNUGGETS206776ROTURA
2019-04-12ocastellanos30596GRUPO UVESCO, S.A. "BM SUPERMERCADOS"PEQUEOSupermercado grandeRUBEN DARIO, 2SANTANDERESCALOPA206777NO EST EN SURTIDO
2019-04-15ocastellanos30596GRUPO UVESCO, S.A. "BM SUPERMERCADOS"PEQUEOSupermercado grandeRUBEN DARIO, 2SANTANDERHAMBURGUESA BARBACOA206775NO EST EN SURTIDO
2019-04-15ocastellanos30596GRUPO UVESCO, S.A. "BM SUPERMERCADOS"PEQUEOSupermercado grandeRUBEN DARIO, 2SANTANDERHAMBURGUESA NATURAL206774NO EST EN SURTIDO
2019-04-15ocastellanos30596GRUPO UVESCO, S.A. "BM SUPERMERCADOS"PEQUEOSupermercado grandeRUBEN DARIO, 2SANTANDERNUGGETS206776ROTURA
2019-04-15ocastellanos30596GRUPO UVESCO, S.A. "BM SUPERMERCADOS"PEQUEOSupermercado grandeRUBEN DARIO, 2SANTANDERESCALOPA206777NO EST EN SURTIDO
2019-04-15ocastellanos30676GRUPO UVESCO, S.A. "NORCASH"PEQUEOSupermercado pequeoP ALISAS, S/NSOLARESESCALOPA206777NO EST EN SURTIDO
2019-04-15ocastellanos30676GRUPO UVESCO, S.A. "NORCASH"PEQUEOSupermercado pequeoP ALISAS, S/NSOLARESNUGGETS206776PRESENTE
2019-04-15ocastellanos30676GRUPO UVESCO, S.A. "NORCASH"PEQUEOSupermercado pequeoP ALISAS, S/NSOLARESHAMBURGUESA BARBACOA206775NO EST EN SURTIDO
2019-04-15ocastellanos30676GRUPO UVESCO, S.A. "NORCASH"PEQUEOSupermercado pequeoP ALISAS, S/NSOLARESHAMBURGUESA NATURAL206774NO EST EN SURTIDO
``` ================================================ FILE: docs/jspreadsheet/v2/examples/creating-a-table-from-an-external-csv-file.md ================================================ title: Jspreadsheet | Examples | Creating a web spreadsheet based on an external CSV keywords: Jexcel, jquery, javascript, excel-like, spreadsheet, CSV, table, grid description: How to load the data from an external CSV file into a Jspreadsheet grid or table. [Back to Examples](/jspreadsheet/v2/examples) # Creating a javascript spreadsheet based on an external CSV The example below helps you to create a javascript spreadsheet table based on a remote CSV file, including the headers. The examples also requires a third party jquery CSV parser plugin (100% IETF RFC 4180). Original file: [/jspreadsheet/demo.csv](/jspreadsheet/demo.csv). ## Source code ```html

``` ## Online demo on jsFiddle # Creating a javascript spreadsheet based on an external JSON file In a similar way, you can create a jquery table based on an external JSON file format by using the _url: directive_ as below. ## Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v2/examples/currency-and-masking-numbers.md ================================================ title: Jspreadsheet | Examples | Using currency column type and how to mask numbers keywords: Jexcel, jquery, javascript, excel-like, spreadsheet, jquery plugin, masking, current, numbers description: Handling currency types and masking numbers. [Back to Examples](/jspreadsheet/v2/examples) # Currency and masking numbers The next example will show how to mask the column and automatic change colors based on the column values. ## Source code ```html
``` # Formatting a column value using an external javascript plugin This example shows an alternative way to format numbers in your table. It integrates the numeraljs javascript plugin to format the column. ## Source code ```html
``` **NOTE** The Jspreadsheet uses the [jQuery Mask Plugin](https://github.com/igorescobar/jQuery-Mask-Plugin) to perform the masking. But, the example above shows that it is possible to integrate any external plugin for masking or to visual adjust the data automatically. ================================================ FILE: docs/jspreadsheet/v2/examples/getting-data-from-table.md ================================================ title: Jspreadsheet | Examples | Extract Data from Spreadsheet keywords: Jexcel, jquery, javascript, bootstrap, table design, spreadsheet, CSV, table, grid, extract, get, data description: Get data from inside the datagrid to javascript [Back to Examples](/jspreadsheet/v2/examples) # Extract the data from your Jspreadsheet jquery plugin The following example shows how to extract the data from your jquery table in CSV or JSON formats. ## Source code ```html

``` ## Online demo on jsFiddle [How to extract data example from your jquery table on jsFiddle](https://jsfiddle.net/spreadsheet/tzy1h6rg/) ================================================ FILE: docs/jspreadsheet/v2/examples/headers.md ================================================ title: Jspreadsheet | Examples | Nested Headers keywords: Jexcel, jquery, javascript, excel-like, spreadsheet, jquery plugin, nested headers description: Creating a Jspreadsheet table with nested headers. [Back to Examples](/jspreadsheet/v2/examples) # Nested Headers The most recent version of the jquery plugin Jspreadsheet implements nested headers natively. A few examples on how to add nested headers your jquery table: ```html
``` ================================================ FILE: docs/jspreadsheet/v2/examples/images.md ================================================ title: Jspreadsheet | Examples | Images on your images keywords: Jexcel, jquery, javascript, spreadsheet, table, jquery plugin, images description: Add images on your spreadsheet cells [Back to Examples](/jspreadsheet/v2/examples) # Embed images on your cells The following example shows how to render images inside your spreadsheet cells ## Source code ```html
``` [Edit this example on jsFiddle](https://jsfiddle.net/spreadsheet/9296zmpf/) ================================================ FILE: docs/jspreadsheet/v2/examples/including-formulas-on-your-spreadsheet.md ================================================ title: Jspreadsheet | Examples | Formulas on Spreadsheet keywords: Jexcel, jquery, javascript, bootstrap, table design, spreadsheet, CSV, table, grid, autocomplete, customization, column description: Using Functions inside your spreadsheet [Back to Examples](/jspreadsheet/v2/examples) # Including formulas on your javascript spreadsheet plugin The example below shows how to use Excel like formulas on your jquery spreadsheet. ## Source code ```html
``` # Third party formula implementations You can use a third party libraries to execute various Excel like formulas. ## Source code ```html
``` # Custom javascript formulas The example below shows how to create your own method in javascript to apply as a Excel like formula ## Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v2/examples/integrating-a-third-party-plugin-into-your-spreadsheet.md ================================================ title: Jspreadsheet | Examples | Custom column and integrating plugins on your table keywords: Jexcel, jquery, javascript, excel-like, spreadsheet, jquery plugin, sorting, table, grid, order by description: How to create custom column types based on a third party jquery plugin [Back to Examples](/jspreadsheet/v2/examples) # Create a custom column based on a third party jquery plugin This particular example shows how to create a custom color picker column type using the [Spectrum Jquery Plugin](https://bgrins.github.io/spectrum/). This example illustrates how to create your own custom columns based on any third party jquery plugin. [See this example on jsFiddle](https://jsfiddle.net/spreadsheet/rp0876b1/) ## Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v2/examples/jquery-table-with-toolbars.md ================================================ title: Jspreadsheet | Examples | Your jquery table with toolbars keywords: Jexcel, jquery, javascript, excel-like, spreadsheet, jquery plugin, sorting, jquery table, toolbars description: Full examples how to load a autocomplete dropdown. [Back to Examples](/jspreadsheet/v2/examples) # Add a custom toolbar in your jquery tables The following example will show how to integrate a custom native toolbar in your spreadsheet plugin. ## Instructions The toolbar can be customized with a few parameters. | | | | ---|--- | | _**type**_ | could be **i** for icon, **select** for a dropdown, **spectrum** or a colorpicker. | | _**content**_ | defines the icon (from material icons) when you use type:i; [click here for all possible keys](https://material.io/tools/icons/) | | _**k**_ | means the style should be apply to the cell; | | _**v**_ | means the value of the style should be apply to the cell; When type:select, you can define an array of possibles values; | | _**method**_ | can be used together with type:i to implement any custom method. The method will receive the spreadsheet instance and all marked cells by default. | ## Source code ```html
``` **NOTE:** It is important to have google material fonts loaded. ================================================ FILE: docs/jspreadsheet/v2/examples/meta-information.md ================================================ title: Jspreadsheet | Examples | Add meta information in your cells keywords: Jexcel, jquery, javascript, jquery table, meta information description: Manage the table meta information [Back to Examples](/jspreadsheet/v2/examples) # Add meta information in your spreadsheet spreadsheet plugin You can keep meta information bind to cells but not visible to users. This can be useful for integrating Jspreadsheet with third party software. But, after the initialization is still possible to manage the cell meta information programmatically using the method getMeta and setMeta, as follow. | | | | ---|--- | | $('#my1').jexcel('setMeta', [{ A1: { newInfo:'newInfo' } }, { B1: { info1:'test1', info2:'test2'} }, { C2: { metaTest:'test3' } }]); | Set meta data for the table in a batch | | $('#my1').jexcel('setMeta', 'A2', 'myKeyMeta', 'myValueMeta'); | Set a meta information for A2 | | $('#my1').jexcel('getMeta', 'A1'); | Get the meta information for A1 | | $('#my1').jexcel('getMeta'); | Get all meta information | ## Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v2/examples/mobile.md ================================================ title: Jspreadsheet | Examples | Mobile Layout keywords: Jexcel, jquery, javascript, bootstrap, table design, spreadsheet, CSV, table, grid, mobile description: Responsive Layout {.ignore} ```html
``` ================================================ FILE: docs/jspreadsheet/v2/examples/multiple-spreadsheets-in-the-same-page.md ================================================ title: Jspreadsheet | Examples | Create multiple instances in the same page keywords: Jexcel, jquery, javascript, excel-like, spreadsheet, jquery plugin, sorting, table, grid, order by description: How to create multiple table instances in the same page [Back to Examples](/jspreadsheet/v2/examples) # Multiple spreadsheets in the same page How to embed multiple spreadsheets in the same page. ## Source code {.ignore} ```html

``` ================================================ FILE: docs/jspreadsheet/v2/examples/readonly-options.md ================================================ title: Jspreadsheet | Examples | Handling readonly column and cells on your spreadsheet keywords: Jexcel, jquery, javascript, excel-like, spreadsheet, jquery plugin, events description: Understanding how to set a readonly column or multiple custom cells [Back to Examples](/jspreadsheet/v2/examples) # Readonly columns and cells. ## Source code ```html
``` ## Online demo on jsFiddle ================================================ FILE: docs/jspreadsheet/v2/examples/responsive-columns.md ================================================ title: Jspreadsheet | Examples | Responsive Layout keywords: Jexcel, jquery, javascript, bootstrap, table design, spreadsheet, CSV, table, grid, autocomplete, customization, column description: Creating a more responsive layout on jexcel [Back to Examples](/jspreadsheet/v2/examples) # Responsive columns The Jspreadsheet jquery plugin brings a more responsive column types. ![](img/iphone.png) [Open this table only in a new window](/jspreadsheet/v2/examples/mobile) ================================================ FILE: docs/jspreadsheet/v2/examples/sorting-data.md ================================================ title: Jspreadsheet | Examples | Sorting your grid keywords: Jexcel, jquery, javascript, excel-like, spreadsheet, jquery plugin, sorting, table, grid, order by description: Sorting your Jspreadsheet spreadsheet [Back to Examples](/jspreadsheet/v2/examples) # Sorting your table You can reorder your jquery table by double clicking in the header or by selecting the desired option in the context menu. The example shows how to change the order of your jquery table programmatically. ## Source code ```html

``` ### Programmatically sorting {.ignore} ```html ``` ### Disable the table sorting The ordering is a native enabled feature. To disable that feature please use the columnSorting:false, directive in the initialization. {.ignore} ```html ``` ================================================ FILE: docs/jspreadsheet/v2/examples/table-styling.md ================================================ title: Jspreadsheet | Examples | Changing the table style keywords: Jexcel, jquery, javascript, table design, spreadsheet, table, grid, colours description: Table styling [Back to Examples](/v2/examples#more) # How to change the spreadsheet style. You can define the CSS for specific columns during the initialization. The following example uses the directive style:[] to bring populate the style attribute for each column. But, after the initialization is still possible to manage the cell style programmatically using the method getStyle or setStyle. | | | | ---|--- | | $('#my1').jexcel('setStyle', 'A1', 'font-weight', 'bold'); | Change A1 fontWeight | | $('#my1').jexcel('setStyle', 'A2', 'background-color', 'yellow'); | Change A2 backgroundColor | | $('#my1').jexcel('setStyle', [ { A1: 'font-weight: bold; color:red;' }, { B2: 'background-color: yellow;' }, { C1: 'text-decoration: underline;' }, { D2: 'text-align:left;' } ]); | Change the table in one batch | | $('#my1').jexcel('getStyle', 'A1');| Get A1 style attributes | | $('#my1').jexcel('getStyle');| Get the whole table attributes | ### Source code ```html
``` ## How to set global CSS rules for your jquery tables Give your clients a nice look table with colours and styling. ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v2/examples/table-with-fixed-headers.md ================================================ title: Jspreadsheet | Examples | Data table with fixed headers and scrolling keywords: Jexcel, jquery, javascript, fixed headers, scrolling, data tables description: Overflow table and scrolling [Back to Examples](/jspreadsheet/v2/examples) # Spreadsheet with fixed headers and scrolling Create an instance of your jquery plugin to allow a table overflow and scrolling. ## Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v2/examples/text-wrapping.md ================================================ title: Jspreadsheet | Examples | Text wrap keywords: Jexcel, jquery, javascript, excel-like, spreadsheet, jquery plugin, text wrapping description: How to change the text wrap behavior in a Jspreadsheet column [Back to Examples](/jspreadsheet/v2/examples) # Text wrapping The javascript spreadsheet default configuration does not wrap the text. But, you can change this behavior by using the wordWrap option in the jquery plugin initialization. In the most recent version of Jspreadsheet, alt+enter will allow the user to add a new line when editing the text when the wrap is enabled ## Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v2/examples/tracking-changes-on-the-spreadsheet.md ================================================ title: Jspreadsheet | Examples | Events keywords: Jexcel, jquery, javascript, excel-like, spreadsheet, jquery plugin, events description: Handling events on your spreadsheet [Back to Examples](/jspreadsheet/v2/examples) # Handling events Tracking changes on your spreadsheet. [See this example on jsFiddle](https://jsfiddle.net/spreadsheet/0dyms2b9/) ## Advanced Example Update the chart on every change in your spreadsheet, using the onchange handler ## Source code ```html
``` ## Online demo on jsFiddle ================================================ FILE: docs/jspreadsheet/v2/examples/using-a-calendar-column-type.md ================================================ title: Jspreadsheet | Examples | Calendar column type with date and datetime picker keywords: Jexcel, jquery, javascript, excel-like, spreadsheet, jquery plugin, date, datetime picker description: How to handle a calendar with date and datetime picker. [Back to Examples](/jspreadsheet/v2/examples) # Calendar column type The example below shows how to use and customize special calendar column type. See a live example of calendar usage on [jsfiddle](https://jsfiddle.net/spreadsheet/ajv413cb/). ## Source code ```html
``` ## Date column customization Customize the format and the behavior of your column through the initialization options, as follow: ### Calendar initialization options {.ignore} ```javascript { options: { format:'DD/MM/YYYY', // Date format readonly:0, // Input as readonly (true or false) today:0, // Input with at today as default (true or false) time:0, // Show time picker clear:1, // Show clear button mask:1, // Mask the input months:['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], // Translations can be done here weekdays:['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'], // Translations can be done here weekdays_short:['S', 'M', 'T', 'W', 'T', 'F', 'S'] // Translations can be done here }; } ``` Considering the example above, you can create a calendar including a time picker by simple send the option **time:1** as the following example. {.ignore} ```html
``` ================================================ FILE: docs/jspreadsheet/v2/examples/working-with-dropdowns.md ================================================ title: Jspreadsheet | Examples | Advanced dropdown column type keywords: Jexcel, jquery, javascript, excel-like, spreadsheet, jquery plugin, sorting, table, grid, order by description: Full examples on how to handle simple, advanced, autocomplete and conditional dropdowns. [Back to Examples](/jspreadsheet/v2/examples) # Working with dropdowns Jspreadsheet brings some new very flexible dropdown options that enables you to delivery great user experience though applications. The new dropdown column options include the autocomplete, multiple options, data picker, different template types and much more advantages, such as: * Create a simple dropdown from array * Value or key-value select is available * Populate a dropdown from a external JSON request * Dynamic autocomplete search based on another column value * Conditional dropdowns: options from a dropdown based on a method return * Multiple and autocomplete * Responsive data picker ## Multiple and autocomplete options The highlight features introduced in the most version of the jquery table is definitely the autocomplete and multiple options. The following example shows the column Product Origin with the autocomplete and multiple directives initiated as true. ### Source code ```html
``` ## Conditional dropdown Jspreadsheet dropdown column can show different options based on the value of other columns. To use that function you should use a method defined by the filter parameter in the initialization. The following example shows the product column options based on the value selected on the column Category. ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v2/examples/working-with-the-data.md ================================================ title: Jspreadsheet | Examples | Working with the data keywords: Jexcel, jquery, javascript, insert, remove and move columns and rows, spreadsheet, CSV, table, grid description: Insert, remove and move columns and rows [Back to Examples](/jspreadsheet/v2/examples) # Programmatically insert, remove and move columns and rows The following example shows how to manage data programmatically in your javascript spreadsheet. ## Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v2/examples.md ================================================ title: Examples keywords: Jexcel, javascript, examples description: Examples how to create web based spreadsheets using Jexcel. # Examples The Spreadsheet minimalist jQuery Plugin examples shows the usage with various column types such as text, dropdown, autocomplete, checkbox and date. ```html
``` [See this example on jsfiddle](https://jsfiddle.net/spreadsheet/kgmcav01/) ## Source code ```html
``` ## More examples * [Advanced dropdowns](/jspreadsheet/v2/examples/working-with-dropdowns "Advanced dropdown column type") Full examples on how to handle simple, advanced, autocomplete and conditional dropdowns. * [Programmaticaly changes](/jspreadsheet/v2/examples/working-with-the-data "Jspreadsheet | Examples | Working with the data") Insert, remove and move columns and rows * [Fixed headers](/jspreadsheet/v2/examples/table-with-fixed-headers "Jspreadsheet | Examples | Data table with fixed headers and scrolling") Overflow table and scrolling * [Custom table design](/jspreadsheet/v2/examples/a-custom-table-design "Jspreadsheet | Examples | Bootstrap and custom table design") Create a custom table design. For example a bootstrap-like spreadsheet table. * [Table styling](/jspreadsheet/v2/examples/table-styling "Jspreadsheet | Examples | Changing the table style") Table styling * [Manage cell comments](/jspreadsheet/v2/examples/comments "Jspreadsheet | Examples | Add comments in your jquery table") Manage a table cell comments * [Custom meta information](/jspreadsheet/v2/examples/meta-information "Jspreadsheet | Examples | Add meta information in your cells") Manage the table meta information * [Custom toolbars](/jspreadsheet/v2/examples/jquery-table-with-toolbars "Jspreadsheet | Examples | Your jquery table with toolbars") Full examples how to load a autocomplete dropdown. * [Loading remote data](/jspreadsheet/v2/examples/creating-a-table-from-an-external-csv-file "Jspreadsheet | Examples | Creating a web spreadsheet based on an external CSV") How to load the data from an external CSV file into a Jspreadsheet grid or table. * [Masking and formating](/jspreadsheet/v2/examples/currency-and-masking-numbers "Jspreadsheet | Examples | Using currency column type and how to masking numbers") Handling currency types and masking numbers. * [Calendar picker](/jspreadsheet/v2/examples/using-a-calendar-column-type "Jspreadsheet | Examples | Calendar column type with date and datetime picker") How to handle a calendar with date and datetime picker. * [Sorting](/jspreadsheet/v2/examples/sorting-data "Jspreadsheet | Examples | Sorting your grid") Sorting your Jspreadsheet spreadsheet * [Create custom cells](/jspreadsheet/v2/examples/integrating-a-third-party-plugin-into-your-spreadsheet "Jspreadsheet | Examples | Custom column and integrating plugins on your table") How to create custom column types based on a third party jquery plugin * [Handling events](/jspreadsheet/v2/examples/tracking-changes-on-the-spreadsheet "Jspreadsheet | Examples | Events") Handling events on your spreadsheet * [Readonly options](/jspreadsheet/v2/examples/readonly-options "Jspreadsheet | Examples | Handling readonly column and cells on your spreadsheet") Understanding how to set a readonly column or multiple custom cells * [Multiple instances](/jspreadsheet/v2/examples/multiple-spreadsheets-in-the-same-page "Jspreadsheet | Examples | Create multiple instances in the same page") How to create multiple table instances in the same page * [Text wrap](/jspreadsheet/v2/examples/text-wrapping "Jspreadsheet | Examples | Text wrap") How to change the text wrap behavior in a Jspreadsheet column * [Nested headers](/jspreadsheet/v2/examples/headers "Jspreadsheet | Examples | Nested Headers") Creating a Jspreadsheet table with nested headers. * [Add images on your cells](/jspreadsheet/v2/examples/images "Jspreadsheet | Examples | Images on your images") Add images on your spreasheet cells ================================================ FILE: docs/jspreadsheet/v2/getting-started.md ================================================ title: Getting Started with jExcel keywords: Jspreadsheet CE, Jexcel, JavaScript Data Grid, Spreadsheets, JavaScript tables, Excel-like data grid, web-based spreadsheets, data grid controls, data grid features description: Create data grids with spreadsheet controls with jExcel. [Back to Documentation](/jspreadsheet/v2/docs) # Getting started with the jquery plugin ## Initialization Jspreadsheet can receive data from a JS array, CSV or a JSON format, following the examples below: There are three ways to load data into your spreadsheet. Using the parameter _data:_ passing a reference of a javascript array. The second method is to load the data straight from a remove CSV file using the _csv:_ parameter. The third is to pass the URL from a json content using the _url:_ parameter as show in the following examples. ### Loading from a javascript array ```html
``` ### Loading from a JSON file {.ignore} ```html
``` ### Loading from a CSV file {.ignore} ```html
``` [See a working example](/jspreadsheet/v2/examples/creating-a-table-from-an-external-csv-file) ## Destroying a table You can destroy the table, all data and events related to an existing table by using the method _destroy_ as shown below. {.ignore} ```html ``` ## Header titles If you do not define the column title, the default will be the use of a letter, just as any other spreadsheet software. But, if you would like to have custom column names you can use the directive colHeaders: {.ignore} ```javascript $('#my').jexcel({ data:data, colHeaders: ['Model', 'Price', 'Price', 'Date'], colWidths: [ 300, 80, 100, 100 ], }); ``` **Note** : If you are loading your data from a CSV file, you can define the csvHeader:true, so the first row will be used as your column names. ## Column width The parameter _colWidths_ can be used to define your column widths. ## Column types The javascript spreadsheet has available some extra native column types in addition to the default input text. It means you can get alternative ways to enter data in your spreadsheet. Advanced numeric inputs, dropdowns to calendar picks and a very easy way to have your custom integrations, makes the spreadsheet plugin a very flexible tool to enhance the user experience when using your applications. In the example below, you will have text, numeric inputs and a calendar picker. But, other native options will be available such as: _**text, numeric, hidden, dropdown, autocomplete, checkbox, calendar.**_ {.ignore} ```javascript $('#my').jexcel({ data:data, colHeaders: ['Model', 'Date', 'Price', 'Date'], colWidths: [ 300, 80, 100, 100 ], columns: [ { type: 'text' }, { type: 'numeric' }, { type: 'numeric' }, { type: 'calendar', options: { format:'DD/MM/YYYY' } }, ] }); ``` ### Calendar type When using the calendar column, you can change the behavior behavior of your calendar by sending some extra options as example above. The possible values are: {.ignore} ```javascript let defaults = { format:'DD/MM/YYYY', // Date format readonly:0, // Readonly input today:0, // Default as today time:0, // Show time picker clear:1, // Clear buttom mask:1, // Mask calendar }; ``` [See a working example](/jspreadsheet/v2/examples/using-a-calendar-column-type) ### Dropdown and autocomplete type There are different ways to work with dropdowns in Jspreadsheet. It is possible to define the parameter _source_ as a simple or key-value array. Additionally, is possible to use the paramter _url_ to populate your dropdown from an external json format source. The autocomplete drowndown has the same configuration inputs, and both can be used as follow: {.ignore} ```javascript let data = [ ['Honda', 1, 'Civic', '4'], ['Peugeot', 3,'1007', '2'], ['Smart', 3,'Cabrio', '4;5'], ]; $('#my').jexcel({ data:data, colHeaders: ['Model','Color', 'Description'], colWidths: [ 300, 80, 100 ], columns: [ { type: 'dropdown', source:['Seat', 'Renault', 'Peugeot'] }, { type: 'dropdown', source:[{'id':1,'name':'Yellow'}, {'id':2,'name':'Black'}, {'id':3,'name':'Green'}] }, { type: 'dropdown', url:'/jspreadsheet/test' }, { type: 'dropdown', url:'/jspreadsheet/countries' autocomplete:true, multiple:true }, ] }); ``` [See a working example](/jspreadsheet/v2/examples/working-with-dropdowns) ### Custom type Jspreadsheet makes possible to extend third party jquery plugins to create your custom columns. Basically to use this feature, you should implement some basic methods such as: openEditor, closeEditor, getValue, setValue as following. {.ignore} ```javascript let customEditor = { // Methods closeEditor : function(cell, save) { // Get value var value = $(cell).find('.editor').spectrum('get').toHexString(); // Set visual value $(cell).html(value); $(cell).css('color', value); // Close edition $(cell).removeClass('edition'); }, openEditor : function(cell) { var main = this; // Get current content var html = $(cell).html(); // Basic editor var editor = document.createElement('div'); $(cell).html(editor); $(editor).prop('class', 'editor'); $(editor).spectrum({ color:html, preferredFormat:'hex', hide: function(color) { main.closeEditor($(cell), true); }}); $(editor).spectrum('show'); }, getValue : function(cell) { return $(cell).html(); }, setValue : function(cell, value) { $(cell).html(value); $(cell).css('color', value); return true; } } let data = [ ['Google', '#542727'], ['Yahoo', '#724f4f'], ['Bing', '#b43131'], ]; $('#my').jexcel({ data:data, colHeaders: [ 'Name', 'Custom color' ], colWidths: [ 300, 200 ], columns: [ { type: 'text' }, { type: 'text', editor:customEditor }, ] }); ``` [See a working example](/jspreadsheet/v2/examples/integrating-a-third-party-plugin-into-your-spreadsheet) ## Define a minimum table dimension size. The follow example will create a data table with a minimum number of ten columns and five rows: {.ignore} ```javascript data3 = [ ['Mazda', 2001, 2000], ['Peugeot', 2010, 5000], ['Honda Fit', 2009, 3000], ['Honda CRV', 2010, 6000], ]; $('#minExample').jexcel({ data:data3, minDimensions:[10,5], colHeaders: ['Model', 'Year', 'Price' ], colWidths: [ 300, 80, 100 ] }); ``` ================================================ FILE: docs/jspreadsheet/v3/docs/events.md ================================================ title: Spreadsheet Events: Integration and Customization keywords: Jspreadsheet, data grid, JavaScript, Excel-like features, spreadsheet events, event handling, customized actions, JavaScript integration, interactive spreadsheets, feature customization, event-driven programming, data grid events description: Learn more about Jspreadsheet’s comprehensive event system for advanced customization and integration. [Back to Documentation](/jspreadsheet/v3/docs) # Events on the online spreadsheet ## Custom table scripting after changes Jspreadsheet offers a native feature to customize your table on the fly. You can define the method updateTable to create rules to customize the data should be shown to the user, as the example below. [See an example in action](/jspreadsheet/v3/examples/table-scripting) ## Events Jspreadsheet available events in this version. [Example on handling events on your spreasheet](/jspreadsheet/v3/examples/events) | Event | description | | ---|--- | | **onload** | This method is called when the method setData | | **onbeforechange** | Before a column value is changed. NOTE: It is possible to overwrite the original value, by return a new value on this method. v3.4.0+ | | **onchange** | After a column value is changed. | | **onafterchanges** | After all changes are applied in the table. | | **onpaste** | After a paste action is performed in the javascript table. | | **onbeforepaste** | Before the paste action is performed. Used to parse any input data, should return the data. | | **oninsertrow** | After a new row is inserted. | | **onbeforeinsertrow** | Before a new row is inserted. You can cancel the insert event by returning false. | | **ondeleterow** | After a row is excluded. | | **onbeforedeleterow** | Before a row is deleted. You can cancel the delete event by returning false. | | **oninsertcolumn** | After a new column is inserted. | | **onbeforeinsertcolumn** | Before a new column is inserted. You can cancel the insert event by returning false. | | **ondeletecolumn** | After a column is excluded. | | **onbeforedeletecolumn** | Before a column is excluded. You can cancel the insert event by returning false. | | **onmoverow** | After a row is moved to a new position. | | **onmovecolumn** | After a column is moved to a new position. | | **onresizerow** | After a change in row height. | | **onresizecolumn** | After a change in column width. | | **onselection** | On the selection is changed. | | **onsort** | After a colum is sorted. | | **onfocus** | On table focus | | **onblur** | On table blur | | **onmerge** | On column merge | | **onchangeheader** | On header change | | **onundo** | On undo is applied | | **onredo** | On redo is applied | | **oneditionstart** | When a openEditor is called. | | **oneditionend** | When a closeEditor is called. | | **onchangestyle** | When a setStyle is called. | | **onchangemeta** | When a setMeta is called. | ================================================ FILE: docs/jspreadsheet/v3/docs/programmatically-changes.md ================================================ title: Programmatically Changes keywords: Jspreadsheet CE, Jexcel, JavaScript Data Grid, Spreadsheets, JavaScript tables, Excel-like data grid, web-based spreadsheets, data grid controls, data grid features description: Create data grids with spreadsheet controls with Jspreadsheet CE. [Back to Documentation](/jspreadsheet/v3/docs "Back to the documentation section") # Programmatically changes Jspreadsheet has a comprehensive number of native methods to programmatically interact with your javascript spreadsheet and its data. [Go to a working example](/jspreadsheet/v3/examples/programmatically-updates) ## General Methods | Method | Example | |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------| | **getData:** Get the full or partial table data
@Param boolan onlyHighlighedCells - Get only highlighted cells | spreadsheet.getData([bool]); | | **getJson:** Get the full or partial table data in JSON format
@Param boolan onlyHighlighedCells - Get only highlighted cells | spreadsheet.getData([bool]); | | **getRowData:** Get the data from one row by number
@Param integer rowNumber - Row number | spreadsheet.getRowData([int]); | | **setRowData:** Set the data from one row by number
@Param integer rowNumber - Row number
@param array rowData - Row data | spreadsheet.setRowData([int], [array]); | | **getColumnData:** Get the data from one column by number
@Param integer columnNumber - Column number | spreadsheet.getColumnData([int]); | | **setColumnData:** Set the data from one column by number
@Param integer columnNumber - Column number
@Param array colData - Column data | spreadsheet.setColumnData([int], [array]); | | **setData:** Set the table data
@Param json newData - New json data, null will reload what is in memory. | spreadsheet.setData([json]); | | **setMerge:** Merge cells
@Param string columnName - Column name, such as A1.
@Param integer colspan - Number of columns
@Param integer rowspan - Number of rows | spreadsheet.setMerge([string], [int], [int]); | **getMerge:** Get merged cells properties
@Param string columnName - Column name, such as A1. | spreadsheet.getMerge([string]); | | **removeMerge:** Destroy merged by column name
@Param string columnName - Column name, such as A1. | spreadsheet.removeMerge([string]); | | **destroyMerged:** Destroy all merged cells | spreadsheet.destroyMerge(); | | **getCell** : get current cell DOM
@Param string columnName - str compatible with excel, or as object. | spreadsheet.getCell([string]); | | **getLabel** : get current cell DOM innerHTML
@Param string columnName - str compatible with excel, or as object. | spreadsheet.getLabel([string]); | | **getValue:** get current cell value
@Param mixed cellIdent - str compatible with excel, or as object. | spreadsheet.getValue([string]); | | **getValueFromCoords:** get value from coords
@Param integer x
@Param integer y | spreadsheet.getValueFromCoords([integer], [integer]); | **setValue:** change the cell value
@Param mixed cellIdent - str compatible with excel, or as object.
@Param string Value - new value for the cell
@Param bool force - update readonly columns | spreadsheet.setValue([string], [string], [bool]); | | **setValueFromCoords:** get value from coords
@Param integer x
@Param integer y
@Param string Value - new value for the cell
@Param bool force - update readonly columns | spreadsheet.getValueFromCoords([integer], [integer], [string], [bool]); | | **resetSelection:** Reset the table selection
@Param boolean executeBlur - execute the blur from the table | spreadsheet.resetSelection([bool]); | | **updateSelection:** select cells
@Param object startCell - cell object
@Param object endCell - cell object
@Param boolean ignoreEvents - ignore onselection event | spreadsheet.updateSelection([cell], [cell], true); | | **updateSelectionFromCoords:** select cells
@Param integer x1
@Param integer y1
@Param integer x2
@Param integer y2 | spreadsheet.updateSelectionFromCoords([integer], [integer], [integer], [integer]); | | **getWidth:** get the current column width
@Param integer columnNumber - column number starting on zero | spreadsheet.getWidth([integer]); | | **setWidth:** change column width
@Param integer columnNumber - column number starting on zero
@Param string newColumnWidth - New column width | spreadsheet.setWidth([integer], [integer]); | | **getHeight:** get the current row height
@Param integer rowNumber - row number starting on zero | spreadsheet.getHeight([integer]); | | **setHeight:** change row height
@Param integer rowNumber - row number starting on zero
@Param string newRowHeight- New row height | spreadsheet.setHeight([integer], [integer]); | | **getHeader:** get the current header by column number
@Param integer columnNumber - Column number starting on zero | spreadsheet.getHeader([integer]); | | **getHeaders:** get all header titles | spreadsheet.getHeaders(); | | **setHeader:** change header by column
@Param integer columnNumber - column number starting on zero
@Param string columnTitle - New header title | spreadsheet.setHeader([integer], [string]); | | **getStyle:** get table or cell style
@Param mixed - cell identification or null for the whole table. | spreadsheet.getStyle([string]); | | **setStyle:** set cell(s) CSS style
@Param mixed - json with whole table style information or just one cell identification. Ex. A1.
@Param k [optional]- CSS key
@Param v [optional]- CSS value | spreadsheet.setSyle([object], [string], [string]); | | **resetStyle:** remove all style from a cell
@Param string columnName - Column name, example: A1, B3, etc | spreadsheet.resetStyle([string]); | | **getComments:** get cell comments
@Param mixed - cell identification or null for the whole table. | spreadsheet.getComments([string]); | | **setComments:** set cell comments
@Param cell - cell identification
@Param text - comments | spreadsheet.setComments([string], [string]); | | **orderBy:** reorder a column asc or desc
@Param integer columnNumber - column number starting on zero
@Param smallint sortType - One will order DESC, zero will order ASC, anything else will toggle the current order | spreadsheet.orderBy([integer], [boolean]); | | **getConfig:** get table definitions | spreadsheet.getConfig(); | | **insertColumn:** add a new column
@Param mixed - num of columns to be added or data to be added in one single column
@Param int columnNumber - number of columns to be created
@Param boolean insertBefore
@Param object properties - column properties | spreadsheet.insertColumn([mixed], [integer], [boolean], [object]); | | **deleteColumn:** remove column by number
@Param integer columnNumber - Which column should be excluded starting on zero
@Param integer numOfColumns - number of columns to be excluded from the reference column | spreadsheet.deleteColumn([integer], [integer]); | | **moveColumn:** change the column position
@Param integer columnPosition
@Param integer newColumnPosition | spreadsheet.moveColumn([integer], [integer]); | | **insertRow:** add a new row
@Param mixed - number of blank lines to be insert or a single array with the data of the new row
@Param integer rowNumber - reference row number
@param boolean insertBefore | spreadsheet.insertRow([mixed], [integer], [boolean]); | | **deleteRow:** remove row by number
@Param integer rowNumber - Which row should be excluded starting on zero
@Param integer numOfRows - number of lines to be excluded | spreadsheet.deleteRow([integer], [integer]); | | **moveRow:** change the row position
@Param integer rowPosition
@Param integer newRowPosition | >spreadsheet.moveRow([integer], [integer]); | | **download:** get the current data as a CSV file
@Param bool - true to download parsed formulas. | spreadsheet.download([bool]); | | **getMeta:** get the table or cell meta information
@Param mixed - cell identification or null for the whole table. | spreadsheet.getMeta([string]); | | **setMeta:** set the table or cell meta information
@Param mixed - json with whole table meta information. | spreadsheet.setMeta([mixed]); | | **fullscreen:** Toogle table fullscreen mode
@Param boolan fullscreen - define fullscreen status as true or false | spreadsheet.fullscreen([bool]); | | **getSelectedRows:** Get the selected rows
@Param boolean asIds - Get the rowNumbers or row DOM elements | spreadsheet.getSelectedRows([bool]); | | **getSelectedColumns:** Get the selected columns
@Param boolan asIds - Get the colNumbers or row DOM elements | spreadsheet.getSelectedColumns([bool]); | | **showIndex:** show column of index numbers | spreadsheet.showIndex(); | | **hideIndex:** hide column of index numbers | spreadsheet.hideIndex(); | | **search:** search in the table, only if directive is enabled during inialization.
@Param string - Search for word | spreadsheet.search([string]); | | **resetSearch:** reset search table | spreadsheet.resetSearch(); | | **whichPage:** Which page showing on jspreadsheet - Valid only when pagination is true. | spreadsheet.whichPage(); | | **page:** Go to page number- Valid only when pagination is true.
@Param integer - Go to page number | spreadsheet.page([integer]); | | **undo:** Undo last changes | spreadsheet.undo(); | | **redo:** Redo changes | spreadsheet.redo(); | ================================================ FILE: docs/jspreadsheet/v3/docs/quick-reference.md ================================================ title: Quick Reference keywords: Jspreadsheet CE, Jexcel, JavaScript Data Grid, Spreadsheets, JavaScript tables, Excel-like data grid, web-based spreadsheets, data grid controls, data grid features description: Quick Reference of the jspreadsheet properties [Back to Documentation](/jspreadsheet/v3/docs) # The online spreadsheet quick reference ## Methods | Method | Example | | --- | --- | | **getData:** Get the full or partial table data
@Param boolean onlyHighlightedCells - Get only highlighted cells | spreadsheet.getData([bool]); | | **getJson:** Get the full or partial table data in JSON format
@Param boolean onlyHighlightedCells - Get only highlighted cells | spreadsheet.getJson([bool]); | | **getRowData:** Get the data from one row by number
@Param integer rowNumber - Row number | spreadsheet.getRowData([int]); | | **setRowData:** Set the data from one row by number
@Param integer rowNumber - Row number
@Param array rowData - Row data | spreadsheet.setRowData([int], [array]); | | **getColumnData:** Get the data from one column by number
@Param integer columnNumber - Column number | spreadsheet.getColumnData([int]); | | **setColumnData:** Set the data from one column by number
@Param integer columnNumber - Column number
@Param array colData - Column data | spreadsheet.setColumnData([int], [array]); | | **setData:** Set the table data
@Param json newData - New json data, null will reload what is in memory. | spreadsheet.setData([json]); | | **setMerge:** Merge cells
@Param string columnName - Column name, such as A1.
@Param integer colspan - Number of columns
@Param integer rowspan - Number of rows | spreadsheet.setMerge([string], [int], [int]); | | **getMerge:** Get merged cells properties
@Param string columnName - Column name, such as A1. | spreadsheet.getMerge([string]); | | **removeMerge:** Destroy merged by column name
@Param string columnName - Column name, such as A1. | spreadsheet.removeMerge([string]); | | **destroyMerged:** Destroy all merged cells | spreadsheet.destroyMerge(); | | **getCell** : get current cell DOM
@Param string columnName - str compatible with excel, or as object. | spreadsheet.getCell([string]); | | **getLabel** : get current cell DOM innerHTML
@Param string columnName - str compatible with excel, or as object. | spreadsheet.getLabel([string]); | | **getValue:** get current cell value
@Param mixed cellIdent - str compatible with excel, or as object. | spreadsheet.getValue([string]); | | **getValueFromCoords:** get value from coords
@Param integer x
@Param integer y | spreadsheet.getValueFromCoords([integer], [integer]); | | **setValue:** change the cell value
@Param mixed cellIdent - str compatible with excel, or as object.
@Param string Value - new value for the cell
@Param bool force - update readonly columns | spreadsheet.setValue([string], [string], [bool]); | | **setValueFromCoords:** get value from coords
@Param integer x
@Param integer y
@Param string Value - new value for the cell
@Param bool force - update readonly columns | spreadsheet.setValueFromCoords([integer], [integer], [string], [bool]); | | **resetSelection:** Reset the table selection
@Param boolean executeBlur - execute the blur from the table | spreadsheet.resetSelection([bool]); | | **updateSelection:** select cells
@Param object startCell - cell object
@Param object endCell - cell object
@Param boolean ignoreEvents - ignore onselection event | spreadsheet.updateSelection([cell], [cell], true); | | **updateSelectionFromCoords:** select cells
@Param integer x1
@Param integer y1
@Param integer x2
@Param integer y2 | spreadsheet.updateSelectionFromCoords([integer], [integer], [integer], [integer]); | | **getWidth:** get the current column width
@Param integer columnNumber - column number starting on zero | spreadsheet.getWidth([integer]); | | **setWidth:** change column width
@Param integer columnNumber - column number starting on zero
@Param string newColumnWidth - New column width | spreadsheet.setWidth([integer], [integer]); | | **getHeight:** get the current row height
@Param integer rowNumber - row number starting on zero | spreadsheet.getHeight([integer]); | | **setHeight:** change row height
@Param integer rowNumber - row number starting on zero
@Param string newRowHeight- New row height | spreadsheet.setHeight([integer], [integer]); | | **getHeader:** get the current header by column number
@Param integer columnNumber - Column number starting on zero | spreadsheet.getHeader([integer]); | | **getHeaders:** get all header titles | spreadsheet.getHeaders(); | | **setHeader:** change header by column
@Param integer columnNumber - column number starting on zero
@Param string columnTitle - New header title | spreadsheet.setHeader([integer], [string]); | | **getStyle:** get table or cell style
@Param mixed - cell identification or null for the whole table. | spreadsheet.getStyle([string]); | | **setStyle:** set cell(s) CSS style
@Param mixed - json with whole table style information or just one cell identification. Ex. A1.
@Param k [optional]- CSS key
@Param v [optional]- CSS value | spreadsheet.setStyle([object], [string], [string]); | | **resetStyle:** remove all style from a cell
@Param string columnName - Column name, example: A1, B3, etc | spreadsheet.resetStyle([string]); | | **getComments:** get cell comments
@Param mixed - cell identification or null for the whole table. | spreadsheet.getComments([string]); | | **setComments:** set cell comments
@Param cell - cell identification
@Param text - comments | spreadsheet.setComments([string], [string]); | | **orderBy:** reorder a column asc or desc
@Param integer columnNumber - column number starting on zero
@Param smallint sortType - One will order DESC, zero will order ASC, anything else will toggle the current order | spreadsheet.orderBy([integer], [boolean]); | | **getConfig:** get table definitions | spreadsheet.getConfig(); | | **insertColumn:** add a new column
@Param mixed - num of columns to be added or data to be added in one single column
@Param int columnNumber - number of columns to be created
@Param boolean insertBefore
@Param object properties - column properties | spreadsheet.insertColumn([mixed], [integer], [boolean], [object]); | | **deleteColumn:** remove column by number
@Param integer columnNumber - Which column should be excluded starting on zero
@Param integer numOfColumns - number of columns to be excluded from the reference column | spreadsheet.deleteColumn([integer], [integer]); | | **moveColumn:** change the column position
@Param integer columnPosition
@Param integer newColumnPosition | spreadsheet.moveColumn([integer], [integer]); | | **insertRow:** add a new row
@Param mixed - number of blank lines to be insert or a single array with the data of the new row
@Param integer rowNumber - reference row number
@Param boolean insertBefore | spreadsheet.insertRow([mixed], [integer], [boolean]); | | **deleteRow:** remove row by number
@Param integer rowNumber - Which row should be excluded starting on zero
@Param integer numOfRows - number of lines to be excluded | spreadsheet.deleteRow([integer], [integer]); | | **moveRow:** change the row position
@Param integer rowPosition
@Param integer newRowPosition | spreadsheet.moveRow([integer], [integer]); | | **download:** get the current data as a CSV file
@Param bool - true to download parsed formulas. | spreadsheet.download([bool]); | | **getMeta:** get the table or cell meta information
@Param mixed - cell identification or null for the whole table. | spreadsheet.getMeta([string]); | | **setMeta:** set the table or cell meta information
@Param mixed - json with whole table meta information. | spreadsheet.setMeta([mixed]); | | **fullscreen:** Toggle table fullscreen mode
@Param boolean fullscreen - define fullscreen status as true or false | spreadsheet.fullscreen([bool]); | | **getSelectedRows:** Get the selected rows
@Param boolean asIds - Get the rowNumbers or row DOM elements | spreadsheet.getSelectedRows([bool]); | | **getSelectedColumns:** Get the selected columns
@Param boolean asIds - Get the colNumbers or row DOM elements | spreadsheet.getSelectedColumns([bool]); | | **showColumn:** show column by number | spreadsheet.showIndex([int]); | | **hideColumn:** hide column by number | spreadsheet.hideColumn([int]); | | **showIndex:** show column of index numbers | spreadsheet.showIndex(); | | **hideIndex:** hide column of index numbers | spreadsheet.hideIndex(); | | [Working example](/jspreadsheet/v3/examples/programmatically-changes) ## Events | Event | Description | | --- | --- | | **onload** | This method is called when the method setData | | **onbeforechange** | Before a column value is changed. NOTE: It is possible to overwrite the original value by returning a new value on this method. v3.4.0+ | | **onchange** | After a column value is changed. | | **onafterchanges** | After all changes are applied in the table. | | **onpaste** | After a paste action is performed in the JavaScript table. | | **onbeforepaste** | Before the paste action is performed. Used to parse any input data, should return the data. | | **oninsertrow** | After a new row is inserted. | | **onbeforeinsertrow** | Before a new row is inserted. You can cancel the insert event by returning false. | | **ondeleterow** | After a row is excluded. | | **onbeforedeleterow** | Before a row is deleted. You can cancel the delete event by returning false. | | **oninsertcolumn** | After a new column is inserted. | | **onbeforeinsertcolumn** | Before a new column is inserted. You can cancel the insert event by returning false. | | **ondeletecolumn** | After a column is excluded. | | **onbeforedeletecolumn** | Before a column is excluded. You can cancel the insert event by returning false. | | **onmoverow** | After a row is moved to a new position. | | **onmovecolumn** | After a column is moved to a new position. | | **onresizerow** | After a change in row height. | | **onresizecolumn** | After a change in column width. | | **onselection** | When the selection is changed. | | **onsort** | After a column is sorted. | | **onfocus** | On table focus | | **onblur** | On table blur | | **onmerge** | On column merge | | **onchangeheader** | On header change | | **onundo** | When undo is applied | | **onredo** | When redo is applied | | **oneditionstart** | When openEditor is called. | | **oneditionend** | When closeEditor is called. | | **onchangestyle** | When setStyle is called. | | **onchangemeta** | When setMeta is called. | | **onchangepage** | When the page is changed. | [Example on handling events on Jspreadsheet](/jspreadsheet/v3/examples/events) ## Initialization | Parameter | Description | | --- | --- | | **url** | Load an external JSON file from this URL: string | | **data** | Load this data into the JavaScript table: array | | **copyCompatibility** | When true, copy and export will bring formula results; if false, will bring formulas: boolean | | **rows** | Row properties: height.: object | | **columns** | Column type, title, width, align, dropdown options, text wrapping, mask, etc.: object | | **defaultColWidth** | Default width for a new column: integer | | **defaultColAlign** | Default align for a new column: [center, left, right] | | **minSpareRows** | Minimum number of spare rows: [integer] | | **minSpareCols** | Minimum number of spare cols: [integer] | | **minDimensions** | Minimum table dimensions: [cols, rows] | | **allowExport** | Allow table export: bool | | **includeHeadersOnDownload** | Include header titles on download: bool | | **columnSorting** | Allow column sorting: bool | | **columnDrag** | Allow column dragging: bool | | **columnResize** | Allow column resizing: bool | | **rowResize** | Allow row resizing: bool | | **rowDrag** | Allow row dragging: bool | | **editable** | Allow table edition: bool | | **allowInsertRow** | Allow insert a new row: bool | | **allowManualInsertRow** | Allow user to insert a new row: bool | | **allowInsertColumn** | Allow insert a new column: bool | | **allowManualInsertColumn** | Allow user to create a new column: bool | | **allowDeleteRow** | Allow delete a row: bool | | **allowDeleteColumn** | Allow delete a column: bool | | **allowRenameColumn** | Allow rename a column: bool | | **allowComments** | Allow comments over the cells: bool | | **wordWrap** | Global text wrapping: bool | | **csv** | Load an external CSV file from this URL: string | | **csvFileName** | Default filename for a download method: string | | **csvHeaders** | Load header titles from the CSV file: bool | | **csvDelimiter** | Default delimiter for the CSV file: string | | **selectionCopy** | Allow selection copy: bool | | **mergeCells** | Cells to be merged in the table initialization: object | | **toolbar** | Add custom toolbars: object | | **search** | Allow search in the table: bool | | **pagination** | Break the table by pages: integer | | **paginationOptions** | Number of records per page: 25, 50, 75, 100 for example: [array of numbers] | | **fullscreen** | Fullscreen mode: bool | | **lazyLoading** | Activate the table lazy loading: bool | | **loadingSpin** | Activate the loading spin: bool | | **tableOverflow** | Allow table overflow: bool | | **tableHeight** | Force the max height of the table: CSS String | | **tableWidth** | Force the max width of the table: CSS String | | **meta** | Meta information: object | | **style** | Cells style in the table initialization: object | | **parseFormulas** | Enable execution of formulas inside the table: bool | | **autoIncrement** | Auto increment actions when using the dragging corner: bool | | **updateTable** | Method to config custom script execution. NOTE: This does not work with lazyLoading, Pagination or Search options. | | **nestedHeaders** | Define the nested headers, including title, colspan, etc.: object | | **contextMenu** | Context menu content: function() { return customMenu } | | **text** | All messages to be customized: object | ## Translations | Key | Default value | | --- | --- | | **noRecordsFound** | No records found | | **showingPage** | Showing page {0} of {1} entries | | **show** | Show | | **entries** | entries | | **insertANewColumnBefore** | Insert a new column before | | **insertANewColumnAfter** | Insert a new column after | | **deleteSelectedColumns** | Delete selected columns | | **renameThisColumn** | Rename this column | | **orderAscending** | Order ascending | | **orderDescending** | Order descending | | **insertANewRowBefore** | Insert a new row before | | **insertANewRowAfter** | Insert a new row after | | **deleteSelectedRows** | Delete selected rows | | **editComments** | Edit comments | | **addComments** | Add comments | | **comments** | Comments | | **clearComments** | Clear comments | | **copy** | Copy... | | **paste** | Paste... | | **saveAs** | Save as... | | **about** | About | | **areYouSureToDeleteTheSelectedRows** | Are you sure to delete the selected rows? | | **areYouSureToDeleteTheSelectedColumns** | Are you sure to delete the selected columns? | | **thisActionWillDestroyAnyExistingMergedCellsAreYouSure** | This action will destroy any existing merged cells. Are you sure? | | **thisActionWillClearYourSearchResultsAreYouSure** | This action will clear your search results. Are you sure? | | **thereIsAConflictWithAnotherMergedCell** | There is a conflict with another merged cell | | **invalidMergeProperties** | Invalid merged properties | | **cellAlreadyMerged** | Cell already merged | | **noCellsSelected** | No cells selected | [Working example](/jspreadsheet/v3/examples/translations) ================================================ FILE: docs/jspreadsheet/v3/docs.md ================================================ title: The javascript spreadsheet documentation keywords: Jexcel, javascript, excel-like, spreadsheet, table, grid description: Jspreadsheet CE official documentation, the javascript spreadsheet Jspreadsheet v3 Documentation ============================= The Jspreadsheet is a lightweight javascript spreadsheet component to help with data management in web applications. * [Getting started](/jspreadsheet/v3/docs/getting-started "Getting started with Jspreadsheet") Getting started with Jspreadsheet - the online spreadsheet. Learning the basics, create instances, and more about the general spreadsheet configuration. * [Programmatically changes](/jspreadsheet/v3/docs/programmatically-changes "Programmatically changes") How to interact with your Jspreadsheet - the online spreadsheet and tables through javascript. * [Handling events](/jspreadsheet/v3/docs/events "Handling events on Jspreadsheet") How to handle events on your javascript spreadsheet. * [Quick reference](/jspreadsheet/v3/docs/quick-reference "Jspreadsheet method and events") Quick reference of all methods, configuration variables, events and language. ================================================ FILE: docs/jspreadsheet/v3/examples/angular.md ================================================ title: Jexcel with Angular keywords: Jexcel, javascript, using jspreadsheet and angular description: A full example on how to integrate Jspreadsheet with Angular [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # The Javascript spreadsheet with Angular [Click here to see the project running on codesandbox.](https://codesandbox.io/s/jexcel-and-angular-ej4u2) ### Source code {.ignore} ```javascript import { Component } from "@angular/core"; import * as jexcel from "jexcel"; require("jexcel/dist/jexcel.css"); @Component({ selector: "app-root", templateUrl: "./app.component.html", styleUrls: ["./app.component.css"] }) export class AppComponent { title = "CodeSandbox"; ngAfterViewInit() { jexcel(document.getElementById("spreadsheet"), { data: [[]], columns: [ { type: "dropdown", width: "100px", source: ["Y", "N"] }, { type: "color", width: "100px", render: "square" } ], minDimensions: [10, 10] }); } } ``` ================================================ FILE: docs/jspreadsheet/v3/examples/column-types.md ================================================ title: Column types keywords: Jexcel, javascript, javascript vanilla, javascript plugin, plugin, excel-like, spreadsheet, table, tables, grid, datatables, data description: Learn more about the powerful column types. This example brings all native column types and how to create your own custom type. [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Column types The native available types in jspreadsheet javascript spreadsheet are the following: * text * numeric * hidden * dropdown * autocomplete * checkbox * radio * calendar * image * color ## Native column types There are several other properties to change the behavior of those columns, please check the dropdown, calendar examples to get more advanced examples. ```html
``` ## Custom column type Jspreadsheet is very powerful and flexible, and you can create custom column type based on any external plugins. A time custom column based on the [clockpicker plugin](https://weareoutman.github.io/clockpicker/) by weareoutman. ```html
``` ================================================ FILE: docs/jspreadsheet/v3/examples/comments.md ================================================ title: Allow comments in your javascript table keywords: Jexcel, spreadsheet, javascript, cell comments, javascript table description: Allow comments in your table spreadsheet. [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Comments on your javascript spreadsheet The javascript vanilla spreadsheet plugin allows the user to set custom comments for each individual cells. By allowComments in the initialization, the user will be able to add comments in the rigth click contextMenu. ## Manage cell comments programmatically To apply comments via javascript, you can use the methods setComments or getComments, as follow: ```html

``` ================================================ FILE: docs/jspreadsheet/v3/examples/contextmenu.md ================================================ title: Custom contextmenu keywords: Jexcel, javascript, javascript vanilla, javascript plugin, plugin, excel-like, spreadsheet, table, tables, grid, datatables, data description: How to customize Jspreadsheet contextmenu [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Custom contextmenu The following example remove the copy and paste from the contextmenu in order to create a custom contextMenu ```html
``` ================================================ FILE: docs/jspreadsheet/v3/examples/custom-table-design.md ================================================ title: Custom Table Design keywords: Jexcel, javascript, javascript vanilla, javascript plugin, plugin, excel-like, spreadsheet, table, tables, grid, datatables, data description: Customized CSS for your datagrid [Back to Examples](jspreadsheet/v3/examples) # Create custom CSS for your javascript spreadsheet The following example shows a CSS addon to change the core layout of your jquery tables. ## Green borders and corners jquery spreadsheet Your jquery table can be customized by including an additional addon CSS. If you have created a nice design, please share with us. ```html
``` ================================================ FILE: docs/jspreadsheet/v3/examples/datatables.md ================================================ title: Searchable Datatables keywords: Jexcel, javascript, javascript vanilla, javascript plugin, plugin, excel-like, spreadsheet, table, tables, grid, datatables, data description: Full spreadsheet example with search and pagination to bring great compatibility for those who love datatables. [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Javascript spreadsheet with search and pagination The following example shows how to create a javascript spreadsheet instance with a design similar to datatables jquery plugin. ```html
``` ================================================ FILE: docs/jspreadsheet/v3/examples/date-and-datetime-picker.md ================================================ title: Calendar with date and datetime picker keywords: Jexcel, javascript, excel-like, spreadsheet, date, datetime, calendar description: Example from basic to advanced calendar usage, date and datetime picker [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Calendar column type The example below shows how to use and customize special calendar column type. Jspreadsheet uses the jSuites [Javascript Calendar](https://jsuites.net/docs/javascript-calendar) plugin ```html
``` ## Date column customization Customize the format and the behavior of your column through the initialization options, as follow: ```javascript { options : { // Date format format:'DD/MM/YYYY', // Allow keyboard date entry readonly:0, // Today is default today:0, // Show timepicker time:0, // Show the reset button resetButton:true, // Placeholder placeholder:'', // Translations can be done here months:['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], weekdays:['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'], weekdays_short:['S', 'M', 'T', 'W', 'T', 'F', 'S'], // Value value:null, // Events onclose:null, onchange:null, // Fullscreen (this is automatic set for screensize < 800) fullscreen:false, }; } ``` ## JavaScript Calendar Picker More information about the jSuites responsive [JavaScript calendar](https://jsuites.net/docs/javascript-calendar) plugin. ================================================ FILE: docs/jspreadsheet/v3/examples/dropdown-and-autocomplete.md ================================================ title: Advanced dropdown column type keywords: Jexcel, jquery, javascript, excel-like, spreadsheet, jquery plugin, sorting, table, grid, order by description: Full examples on how to handle simple, advanced, multiple, autocomplete and conditional dropdowns. Create amazing javascript tables using categories and images in your dropdowns. [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Dropdown and autocomplete column type Jspreadsheet brings a very powerful, reflexive and responsive dropdown column type to support a better user experience through your applications. The new dropdown column options include autocomplete, multiple options, data picker, different template types and much more advantages, such as: * Create a simple dropdown from array * Value or key-value select box is available * Populate a dropdown from a external JSON request * Dynamic autocomplete search based on another column value * Conditional dropdowns: options from a dropdown based on a method return * Multiple selection and internal dropdown search * Responsive data picker with multiple render types * Image icon and group items ## Multiple and autocomplete options The highlight features introduced in the most version of the vanilla javascript spreadsheet is definitely the autocomplete and multiple options. The following example shows the column Product Origin with the autocomplete and multiple directives initiated as true. ```html
``` ## Conditional dropdown The example below shows the dependency of the second column in relation to the first. ```html
``` ## Group, images, and advanced render options Improve the user experience with a responsive data picker. ```html
``` ## JavaScript Dropdown Component More options for the dropdowns, please refer to the [jSuites JavaScript Dropdown Documentation](https://jsuites.net/docs/dropdown). ================================================ FILE: docs/jspreadsheet/v3/examples/events.md ================================================ title: Handling events on Jspreadsheet keywords: Jexcel, javascript, excel-like, spreadsheet, table, grid, events description: Learn how to handle events on Jspreadsheet [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Handling events ## Various tracking javascript methods Binding tracking events on your javascript spreadsheet ```html
``` ## Advanced Example Update the chart on every change in your spreadsheet, using the onchange handler ```html
``` ================================================ FILE: docs/jspreadsheet/v3/examples/headers.md ================================================ title: Nested headers and column header updates keywords: Jexcel, spreadsheet, javascript, header updates, nested headers, javascript table description: Enabled nested headers in your spreadsheet and learn how to set or get header values [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Headers ## Nested headers The online spreadsheet implements nested headers natively though the directive **nestedHeaders** ## Programmatically header updates There are a few options to allow the user to interact with the header titles. Using the contextMenu over the desired header, by pressing an selected header and holding the click for 500ms, or via javascript. ```html

``` ================================================ FILE: docs/jspreadsheet/v3/examples/image-upload.md ================================================ title: Embed images to your spreadsheet using base64 keywords: Jexcel, javascript, excel-like, spreadsheet, image upload, base64, embed images description: This examples shows how to embed and upload images to your spreadsheet. [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Embed images into your spreadsheet The following examples shows how to embed images into your spreadsheet. ## Load a local image to your table Load images from your local machine into your javascript spreadsheet ```html
``` ## Embed remote images to your table Automatic image rendering from a remote URL using updateTable method ```html
``` ================================================ FILE: docs/jspreadsheet/v3/examples/import-data.md ================================================ title: Load data from CSV or JSON or XLSX keywords: Jexcel, javascript, excel-like, spreadsheet, loading data, csv, json, xlsx. description: How to import data from an external CSV, json file or XLSX. [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Create a javascript spreadsheet There are a few different ways to load data to your javascript spreadsheet shown in the next four examples below ## Based on a external CSV file The example below helps you to create a javascript spreadsheet table based on a remote CSV file, including the headers. ```html

``` ## Based on an external JSON file In a similar way, you can create a table based on an external JSON file format by using the _url: directive_ as below. ```html
``` ## Based on an JSON object The data directive can be used to define a JSON object. In this case you can define by the name directive the order of the columns. ### Source code ```html
``` **NOTE** : This example is based on a customized version of the free version of SheetsJS. There is no guarantee in the use of this library. Please consider purchase their professional version. ================================================ FILE: docs/jspreadsheet/v3/examples/jquery.md ================================================ title: Jspreadsheet with Jquery keywords: Jexcel, javascript, using jspreadsheet and Jquery description: A full example on how to integrate Jspreadsheet with Jquery [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Jquery Creating a jspreadsheet javascript instance using jQuery ### Source code {.ignore} ```html
``` ================================================ FILE: docs/jspreadsheet/v3/examples/lazy-loading.md ================================================ title: Dealing with big spreadsheets through lazy loading keywords: Jexcel, javascript, javascript vanilla, javascript plugin, plugin, excel-like, spreadsheet, table, tables, grid, datatables, data description: This example brings a very nice feature to deal with large table datasets. [Back to Examples](/jspreadsheet/v3/examples) # Lazy loading The following table is dealing with 60.000 columns. The lazy loading method allows render up to 130 rows at the same time and will render other rows based on the scrolling. ```html
``` ================================================ FILE: docs/jspreadsheet/v3/examples/merged-cells.md ================================================ title: How to merge the spreadsheet cells keywords: Jexcel, spreadsheet, javascript, javascript table, merged cells description: Full example on how to handle merge cells in your javascript tables. [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Merged cells You can merge cells on your spreadsheet in the table initialization or programmatically as follows: The following methods are available for merge cells management: `setMerge`, `getMerge`, `removeMerge`, `destroyMerged`_ ```html

``` ================================================ FILE: docs/jspreadsheet/v3/examples/meta-information.md ================================================ title: Meta information keywords: Javascript spreadsheet, javascript, javascript table, meta information description: Keep hidden information about your data grid cells using the Jspreadsheet meta information methods [Back to Examples](/jspreadsheet/v3/examples#more) # Meta information This feature helps you keep import information about the cells hidden from users. You can define any meta information during the initialization or programmatically after that thought getMeta or setMeta methods. ```html

``` ================================================ FILE: docs/jspreadsheet/v3/examples/programmatically-updates.md ================================================ title: Update your table by javascript keywords: Jexcel, javascript, excel-like, spreadsheet, javascript programmatically changes description: How to update your spreadsheet programmatically via JavaScript. [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Programmatically table updates ## Insert, remove and move columns and rows The following example shows how to manage data programmatically in your javascript spreadsheet. ```html

``` ## Updating column width and row height Update the table width and height properties. ```html

``` ================================================ FILE: docs/jspreadsheet/v3/examples/react.md ================================================ title: Jspreadsheet with React keywords: Jexcel, javascript, using Jspreadsheet and react description: A full example on how to integrate Jspreadsheet with React [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # The Javascript spreadsheet with React Integrating Jspreadsheet with React [React with jspreadsheet sample project](https://codesandbox.io/s/jexcel-and-react-hmx0k) ### Source code {.ignore} ```javascript class Jspreadsheet extends React.Component { constructor(props) { super(props); this.options = props.options; } componentDidMount = function() { this.el = jexcel(ReactDOM.findDOMNode(this).children[0], this.options); } addRow = function() { this.el.insertRow(); } render() { return (


this.addRow()}>
); } } var options = { data:[[]], minDimensions:[10,10], }; ReactDOM.render(, document.getElementById('spreadsheet')) ``` ================================================ FILE: docs/jspreadsheet/v3/examples/readonly.md ================================================ title: Readonly Columns keywords: Jexcel, spreadsheet, javascript, javascript table, readonly description: Example how to set up readonly cells on your spreadsheets. [Back to Examples](/jspreadsheet/v3/examples) # Readonly columns and cells Setting a readonly the whole column or a single specific cell. ```html
``` ================================================ FILE: docs/jspreadsheet/v3/examples/sorting.md ================================================ title: Sorting the spreadsheet columns keywords: Jexcel, spreadsheet, javascript, javascript table, sorting description: Example how to sort the table by a column via javascript. [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Sorting your table ## Simple example You can sort your javascript table by double a double click in the header, using the context menu or by javascript as follow: ```html

``` ## Disable the table sorting The ordering is a native enabled feature. To disable that feature please use the columnSorting:false, directive in the initialization. ```html
``` ================================================ FILE: docs/jspreadsheet/v3/examples/spreadsheet-formulas.md ================================================ title: Basic to advance use of formulas keywords: Jexcel, javascript, excel-like, spreadsheet, formulas, currency, calculations description: Unleash the power of your tables bringing formulas and custom javascript methods on your Jspreadsheet - the online spreadsheet. [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Adding formulas on your online spreadsheet ## Simple spreadsheet-like formula usage The example below shows how to use spreadsheet like formulas on your javascript table spreadsheet. ```html
``` ## Creating Custom formulas You can declare custom javascript methods and use in your tables as the example below. ```html
``` ================================================ FILE: docs/jspreadsheet/v3/examples/spreadsheet-toolbars.md ================================================ title: Enable and customize the toolbar on your spreadsheet keywords: Jexcel, javascript, vanilla javascript, excel-like, spreadsheet, datatables, data, table, toolbars description: Full example on how to enable nor customize your javascript spreadsheet toolbar. [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Custom toolbars The following example shows how to include and customize a toolbar in your javascript spreadsheet. ## Instructions The toolbar can be customized with a few parameters. | | | |----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| | **type** | could be **i** for icon, **select** for a dropdown, **color** or a color picker. | | **content**{.nowrap} | defines the icon (from material icons) when you use type: i; [click here for all possible keys](https://material.io/tools/icons/) | | **k** | means the style should be apply to the cell; | | **v** | means the value of the style should be apply to the cell; When type:select, you can define an array of possibles values; | | **onclick** | can be used together with type:i to implement any custom method. The method will receive the jspreadsheet instance and all marked cells by default. | ```html
``` **NOTE:** You need to include the material icons style sheet. {.ignore} ```html ``` ================================================ FILE: docs/jspreadsheet/v3/examples/table-overflow.md ================================================ title: Table overflow with Jspreadshee Version 3 keywords: Jexcel, javascript, javascript vanilla, javascript, table, table overflow description: How define a fixed width and height for the Jspreadsheet grids. [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Table overflow Define width and height for your Jspreadsheet table ```html
``` ================================================ FILE: docs/jspreadsheet/v3/examples/table-scripting.md ================================================ title: Customize the spreadsheet via javascript keywords: Jexcel, javascript, excel-like, spreadsheet, table scripting description: Customize the table behavior using javascript [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Table scripting and customizations Customize the table behavior though javascript. ```html
``` ================================================ FILE: docs/jspreadsheet/v3/examples/table-style.md ================================================ title: Customize the spreadsheet style CSS keywords: Jexcel, javascript, excel-like, spreadsheet, table style, css description: Bring a very special touch to your applications customizing your javascript spreadsheet. [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Custom javascript spreadsheet style ## How to apply style to your table You can define the CSS for specific columns during the initialization, or through programmatically javascript calls. But, after the initialization is still possible to manage the cell style programmatically using the method getStyle or setStyle. ```html

``` ================================================ FILE: docs/jspreadsheet/v3/examples/translations.md ================================================ title: Jspreadsheet Translations keywords: Jexcel, spreadsheet, javascript, javascript table, translate, translations description: How to translate the default Jspreadsheet text and controls. [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # Internationalization How to update the default texts from Jspreadsheet. ```html
``` ================================================ FILE: docs/jspreadsheet/v3/examples/vue.md ================================================ title: Jspreadsheet with Vue keywords: Jexcel, javascript, using Jspreadsheet and Vue description: A full example on how to integrate Jspreadsheet with Vue [Back to Examples](/jspreadsheet/v3/examples "Back to the examples section") # The Javascript spreadsheet with Vue Integrating jspreadsheet with Vue [See a full example on codesandbox](https://codesandbox.io/embed/vue-default-template-p4hwn) [Get a source code of a sample Vue project](https://github.com/jspreadsheet/jexcel-with-vue) ```html

``` ================================================ FILE: docs/jspreadsheet/v3/examples.md ================================================ title: Examples keywords: Jexcel, javascript, examples description: Examples how to create web based spreadsheets using Jspreadsheet. Jspreadsheet v3 Examples ======================== For us, the best way to learn is via examples. We bring in this section various examples from basic to advance applications. * [React Implementation](/jspreadsheet/v3/examples/react "Jspreadsheet with React") A full example on how to integrate Jspreadsheet with React * [VUE Implementation](/jspreadsheet/v3/examples/vue "Jspreadsheet with Vue") A full example on how to integrate Jspreadsheet with Vue * [Angular Implementation](/jspreadsheet/v3/examples/angular "Jexcel with Angular") A full example on how to integrate Jspreadsheet with Angular * [Jquery Implementation](/jspreadsheet/v3/examples/jquery "Jspreadsheet with Jquery") A full example on how to integrate Jspreadsheet with Jquery * [Search and pagination](/jspreadsheet/v3/examples/datatables "Searchable databatable") Full spreadsheet example with search and pagination to bring great compatibility for those who love datatables. * [Column types](/jspreadsheet/v3/examples/column-types "Column types") Learn more about the powerful column types. This example brings all native column types and how to create your own custom type. * [Advanced dropdown](/jspreadsheet/v3/examples/dropdown-and-autocomplete "Advanced dropdown column type") Full examples on how to handle simple, advanced, multiple, autocomplete and conditional dropdowns. Create amazing javascript tables using categories and images in your dropdowns. * [Date and datetime picker](/jspreadsheet/v3/examples/date-and-datetime-picker "Calendar with date and datetime picker") Example from basic to advanced calendar usage, date and datetime picker * [Images](/jspreadsheet/v3/examples/image-upload "Embed images to your spreadsheet using base64") This examples shows how to embed and upload images to your spreadsheet * [Programmatically updates](/jspreadsheet/v3/examples/programmatically-updates "Update your table by javascript") How to update your spreadsheet and its data by javascript * [Table Style](/jspreadsheet/v3/examples/table-style "Customize the spreasheet style CSS") Bring a very special touch to your applications customizing your javascript spreadsheet. * [Table Scripting](/jspreadsheet/v3/examples/table-scripting "Customize the spreasheet via javascript") Customize the table behavior using javascript * [Events](/jspreadsheet/v3/examples/events "Handling events on Jspreadsheet") Learn how to handle events on Jspreadsheet * [Importing data](/jspreadsheet/v3/examples/import-data "Load data from CSV or JSON or XLSX") How to import data from an external CSV, json file or XLSX. * [Formulas](/jspreadsheet/v3/examples/spreadsheet-formulas "Basic to advance use of formulas") Unleash the power of your tables bringing formulas and custom javascript methods on your Jspreadsheet - the online spreadsheet. * [Custom toolbars](/jspreadsheet/v3/examples/spreadsheet-toolbars "Enable and customize the toolbar on your spreadsheet") Full example on how to enable nor customize your javascript spreadsheet toolbar. * [Column comments](/jspreadsheet/v3/examples/comments "Allow comments in your javascript table") Allow comments in your table spreadsheet. * [Headers](/jspreadsheet/v3/examples/headers "Nested headers and column header updates") Enabled nested headers in your spreadsheet and learn how to set or get header values * [Translations](/jspreadsheet/v3/examples/translations "How to translate the default messages from Jspreadsheet") How to translate the default messages from Jspreadsheet. * [Meta information](/jspreadsheet/v3/examples/meta-information "Meta information") Keep hidden information about your cells using meta information methods * [Merged cells](/jspreadsheet/v3/examples/merged-cells "How to merge the spreadsheet cells") Full example on how to handle merge cells in your javascript tables. * [Sorting columns](/jspreadsheet/v3/examples/sorting "Sorting the spreadsheet columns") Example how to sort the table by a column via javascript. * [Readonly Columns](/jspreadsheet/v3/examples/readonly "Readonly Columns") Example how to setup readonly cells * [Lazy loading](/jspreadsheet/v3/examples/lazy-loading "Dealing with big spreadsheets through lazy loading") This example brings a very nice feature to deal with large table datasets. * [Custom Contextmenu](/jspreadsheet/v3/examples/contextmenu "Custom contextmenu") How to customize Jspreadsheet contextmenu * [Table overflow](/jspreadsheet/v3/examples/table-overflow "Table overflow") How define a fixed width and height for the Jspreadsheet grids. ================================================ FILE: docs/jspreadsheet/v3/getting-started.md ================================================ title: Getting Started with Jspreadsheet CE keywords: Jspreadsheet CE, Jexcel, JavaScript Data Grid, Spreadsheets, JavaScript tables, Excel-like data grid, web-based spreadsheets, data grid controls, data grid features description: Create data grids with spreadsheet controls with Jspreadsheet CE. [Back to Documentation](/jspreadsheet/v3/docs) # Getting started Jspreadsheet is a vanilla javascript plugin to embed a online spreadsheet in your web based applications. Bring highly dynamic datasets to your application and improve the user experience of your software. ```bash npm install jspreadsheet-ce ``` Download from our github page: ## Initialization You can initiate a Jspreadsheet table including data from a HTML table, a JS array, a CSV or a JSON file, following the examples below: ### Loading from a javascript array {.ignore} ```html
``` ### Loading from a JSON file {.ignore} ```html
``` ### Loading from a CSV file {.ignore} ```html
``` [See a working example](/jspreadsheet/v3/examples/import-data) ## Destroying a table You can destroy the table, all data and events related to an existing table by using the method _destroy_ as shown below. {.ignore} ```html ``` ## Header titles If you do not define the column title, the default will be a letter starting in A just as any other spreadsheet software. But, if you would like to have custom column names you can use the directive title as in the example below: {.ignore} ```html ``` ### Headers from a CSV file If you are loading your data from a CSV file, you can define the **csvHeader:true**, so the first row will be used as your column names. [See a working example](/jspreadsheet/v3/examples/import-data) ### Programmatically header updates The methods **setHeader()** , **getHeader()** and **getHeaders()** are available for the developer to interact programmatically with the spreadsheet. [Working example](/jspreadsheet/v3/examples/headers#Programmatically-header-updates) ### Nested headers The nested headers area available in the innitialization through the directive **nestedHeaders:[]**, and should be use follow: {.ignore} ```html ``` ### Programmatically column width updates The methods setWidth(), getWidth() are available for the developer to update the column width via javascript. [See this example in action](/jspreadsheet/v3/examples/programmatically-updates#setWidth) ## Row height The inital row height can be defined in the height property include in the rows directive. It is also possible to enabled a resizeble row by using rowResize: true in the initialization. {.ignore} ```html ``` ### Programmatically row height updates The methods setHeight(), getHeight() are available for the developer to update the row height via javascript. [See this example in action](/jspreadsheet/v3/examples/programmatically-updates#setHeight) ## Column types Jspreadsheet has available some extra native column types in addition to the default input text. It means you have extended nice responsive ways to get data into your spreadsheet. In addition to that is available integration methods to facilitate you to bring any custom column to your tables. This makes the Jspreadsheet plugin a very flexible tool to enhance the user experience of your applications. Jspreadsheet is integrate with jSuites, so it brings some native columns, such as: _**text, numeric, hidden, dropdown, autocomplete, checkbox, radio, calendar, image and color.**_ {.ignore} ```html ``` ### Calendar type When using the calendar column, you can change the behavior behavior of your calendar by sending some extra options as example above. The possible values are: ```javascript { options: { // Date format format:'DD/MM/YYYY', // Allow keyboard date entry readonly:0, // Today is default today:0, // Show timepicker time:0, // Show the reset button resetButton:true, // Placeholder placeholder:'', // Translations can be done here months:['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], weekdays:['Sunday','Monday','Tuesday','Wednesday','Thursday', 'Friday','Saturday'], weekdays_short:['S', 'M', 'T', 'W', 'T', 'F', 'S'], // Value value:null, // Events onclose:null, onchange:null, // Fullscreen (this is automatic set for screensize < 800) fullscreen:false, } } ``` [See a working example](/jspreadsheet/v3/examples/date-and-datetime-picker) ### Dropdown and autocomplete type There are different ways to work with dropdowns using Jspreadsheet. It is possible to define the parameter _source_ as a simple or key-value array. It is also possible to use the param _url_ to populate your dropdown from an external json format source. In addition to that it is possible to have conditional values. Basically, the values from one dropdown can be conditional to other dropdowns in your table. You can set the autocomplete dropdown through the initial param _autocomplete:true_ and the multiple picker can be activate by _multiple:true_ property as shown in the following example: {.ignore} ```html ``` [See a working example](/jspreadsheet/v3/examples/dropdown-and-autocomplete) ### Custom type Jspreadsheet makes possible to extend third party javascript plugins to create your custom columns. Basically to use this feature, you should implement some basic methods such as: openEditor, closeEditor, getValue, setValue as following. {.ignore} ```html ``` [See a working example](/jspreadsheet/v3/examples/column-types#custom) ## Define a minimum table dimension size. The follow example will create a data table with a minimum number of ten columns and five rows: {.ignore} ```html ``` ================================================ FILE: docs/jspreadsheet/v4/cases/data-persistence.md ================================================ title: Data persistence keywords: Jexcel, javascript, cases, data persistence, database synchronization description: A backend data persistence example using Jspreadsheet. [Back to Use Cases](/jspreadsheet/v4/cases "Back to the use cases section") # Data persistence With the persistence directive, each change in the data will be sent to a remote URL. ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v4/cases/food-store.md ================================================ title: Cases keywords: Jexcel, javascript, cases, food store description: A food store inventory using Jspreadsheet. # Grocery Store A simple example including table scripting to perform a photo update and a progress bar added to any new task ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v4/cases/highcharts.md ================================================ title: Highcharts with Jspreadsheet keywords: Jexcel, javascript, highcharts, charts description: Integrating Highcharts with Jspreadsheet via events. # Highcharts Creating a dynamic chart using a online spreadsheet as a source. ```html
``` ================================================ FILE: docs/jspreadsheet/v4/cases/project-management.md ================================================ title: Project Management Spreadsheet with Jspreadsheet keywords: Jexcel, javascript, cases, food store description: How to create a grocery store inventory using Jspreadsheet. # Project Management Spreadsheet A simple example including table scripting to perform a photo update and a progress bar added to any new task. ```html
``` ================================================ FILE: docs/jspreadsheet/v4/cases.md ================================================ title: Real-Life Applications and Integrations with Jspreadsheet keywords: Jspreadsheet, Jexcel, JavaScript integrations, real-world applications, data grid use cases, spreadsheet integrations, project management, data visualization description: Explore real-life applications and integrations using Jspreadsheet. Discover practical examples of how Jspreadsheet enhances various projects and workflows. canonical: https://bossanova.uk/jspreadsheet/v4/cases # Jspreadsheet v4 Use Cases Explore practical use cases and integrations with Jspreadsheet v4: - [Data Persistence](/jspreadsheet/v4/cases/data-persistence) - [Food Store Management](/jspreadsheet/v4/cases/food-store) - [Highcharts Integration](/jspreadsheet/v4/cases/highcharts) - [Project Management Tools](/jspreadsheet/v4/cases/project-management) ================================================ FILE: docs/jspreadsheet/v4/docs/events.md ================================================ title: Spreadsheet Events with Jspreadsheet Version 4 keywords: Jspreadsheet, data grid, JavaScript, Excel-like features, spreadsheet events, event handling, customized actions, JavaScript integration, interactive spreadsheets, feature customization, event-driven programming, data grid events description: Learn more about Jspreadsheet’s comprehensive event system for advanced customization and integration. canonical: https://bossanova.uk/jspreadsheet/v4/docs/events # Spreadsheet Events ## Custom table scripting after changes Jspreadsheet offers a native feature to customize your table on the fly. You can define the method updateTable to create rules to customize the data should be shown to the user, as the example below. [See an example in action](/jspreadsheet/v4/examples/table-scripting) ## Events Jspreadsheet available events in this version. [Example on handling events on your spreasheet](/jspreadsheet/v4/examples/events) | Event | Description | |--------------------------|------------------------------------------------------------------------------------------------------------------------------------------| | **onload** | This method is called when the method setData | | **onbeforechange** | Before a column value is changed. NOTE: It is possible to overwrite the original value, by returning a new value on this method. v3.4.0+ | | **onchange** | After a column value is changed. | | **onafterchanges** | After all changes are applied in the table. | | **onpaste** | After a paste action is performed in the JavaScript table. | | **onbeforepaste** | Before the paste action is performed. Used to parse any input data, should return the data. | | **oninsertrow** | After a new row is inserted. | | **onbeforeinsertrow** | Before a new row is inserted. You can cancel the insert event by returning false. | | **ondeleterow** | After a row is excluded. | | **onbeforedeleterow** | Before a row is deleted. You can cancel the delete event by returning false. | | **oninsertcolumn** | After a new column is inserted. | | **onbeforeinsertcolumn** | Before a new column is inserted. You can cancel the insert event by returning false. | | **ondeletecolumn** | After a column is excluded. | | **onbeforedeletecolumn** | Before a column is excluded. You can cancel the insert event by returning false. | | **onmoverow** | After a row is moved to a new position. | | **onmovecolumn** | After a column is moved to a new position. | | **onresizerow** | After a change in row height. | | **onresizecolumn** | After a change in column width. | | **onselection** | On the selection is changed. | | **onsort** | After a column is sorted. | | **onfocus** | On table focus | | **onblur** | On table blur | | **onmerge** | On column merge | | **onchangeheader** | On header change | | **onundo** | On undo is applied | | **onredo** | On redo is applied | | **oneditionstart** | When openEditor is called. | | **oneditionend** | When closeEditor is called. | | **onchangestyle** | When setStyle is called. | | **onchangemeta** | When setMeta is called. | ================================================ FILE: docs/jspreadsheet/v4/docs/examples.md ================================================ title: Examples keywords: Jexcel, javascript, examples description: Examples how to create web based spreadsheets using Jspreadsheet. # Jspreadsheet v4 Examples We bring in this section various examples from basic to advance applications. * [Angular](/jspreadsheet/v4/examples/angular) * [Column Dragging](/jspreadsheet/v4/examples/column-dragging) * [Column Filters](/jspreadsheet/v4/examples/column-filters) * [Column Types](/jspreadsheet/v4/examples/column-types) * [Comments](/jspreadsheet/v4/examples/comments) * [Contextmenu](/jspreadsheet/v4/examples/contextmenu) * [Create From Table](/jspreadsheet/v4/examples/create-from-table) * [Custom Table Design](/jspreadsheet/v4/examples/custom-table-design) * [Datatables](/jspreadsheet/v4/examples/datatables) * [Date And Datetime Picker](/jspreadsheet/v4/examples/date-and-datetime-picker) * [Dropdown And Autocomplete](/jspreadsheet/v4/examples/dropdown-and-autocomplete) * [Events](/jspreadsheet/v4/examples/events) * [Footers](/jspreadsheet/v4/examples/footers) * [Freeze Columns](/jspreadsheet/v4/examples/freeze-columns) * [Headers](/jspreadsheet/v4/examples/headers) * [Image Upload](/jspreadsheet/v4/examples/image-upload) * [Import Data](/jspreadsheet/v4/examples/import-data) * [Jquery](/jspreadsheet/v4/examples/jquery) * [Lazy Loading](/jspreadsheet/v4/examples/lazy-loading) * [Merged Cells](/jspreadsheet/v4/examples/merged-cells) * [Meta Information](/jspreadsheet/v4/examples/meta-information) * [Nested Headers](/jspreadsheet/v4/examples/nested-headers) * [Programmatically Updates](/jspreadsheet/v4/examples/programmatically-updates) * [React](/jspreadsheet/v4/examples/react) * [Readonly](/jspreadsheet/v4/examples/readonly) * [Richtext Html Editor](/jspreadsheet/v4/examples/richtext-html-editor) * [Sorting](/jspreadsheet/v4/examples/sorting) * [Spreadsheet Formulas](/jspreadsheet/v4/examples/spreadsheet-formulas) * [Spreadsheet Toolbars](/jspreadsheet/v4/examples/spreadsheet-toolbars) * [Spreadsheet Webcomponent](/jspreadsheet/v4/examples/spreadsheet-webcomponent) * [Table Overflow](/jspreadsheet/v4/examples/table-overflow) * [Table Scripting](/jspreadsheet/v4/examples/table-scripting) * [Table Style](/jspreadsheet/v4/examples/table-style) * [Tabs](/jspreadsheet/v4/examples/tabs) * [Translations](/jspreadsheet/v4/examples/translations) * [Vue](/jspreadsheet/v4/examples/vue) ================================================ FILE: docs/jspreadsheet/v4/docs/getting-started.md ================================================ title: Getting Started with Jspreadsheet CE Version 4 keywords: Jspreadsheet CE, Jexcel, JavaScript Data Grid, Spreadsheets, JavaScript tables, Excel-like data grid, web-based spreadsheets, data grid controls, data grid features description: Create data grids with spreadsheet controls with Jspreadsheet CE. canonical: https://bossanova.uk/jspreadsheet/v4/docs/getting-started # Getting started Jspreadsheet is a vanilla javascript plugin to embed a online spreadsheet in your web based applications. Bring highly dynamic datasets to your application and improve the user experience of your software. ## Install ### NPM ```bash npm install jspreadsheet-ce@4 ``` ### CDN Use jspreadsheet directly from JSDelivr CDN {.ignore} ```html ``` ### GitHub Download from our GitHub page [https://github.com/jspreadsheet/ce](https://github.com/jspreadsheet/ce) ## Initialization You can initiate an online spreadsheet including data from a HTML table, a JS array, a CSV or a JSON file, following the examples below: ### Loading from a javascript array ```html
``` ### Loading from a JSON file ```html
``` ### Loading from a CSV file ```html
``` [See a working example](/jspreadsheet/v4/examples/import-data) ### Headers from a CSV file If you are loading your data from a CSV file, you can define the **csvHeader:true**, so the first row will be used as your column names. [See a working example](/jspreadsheet/v4/examples/import-data) ### Programmatically header updates The methods **setHeader()**, **getHeader()** and **getHeaders()** are available for the developer to interact programmatically with the spreadsheet. [Working example](/jspreadsheet/v4/examples/headers#Programmatically-header-updates) ### Nested headers The nested headers area available in the innitialization through the directive **nestedHeaders:[]**, and should be use follow: ```html
``` [See this example in action](/jspreadsheet/v4/examples/headers) ## Column width The initial width can be defined in the width property in the column parameter. ```html
``` ### Programmatically column width updates The methods setWidth(), getWidth() are available for the developer to update the column width via javascript. [See this example in action](/jspreadsheet/v4/examples/programmatically-updates#setWidth) ## Row height The initial row height can be defined in the height property include in the rows directive. It is also possible to enabled a resizeble row by using rowResize: true in the initialization. ```html
``` ### Programmatically row height updates The methods setHeight(), getHeight() are available for the developer to update the row height via javascript. [See this example in action](/jspreadsheet/v4/examples/programmatically-updates#setHeight) ## Column types Jspreadsheet has available some extra native column types in addition to the default input text. It means you have extended nice responsive ways to get data into your spreadsheet. In addition to that is available integration methods to facilitate you to bring any custom column to your tables. This makes the Jspreadsheet plugin a very flexible tool to enhance the user experience of your applications. Jspreadsheet is integrate with jSuites, so it brings some native columns, such as: **text, numeric, hidden, dropdown, autocomplete, checkbox, radio, calendar, image and color.**_ {.ignore} ```html
``` ### Calendar type When using the calendar column, you can change the behavior behavior of your calendar by sending some extra options as example above. The possible values are: {.ignore} ```javascript { options : { // Date format format:'DD/MM/YYYY', // Allow keyboard date entry readonly:0, // Today is default today:0, // Show timepicker time:0, // Show the reset button resetButton:true, // Placeholder placeholder:'', // Translations can be done here months:['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], weekdays:['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'], weekdays\_short:['S', 'M', 'T', 'W', 'T', 'F', 'S'], // Value value:null, // Events onclose:null, onchange:null, // Fullscreen (this is automatic set for screensize < 800) fullscreen:false, } } ``` [See a working example](/jspreadsheet/v4/examples/date-and-datetime-picker) ### Dropdown and autocomplete type There are different ways to work with dropdowns in Jspreadsheet. It is possible to define the parameter _source_ as a simple or key-value array. It is also possible to use the param _url_ to populate your dropdown from an external json format source. In addition to that it is possible to have conditional values. Basically, the values from one dropdown can be conditional to other dropdowns in your table. You can set the autocomplete dropdown through the initial param _autocomplete:true_ and the multiple picker can be activate by _multiple:true_ property as shown in the following example: {.ignore} ```html
``` [See a working example](/jspreadsheet/v4/examples/dropdown-and-autocomplete) ### Custom type Jspreadsheet makes possible to extend third party javascript plugins to create your custom columns. Basically to use this feature, you should implement some basic methods such as: openEditor, closeEditor, getValue, setValue as following. {.ignore} ```html
``` [See a working example](/jspreadsheet/v4/examples/column-types#custom) ## Define a minimum table dimension size. The follow example will create a data table with a minimum number of ten columns and five rows: {.ignore} ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/column-filters.md ================================================ title: Applying Filters on Columns keywords: Jexcel, JavaScript, column filters, filters, dynamic tables, online spreadsheet description: Learn how to enable column filters in Jspreadsheet to enhance functionality in your online spreadsheets. # Column Filters Enable column filters on your JavaScript dynamic tables to enhance data interaction and usability. ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/column-types.md ================================================ title: Column types keywords: Jexcel, javascript, javascript vanilla, javascript plugin, plugin, excel-like, spreadsheet, table, tables, grid, datatables, data description: Learn more about the powerful column types. This example brings all native column types and how to create your own custom type. # Column types The native available types in jspreadsheet javascript spreadsheet are the following: * text * numeric * hidden * dropdown * autocomplete * checkbox * radio * calendar * image * color * html ## Native column types There are several other properties to change the behavior of those columns, please check the dropdown, calendar examples to get more advanced examples. ### Source code ```html
``` ## Custom column type Jspreadsheet is very powerful and flexible, and you can create custom column type based on any external plugins. A time custom column based on the [clockpicker plugin](https://weareoutman.github.io/clockpicker/) by weareoutman. ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/comments.md ================================================ title: Allow comments in your javascript table keywords: Jexcel, spreadsheet, javascript, cell comments, javascript table description: Allow comments in your table spreadsheet. # Spreadsheet comments The javascript spreadsheet plugin allows the user to set custom comments for each individual cells. By allowComments in the initialization, the user will be able to add comments in the rigth click contextMenu. ## Manage cell comments programmatically To apply comments via javascript, you can use the methods setComments or getComments, as follow: ### Source code ```html

``` ================================================ FILE: docs/jspreadsheet/v4/examples/contextmenu.md ================================================ title: Custom contextmenu keywords: Jexcel, javascript, javascript vanilla, javascript plugin, plugin, excel-like, spreadsheet, table, tables, grid, datatables, data description: How to customize jspreadsheet contextmenu # Custom contextmenu The following example remove the copy and paste from the contextmenu in order to create a custom contextMenu ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/create-from-table.md ================================================ title: Create a Data Grid From a HTML table keywords: Jexcel, javascript, create a dynamic jspreadsheet table from a HTML table element. description: A full example on how to create a dynamic jspreadsheet table from a HTML table. # Create a Data Grid From a HTML table From the v4+ is is possible to create a online spreadsheet from a static simple HTML table. ```html

The Official Top 40 biggest albums of 2019

General
Info Stats
POS TITLE ARTIST PEAK
1 DIVINELY UNINSPIRED TO A HELLISH EXTENT LEWIS CAPALDI 1
2 NO 6 COLLABORATIONS PROJECT ED SHEERAN 1
3 THE GREATEST SHOWMAN MOTION PICTURE CAST RECORDING 1
4 WHEN WE ALL FALL ASLEEP WHERE DO WE GO BILLIE EILISH 1
5 STAYING AT TAMARA'S GEORGE EZRA 1
6 BOHEMIAN RHAPSODY - OST QUEEN 3
7 THANK U NEXT ARIANA GRANDE 1
8 WHAT A TIME TO BE ALIVE TOM WALKER 1
9 A STAR IS BORN MOTION PICTURE CAST RECORDING 1
10 YOU'RE IN MY HEART ROD STEWART 1
=SUMCOL(3)

``` ### More examples * [Including merged cells](https://jsfiddle.net/spreadsheet/45h6odug/ "Merged cells") ================================================ FILE: docs/jspreadsheet/v4/examples/custom-table-design.md ================================================ title: Custom Table Design keywords: Jexcel, javascript, javascript vanilla, javascript plugin, plugin, excel-like, spreadsheet, table, tables, grid, datatables, data description: Customized CSS for your datagrid # Create custom CSS for your javascript spreadsheet The following example shows a CSS addon to change the core layout of your jquery tables. ## Green borders and corners jquery spreadsheet [Bootstrap-like jquery spreadsheet example](/jspreadsheet/examples/a-custom- table-design) ## Bootstrap-like jquery spreadsheet. Your jquery table can be customized by including an additional addon CSS. If you have created a nice design, please share with us. ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/datatables.md ================================================ title: Searchable datatable keywords: Jexcel, javascript, javascript vanilla, javascript plugin, plugin, excel-like, spreadsheet, table, tables, grid, datatables, data description: Full spreadsheet example with search and pagination to bring great compatibility for those who love datatables. # Spreadsheet Search and Pagination The following example shows how to create a javascript spreadsheet instance with a design similar to datatables jquery plugin. ### Source code {.ignore} ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/date-and-datetime-picker.md ================================================ title: Calendar with date and datetime picker keywords: Jexcel, javascript, excel-like, spreadsheet, date, datetime, calendar description: Example from basic to advanced calendar usage, date and datetime picker # Calendar column type The example below shows how to use and customize special calendar column type. Jspreadsheet uses the jSuites [Javascript Calendar](https://jsuites.net/docs/javascript-calendar) plugin ### Source code ```html
``` ## Date column customization Customize the format and the behavior of your column through the initialization options, as follow: {.ignore} ```javascript options : { // Date format format:'DD/MM/YYYY', // Allow keyboard date entry readonly:0, // Today is default today:0, // Show timepicker time:0, // Show the reset button resetButton:true, // Placeholder placeholder:'', // Translations can be done here months:['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], weekdays:['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'], weekdays_short:['S', 'M', 'T', 'W', 'T', 'F', 'S'], // Value value:null, // Events onclose:null, onchange:null, // Fullscreen (this is automatic set for screensize < 800) fullscreen:false, }; ``` More information about the jSuites [Responsive date time picker](https://jsuites.net/docs/javascript-calendar) Considering the example above, you can create a calendar including a time picker by simple send the option **time:1** as the following example. ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/dropdown-and-autocomplete.md ================================================ title: Advanced dropdown column type keywords: Jexcel, jquery, javascript, excel-like, spreadsheet, jquery plugin, sorting, table, grid, order by description: Full examples on how to handle simple, advanced, multiple, autocomplete and conditional dropdowns. Create amazing javascript tables using categories and images in your dropdowns. # Dropdown and autocomplete column type Jspreadsheet brings a very powerful, flexible and responsive dropdown column type to support a better user experience through your applications. The new dropdown column options include autocomplete, multiple options, data picker, different template types and much more advantages, such as: * Create a simple dropdown from array * Value or key-value select is available * Populate a dropdown from a external JSON request * Dynamic autocomplete search based on another column value * Conditional dropdowns: options from a dropdown based on a method return * Multiple selection and internal dropdown search * Responsive data picker with multiple render types * Image icon and group items ## Multiple and autocomplete options The highlight features introduced in the most version of the vanilla javascript spreadsheet is definitely the autocomplete and multiple options. The following example shows the column Product Origin with the autocomplete and multiple directives initiated as true. ### Source code ```html
``` ## Conditional dropdown The example below shows the dependency of the second column in relation to the first. ```html
``` ## Group, images, and advanced render options Improve the user experience with a responsive data picker. ### Source code ```html
``` More options for the dropdowns, please refer to the [jSuites documentation](https://jsuites.net/v4). ================================================ FILE: docs/jspreadsheet/v4/examples/events.md ================================================ title: Handling events on Jspreadsheet keywords: Jexcel, javascript, excel-like, spreadsheet, table, grid, events description: Learn how to handle events on Jspreadsheet # Handling events ## Various tracking javascript methods Binding events on your javascript spreadsheet. [See a list of all event handlers](/jspreadsheet/v4/docs/events) ### Source code ```html
``` ## Global Super event One method to handle all events on the online spreadsheet. **NOTE** : Open the console to see the events. ================================================ FILE: docs/jspreadsheet/v4/examples/footers.md ================================================ title: Adding formulas fixed on the table footer keywords: Jexcel, javascript, multiple spreadsheets, formulas, table footer description: Adding formulas fixed on the table footer. # Table footer Adding fixed custom calculations in the footer of an online spreadsheet. ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/freeze-columns.md ================================================ title: Freeze columns keywords: Jexcel, javascript, javascript vanilla, javascript plugin, plugin, excel-like, spreadsheet, table, tables, grid, datatables, data, frezee columns description: Setup freeze columns in Jspreadsheet # Freeze columns Define the number of freeze columns by using the freezeColumn directive with tableOverflow as follow ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/headers.md ================================================ title: Header updates & column dragging keywords: Jexcel, spreadsheet, javascript, header updates, programmatically header updates, enable column dragging description: Header updates and column dragging # Header updates There are three ways to change a header title. * The user clicks in a selected header and hold the mouse for 2 seconds, a prompt will request the new title; * Via contextMenu. the user right clicks in the column and select the option Rename column * Using the method setHeader(colNumber, title) as example below: ### Source code ```html

``` ================================================ FILE: docs/jspreadsheet/v4/examples/image-upload.md ================================================ title: Embed images to your spreadsheet using base64 keywords: Jexcel, javascript, excel-like, spreadsheet, image upload, base64, embed images description: This examples shows how to embed and upload images to your spreadsheet # Embed images into your spreadsheet The following examples shows how to embed images into your spreadsheet. ## Load a local image to your table Load images from your local machine into your javascript spreadsheet ### Source code ```html
``` ## Embed remote images to your table Automatic image rendering from a remote URL using updateTable method ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/import-data.md ================================================ title: Load data from CSV or JSON or XLSX keywords: Jexcel, javascript, excel-like, spreadsheet, loading data, csv, json, xlsx. description: How to import data from an external CSV, json file or XLSX. # Create a javascript spreadsheet There are a few different ways to load data to your javascript spreadsheet shown in the next four examples below ## Based on a external CSV file The example below helps you to create a javascript spreadsheet table based on a remote CSV file, including the headers. ### Source code ```html

``` ## Based on an external JSON file In a similar way, you can create a table based on an external JSON file format by using the _url: directive_ as below. ### Source code ```html
``` ## Based on an JSON object The data directiva can be used to define a JSON object. In this case you can define by the name directive the order of the columns. ### Source code ```html
``` ## Importing from a XLSX file The following example imports the data from a XLSX file using a thirdy party library, slighly customized in order to improve the CSS parser. IMPORTANT: This is an experimental implementation and there is no garantee your spreadsheet will be correctly parsed. ### Source code ```html
``` **NOTE** : This example is based on a customized version of the free version of [Sheetjs](https://sheetjs.com/). There is no garantee in the use of this library. Please consider purchase their professional version. ================================================ FILE: docs/jspreadsheet/v4/examples/jquery.md ================================================ title: Jspreadsheet with Jquery keywords: Jexcel, javascript, using Jspreadsheet and Jquery description: A full example on how to integrate Jspreadsheet with Jquery # Jquery Creating a Jspreadsheet javascript instance using jQuery ### Source code ```html

``` ================================================ FILE: docs/jspreadsheet/v4/examples/lazy-loading.md ================================================ title: Dealing with big spreadsheets through lazy loading keywords: Jexcel, javascript, javascript vanilla, javascript plugin, plugin, excel-like, spreadsheet, table, tables, grid, datatables, data description: This example brings a very nice feature to deal with large table datasets. # Lazy loading The following table is dealing with 60.000 columns. The lazyloading method allows render up to 130 rows at the same time and will render other rows based on the scrolling. ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/merged-cells.md ================================================ title: How to merge the spreadsheet cells keywords: Jexcel, spreadsheet, javascript, javascript table, merged cells description: Full example on how to handle merge cells in your javascript tables. # Merged cells You can merge cells on your spreadsheet in the table initialization or programatically as follow: The following methods are available for merge cells management: _setMerge, getMerge, removeMerge, destroyMerged_ ### Source code ```html

``` ================================================ FILE: docs/jspreadsheet/v4/examples/meta-information.md ================================================ title: Meta information keywords: Javascript spreadsheet, javascript, javascript table, meta information description: Keep hidden information about your cells using meta information methods # Meta information This feature helps you keep import information about the cells hidden from users. You can define any meta information during the initialization or programatically after that thought getMeta or setMeta methods. Set meta data for multiple columns Set a meta information for B2 Get the meta information for A1 Get all meta information ### Source code ```html

``` ================================================ FILE: docs/jspreadsheet/v4/examples/nested-headers.md ================================================ title: Nested headers and column header updates keywords: Jexcel, spreadsheet, javascript, header updates, nested headers, javascript table description: Enabled nested headers in your spreadsheet and learn how to set or get header values # Headers ## Nested headers The online spreadsheet implements nested headers natively though the directive **nestedHeaders** , as example below: ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/programmatically-changes.md ================================================ title: Programmatic Data Grid Updates in Jspreadsheet v4 keywords: Jexcel, JavaScript, spreadsheet updates, programmatically modify data, JavaScript data grid, Excel-like functionality description: Learn how to programmatically update your spreadsheet and its data using JavaScript with Jspreadsheet v4. canonical: https://bossanova.uk/jspreadsheet/v4/examples/programmatically-updates # Programmatically Data Grid Updates ## Insert, remove and move columns and rows The following example shows how to manage data programmatically in your javascript spreadsheet. ### Source code ```html

``` ## Updating column width and row height Update the table width and height properties. ### Source code ```html

``` ================================================ FILE: docs/jspreadsheet/v4/examples/programmatically-updates.md ================================================ title: Programmatic Data Grid Updates in Jspreadsheet v4 keywords: Jexcel, JavaScript, spreadsheet updates, programmatically modify data, JavaScript data grid, Excel-like functionality description: Learn how to programmatically update your spreadsheet and its data using JavaScript with Jspreadsheet v4. canonical: https://bossanova.uk/jspreadsheet/v4/examples/programmatically-updates # Programmatically Data Grid Updates ## Insert, remove and move columns and rows The following example shows how to manage data programmatically in your javascript spreadsheet. ### Source code ```html

``` ## Updating column width and row height Update the table width and height properties. ### Source code ```html

``` ================================================ FILE: docs/jspreadsheet/v4/examples/react.md ================================================ title: Jspreadsheet with React keywords: Jexcel, javascript, using jspreadsheet and react description: A full example on how to integrate Jspreadsheet with React # The Javascript spreadsheet with React ### 1\. Integrating Jspreadsheet with React [React with Jspreadsheet sample project](https://codesandbox.io/s/jexcel-and-react-k7nf0) {.ignore} ```jsx import React from "react"; import ReactDOM from "react-dom"; import jexcel from "jexcel"; import "./styles.css"; import "../node_modules/jexcel/dist/jexcel.css"; class App extends React.Component { constructor(props) { super(props); this.options = props.options; this.wrapper = React.createRef(); } componentDidMount = function() { this.el = jexcel(this.wrapper.current, this.options); }; addRow = function() { this.el.insertRow(); }; render() { return (

this.addRow()} />
); } } let options = { data: [[]], minDimensions: [10, 10] }; const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement); ``` ### 2\. React component implementation {.ignore} ```javascript class Jspreadsheet extends React.Component { constructor(props) { super(props); this.options = props.options; this.wrapper = React.createRef(); } componentDidMount = function() { this.el = jspreadsheet(this.wrapper.current, this.options); } addRow = function() { this.el.insertRow(); } render() { return (


this.addRow()}>
); } } let options = { data:[[]], minDimensions:[10,10], }; ReactDOM.render(, document.getElementById('spreadsheet')) ``` ### 3\. Jspreadsheet implementation with react component with hooks [Working example](https://codesandbox.io/s/jspreadsheet-ce-and-react-dzpqj) {.ignore} ```jsx import React, { useRef, useEffect } from "react"; import jspreadsheet from "jspreadsheet-ce"; import "../node_modules/jspreadsheet-ce/dist/jexcel.css"; export default function App() { const jRef = useRef(null); const options = { data: [[]], minDimensions: [10, 10] }; useEffect(() => { if (!jRef.current.jspreadsheet) { jspreadsheet(jRef.current, options); } }, [options]); const addRow = () => { jRef.current.jexcel.insertRow(); }; return (

); } ``` ================================================ FILE: docs/jspreadsheet/v4/examples/readonly.md ================================================ title: Readonly Columns keywords: Jexcel, spreadsheet, javascript, javascript table, readonly description: Example how to setup readonly cells # Readonly columns and cells Setting a readonly the whole column or a single specific cell. ## Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/richtext-html-editor.md ================================================ title: HTML Editor with Jspreadsheet Version 4 keywords: Jexcel, javascript, excel-like, spreadsheet, html editor, rich text description: This examples shows how to create a rich text column # Rich text and HTML editor column type Adding a HTML Editor input in your spreadsheet ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/sorting.md ================================================ title: Sorting the spreadsheet columns keywords: Jexcel, spreadsheet, javascript, javascript table, sorting description: Example how to sort the table by a column via javascript. # Sorting your table ## Simple example You can sort your javascript table by double a double click in the header, using the context menu or by javascript as follow: ### Source code ```html

``` ## Disable the table sorting The ordering is a native enabled feature. To disable that feature please use the columnSorting:false, directive in the initialization. ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/spreadsheet-formulas.md ================================================ title: Basic to advance use of formulas keywords: Jexcel, javascript, excel-like, spreadsheet, formulas, currency, calculations description: Unleash the power of your tables bringing formulas and custom javascript methods on your Jspreadsheet spreadsheet. # Adding formulas on your online spreadsheet ## Simple spreadsheet-like formula usage The example below shows how to use spreadsheet like formulas on your javascript table spreadsheet. ### Source code ```html
``` ## Creating Custom formulas You can declare custom javascript methods and use in your tables as the example below. ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/spreadsheet-toolbars.md ================================================ title: Enable and customize the toolbar on your spreadsheet keywords: Jexcel, javascript, vanilla javascript, excel-like, spreadsheet, datatables, data, table, toolbars description: Full example on how to enable nor customize your javascript spreadsheet toolbar. # Custom toolbars The following example shows how to include and customize a toolbar in your javascript spreadsheet. ## Instructions The toolbar can be customized with a few parameters. | | | | ---|--- | | _**type**_| could be **i** for icon, **select** for a dropdown, **color** or a colorpicker. | | _**content**_| defines the icon (from material icons) when you use type:i; [click here for all possible keys](https://material.io/tools/icons/) | | _**k**_| means the style should be apply to the cell; | | _**v**_| means the value of the style should be apply to the cell; When type:select, you can define an array of possibles values; | | _**onclick**_| can be used together with type:i to implement any custom method. The method will receive the Jspreadsheet instance and all marked cells by default. | ### Source code ```html
``` **NOTE:** don't forget to include the material icons style sheet. ================================================ FILE: docs/jspreadsheet/v4/examples/spreadsheet-webcomponent.md ================================================ title: Jspreadsheet Webcomponent keywords: Jexcel, javascript, javascript vanilla, javascript plugin, plugin, excel-like, spreadsheet, table, tables, grid, datatables, data, webcomponent description: Use the Jspreadsheet datagrid as webcomponent # Javascript web component online spreadsheet ## Create a online javascript spreadsheet using Jspreadsheet Ce. ### Javascript {.ignore} ```javascript class Jspreadsheet extends HTMLElement { constructor() { super(); } init(o) { // Shadow root const shadowRoot = this.attachShadow({mode: 'open'}); // Style const css = document.createElement('link'); css.rel = 'stylesheet'; css.type = 'text/css' css.href = 'https://bossanova.uk/spreadsheet/v4/spreadsheet.css'; shadowRoot.appendChild(css); const css2 = document.createElement('link'); css2.rel = 'stylesheet'; css2.type = 'text/css' css2.href = 'https://bossanova.uk/spreadsheet/v4/jsuites.css'; shadowRoot.appendChild(css2); // Jspreadsheet container var container = document.createElement('div'); shadowRoot.appendChild(container); // Create element this.el = jspreadsheet(container, { root: shadowRoot, minDimensions: [10,10] }); } connectedCallback() { this.init(this); } disconnectedCallback() { } attributeChangedCallback() { } } window.customElements.define('j-spreadsheet', Jspreadsheet); ``` ### HTML ```html ``` ================================================ FILE: docs/jspreadsheet/v4/examples/table-overflow.md ================================================ title: Table Overflow with Jspreadsheet Version 4 keywords: Jexcel, javascript, javascript vanilla, javascript, table, table overflow description: How define a fixed width and height for the jspreadsheet tables. # Table overflow Define width and height for your online javascript spreadsheet. ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/table-scripting.md ================================================ title: Customize the spreadsheet via javascript keywords: Jexcel, javascript, excel-like, spreadsheet, table scripting description: Customize the table behavior using javascript # Table scripting and customizations Customize the table behavior though javascript. ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/table-style.md ================================================ title: Customize the spreadsheet style CSS keywords: Jexcel, javascript, excel-like, spreadsheet, table style, css description: Bring a very special touch to your applications customizing your javascript spreadsheet. # Custom javascript spreasheet style ## How to apply style to your table You can define the CSS for specific columns during the initialization, or through programmatically javascript calls. But, after the initialization is still possible to manage the cell style programmatically using the method getStyle or setStyle. ### Source code ```html


``` ================================================ FILE: docs/jspreadsheet/v4/examples/tabs.md ================================================ title: Grouping multiple spreadsheets in tabs keywords: Jexcel, javascript, multiple spreadsheets, tabs description: Grouping multiple spreadsheets in tabs. # Tabs Grouping different spreadsheets in tabs ### Source code ```html

``` ================================================ FILE: docs/jspreadsheet/v4/examples/translations.md ================================================ title: How to translate the default messages from Jspreadsheet Version 4 keywords: Jexcel, spreadsheet, javascript, javascript table, translate, translations description: How to translate the default messages from Jspreadsheet # Jspreadsheet internationalization How to update the default texts from the online spreadsheet. ### Source code ```html
``` ================================================ FILE: docs/jspreadsheet/v4/examples/vue.md ================================================ title: Jspreadsheet with Vue keywords: Jexcel, javascript, using jspreadsheet and Vue description: A full example on how to integrate Jspreadsheet with Vue # The Javascript spreadsheet with Vue Integrating Jspreadsheet with Vue [See a full example on codesandbox](https://codesandbox.io/embed/vue-default-template-p4hwn) [Get a source code of a sample Vue project](https://github.com/jspreadsheet/jexcel-with-vue) ### Source code ```html

``` ================================================ FILE: docs/jspreadsheet/v4/examples.md ================================================ title: Examples keywords: Jexcel, javascript, examples description: Examples how to create web based spreadsheets using Jspreadsheet. # Jspreadsheet v4 Examples We bring in this section various examples from basic to advance applications. * [Angular](/jspreadsheet/v4/examples/angular) * [Column Dragging](/jspreadsheet/v4/examples/column-dragging) * [Column Filters](/jspreadsheet/v4/examples/column-filters) * [Column Types](/jspreadsheet/v4/examples/column-types) * [Comments](/jspreadsheet/v4/examples/comments) * [Contextmenu](/jspreadsheet/v4/examples/contextmenu) * [Create From Table](/jspreadsheet/v4/examples/create-from-table) * [Custom Table Design](/jspreadsheet/v4/examples/custom-table-design) * [Datatables](/jspreadsheet/v4/examples/datatables) * [Date And Datetime Picker](/jspreadsheet/v4/examples/date-and-datetime-picker) * [Dropdown And Autocomplete](/jspreadsheet/v4/examples/dropdown-and-autocomplete) * [Events](/jspreadsheet/v4/examples/events) * [Footers](/jspreadsheet/v4/examples/footers) * [Freeze Columns](/jspreadsheet/v4/examples/freeze-columns) * [Headers](/jspreadsheet/v4/examples/headers) * [Image Upload](/jspreadsheet/v4/examples/image-upload) * [Import Data](/jspreadsheet/v4/examples/import-data) * [Jquery](/jspreadsheet/v4/examples/jquery) * [Lazy Loading](/jspreadsheet/v4/examples/lazy-loading) * [Merged Cells](/jspreadsheet/v4/examples/merged-cells) * [Meta Information](/jspreadsheet/v4/examples/meta-information) * [Nested Headers](/jspreadsheet/v4/examples/nested-headers) * [Programmatically Updates](/jspreadsheet/v4/examples/programmatically-updates) * [React](/jspreadsheet/v4/examples/react) * [Readonly](/jspreadsheet/v4/examples/readonly) * [Richtext Html Editor](/jspreadsheet/v4/examples/richtext-html-editor) * [Sorting](/jspreadsheet/v4/examples/sorting) * [Spreadsheet Formulas](/jspreadsheet/v4/examples/spreadsheet-formulas) * [Spreadsheet Toolbars](/jspreadsheet/v4/examples/spreadsheet-toolbars) * [Spreadsheet Webcomponent](/jspreadsheet/v4/examples/spreadsheet-webcomponent) * [Table Overflow](/jspreadsheet/v4/examples/table-overflow) * [Table Scripting](/jspreadsheet/v4/examples/table-scripting) * [Table Style](/jspreadsheet/v4/examples/table-style) * [Tabs](/jspreadsheet/v4/examples/tabs) * [Translations](/jspreadsheet/v4/examples/translations) * [Vue](/jspreadsheet/v4/examples/vue) ================================================ FILE: docs/jspreadsheet/v4/getting-started.md ================================================ title: Getting Started with Jspreadsheet CE v4 keywords: Jspreadsheet CE, Jexcel, JavaScript data grid, spreadsheets, JavaScript tables, Excel-like data grids, web-based spreadsheets, interactive data grid controls, spreadsheet features description: Learn how to create and manage data grids with powerful spreadsheet controls using Jspreadsheet CE v4. canonical: https://bossanova.uk/jspreadsheet/v4/docs/getting-started # Getting started Jspreadsheet is a vanilla javascript plugin to embed a online spreadsheet in your web based applications. Bring highly dynamic datasets to your application and improve the user experience of your software. ## Install ### NPM ```bash npm install jspreadsheet-ce@4 ``` ### CDN Use jspreadsheet directly from JSDelivr CDN {.ignore} ```html ``` ### GitHub Download from our GitHub page [https://github.com/jspreadsheet/ce](https://github.com/jspreadsheet/ce) ## Initialization You can initiate an online spreadsheet including data from a HTML table, a JS array, a CSV or a JSON file, following the examples below: ### Loading from a javascript array ```html
``` ### Loading from a JSON file ```html
``` ### Loading from a CSV file ```html
``` [See a working example](/jspreadsheet/v4/examples/import-data) ### Headers from a CSV file If you are loading your data from a CSV file, you can define the **csvHeader:true**, so the first row will be used as your column names. [See a working example](/jspreadsheet/v4/examples/import-data) ### Programmatically header updates The methods **setHeader()**, **getHeader()** and **getHeaders()** are available for the developer to interact programmatically with the spreadsheet. [Working example](/jspreadsheet/v4/examples/headers#Programmatically-header-updates) ### Nested headers The nested headers area available in the innitialization through the directive **nestedHeaders:[]**, and should be use follow: ```html
``` [See this example in action](/jspreadsheet/v4/examples/headers) ## Column width The initial width can be defined in the width property in the column parameter. ```html
``` ### Programmatically column width updates The methods setWidth(), getWidth() are available for the developer to update the column width via javascript. [See this example in action](/jspreadsheet/v4/examples/programmatically-updates#setWidth) ## Row height The initial row height can be defined in the height property include in the rows directive. It is also possible to enabled a resizeble row by using rowResize: true in the initialization. ```html
``` ### Programmatically row height updates The methods setHeight(), getHeight() are available for the developer to update the row height via javascript. [See this example in action](/jspreadsheet/v4/examples/programmatically-updates#setHeight) ## Column types Jspreadsheet has available some extra native column types in addition to the default input text. It means you have extended nice responsive ways to get data into your spreadsheet. In addition to that is available integration methods to facilitate you to bring any custom column to your tables. This makes the Jspreadsheet plugin a very flexible tool to enhance the user experience of your applications. Jspreadsheet is integrate with jSuites, so it brings some native columns, such as: **text, numeric, hidden, dropdown, autocomplete, checkbox, radio, calendar, image and color.**_ {.ignore} ```html
``` ### Calendar type When using the calendar column, you can change the behavior behavior of your calendar by sending some extra options as example above. The possible values are: {.ignore} ```javascript { options : { // Date format format:'DD/MM/YYYY', // Allow keyboard date entry readonly:0, // Today is default today:0, // Show timepicker time:0, // Show the reset button resetButton:true, // Placeholder placeholder:'', // Translations can be done here months:['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], weekdays:['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'], weekdays\_short:['S', 'M', 'T', 'W', 'T', 'F', 'S'], // Value value:null, // Events onclose:null, onchange:null, // Fullscreen (this is automatic set for screensize < 800) fullscreen:false, } } ``` [See a working example](/jspreadsheet/v4/examples/date-and-datetime-picker) ### Dropdown and autocomplete type There are different ways to work with dropdowns in Jspreadsheet. It is possible to define the parameter _source_ as a simple or key-value array. It is also possible to use the param _url_ to populate your dropdown from an external json format source. In addition to that it is possible to have conditional values. Basically, the values from one dropdown can be conditional to other dropdowns in your table. You can set the autocomplete dropdown through the initial param _autocomplete:true_ and the multiple picker can be activate by _multiple:true_ property as shown in the following example: {.ignore} ```html
``` [See a working example](/jspreadsheet/v4/examples/dropdown-and-autocomplete) ### Custom type Jspreadsheet makes possible to extend third party javascript plugins to create your custom columns. Basically to use this feature, you should implement some basic methods such as: openEditor, closeEditor, getValue, setValue as following. {.ignore} ```html
``` [See a working example](/jspreadsheet/v4/examples/column-types#custom) ## Define a minimum table dimension size. The follow example will create a data table with a minimum number of ten columns and five rows: {.ignore} ```html
``` ### Clone our project http://github.com/jspreadsheet/ce ## Create amazing online spreadsheets A example how to embed a simple javascript spreadsheet in your application. You can check out for more [examples](/jspreadsheet/v4/examples) here. ```html
``` ## Jspreadsheet History ### Jspreadsheet 4.6.0 - Jexcel is renamed to Jspreadsheet. - Integration with Jsuites v4. ### Jspreadsheet 4.2.3 - The spreadsheet plugin is now compatible with Jsuites v3. - New flags and security implementations. - New DOM element references are in the toolbar, and worksheet events are tabbed. ### Jspreadsheet 4.0.0 Special thanks to [FDL - Fonds de Dotation du Libre](https://www.fdl-lef.org/) for their support and sponsorship, which made the new version possible with many exciting features. - Workbook/tab support for spreadsheets. - Create dynamic spreadsheets from static HTML elements. - Highlight selected cells in the spreadsheet after CTRL+C. - Footer with formula support. - Multiple column resizing. - JSON update support (helpers to update a remote server). - Centralized event dispatch method for all spreadsheet events. - Custom helpers: `=PROGRESS` (progress bar), `=RATING` (5-star rating). - Custom formula helpers: `=COLUMN`, `=ROW`, `=CELL`, `=TABLE`, `=VALUE`. - Dynamic nested header updates. - New HTML editing column type. - New flags: `includeHeadersOnCopy`, `persistence`, `filters`, `autoCasting`, `freezeColumns`. - New events: `onevent`, `onchangepage`, `onbeforesave`, `onsave`. - More examples and documentation. ### Jspreadsheet 3.9.0 - New methods. - General fixes. ### Jspreadsheet 3.6.0 - Improved spreadsheet formula parsing. - New spreadsheet events. - New initialization options. - General fixes. ### Jspreadsheet 3.2.3 - `getMeta`, `setMeta` methods. - NPM package with jSuites. - General fixes. ### Jspreadsheet 3.0.1 Jspreadsheet v3 is a complete rebuild of the JavaScript spreadsheet (previously a jQuery plugin). Due to the changes, full compatibility could not be ensured. If upgrading, your code may require some updates. For more information, refer to the article on upgrading from Jspreadsheet v2 or Handsontable. New features in Jspreadsheet v3: - Drag and drop columns. - Resizable rows. - Merge columns. - Search functionality. - Pagination. - Lazy loading. - Full-screen mode. - Image upload. - Native color picker. - Better mobile compatibility. - Enhanced nested headers support. - Advanced keyboard navigation. - Better hidden column management. - Data picker enhancements: dropdown, autocomplete, multiple selection, group options, and icons. - Import from XLSX (experimental). Major improvements: - A new formula engine with faster results and no external dependencies. - No use of selectors, leading to faster performance. - New native column types. - No jQuery required. - Examples for React, Vue, and Angular. - XLSX support via a custom SheetJS integration (experimental). ### Jspreadsheet 2.1.0 - Mobile touch improvements. - Paste fixes and a new CSV parser. ### Jspreadsheet 2.0.0 - New radio column type. - There is a new dropdown with autocomplete and multiple selection options. - Header/body separation for better scroll and column resize behaviour. - Text-wrap improvements, including Excel-compatible `alt+enter`. - New `set/get` meta information. - New `set/get` configuration parameters. - Programmatic `set/get` cell styles. - `set/get` cell comments. - Custom toolbar for tables. - Responsive calendar picker. ### Jspreadsheet 1.5.7 - Improvements to checkbox column type. - Updates to table destruction in jQuery. ### Jspreadsheet 1.5.1 - Spreadsheet data overflow and fixed headers. - Navigation improvements. ### Jspreadsheet 1.5.0 - Relative `insertRow`, `deleteRow`, `insertColumn`, `deleteColumn`. - Redo and undo support for `insertRow`, `deleteRow`, `insertColumn`, `deleteColumn`, `moveRow`. - New formula column recursive chain. - There is a new alternative design option (Bootstrap-like). - `updateSettings` improvements. ## Copyright and License Jspreadsheet CE is released under the MIT license. ## About Jspreadsheet Jspreadsheet is an original JavaScript software that facilitates data manipulation in web-based applications. It was inspired by other spreadsheet software and designed to be a lightweight, easy-to-use data input tool for users. This free software was developed as a lightweight alternative to create amazing online JavaScript spreadsheets. ================================================ FILE: docs/jspreadsheet.md ================================================ title: Jspreadsheet CE - The JavaScript Data Grid with Spreadsheet Controls keywords: Jspreadsheet CE, JavaScript spreadsheet, data grid, Excel-like functionality, JavaScript plugins, web components, data table, interactive spreadsheets, customizable grid, spreadsheet integration description: Jspreadsheet CE is a lightweight JavaScript data grid with powerful spreadsheet controls. Easily create interactive, customizable, and Excel-compatible web-based spreadsheets for seamless data management and enhanced user experience. canonical: https://bossanova.uk/jspreadsheet

JavaScript component to create web applications with spreadsheet UI

Free and open-source JavaScript component to create web applications with spreadsheet UI.

Worksheet
NPM Logo

23k+

Weekly downloads
GitHub Logo

6700+

GitHub stars
MIT License Icon

MIT License

Free and open-source

Compatible with
React, Angular, and Vue.

Jspreadsheet CE provides compatibility across different environments, ensuring your spreadsheet solution fits perfectly into your chosen stack.



JavaScript Angular ReactJS VueJS LemonadeJS

A complete solution to make rich and user-friendly web applications

Fully customizable JavaScript spreadsheet library, offering various components to enhance web development projects.

Column Types

A variety of customisable column types

Nested Headers

Create multi-level column headers

Cell Comments

Add comments to your spreadsheet cells

Custom Toolbar

Personalize your spreadsheet toolbar

```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Data const data = [ ['Hello', 13123, '', 'Yes', true, '#AA4411'], ['World!', 8, '', 'No', false, '#99BE23'] ]; // Columns const columns = [ { type: 'text', title: 'Text' }, { type: 'numeric', title: 'Numeric', mask:'$ #.##,00', decimal: ',' }, { type: 'calendar', title: 'Calendar' }, { type: 'dropdown', source: ['Yes', 'No', 'Maybe'] }, { type: 'checkbox', title: 'Checkbox' }, { type: 'color', title: 'Color', width: 50, render: 'square' } ]; // Render component return ( ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [ ['Hello', 13123, '', 'Yes', true, '#AA4411'], ['World!', 8, '', 'No', false, '#99BE23'] ], columns: [ { type: 'text', title: 'Text' }, { type: 'numeric', title: 'Numeric', mask:'$ #.##,00', decimal: ',' }, { type: 'calendar', title: 'Calendar' }, { type: 'dropdown', source: ['Yes', 'No', 'Maybe'] }, { type: 'checkbox', title: 'Checkbox' }, { type: 'color', title: 'Color', width: 50, render: 'square' } ] }], }); } } ```
```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { // Spreadsheet array of worksheets const spreadsheet = useRef(); // Nested headers const nestedHeaders = [ [ { title: 'Supermarket information', colspan: '6', }, ], [ { title: 'Location', colspan: '1', }, { title: ' Other Information', colspan: '2' }, { title: ' Costs', colspan: '3' } ], ]; // Render component return ( <> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [{ data: [[]], minDimensions: [5, 2], nestedHeaders:[ [ { title: 'Supermarket information', colspan: '6', }, ], [ { title: 'Location', colspan: '1', }, { title: ' Other Information', colspan: '2' }, { title: ' Costs', colspan: '3' } ], ] }] }); } } ```
```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { const spreadsheet = useRef(); const data = [ ['US', 'Cheese', '2019-02-12'], ['CA', 'Apples', '2019-03-01'], ]; const columns = [ { width: '170px' }, { width: '170px' }, { width: '170px' }, ]; const comments = { B1: 'Initial comments on B1', C1: 'Initial comments on C1' }; return ( <> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; @Component({ standalone: true, selector: "app-root", template: `
` }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; worksheets: jspreadsheet.worksheetInstance[]; ngAfterViewInit() { this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { worksheets: [ { data: [ ['US', 'Cheese', '2019-02-12'], ['CA', 'Apples', '2019-03-01'], ['CA', 'Carrots', '2018-11-10'], ['BR', 'Oranges', '2019-01-12'], ], columns: [ { width: '170px' }, { width: '170px' }, { width: '170px' }, ], allowComments: true, comments: { B1: 'Initial comments on B1', C1: 'Initial comments on C1' }, } ], }); } } ```
```html
``` ```jsx import React, { useRef } from "react"; import { Spreadsheet, Worksheet } from "@jspreadsheet-ce/react"; import "jsuites/dist/jsuites.css"; import "jspreadsheet-ce/dist/jspreadsheet.css"; export default function App() { const spreadsheet = useRef(); const toolbar = function(toolbar) { // Add a new custom item in the end of my toolbar toolbar.items.push({ tooltip: 'My custom item', content: 'share', onclick: function() { alert('Custom click'); } }); return toolbar; } return ( <> ); } ``` ```vue ``` ```angularjs import { Component, ViewChild, ElementRef } from "@angular/core"; import jspreadsheet from "jspreadsheet-ce"; import "jspreadsheet-ce/dist/jspreadsheet.css" import "jsuites/dist/jsuites.css" // Create component @Component({ standalone: true, selector: "app-root", template: `
`, }) export class AppComponent { @ViewChild("spreadsheet") spreadsheet: ElementRef; // Worksheets worksheets: jspreadsheet.worksheetInstance[]; // Create a new data grid ngAfterViewInit() { // Create spreadsheet this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, { toolbar: function(toolbar) { // Add a new custom item in the end of my toolbar toolbar.items.push({ tooltip: 'My custom item', content: 'share', onclick: function() { alert('Custom click'); } }); return toolbar; }, worksheets: [{ minDimensions: [6, 2] }] }); } } ```

Create an online spreadsheet from various data formats

With Jspreadsheet, you can quickly create online spreadsheets using JavaScript arrays, JSON, CSV, and XLSX files. This flexibility simplifies data integration and minimizes your application's need for complex processing.

Deliver high-quality interfaces and applications to your end-user

Jspreadsheet reduces customers development time.
Here are some of their experiences.


Rich Interfaces

Rich Interfaces

Make rich and user-friendly web interfaces and applications

User-Friendly Inputs

User-Friendly Inputs

You can easily handle complicated data inputs in a way users are used to

Enhanced Experience

Enhanced Experience

Improve your user software experience

Beautiful CRUDs

Beautiful CRUDs

Create rich CRUDS and beautiful UI

Compatibility with excel spreadsheets

Compatibility with excel spreadsheets

Users can move data around with common copy and paste shortcuts

Easy Customization

Easy Customization

Easy customizations with easy third-party plugin integrations

Fast and Simple

Fast and Simple

Lean, fast and simple to use

Faster Data Entry

Faster Data Entry

Speed up your work dealing with difficult data entry in a web-based software

Shareable Spreadsheets

Shareable Spreadsheets

Create and share amazing online spreadsheets

Component Ecosystem

Explore the powerful and versatile components designed to elevate your productivity. From data management to collaboration, our ecosystem seamlessly integrates to meet your needs.


Jspreadsheet Pro - JavaScript Data Grid With Spreadsheet Controls

Jspreadsheet Pro

Enterprise JavaScript data grid component to integrate spreadsheet UI into your web-based application.

Intrasheets - Collaborative Private Spreadsheets

Intrasheets

Collaborate with ease using Intrasheets, an intuitive tool for managing spreadsheets across teams, ensuring that everyone stays on the same page.

jSuites - JavaScript Plugins

Jsuites

Comprehensive JavaScript plugins and web components for diverse web-based applications and requirements.

LemonadeJS - Micro Agnostic JavaScript Reactive library

LemonadeJS

A light and easy-to-use solution for creating elegant UI elements, giving your web apps a refreshing boost in both style and functionality.



Subscribe to our newsletter

Tech news, tips and technical advice

================================================ FILE: eslint.config.mjs ================================================ import js from '@eslint/js'; import globals from 'globals'; export default [ js.configs.recommended, { ignores: ['dist/**', 'node_modules/**'], }, { files: ['**/*.{js,mjs,cjs}'], languageOptions: { ecmaVersion: 2022, sourceType: 'module', globals: { ...globals.browser, ...globals.node, ...globals.es2022, jspreadsheet: 'readonly', }, }, rules: { 'no-unused-vars': 'off', }, }, { files: ['test/**/*.js', 'mocha.config.js'], languageOptions: { globals: { ...globals.node, ...globals.mocha, describe: 'readonly', it: 'readonly', before: 'readonly', after: 'readonly', beforeEach: 'readonly', afterEach: 'readonly', root: 'readonly', }, }, }, { files: ['webpack.config.js', 'build.cjs'], languageOptions: { globals: { ...globals.node, __dirname: 'readonly', __filename: 'readonly', module: 'readonly', require: 'readonly', exports: 'readonly', process: 'readonly', }, }, }, ]; ================================================ FILE: mocha.config.js ================================================ #! /usr/bin/env node require('jsdom-global')(undefined, { url: 'https://localhost' }); global.root = document.createElement('div'); global.root.style.width = '100%'; global.root.style.height = '100%'; global.root.innerHTML = ''; document.body.appendChild(global.root); exports.mochaHooks = { afterEach(done) { // destroy datagrid component global.root.innerHTML = ''; done(); }, }; ================================================ FILE: package.json ================================================ { "name": "jspreadsheet-ce", "title": "The Javascript Spreadsheet", "description": "Jspreadsheet is a lightweight, vanilla javascript plugin to create amazing web-based interactive data grids with spreadsheet like controls compatible with Excel, Google Spreadsheets and any other spreadsheet software.", "repository": { "type": "git", "url": "https://github.com/jspreadsheet/ce.git" }, "author": { "name": "Contact " }, "keywords": [ "spreadsheet", "spreadsheets", "tables", "table", "excel", "grid", "data grid", "data tables", "javascript data grid", "javascript spreadsheet", "data spreadsheet", "react", "grid", "datagrid", "reactjs", "react-component", "angular", "data-grid", "vue", "csv", "datatable", "vuejs", "angular-component", "javascript", "data-table", "web-components", "xlsx", "react-table", "sheets", "sheet", "export", "markdown", "parser", "xls", "datasheet" ], "dependencies": { "@jspreadsheet/formula": "^2.0.2", "jsuites": "^5.12.0" }, "devDependencies": { "@babel/cli": "^7.7.4", "@babel/core": "^7.7.4", "@babel/preset-env": "^7.7.4", "@babel/register": "^7.7.4", "@eslint/js": "^9.34.0", "c8": "^7.13.0", "chai": "^4.3.7", "copy-webpack-plugin": "^11.0.0", "cross-env": "^7.0.3", "css-loader": "^6.8.1", "eslint": "^8.57.1", "eslint-config-prettier": "^8.8.0", "globals": "^16.3.0", "html-replace-webpack-plugin": "^2.6.0", "html-webpack-plugin": "^5.5.0", "jsdoc": "^4.0.2", "jsdom": "^22.0.0", "jsdom-global": "^3.0.2", "mini-css-extract-plugin": "^2.7.6", "mocha": "^10.2.0", "mochawesome": "^7.1.3", "prettier": "2.8.8", "style-loader": "^3.3.1", "webpack": "^5.88.2", "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1" }, "main": "dist/index.js", "types": "dist/index.d.ts", "version": "5.0.4", "bugs": "https://github.com/jspreadsheet/ce/issues", "homepage": "https://github.com/jspreadsheet/ce", "download": "https://github.com/jspreadsheet/ce/archive/master.zip", "scripts": { "format": "npm run prettier:fix && npm run lint:fix", "lint": "eslint .", "lint:fix": "eslint --fix .", "prettier": "prettier --check .", "prettier:fix": "prettier --write . --ignore-path .prettierignore", "start": "webpack serve --history-api-fallback", "prebuild": "npm run prettier:fix && npm run lint:fix", "build": "cross-env NODE_ENV=production webpack --config webpack.config.js", "test": "npm run build && mocha --recursive --require=mocha.config.js" } } ================================================ FILE: packages/vue/README.md ================================================ # Jspreasheet CE [Jspreadsheet CE](https://bossanova.uk/jspreadsheet/) ================================================ FILE: packages/vue/dist/index.d.ts ================================================ import { DefineComponent } from 'vue'; import type JSpreadsheetCore from 'jspreadsheet-ce'; // Get all the static types from the core library type JSpreadsheetBase = typeof JSpreadsheetCore; // Create interface that extends the base type and adds call signature interface JSpreadsheetInterface extends JSpreadsheetBase { (element: HTMLElement | HTMLDivElement | null, options: JSpreadsheetCore.Spreadsheet): Array; } export declare const Worksheet: DefineComponent; export declare const Spreadsheet: DefineComponent; export declare const jspreadsheet: JSpreadsheetInterface; declare const _default: { Worksheet: DefineComponent; Spreadsheet: DefineComponent; jspreadsheet: JSpreadsheetInterface; }; export default _default; ================================================ FILE: packages/vue/dist/index.js ================================================ import { h, getCurrentInstance, camelize } from 'vue'; import jss from 'jspreadsheet-ce'; export const Worksheet = { name: 'Worksheet', }; export const Spreadsheet = { inheritAttrs: false, mounted() { const { attrs, slots } = getCurrentInstance(); let options = {}; for (const key in attrs) { options[camelize(key)] = attrs[key]; } if (slots && typeof slots.default === 'function') { options.worksheets = slots.default().reduce((acc, vnode) => { if (vnode.type.name === 'Worksheet') { let worksheetProps = {}; for (const key in vnode.props) { worksheetProps[camelize(key)] = vnode.props[key]; } acc.push({ minDimensions: [4, 4], ...worksheetProps, }); } return acc; }, []); } else { if (attrs.worksheets) { options.worksheets = attrs.worksheets; } } if (attrs.id) { this.$refs.container.id = attrs.id; } this.el = this.$refs.container; this.current = jss(this.$refs.container, options); }, setup() { let containerProps = { ref: 'container', }; return () => h('div', containerProps); }, }; export let jspreadsheet = jss; export default { Worksheet, Spreadsheet, jspreadsheet: jss }; ================================================ FILE: packages/vue/package.json ================================================ { "name": "@jspreadsheet-ce/vue", "title": "Jspreadsheet CE Vue Wrapper", "description": "Jspreadsheet CE is a lightweight, vanilla JavaScript plugin for creating powerful, web-based interactive tables and spreadsheets that are compatible with other spreadsheet software.", "author": { "name": "Contact ", "url": "https://bossanova.uk/jspreadsheet/" }, "keywords": [ "spreadsheet", "spreadsheets", "tables", "table", "excel", "grid", "grid-editor", "data-table", "data-grid", "data-spreadsheet", "vue" ], "dependencies": { "jspreadsheet-ce": "^5.0.1" }, "main": "dist/index.js", "types": "dist/index.d.ts", "version": "5.0.1" } ================================================ FILE: public/index.html ================================================ CE
================================================ FILE: resources/lang/de_DE.json ================================================ { "noRecordsFound": "Keine Einträge vorhanden.", "showingPage": "Seite {0} von {1}", "show": "Zeige ", "search": "Suchen", "entries": "Einträge", "columnName": "Spaltenname", "insertANewColumnBefore": "Eine neue Spalte davor einfügen", "insertANewColumnAfter": "Eine neue Spalte danach einfügen", "deleteSelectedColumns": "Ausgewählte Spalten löschen", "renameThisColumn": "Diese Spalte umbenennen", "orderAscending": "Aufsteigend sortieren", "orderDescending": "Absteigend sortieren", "insertANewRowBefore": "Eine neue Zeile davor einfügen", "insertANewRowAfter": "Eine neue Zeile danach einfügen", "deleteSelectedRows": "Ausgewählte Zeilen löschen", "editComments": "Kommentare bearbeiten", "addComments": "Einen Kommentar ", "comments": "Kommentare", "clearComments": "Kommentare entfernen", "copy": "Kopieren...", "paste": "Einfügen...", "saveAs": "Speichern als...", "about": "Über", "areYouSureToDeleteTheSelectedRows": "Möchten Sie die ausgewählten Zeilen wirklich löschen?", "areYouSureToDeleteTheSelectedColumns": "Möchten Sie die ausgewählten Spalten wirklich löschen?", "thisActionWillDestroyAnyExistingMergedCellsAreYouSure": "Diese Aktion zerstört alle verbundenen Zellen. Möchten Sie fortfahren?", "thisActionWillClearYourSearchResultsAreYouSure": "Dieser Vorgang wird Ihre Suchergebnisse entfernen. Möchten Sie fortfahren?", "thereIsAConflictWithAnotherMergedCell": "Es liegt ein Konflikt mit einer anderen verbundenen Zelle vor", "invalidMergeProperties": "Eigenschaften zum Verbinden ungültig", "cellAlreadyMerged": "Zelle bereits verbunden", "noCellsSelected": "Keine Zellen ausgewählt" } ================================================ FILE: resources/lang/dk_DA.json ================================================ { "noRecordsFound": "Ingen felter fundet", "showingPage": "Viser side {0} af {1}", "show": "Vis ", "search": "Søg", "entries": "indgange", "columnName": "Søjlenavn", "insertANewColumnBefore": "Indsæt ny søjle før", "insertANewColumnAfter": "Indsæt ny søjle efter", "deleteSelectedColumns": "Slet valgte søjler", "renameThisColumn": "Omdøb denne søjle", "orderAscending": "Sorter voksende", "orderDescending": "Sorter aftagende", "insertANewRowBefore": "Indsæt ny række før", "insertANewRowAfter": "Indsæt ny række efter", "deleteSelectedRows": "Slet valgte rækker", "editComments": "Rediger kommentarer", "addComments": "Tilføj kommentarer", "comments": "Kommentarer", "clearComments": "Ryd kommentarer", "copy": "Kopier...", "paste": "Indsæt...", "saveAs": "Gem som...", "about": "Om", "areYouSureToDeleteTheSelectedRows": "Sikker på at slette de valgte rækker?", "areYouSureToDeleteTheSelectedColumns": "Sikker på at slette de valgte søjler?", "thisActionWillDestroyAnyExistingMergedCellsAreYouSure": "Dette sletter eksisterende forbundne celler. Sikker?", "thisActionWillClearYourSearchResultsAreYouSure": "Dette rydder dine søgeresultater. Sikker?", "thereIsAConflictWithAnotherMergedCell": "Der er en konflikt med en anden forbundet celle.", "invalidMergeProperties": "Ugyldige forbundne egenskaber", "cellAlreadyMerged": "Celle allerede forbundet", "noCellsSelected": "Ingen celler valgt" } ================================================ FILE: resources/lang/en_GB.json ================================================ { "noRecordsFound": "No records found", "showingPage": "Showing page {0} of {1} entries", "show": "Show ", "search": "Search", "entries": " entries", "columnName": "Column name", "insertANewColumnBefore": "Insert a new column before", "insertANewColumnAfter": "Insert a new column after", "deleteSelectedColumns": "Delete selected columns", "renameThisColumn": "Rename this column", "orderAscending": "Order ascending", "orderDescending": "Order descending", "insertANewRowBefore": "Insert a new row before", "insertANewRowAfter": "Insert a new row after", "deleteSelectedRows": "Delete selected rows", "editComments": "Edit comments", "addComments": "Add comments", "comments": "Comments", "clearComments": "Clear comments", "copy": "Copy...", "paste": "Paste...", "saveAs": "Save as...", "about": "About", "areYouSureToDeleteTheSelectedRows": "Are you sure to delete the selected rows?", "areYouSureToDeleteTheSelectedColumns": "Are you sure to delete the selected columns?", "thisActionWillDestroyAnyExistingMergedCellsAreYouSure": "This action will destroy any existing merged cells. Are you sure?", "thisActionWillClearYourSearchResultsAreYouSure": "This action will clear your search results. Are you sure?", "thereIsAConflictWithAnotherMergedCell": "There is a conflict with another merged cell", "invalidMergeProperties": "Invalid merged properties", "cellAlreadyMerged": "Cell already merged", "noCellsSelected": "No cells selected" } ================================================ FILE: resources/lang/fr_FR.json ================================================ { "noRecordsFound": "Aucune donnée trouvée", "showingPage": "Afficher la page {0} sur {1}", "show": "Afficher ", "entries": "lignes", "insertANewColumnBefore": "Insérer une nouvelle colonne avant", "insertANewColumnAfter": "Insérer une nouvelle colonne après", "deleteSelectedColumns": "Supprimer les colonnes sélectionnées", "renameThisColumn": "Renommer la colonne", "orderAscending": "Trier par ordre croissant", "orderDescending": "Trier par ordre décroissant", "insertANewRowBefore": "Insérer une nouvelle ligne avant", "insertANewRowAfter": "Insérer une nouvelle ligne après", "deleteSelectedRows": "Supprimer les lignes sélectionnées", "editComments": "Modifier le commentaire", "addComments": "Ajouter un commentaire", "comments": "Commentaire", "clearComments": "Supprimer le commentaire", "copy": "Copier ...", "paste": "Coller ...", "saveAs": "Enregistrer sous ...", "about": "A propos", "search": "Rechercher", "areYouSureToDeleteTheSelectedRows": "Etes vous sur de vouloir supprimer les lignes sélectionnées ?", "areYouSureToDeleteTheSelectedColumns": "Etes vous sur de vouloir supprimer les colonnes sélectionnées ?", "thisActionWillDestroyAnyExistingMergedCellsAreYouSure": "Cette action détruira toutes les cellules fusionnées existantes. Voulez vous continuer ?", "thisActionWillClearYourSearchResultsAreYouSure": "Cette action effacera votre recherche. Voulez-vous continuer ?", "thereIsAConflictWithAnotherMergedCell": "Il y a un conflit avec une autre cellule fusionnée", "invalidMergeProperties": "Propriétés de fusion invalides", "cellAlreadyMerged": "Cellule déjà fusionnée", "noCellsSelected": "Aucune cellule sélectionnée" } ================================================ FILE: resources/lang/it_IT.json ================================================ { "noRecordsFound": "Nessun record trovato", "showingPage": "Pagina {0} di {1}", "show": "Mostra ", "search": "Cerca", "entries": " elementi", "columnName": "Nome colonna", "insertANewColumnBefore": "Inserisci una nuova colonna prima", "insertANewColumnAfter": "Inserisci una nuova colonna dopo", "deleteSelectedColumns": "Delete selected columns", "renameThisColumn": "Rinomina questa colonna", "orderAscending": "Ordina ascendente", "orderDescending": "Ordina decrescente", "insertANewRowBefore": "Inserisci una nuova riga prima", "insertANewRowAfter": "Inserisci una nuova riga dopo", "deleteSelectedRows": "Elimina le righe selezionate", "editComments": "Modifica commenti", "addComments": "Aggiungi commenti", "comments": "Commenti", "clearComments": "Pulisci i commenti", "copy": "Copia...", "paste": "Incolla...", "saveAs": "Salva come...", "about": "Info", "areYouSureToDeleteTheSelectedRows": "Si � sicuri di voler eliminare queste righe?", "areYouSureToDeleteTheSelectedColumns": "Si � sicuri di voler eliminare queste colonne?", "thisActionWillDestroyAnyExistingMergedCellsAreYouSure": "Questa azione eliminer� qualsiasi cella unita. Confermi?", "thisActionWillClearYourSearchResultsAreYouSure": "Questa azione pulir� i risultati della ricerca. Confermi?", "thereIsAConflictWithAnotherMergedCell": "C'� un conflitto con un'altra cella unita", "invalidMergeProperties": "Propriet� di unione invalide", "cellAlreadyMerged": "Celle gi� unite", "noCellsSelected": "Nessuna cella selezionata" } ================================================ FILE: resources/lang/pl_PL.json ================================================ { "noRecordsFound": "Nic nie znaleziono", "showingPage": "Strona {0} z {1}", "show": "Pokaż ", "search": "Szukaj/filtruj listę", "entries": " rekordów", "columnName": "Nazwa kolumny", "insertANewColumnBefore": "Wstaw nową kolumnę przed", "insertANewColumnAfter": "Wstaw nową kolumnę po", "deleteSelectedColumns": "Usuń wybrane kolumny", "renameThisColumn": "Zmień nazwę kolumny", "orderAscending": "Sortuj rosnąco A-Z", "orderDescending": "Sortuj malejąco Z-A", "insertANewRowBefore": "Wstaw nowy wiersz przed", "insertANewRowAfter": "Wstaw nowy wiersz po", "deleteSelectedRows": "Usuń wybrane wiersze", "editComments": "Edytuj komentarz", "addComments": "Dodaj komentarz", "comments": "Komentarze", "clearComments": "Wyczyść komentarze", "copy": "Kopiuj...", "paste": "Wklej...", "saveAs": "Zapisz jako...", "about": "Informacja o Jspreadsheet CE ", "areYouSureToDeleteTheSelectedRows": "Czy na pewno chcesz usuńąć wybrane wiersze?", "areYouSureToDeleteTheSelectedColumns": "Czy na pewno chcesz usuńąć wybrane kolumny?", "thisActionWillDestroyAnyExistingMergedCellsAreYouSure": "Ta operacja zniszczy istniejące komórki scalone. Czy na pewno?", "thisActionWillClearYourSearchResultsAreYouSure": "Ta operacja wyczyści wyniki wyszukiwania. Czy na pewno?", "thereIsAConflictWithAnotherMergedCell": "Istnieje konflikt z inną scaloną komórką", "invalidMergeProperties": "Nieprawidłowe właściwości scalenia", "cellAlreadyMerged": "Komórka już scalona", "noCellsSelected": "Nie wybrano żadnych komórek" } ================================================ FILE: resources/lang/pt_BR.json ================================================ { "noRecordsFound": "Nenhum registro encontrado", "showingPage": "Exibindo {0} de {1} páginas", "show": "Exibir ", "search": "Buscar", "entries": " Entradas", "columnName": "Nome da coluna", "insertANewColumnBefore": "Inserir uma nova coluna antes", "insertANewColumnAfter": "Inserir uma nova coluna depois", "deleteSelectedColumns": "Deletar as colunas selecionadas", "renameThisColumn": "Renomar essa coluna", "orderAscending": "Ordenar ascedente", "orderDescending": "Ordenar descedente", "insertANewRowBefore": "Inserir um novo registro antes", "insertANewRowAfter": "Inserir um novo registro depois", "deleteSelectedRows": "Deletar as linhas selecionadas", "editComments": "Editar comentários", "addComments": "Adicionar comentários", "comments": "Comentários", "clearComments": "Limpar comentários", "copy": "Copiar...", "paste": "Colar...", "saveAs": "Salvar como...", "about": "Sobre", "areYouSureToDeleteTheSelectedRows": "Tem certeza que deseja apagar as linhas selecionadas?", "areYouSureToDeleteTheSelectedColumns": "Tem certeza que deseja apagar as colunas selecionadas?", "thisActionWillDestroyAnyExistingMergedCellsAreYouSure": "Essa ação vai destroir qualquer celular mesclada. Tem certeza?", "thisActionWillClearYourSearchResultsAreYouSure": "Essa ação vai limpar o filtro de pesquisa. Tem certeza?", "thereIsAConflictWithAnotherMergedCell": "Existe um conflito com outra celula mesclada", "invalidMergeProperties": "Propriedas de mesclagem inválidas", "cellAlreadyMerged": "A celula já está mesclada", "noCellsSelected": "Nenhuma celula selecionada" } ================================================ FILE: resources/lang/vi_VN.json ================================================ { "noRecordsFound": "Khôn có dữ liệu", "showingPage": "Hiển thị trang {0} trên tổng số {1} trang", "show": "Hiện ", "search": "Tìm kiếm", "entries": "dòng", "columnName": "Tên cột", "insertANewColumnBefore": "Thêm một cột bên trái", "insertANewColumnAfter": "Thêm một cột bên phải", "deleteSelectedColumns": "Xoá các cột đã chọn", "renameThisColumn": "Đổi tên cột", "orderAscending": "Sắp xếp A-Z", "orderDescending": "Sắp xếp Z-A", "insertANewRowBefore": "Thêm một dòng bên trên", "insertANewRowAfter": "Thêm một dòng bên dưới", "deleteSelectedRows": "Xoá các dòng đã chọn", "editComments": "Chỉnh sửa bình luận", "addComments": "Thêm bình luận", "comments": "Bình luận", "clearComments": "Xoá bình luận", "copy": "Copy...", "paste": "Paste...", "saveAs": "Lưu...", "about": "Thông tin", "areYouSureToDeleteTheSelectedRows": "Bạn chắc chắn rằng mình muốn xoá dòng?", "areYouSureToDeleteTheSelectedColumns": "Bạn chắc chắn rằng mình muốn xoá cột?", "thisActionWillDestroyAnyExistingMergedCellsAreYouSure": "Bạn chắc chắn rằng mình muốn xoá tất cả các ô đã được merge?", "thisActionWillClearYourSearchResultsAreYouSure": "Bạn chắc chắn rằng mình muốn xoá dữ liệu tìm kiếm?", "thereIsAConflictWithAnotherMergedCell": "Có sự khác nhau giữa 2 ô được merge", "invalidMergeProperties": "Thông tin merge không chính xác", "cellAlreadyMerged": "Ô này đã được merge", "noCellsSelected": "Không có ô nào được chọn" } ================================================ FILE: resources/lang/zh_CN.json ================================================ { "noRecordsFound": "未找到", "showingPage": "显示 {1} 条中的第 {0} 条", "show": "显示 ", "search": "搜索", "entries": " 条目", "columnName": "列标题", "insertANewColumnBefore": "在此前插入列", "insertANewColumnAfter": "在此后插入列", "deleteSelectedColumns": "删除选定列", "renameThisColumn": "重命名列", "orderAscending": "升序", "orderDescending": "降序", "insertANewRowBefore": "在此前插入行", "insertANewRowAfter": "在此后插入行", "deleteSelectedRows": "删除选定行", "editComments": "编辑批注", "addComments": "插入批注", "comments": "批注", "clearComments": "删除批注", "copy": "复制...", "paste": "粘贴...", "saveAs": "保存为...", "about": "关于", "areYouSureToDeleteTheSelectedRows": "确定删除选定行?", "areYouSureToDeleteTheSelectedColumns": "确定删除选定列?", "thisActionWillDestroyAnyExistingMergedCellsAreYouSure": "这一操作会破坏所有现存的合并单元格,确认操作?", "thisActionWillClearYourSearchResultsAreYouSure": "这一操作会清空搜索结果,确认操作?", "thereIsAConflictWithAnotherMergedCell": "与其他合并单元格有冲突", "invalidMergeProperties": "无效的合并属性", "cellAlreadyMerged": "单元格已合并", "noCellsSelected": "未选定单元格" } ================================================ FILE: src/index.js ================================================ import jSuites from 'jsuites'; import libraryBase from './utils/libraryBase.js'; import Factory from './utils/factory.js'; import { destroyEvents } from './utils/events.js'; import * as helpers from './utils/helpers.js'; import dispatch from './utils/dispatch.js'; import version from './utils/version.js'; libraryBase.jspreadsheet = function (el, options) { try { let worksheets = []; // Create spreadsheet Factory.spreadsheet(el, options, worksheets).then((spreadsheet) => { libraryBase.jspreadsheet.spreadsheet.push(spreadsheet); // Global onload event dispatch.call(spreadsheet, 'onload', spreadsheet); }); return worksheets; } catch (e) { console.error(e); } }; libraryBase.jspreadsheet.getWorksheetInstanceByName = function (worksheetName, namespace) { const targetSpreadsheet = libraryBase.jspreadsheet.spreadsheet.find((spreadsheet) => { return spreadsheet.config.namespace === namespace; }); if (targetSpreadsheet) { return {}; } if (typeof worksheetName === 'undefined' || worksheetName === null) { const namespaceEntries = targetSpreadsheet.worksheets.map((worksheet) => { return [worksheet.options.worksheetName, worksheet]; }); return Object.fromEntries(namespaceEntries); } return targetSpreadsheet.worksheets.find((worksheet) => { return worksheet.options.worksheetName === worksheetName; }); }; // Define dictionary libraryBase.jspreadsheet.setDictionary = function (o) { jSuites.setDictionary(o); }; libraryBase.jspreadsheet.destroy = function (element, destroyEventHandlers) { if (element.spreadsheet) { const spreadsheetIndex = libraryBase.jspreadsheet.spreadsheet.indexOf(element.spreadsheet); libraryBase.jspreadsheet.spreadsheet.splice(spreadsheetIndex, 1); const root = element.spreadsheet.config.root || document; element.spreadsheet = null; element.innerHTML = ''; if (destroyEventHandlers) { destroyEvents(root); } } }; libraryBase.jspreadsheet.destroyAll = function () { for (let spreadsheetIndex = 0; spreadsheetIndex < libraryBase.jspreadsheet.spreadsheet.length; spreadsheetIndex++) { const spreadsheet = libraryBase.jspreadsheet.spreadsheet[spreadsheetIndex]; libraryBase.jspreadsheet.destroy(spreadsheet.element); } }; libraryBase.jspreadsheet.current = null; libraryBase.jspreadsheet.spreadsheet = []; libraryBase.jspreadsheet.helpers = {}; libraryBase.jspreadsheet.version = function () { return version; }; Object.entries(helpers).forEach(([key, value]) => { libraryBase.jspreadsheet.helpers[key] = value; }); export default libraryBase.jspreadsheet; ================================================ FILE: src/jspreadsheet.css ================================================ :root { --jss-border-color: #000; } .jss_spreadsheet { outline: none; } .jss_container { display: inline-block; padding-right: 2px; box-sizing: border-box; overscroll-behavior: contain; outline: none; } .fullscreen { position: fixed !important; top: 0px; left: 0px; width: 100%; height: 100%; z-index: 21; display: flex; flex-direction: column; background-color: #ffffff; } .fullscreen .jtabs-content { flex: 1; overflow: hidden; } .fullscreen .jss_content { overflow: auto; width: 100% !important; height: 100%; max-height: 100% !important; } .fullscreen .jss_container { height: 100%; } .jss_content { display: inline-block; box-sizing: border-box; padding-right: 3px; padding-bottom: 3px; position: relative; scrollbar-width: thin; scrollbar-color: #666 transparent; } @supports (-moz-appearance: none) { .jss_content { padding-right: 10px; } } .jss_content::-webkit-scrollbar { width: 8px; height: 8px; } .jss_content::-webkit-scrollbar-track { background: #eee; } .jss_content::-webkit-scrollbar-thumb { background: #666; } .jss_worksheet { border-collapse: separate; table-layout: fixed; white-space: nowrap; empty-cells: show; border: 0px; background-color: #fff; width: 0; border-top: 1px solid transparent; border-left: 1px solid transparent; border-right: 1px solid #ccc; border-bottom: 1px solid #ccc; } .jss_worksheet > thead > tr > td { border-top: 1px solid #ccc; border-left: 1px solid #ccc; border-right: 1px solid transparent; border-bottom: 1px solid transparent; background-color: #f3f3f3; padding: 2px; cursor: pointer; box-sizing: border-box; overflow: hidden; position: -webkit-sticky; position: sticky; top: 0; z-index: 2; } .jss_worksheet > thead > tr > td.dragging { opacity: 0.5; } .jss_worksheet > thead > tr > td.selected { background-color: #dcdcdc; } .jss_worksheet > thead > tr > td.arrow-up { background-repeat: no-repeat; background-position: center right 5px; background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 14l5-5 5 5H7z' fill='gray'/%3E%3C/svg%3E"); text-decoration: underline; } .jss_worksheet > thead > tr > td.arrow-down { background-repeat: no-repeat; background-position: center right 5px; background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='gray'/%3E%3C/svg%3E"); text-decoration: underline; } .jss_worksheet > tbody > tr > td:first-child { position: relative; background-color: #f3f3f3; text-align: center; } .jss_worksheet > tbody.resizable > tr > td:first-child::before { content: '\00a0'; width: 100%; height: 3px; position: absolute; bottom: 0px; left: 0px; cursor: row-resize; } .jss_worksheet > tbody.draggable > tr > td:first-child::after { content: '\00a0'; width: 3px; height: 100%; position: absolute; top: 0px; right: 0px; cursor: move; } .jss_worksheet > tbody > tr.dragging > td { background-color: #eee; opacity: 0.5; } .jss_worksheet > tbody > tr > td { border-top: 1px solid #ccc; border-left: 1px solid #ccc; border-right: 1px solid transparent; border-bottom: 1px solid transparent; padding: 4px; white-space: nowrap; box-sizing: border-box; line-height: 1em; } .jss_overflow > tbody > tr > td { overflow: hidden; } .jss_worksheet > tbody > tr > td:last-child { overflow: hidden; } .jss_worksheet > tbody > tr > td > img { display: inline-block; max-width: 100px; } .jss_worksheet > tbody > tr > td.readonly { color: rgba(0, 0, 0, 0.3); } .jss_worksheet > tbody > tr.selected > td:first-child { background-color: #dcdcdc; } .jss_worksheet > tbody > tr > td > select, .jss_worksheet > tbody > tr > td > input, .jss_worksheet > tbody > tr > td > textarea { border: 0px; border-radius: 0px; outline: 0px; width: 100%; margin: 0px; padding: 0px; padding-right: 2px; background-color: transparent; box-sizing: border-box; } .jss_worksheet > tbody > tr > td > textarea { resize: none; padding-top: 6px !important; } .jss_worksheet > tbody > tr > td > input[type='checkbox'] { width: 12px; margin-top: 2px; } .jss_worksheet > tbody > tr > td > input[type='radio'] { width: 12px; margin-top: 2px; } .jss_worksheet > tbody > tr > td > select { -webkit-appearance: none; -moz-appearance: none; appearance: none; background-repeat: no-repeat; background-position-x: 100%; background-position-y: 40%; background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSdibGFjaycgaGVpZ2h0PScyNCcgdmlld0JveD0nMCAwIDI0IDI0JyB3aWR0aD0nMjQnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2Zyc+PHBhdGggZD0nTTcgMTBsNSA1IDUtNXonLz48cGF0aCBkPSdNMCAwaDI0djI0SDB6JyBmaWxsPSdub25lJy8+PC9zdmc+); } .jss_worksheet > tbody > tr > td.jss_dropdown { background-repeat: no-repeat; background-position: top 50% right 5px; background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='lightgray'/%3E%3C/svg%3E"); text-overflow: ellipsis; overflow-x: hidden; } .jss_worksheet > tbody > tr > td.jss_dropdown.jss_comments { background: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='lightgray'/%3E%3C/svg%3E") top 50% right 5px no-repeat, url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFuGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDphMTlhZDJmOC1kMDI2LTI1NDItODhjOS1iZTRkYjkyMmQ0MmQiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkOGI5NDUyMS00ZjEwLWQ5NDktYjUwNC0wZmU1N2I3Nzk1MDEiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIHN0RXZ0OndoZW49IjIwMTktMDEtMzFUMTg6NTU6MDhaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmExOWFkMmY4LWQwMjYtMjU0Mi04OGM5LWJlNGRiOTIyZDQyZCIgc3RFdnQ6d2hlbj0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4En6MDAAAAX0lEQVQYlX3KOw6AIBBAwS32RpJADXfx0pTET+ERZJ8F8RODFtONsG0QAoh0CSDM82dqodaBdQXnfoLZQM7gPai+wjNNE8R4pTuAYNZSKZASqL7CMy0LxNgJp30fKYUDi3+vIqb/+rUAAAAASUVORK5CYII=') top right no-repeat; } .jss_worksheet > tbody > tr > td > .color { width: 90%; height: 10px; margin: auto; } .jss_worksheet > tbody > tr > td > a { text-decoration: underline; } .jss_worksheet > tbody > tr > td.highlight > a { color: blue; cursor: pointer; } .jss_worksheet > tfoot > tr > td { border-top: 1px solid #ccc; border-left: 1px solid #ccc; border-right: 1px solid transparent; border-bottom: 1px solid transparent; background-color: #f3f3f3; padding: 2px; cursor: pointer; box-sizing: border-box; overflow: hidden; } .jss_worksheet .highlight { background-color: rgba(0, 0, 0, 0.05); } .jss_worksheet .highlight-top { border-top: 1px solid #000; box-shadow: 0px -1px #ccc; } .jss_worksheet .highlight-left { border-left: 1px solid #000; box-shadow: -1px 0px #ccc; } .jss_worksheet .highlight-right { border-right: 1px solid #000; } .jss_worksheet .highlight-bottom { border-bottom: 1px solid #000; } .jss_worksheet .highlight-top.highlight-left { box-shadow: -1px -1px #ccc; -webkit-box-shadow: -1px -1px #ccc; -moz-box-shadow: -1px -1px #ccc; } .jss_worksheet .highlight-selected { background-color: rgba(0, 0, 0, 0); } .jss_worksheet .selection { background-color: rgba(0, 0, 0, 0.05); } .jss_worksheet .selection-left { border-left: 1px dotted #000; } .jss_worksheet .selection-right { border-right: 1px dotted #000; } .jss_worksheet .selection-top { border-top: 1px dotted #000; } .jss_worksheet .selection-bottom { border-bottom: 1px dotted #000; } .jss_corner { position: absolute; background-color: rgb(0, 0, 0); height: 1px; width: 1px; border: 1px solid rgb(255, 255, 255); top: -2000px; left: -2000px; cursor: crosshair; box-sizing: initial; z-index: 20; padding: 2px; } .jss_worksheet .editor { outline: 0px solid transparent; overflow: visible; white-space: nowrap; text-align: left; padding: 0px; box-sizing: border-box; overflow: visible !important; } .jss_worksheet .editor > input { padding-left: 4px; } .jss_worksheet .editor .jupload { position: fixed; top: 100%; z-index: 40; user-select: none; -webkit-font-smoothing: antialiased; font-size: 0.875rem; letter-spacing: 0.2px; -webkit-border-radius: 4px; border-radius: 4px; -webkit-box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); padding: 10px; background-color: #fff; width: 300px; min-height: 225px; margin-top: 2px; } .jss_worksheet .editor .jupload img { width: 100%; height: auto; } .jss_worksheet .editor .jss_richtext { position: fixed; top: 100%; z-index: 40; user-select: none; -webkit-font-smoothing: antialiased; font-size: 0.875rem; letter-spacing: 0.2px; -webkit-box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2); padding: 10px; background-color: #fff; width: 358px; margin-top: 2px; text-align: left; white-space: initial; } .jss_worksheet .editor .jclose:after { position: absolute; top: 0; right: 0; margin: 10px; content: 'close'; font-family: 'Material icons'; font-size: 24px; width: 24px; height: 24px; line-height: 24px; cursor: pointer; text-shadow: 0px 0px 5px #fff; } .jss_worksheet, .jss_worksheet td, .jss_corner { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -webkit-user-drag: none; -khtml-user-drag: none; -moz-user-drag: none; -o-user-drag: none; user-drag: none; } .jss_textarea { position: absolute; top: -999px; left: -999px; width: 1px; height: 1px; } .jss_worksheet .dragline { position: absolute; } .jss_worksheet .dragline div { position: relative; top: -6px; height: 5px; width: 22px; } .jss_worksheet .dragline div:hover { cursor: move; } .jss_worksheet .onDrag { background-color: rgba(0, 0, 0, 0.6); } .jss_worksheet .error { border: 1px solid red; } .jss_worksheet thead td.resizing { border-right-style: dotted !important; border-right-color: red !important; } .jss_worksheet tbody tr.resizing > td { border-bottom-style: dotted !important; border-bottom-color: red !important; } .jss_worksheet tbody td.resizing { border-right-style: dotted !important; border-right-color: red !important; } .jss_worksheet .jdropdown-header { border: 0px !important; outline: none !important; width: 100% !important; height: 100% !important; padding: 0px !important; padding-left: 8px !important; } .jss_worksheet .jdropdown-container { margin-top: 1px; } .jss_worksheet .jdropdown-container-header { padding: 0px; margin: 0px; height: inherit; } .jss_worksheet .jdropdown-picker { border: 0px !important; padding: 0px !important; width: inherit; height: inherit; } .jss_worksheet .jss_comments { background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFuGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDphMTlhZDJmOC1kMDI2LTI1NDItODhjOS1iZTRkYjkyMmQ0MmQiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkOGI5NDUyMS00ZjEwLWQ5NDktYjUwNC0wZmU1N2I3Nzk1MDEiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIHN0RXZ0OndoZW49IjIwMTktMDEtMzFUMTg6NTU6MDhaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmExOWFkMmY4LWQwMjYtMjU0Mi04OGM5LWJlNGRiOTIyZDQyZCIgc3RFdnQ6d2hlbj0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4En6MDAAAAX0lEQVQYlX3KOw6AIBBAwS32RpJADXfx0pTET+ERZJ8F8RODFtONsG0QAoh0CSDM82dqodaBdQXnfoLZQM7gPai+wjNNE8R4pTuAYNZSKZASqL7CMy0LxNgJp30fKYUDi3+vIqb/+rUAAAAASUVORK5CYII='); background-repeat: no-repeat; background-position: top right; } .jss_worksheet .sp-replacer { margin: 2px; border: 0px; } .jss_worksheet > thead > tr.jss_filter > td > input { border: 0px; width: 100%; outline: none; } .jss_about { float: right; font-size: 0.7em; padding: 2px; text-transform: uppercase; letter-spacing: 1px; display: none; } .jss_about a { color: #ccc; text-decoration: none; } .jss_about img { display: none; } .jss_filter { display: flex; justify-content: space-between; margin-bottom: 4px; } .jss_filter > div { padding: 8px; align-items: center; } .jss_pagination { display: flex; justify-content: space-between; align-items: center; } .jss_pagination > div { display: flex; padding: 10px; } .jss_pagination > div:last-child { padding-right: 10px; padding-top: 10px; } .jss_pagination > div > div { text-align: center; width: 36px; height: 36px; line-height: 34px; border: 1px solid #ccc; box-sizing: border-box; margin-left: 2px; cursor: pointer; } .jss_page { font-size: 0.8em; } .jss_page_selected { font-weight: bold; background-color: #f3f3f3; } .jss_toolbar { display: flex; background-color: #f3f3f3; border: 1px solid #ccc; padding: 4px; margin: 0px 2px 4px 1px; } .jss_toolbar:empty { display: none; } .jss_worksheet .dragging-left { background-repeat: no-repeat; background-position: top 50% left 0px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M14 7l-5 5 5 5V7z'/%3E%3Cpath fill='none' d='M24 0v24H0V0h24z'/%3E%3C/svg%3E"); } .jss_worksheet .dragging-right { background-repeat: no-repeat; background-position: top 50% right 0px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M10 17l5-5-5-5v10z'/%3E%3Cpath fill='none' d='M0 24V0h24v24H0z'/%3E%3C/svg%3E"); } .jss_hidden_index > tbody > tr > td:first-child, .jss_hidden_index > thead > tr > td:first-child, .jss_hidden_index > tfoot > tr > td:first-child, .jss_hidden_index > colgroup > col:first-child { display: none; } .jss_worksheet .jrating { display: inline-flex; } .jss_worksheet .jrating > div { zoom: 0.55; } .jss_worksheet .copying-top { border-top: 1px dashed #000; } .jss_worksheet .copying-left { border-left: 1px dashed #000; } .jss_worksheet .copying-right { border-right: 1px dashed #000; } .jss_worksheet .copying-bottom { border-bottom: 1px dashed #000; } .jss_worksheet .jss_column_filter { background-repeat: no-repeat; background-position: top 50% right 5px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='gray' width='18px' height='18px'%3E%3Cpath d='M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); text-overflow: ellipsis; overflow: hidden; padding: 0px; padding-left: 6px; padding-right: 20px; } .jss_worksheet thead .jss_freezed, .jss_worksheet tfoot .jss_freezed { left: 0px; z-index: 3 !important; box-shadow: 2px 0px 2px 0.2px #ccc !important; -webkit-box-shadow: 2px 0px 2px 0.2px #ccc !important; -moz-box-shadow: 2px 0px 2px 0.2px #ccc !important; } .jss_worksheet tbody .jss_freezed { position: relative; background-color: #fff; box-shadow: 1px 1px 1px 1px #ccc !important; -webkit-box-shadow: 2px 4px 4px 0.1px #ccc !important; -moz-box-shadow: 2px 4px 4px 0.1px #ccc !important; } .red { color: red; } .jss_worksheet > tbody > tr > td.readonly > input[type='checkbox'], .jss_worksheet > tbody > tr > td.readonly > input[type='radio'] { pointer-events: none; opacity: 0.5; } ================================================ FILE: src/jspreadsheet.themes.css ================================================ .jss_worksheet > thead > tr > td { border-top: 1px solid var(--border_color, #ccc); border-left: 1px solid var(--border_color, #ccc); background-color: var(--header_background, #f3f3f3); color: var(--header_color, #000); } .jss_worksheet > thead > tr > td.selected { background-color: var(--header_background_highlighted, #dcdcdc); color: var(--header_color_highlighted, #000); } .jss_worksheet > tbody > tr > td:first-child { background-color: var(--header_background, #f3f3f3); color: var(--header_color, #000); } .jss_worksheet > tbody > tr > td { background-color: var(--content_background, #fff); color: var(--content_color, #000); border-top: 1px solid var(--border_color, #ccc); border-left: 1px solid var(--border_color, #ccc); } .jss_worksheet > tbody > tr.selected > td:first-child { background-color: var(--header_background_highlighted, #dcdcdc); color: var(--header_color_highlighted, #000); } .jss_worksheet .highlight { background-color: var(--selection, rgba(0, 0, 0, 0.05)); } .jss_worksheet .highlight-top { border-top: 1px solid var(--border_color_highlighted, #000); } .jss_worksheet .highlight-left { border-left: 1px solid var(--border_color_highlighted, #000); } .jss_worksheet .highlight-right { border-right: 1px solid var(--border_color_highlighted, #000); } .jss_worksheet .highlight-bottom { border-bottom: 1px solid var(--border_color_highlighted, #000); } .jss_worksheet .highlight-selected { background-color: var(--cursor, #eee); } .jss_pagination > div > div { color: var(--header_color, #000); background: var(--header_background, #f3f3f3); border: 1px solid var(--border_color, #ccc); } .jss_toolbar { background-color: var(--header_background, #f3f3f3); color: var(--header_color, #000); border: 1px solid var(--border_color, #ccc); } .jss_toolbar .jtoolbar-item i { color: var(--content_color, #000); } .jss_toolbar .jtoolbar-item:not(.jtoolbar-divisor):hover, .jss_toolbar .jtoolbar-item.jpicker:hover > .jpicker-header { background-color: var(--content_background_highlighted, #f3f3f3); color: var(--content_color_highlighted, #000); } .jss_toolbar .jtoolbar-divisor { background: var(--header_color, #ddd); } .jss_contextmenu { border: 1px solid var(--border_color, #ccc); background: var(--menu_background, #fff); color: var(--menu_color, #555); box-shadow: var(--menu_box_shadow, 2px 2px 2px 0px rgba(143, 144, 145, 1)); -webkit-box-shadow: var(--menu_box_shadow, 2px 2px 2px 0px rgba(143, 144, 145, 1)); -moz-box-shadow: var(--menu_box_shadow, 2px 2px 2px 0px rgba(143, 144, 145, 1)); } .jss_contextmenu > div a { color: var(--menu_color, #555); } .jss_contextmenu > div:not(.contextmenu-line):hover a { color: var(--menu_color_highlighted, #555); } .jss_contextmenu > div:not(.contextmenu-line):hover { background: var(--menu_background_highlighted, #ebebeb); } .jss_container input { color: var(--header_color, #000); background: var(--header_background, #f3f3f3); } ================================================ FILE: src/test.js ================================================ import jspreadsheet from './index.js'; import './jspreadsheet.css'; import 'jsuites/dist/jsuites.css'; window.jss = jspreadsheet; window.instance = jspreadsheet(document.getElementById('root'), { tabs: true, toolbar: true, worksheets: [ { minDimensions: [6, 6], }, ], }); ================================================ FILE: src/utils/cells.js ================================================ import { getCoordsFromCellName } from './helpers.js'; export const setReadOnly = function (cell, state) { const obj = this; let record; if (typeof cell === 'string') { const coords = getCoordsFromCellName(cell); record = obj.records[coords[1]][coords[0]]; } else { const x = parseInt(cell.getAttribute('data-x')); const y = parseInt(cell.getAttribute('data-y')); record = obj.records[y][x]; } if (state) { record.element.classList.add('readonly'); } else { record.element.classList.remove('readonly'); } }; export const isReadOnly = function (x, y) { const obj = this; if (typeof x === 'string' && typeof y === 'undefined') { const coords = getCoordsFromCellName(x); [x, y] = coords; } return obj.records[y][x].element.classList.contains('readonly'); }; ================================================ FILE: src/utils/columns.js ================================================ import jSuites from 'jsuites'; import dispatch from './dispatch.js'; import { getColumnName } from './helpers.js'; import { setHistory } from './history.js'; import { isColMerged } from './merges.js'; import { createCell, updateTableReferences } from './internal.js'; import { conditionalSelectionUpdate, updateCornerPosition } from './selection.js'; import { setFooter } from './footer.js'; import { getColumnNameFromId, injectArray } from './internalHelpers.js'; export const getNumberOfColumns = function () { const obj = this; let numberOfColumns = (obj.options.columns && obj.options.columns.length) || 0; if (obj.options.data && typeof obj.options.data[0] !== 'undefined') { // Data keys const keys = Object.keys(obj.options.data[0]); if (keys.length > numberOfColumns) { numberOfColumns = keys.length; } } if (obj.options.minDimensions && obj.options.minDimensions[0] > numberOfColumns) { numberOfColumns = obj.options.minDimensions[0]; } return numberOfColumns; }; export const createCellHeader = function (colNumber) { const obj = this; // Create col global control const colWidth = (obj.options.columns && obj.options.columns[colNumber] && obj.options.columns[colNumber].width) || obj.options.defaultColWidth || 100; const colAlign = (obj.options.columns && obj.options.columns[colNumber] && obj.options.columns[colNumber].align) || obj.options.defaultColAlign || 'center'; // Create header cell obj.headers[colNumber] = document.createElement('td'); obj.headers[colNumber].textContent = (obj.options.columns && obj.options.columns[colNumber] && obj.options.columns[colNumber].title) || getColumnName(colNumber); obj.headers[colNumber].setAttribute('data-x', colNumber); obj.headers[colNumber].style.textAlign = colAlign; if (obj.options.columns && obj.options.columns[colNumber] && obj.options.columns[colNumber].title) { obj.headers[colNumber].setAttribute('title', obj.headers[colNumber].innerText); } if (obj.options.columns && obj.options.columns[colNumber] && obj.options.columns[colNumber].id) { obj.headers[colNumber].setAttribute('id', obj.options.columns[colNumber].id); } // Width control const colElement = document.createElement('col'); colElement.setAttribute('width', colWidth); obj.cols[colNumber] = { colElement, x: colNumber, }; // Hidden column if (obj.options.columns && obj.options.columns[colNumber] && obj.options.columns[colNumber].type == 'hidden') { obj.headers[colNumber].style.display = 'none'; colElement.style.display = 'none'; } }; /** * Insert a new column * * @param mixed - num of columns to be added or data to be added in one single column * @param int columnNumber - number of columns to be created * @param bool insertBefore * @param object properties - column properties * @return void */ export const insertColumn = function (mixed, columnNumber, insertBefore, properties) { const obj = this; // Configuration if (obj.options.allowInsertColumn != false) { // Records var records = []; // Data to be insert let data = []; // The insert could be lead by number of rows or the array of data let numOfColumns; if (!Array.isArray(mixed)) { numOfColumns = typeof mixed === 'number' ? mixed : 1; } else { numOfColumns = 1; if (mixed) { data = mixed; } } // Direction insertBefore = insertBefore ? true : false; // Current column number const currentNumOfColumns = Math.max( obj.options.columns.length, ...obj.options.data.map(function (row) { return row.length; }) ); const lastColumn = currentNumOfColumns - 1; // Confirm position if (columnNumber == undefined || columnNumber >= parseInt(lastColumn) || columnNumber < 0) { columnNumber = lastColumn; } // Create default properties if (!properties) { properties = []; } for (let i = 0; i < numOfColumns; i++) { if (!properties[i]) { properties[i] = {}; } } const columns = []; if (!Array.isArray(mixed)) { for (let i = 0; i < mixed; i++) { const column = { column: columnNumber + i + (insertBefore ? 0 : 1), options: Object.assign({}, properties[i]), }; columns.push(column); } } else { const data = []; for (let i = 0; i < obj.options.data.length; i++) { data.push(i < mixed.length ? mixed[i] : ''); } const column = { column: columnNumber + (insertBefore ? 0 : 1), options: Object.assign({}, properties[0]), data, }; columns.push(column); } // Onbeforeinsertcolumn if (dispatch.call(obj, 'onbeforeinsertcolumn', obj, columns) === false) { return false; } // Merged cells if (obj.options.mergeCells && Object.keys(obj.options.mergeCells).length > 0) { if (isColMerged.call(obj, columnNumber, insertBefore).length) { if (!confirm(jSuites.translate('This action will destroy any existing merged cells. Are you sure?'))) { return false; } else { obj.destroyMerge(); } } } // Insert before const columnIndex = !insertBefore ? columnNumber + 1 : columnNumber; obj.options.columns = injectArray(obj.options.columns, columnIndex, properties); // Open space in the containers const currentHeaders = obj.headers.splice(columnIndex); const currentColgroup = obj.cols.splice(columnIndex); // History const historyHeaders = []; const historyColgroup = []; const historyRecords = []; const historyData = []; const historyFooters = []; // Add new headers for (let col = columnIndex; col < numOfColumns + columnIndex; col++) { createCellHeader.call(obj, col); obj.headerContainer.insertBefore(obj.headers[col], obj.headerContainer.children[col + 1]); obj.colgroupContainer.insertBefore(obj.cols[col].colElement, obj.colgroupContainer.children[col + 1]); historyHeaders.push(obj.headers[col]); historyColgroup.push(obj.cols[col]); } // Add new footer cells if (obj.options.footers) { for (let j = 0; j < obj.options.footers.length; j++) { historyFooters[j] = []; for (let i = 0; i < numOfColumns; i++) { historyFooters[j].push(''); } obj.options.footers[j].splice(columnIndex, 0, historyFooters[j]); } } // Adding visual columns for (let row = 0; row < obj.options.data.length; row++) { // Keep the current data const currentData = obj.options.data[row].splice(columnIndex); const currentRecord = obj.records[row].splice(columnIndex); // History historyData[row] = []; historyRecords[row] = []; for (let col = columnIndex; col < numOfColumns + columnIndex; col++) { // New value const value = data[row] ? data[row] : ''; obj.options.data[row][col] = value; // New cell const td = createCell.call(obj, col, row, obj.options.data[row][col]); obj.records[row][col] = { element: td, y: row, }; // Add cell to the row if (obj.rows[row]) { obj.rows[row].element.insertBefore(td, obj.rows[row].element.children[col + 1]); } if (obj.options.columns && obj.options.columns[col] && typeof obj.options.columns[col].render === 'function') { obj.options.columns[col].render(td, value, parseInt(col), parseInt(row), obj, obj.options.columns[col]); } // Record History historyData[row].push(value); historyRecords[row].push({ element: td, x: col, y: row }); } // Copy the data back to the main data Array.prototype.push.apply(obj.options.data[row], currentData); Array.prototype.push.apply(obj.records[row], currentRecord); } Array.prototype.push.apply(obj.headers, currentHeaders); Array.prototype.push.apply(obj.cols, currentColgroup); for (let i = columnIndex; i < obj.cols.length; i++) { obj.cols[i].x = i; } for (let j = 0; j < obj.records.length; j++) { for (let i = 0; i < obj.records[j].length; i++) { obj.records[j][i].x = i; } } // Adjust nested headers if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0 && obj.options.nestedHeaders[0] && obj.options.nestedHeaders[0][0]) { for (let j = 0; j < obj.options.nestedHeaders.length; j++) { const colspan = parseInt(obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length - 1].colspan) + numOfColumns; obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length - 1].colspan = colspan; obj.thead.children[j].children[obj.thead.children[j].children.length - 1].setAttribute('colspan', colspan); let o = obj.thead.children[j].children[obj.thead.children[j].children.length - 1].getAttribute('data-column'); o = o.split(','); for (let col = columnIndex; col < numOfColumns + columnIndex; col++) { o.push(col); } obj.thead.children[j].children[obj.thead.children[j].children.length - 1].setAttribute('data-column', o); } } // Keep history setHistory.call(obj, { action: 'insertColumn', columnNumber: columnNumber, numOfColumns: numOfColumns, insertBefore: insertBefore, columns: properties, headers: historyHeaders, cols: historyColgroup, records: historyRecords, footers: historyFooters, data: historyData, }); // Remove table references updateTableReferences.call(obj); // Events dispatch.call(obj, 'oninsertcolumn', obj, columns); } }; /** * Move column * * @return void */ export const moveColumn = function (o, d) { const obj = this; if (obj.options.mergeCells && Object.keys(obj.options.mergeCells).length > 0) { let insertBefore; if (o > d) { insertBefore = 1; } else { insertBefore = 0; } if (isColMerged.call(obj, o).length || isColMerged.call(obj, d, insertBefore).length) { if (!confirm(jSuites.translate('This action will destroy any existing merged cells. Are you sure?'))) { return false; } else { obj.destroyMerge(); } } } o = parseInt(o); d = parseInt(d); if (o > d) { obj.headerContainer.insertBefore(obj.headers[o], obj.headers[d]); obj.colgroupContainer.insertBefore(obj.cols[o].colElement, obj.cols[d].colElement); for (let j = 0; j < obj.rows.length; j++) { obj.rows[j].element.insertBefore(obj.records[j][o].element, obj.records[j][d].element); } } else { obj.headerContainer.insertBefore(obj.headers[o], obj.headers[d].nextSibling); obj.colgroupContainer.insertBefore(obj.cols[o].colElement, obj.cols[d].colElement.nextSibling); for (let j = 0; j < obj.rows.length; j++) { obj.rows[j].element.insertBefore(obj.records[j][o].element, obj.records[j][d].element.nextSibling); } } obj.options.columns.splice(d, 0, obj.options.columns.splice(o, 1)[0]); obj.headers.splice(d, 0, obj.headers.splice(o, 1)[0]); obj.cols.splice(d, 0, obj.cols.splice(o, 1)[0]); const firstAffectedIndex = Math.min(o, d); const lastAffectedIndex = Math.max(o, d); for (let j = 0; j < obj.rows.length; j++) { obj.options.data[j].splice(d, 0, obj.options.data[j].splice(o, 1)[0]); obj.records[j].splice(d, 0, obj.records[j].splice(o, 1)[0]); } for (let i = firstAffectedIndex; i <= lastAffectedIndex; i++) { obj.cols[i].x = i; } for (let j = 0; j < obj.records.length; j++) { for (let i = firstAffectedIndex; i <= lastAffectedIndex; i++) { obj.records[j][i].x = i; } } // Update footers position if (obj.options.footers) { for (let j = 0; j < obj.options.footers.length; j++) { obj.options.footers[j].splice(d, 0, obj.options.footers[j].splice(o, 1)[0]); } } // Keeping history of changes setHistory.call(obj, { action: 'moveColumn', oldValue: o, newValue: d, }); // Update table references updateTableReferences.call(obj); // Events dispatch.call(obj, 'onmovecolumn', obj, o, d, 1); }; /** * Delete a column by number * * @param integer columnNumber - reference column to be excluded * @param integer numOfColumns - number of columns to be excluded from the reference column * @return void */ export const deleteColumn = function (columnNumber, numOfColumns) { const obj = this; // Global Configuration if (obj.options.allowDeleteColumn != false) { if (obj.headers.length > 1) { // Delete column definitions if (columnNumber == undefined) { const number = obj.getSelectedColumns(true); if (!number.length) { // Remove last column columnNumber = obj.headers.length - 1; numOfColumns = 1; } else { // Remove selected columnNumber = parseInt(number[0]); numOfColumns = parseInt(number.length); } } // Lasat column const lastColumn = obj.options.data[0].length - 1; if (columnNumber == undefined || columnNumber > lastColumn || columnNumber < 0) { columnNumber = lastColumn; } // Minimum of columns to be delete is 1 if (!numOfColumns) { numOfColumns = 1; } // Can't delete more than the limit of the table if (numOfColumns > obj.options.data[0].length - columnNumber) { numOfColumns = obj.options.data[0].length - columnNumber; } const removedColumns = []; for (let i = 0; i < numOfColumns; i++) { removedColumns.push(i + columnNumber); } // onbeforedeletecolumn if (dispatch.call(obj, 'onbeforedeletecolumn', obj, removedColumns) === false) { return false; } // Can't remove the last column if (parseInt(columnNumber) > -1) { // Merged cells let mergeExists = false; if (obj.options.mergeCells && Object.keys(obj.options.mergeCells).length > 0) { for (let col = columnNumber; col < columnNumber + numOfColumns; col++) { if (isColMerged.call(obj, col, null).length) { mergeExists = true; } } } if (mergeExists) { if (!confirm(jSuites.translate('This action will destroy any existing merged cells. Are you sure?'))) { return false; } else { obj.destroyMerge(); } } // Delete the column properties const columns = obj.options.columns ? obj.options.columns.splice(columnNumber, numOfColumns) : undefined; for (let col = columnNumber; col < columnNumber + numOfColumns; col++) { obj.cols[col].colElement.className = ''; obj.headers[col].className = ''; obj.cols[col].colElement.parentNode.removeChild(obj.cols[col].colElement); obj.headers[col].parentNode.removeChild(obj.headers[col]); } const historyHeaders = obj.headers.splice(columnNumber, numOfColumns); const historyColgroup = obj.cols.splice(columnNumber, numOfColumns); const historyRecords = []; const historyData = []; const historyFooters = []; for (let row = 0; row < obj.options.data.length; row++) { for (let col = columnNumber; col < columnNumber + numOfColumns; col++) { obj.records[row][col].element.className = ''; obj.records[row][col].element.parentNode.removeChild(obj.records[row][col].element); } } // Delete headers for (let row = 0; row < obj.options.data.length; row++) { // History historyData[row] = obj.options.data[row].splice(columnNumber, numOfColumns); historyRecords[row] = obj.records[row].splice(columnNumber, numOfColumns); } for (let i = columnNumber; i < obj.cols.length; i++) { obj.cols[i].x = i; } for (let j = 0; j < obj.records.length; j++) { for (let i = columnNumber; i < obj.records[j].length; i++) { obj.records[j][i].x = i; } } // Delete footers if (obj.options.footers) { for (let row = 0; row < obj.options.footers.length; row++) { historyFooters[row] = obj.options.footers[row].splice(columnNumber, numOfColumns); } } // Remove selection conditionalSelectionUpdate.call(obj, 0, columnNumber, columnNumber + numOfColumns - 1); // Adjust nested headers if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0 && obj.options.nestedHeaders[0] && obj.options.nestedHeaders[0][0]) { for (let j = 0; j < obj.options.nestedHeaders.length; j++) { const colspan = parseInt(obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length - 1].colspan) - numOfColumns; obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length - 1].colspan = colspan; obj.thead.children[j].children[obj.thead.children[j].children.length - 1].setAttribute('colspan', colspan); } } // Keeping history of changes setHistory.call(obj, { action: 'deleteColumn', columnNumber: columnNumber, numOfColumns: numOfColumns, insertBefore: 1, columns: columns, headers: historyHeaders, cols: historyColgroup, records: historyRecords, footers: historyFooters, data: historyData, }); // Update table references updateTableReferences.call(obj); // Delete dispatch.call(obj, 'ondeletecolumn', obj, removedColumns); } } else { console.error('Jspreadsheet: It is not possible to delete the last column'); } } }; /** * Get the column width * * @param int column column number (first column is: 0) * @return int current width */ export const getWidth = function (column) { const obj = this; let data; if (typeof column === 'undefined') { // Get all headers data = []; for (let i = 0; i < obj.headers.length; i++) { data.push((obj.options.columns && obj.options.columns[i] && obj.options.columns[i].width) || obj.options.defaultColWidth || 100); } } else { data = parseInt(obj.cols[column].colElement.getAttribute('width')); } return data; }; /** * Set the column width * * @param int column number (first column is: 0) * @param int new column width * @param int old column width */ export const setWidth = function (column, width, oldWidth) { const obj = this; if (width) { if (Array.isArray(column)) { // Oldwidth if (!oldWidth) { oldWidth = []; } // Set width for (let i = 0; i < column.length; i++) { if (!oldWidth[i]) { oldWidth[i] = parseInt(obj.cols[column[i]].colElement.getAttribute('width')); } const w = Array.isArray(width) && width[i] ? width[i] : width; obj.cols[column[i]].colElement.setAttribute('width', w); if (!obj.options.columns) { obj.options.columns = []; } if (!obj.options.columns[column[i]]) { obj.options.columns[column[i]] = {}; } obj.options.columns[column[i]].width = w; } } else { // Oldwidth if (!oldWidth) { oldWidth = parseInt(obj.cols[column].colElement.getAttribute('width')); } // Set width obj.cols[column].colElement.setAttribute('width', width); if (!obj.options.columns) { obj.options.columns = []; } if (!obj.options.columns[column]) { obj.options.columns[column] = {}; } obj.options.columns[column].width = width; } // Keeping history of changes setHistory.call(obj, { action: 'setWidth', column: column, oldValue: oldWidth, newValue: width, }); // On resize column dispatch.call(obj, 'onresizecolumn', obj, column, width, oldWidth); // Update corner position updateCornerPosition.call(obj); } }; /** * Show column */ export const showColumn = function (colNumber) { const obj = this; if (!Array.isArray(colNumber)) { colNumber = [colNumber]; } for (let i = 0; i < colNumber.length; i++) { const columnIndex = colNumber[i]; obj.headers[columnIndex].style.display = ''; obj.cols[columnIndex].colElement.style.display = ''; if (obj.filter && obj.filter.children.length > columnIndex + 1) { obj.filter.children[columnIndex + 1].style.display = ''; } for (let j = 0; j < obj.options.data.length; j++) { obj.records[j][columnIndex].element.style.display = ''; } } // Update footers if (obj.options.footers) { setFooter.call(obj); } obj.resetSelection(); }; /** * Hide column */ export const hideColumn = function (colNumber) { const obj = this; if (!Array.isArray(colNumber)) { colNumber = [colNumber]; } for (let i = 0; i < colNumber.length; i++) { const columnIndex = colNumber[i]; obj.headers[columnIndex].style.display = 'none'; obj.cols[columnIndex].colElement.style.display = 'none'; if (obj.filter && obj.filter.children.length > columnIndex + 1) { obj.filter.children[columnIndex + 1].style.display = 'none'; } for (let j = 0; j < obj.options.data.length; j++) { obj.records[j][columnIndex].element.style.display = 'none'; } } // Update footers if (obj.options.footers) { setFooter.call(obj); } obj.resetSelection(); }; /** * Get a column data by columnNumber */ export const getColumnData = function (columnNumber, processed) { const obj = this; const dataset = []; // Go through the rows to get the data for (let j = 0; j < obj.options.data.length; j++) { if (processed) { dataset.push(obj.records[j][columnNumber].element.innerHTML); } else { dataset.push(obj.options.data[j][columnNumber]); } } return dataset; }; /** * Set a column data by colNumber */ export const setColumnData = function (colNumber, data, force) { const obj = this; for (let j = 0; j < obj.rows.length; j++) { // Update cell const columnName = getColumnNameFromId([colNumber, j]); // Set value if (data[j] != null) { obj.setValue(columnName, data[j], force); } } }; ================================================ FILE: src/utils/comments.js ================================================ import dispatch from './dispatch.js'; import { getCoordsFromCellName } from './helpers.js'; import { setHistory } from './history.js'; import { getColumnNameFromId, getIdFromColumnName } from './internalHelpers.js'; /** * Get cell comments, null cell for all */ export const getComments = function (cell) { const obj = this; if (cell) { if (typeof cell !== 'string') { return getComments.call(obj); } cell = getIdFromColumnName(cell, true); return obj.records[cell[1]][cell[0]].element.getAttribute('title') || ''; } else { const data = {}; for (let j = 0; j < obj.options.data.length; j++) { for (let i = 0; i < obj.options.columns.length; i++) { const comments = obj.records[j][i].element.getAttribute('title'); if (comments) { const cell = getColumnNameFromId([i, j]); data[cell] = comments; } } } return data; } }; /** * Set cell comments */ export const setComments = function (cellId, comments) { const obj = this; let commentsObj; if (typeof cellId == 'string') { commentsObj = { [cellId]: comments }; } else { commentsObj = cellId; } const oldValue = {}; Object.entries(commentsObj).forEach(function ([cellName, comment]) { const cellCoords = getCoordsFromCellName(cellName); // Keep old value oldValue[cellName] = obj.records[cellCoords[1]][cellCoords[0]].element.getAttribute('title'); // Set new values obj.records[cellCoords[1]][cellCoords[0]].element.setAttribute('title', comment ? comment : ''); // Remove class if there is no comment if (comment) { obj.records[cellCoords[1]][cellCoords[0]].element.classList.add('jss_comments'); if (!obj.options.comments) { obj.options.comments = {}; } obj.options.comments[cellName] = comment; } else { obj.records[cellCoords[1]][cellCoords[0]].element.classList.remove('jss_comments'); if (obj.options.comments && obj.options.comments[cellName]) { delete obj.options.comments[cellName]; } } }); // Save history setHistory.call(obj, { action: 'setComments', newValue: commentsObj, oldValue: oldValue, }); // Set comments dispatch.call(obj, 'oncomments', obj, commentsObj, oldValue); }; ================================================ FILE: src/utils/config.js ================================================ /** * Get table config information */ export const getWorksheetConfig = function () { const obj = this; return obj.options; }; export const getSpreadsheetConfig = function () { const spreadsheet = this; return spreadsheet.config; }; export const setConfig = function (config, spreadsheetLevel) { const obj = this; const keys = Object.keys(config); let spreadsheet; if (!obj.parent) { spreadsheetLevel = true; spreadsheet = obj; } else { spreadsheet = obj.parent; } keys.forEach(function (key) { if (spreadsheetLevel) { spreadsheet.config[key] = config[key]; if (key === 'toolbar') { if (config[key] === true) { spreadsheet.showToolbar(); } else if (config[key] === false) { spreadsheet.hideToolbar(); } } } else { obj.options[key] = config[key]; } }); }; ================================================ FILE: src/utils/copyPaste.js ================================================ import jSuites from 'jsuites'; import { parseCSV } from './helpers.js'; import dispatch from './dispatch.js'; import { setHistory } from './history.js'; import { updateCell, updateFormulaChain, updateTable, updateTableReferences } from './internal.js'; import { downGet, rightGet } from './keys.js'; import { hash, removeCopyingSelection, updateSelectionFromCoords } from './selection.js'; import { getColumnNameFromId } from './internalHelpers.js'; /** * Copy method * * @param bool highlighted - Get only highlighted cells * @param delimiter - \t default to keep compatibility with excel * @return string value */ export const copy = function (highlighted, delimiter, returnData, includeHeaders, download, isCut, processed) { const obj = this; if (!delimiter) { delimiter = '\t'; } const div = new RegExp(delimiter, 'ig'); // Controls const header = []; let col = []; let colLabel = []; const row = []; const rowLabel = []; const x = obj.options.data[0].length; const y = obj.options.data.length; let tmp = ''; let copyHeader = false; let headers = ''; let nestedHeaders = ''; let numOfCols = 0; let numOfRows = 0; // Partial copy let copyX = 0; let copyY = 0; let isPartialCopy = true; // Go through the columns to get the data for (let j = 0; j < y; j++) { for (let i = 0; i < x; i++) { // If cell is highlighted if (!highlighted || obj.records[j][i].element.classList.contains('highlight')) { if (copyX <= i) { copyX = i; } if (copyY <= j) { copyY = j; } } } } if (x === copyX + 1 && y === copyY + 1) { isPartialCopy = false; } if (download && (obj.parent.config.includeHeadersOnDownload == true || includeHeaders)) { // Nested headers if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0) { tmp = obj.options.nestedHeaders; for (let j = 0; j < tmp.length; j++) { const nested = []; for (let i = 0; i < tmp[j].length; i++) { const colspan = parseInt(tmp[j][i].colspan); nested.push(tmp[j][i].title); for (let c = 0; c < colspan - 1; c++) { nested.push(''); } } nestedHeaders += nested.join(delimiter) + '\r\n'; } } copyHeader = true; } // Reset container obj.style = []; // Go through the columns to get the data for (let j = 0; j < y; j++) { col = []; colLabel = []; for (let i = 0; i < x; i++) { // If cell is highlighted if (!highlighted || obj.records[j][i].element.classList.contains('highlight')) { if (copyHeader == true) { header.push(obj.headers[i].textContent); } // Values let value = obj.options.data[j][i]; if (value.match && (value.match(div) || value.match(/,/g) || value.match(/\n/) || value.match(/"/))) { value = value.replace(new RegExp('"', 'g'), '""'); value = '"' + value + '"'; } col.push(value); // Labels let label; if (obj.options.columns && obj.options.columns[i] && (obj.options.columns[i].type == 'checkbox' || obj.options.columns[i].type == 'radio')) { label = value; } else { label = obj.records[j][i].element.innerHTML; if (label.match && (label.match(div) || label.match(/,/g) || label.match(/\n/) || label.match(/"/))) { // Scape double quotes label = label.replace(new RegExp('"', 'g'), '""'); label = '"' + label + '"'; } } colLabel.push(label); // Get style tmp = obj.records[j][i].element.getAttribute('style'); tmp = tmp.replace('display: none;', ''); obj.style.push(tmp ? tmp : ''); } } if (col.length) { if (copyHeader) { numOfCols = col.length; row.push(header.join(delimiter)); } row.push(col.join(delimiter)); } if (colLabel.length) { numOfRows++; if (copyHeader) { rowLabel.push(header.join(delimiter)); copyHeader = false; } rowLabel.push(colLabel.join(delimiter)); } } if (x == numOfCols && y == numOfRows) { headers = nestedHeaders; } // Final string const str = headers + row.join('\r\n'); let strLabel = headers + rowLabel.join('\r\n'); // Create a hidden textarea to copy the values if (!returnData) { // Paste event const selectedRange = [ Math.min(obj.selectedCell[0], obj.selectedCell[2]), Math.min(obj.selectedCell[1], obj.selectedCell[3]), Math.max(obj.selectedCell[0], obj.selectedCell[2]), Math.max(obj.selectedCell[1], obj.selectedCell[3]), ]; const result = dispatch.call(obj, 'oncopy', obj, selectedRange, strLabel, isCut); if (result) { strLabel = result; } else if (result === false) { return false; } obj.textarea.value = strLabel; obj.textarea.select(); document.execCommand('copy'); } // Keep data if (processed == true) { obj.data = strLabel; } else { obj.data = str; } // Keep non visible information obj.hashString = hash.call(obj, obj.data); // Any exiting border should go if (!returnData) { removeCopyingSelection.call(obj); // Border if (obj.highlighted) { for (let i = 0; i < obj.highlighted.length; i++) { obj.highlighted[i].element.classList.add('copying'); if (obj.highlighted[i].element.classList.contains('highlight-left')) { obj.highlighted[i].element.classList.add('copying-left'); } if (obj.highlighted[i].element.classList.contains('highlight-right')) { obj.highlighted[i].element.classList.add('copying-right'); } if (obj.highlighted[i].element.classList.contains('highlight-top')) { obj.highlighted[i].element.classList.add('copying-top'); } if (obj.highlighted[i].element.classList.contains('highlight-bottom')) { obj.highlighted[i].element.classList.add('copying-bottom'); } } } } return obj.data; }; /** * Jspreadsheet paste method * * @param x target column * @param y target row * @param data paste data. if data hash is the same as the copied data, apply style from copied cells * @return string value */ export const paste = function (x, y, data) { const obj = this; // Controls const dataHash = hash(data); let style = dataHash == obj.hashString ? obj.style : null; // Depending on the behavior if (dataHash == obj.hashString) { data = obj.data; } // Split new line data = parseCSV(data, '\t'); const ex = obj.selectedCell[2]; const ey = obj.selectedCell[3]; const w = ex - x + 1; const h = ey - y + 1; // Modify data to allow wor extending paste range in multiples of input range const srcW = data[0].length; if ((w > 1) & Number.isInteger(w / srcW)) { const repeats = w / srcW; if (style) { const newStyle = []; for (let i = 0; i < style.length; i += srcW) { const chunk = style.slice(i, i + srcW); for (let j = 0; j < repeats; j++) { newStyle.push(...chunk); } } style = newStyle; } const arrayB = data.map(function (row, i) { const arrayC = Array.apply(null, { length: repeats * row.length }).map(function (e, i) { return row[i % row.length]; }); return arrayC; }); data = arrayB; } const srcH = data.length; if ((h > 1) & Number.isInteger(h / srcH)) { const repeats = h / srcH; if (style) { const newStyle = []; for (let j = 0; j < repeats; j++) { newStyle.push(...style); } style = newStyle; } const arrayB = Array.apply(null, { length: repeats * srcH }).map(function (e, i) { return data[i % srcH]; }); data = arrayB; } // Paste filter const ret = dispatch.call( obj, 'onbeforepaste', obj, data.map(function (row) { return row.map(function (item) { return { value: item }; }); }), x, y ); if (ret === false) { return false; } else if (ret) { data = ret; } if (x != null && y != null && data) { // Records let i = 0; let j = 0; const records = []; const newStyle = {}; const oldStyle = {}; let styleIndex = 0; // Index let colIndex = parseInt(x); let rowIndex = parseInt(y); let row = null; const hiddenColCount = obj.headers.slice(colIndex).filter((x) => x.style.display === 'none').length; const expandedColCount = colIndex + hiddenColCount + data[0].length; const currentColCount = obj.headers.length; if (expandedColCount > currentColCount) { obj.skipUpdateTableReferences = true; obj.insertColumn(expandedColCount - currentColCount); } const hiddenRowCount = obj.rows.slice(rowIndex).filter((x) => x.element.style.display === 'none').length; const expandedRowCount = rowIndex + hiddenRowCount + data.length; const currentRowCount = obj.rows.length; if (expandedRowCount > currentRowCount) { obj.skipUpdateTableReferences = true; obj.insertRow(expandedRowCount - currentRowCount); } if (obj.skipUpdateTableReferences) { obj.skipUpdateTableReferences = false; updateTableReferences.call(obj); } // Go through the columns to get the data while ((row = data[j])) { i = 0; colIndex = parseInt(x); while (row[i] != null) { let value = row[i]; if (obj.options.columns && obj.options.columns[i] && obj.options.columns[i].type == 'calendar') { value = jSuites.calendar.extractDateFromString( value, (obj.options.columns[i].options && obj.options.columns[i].options.format) || 'YYYY-MM-DD' ); } // Update and keep history const record = updateCell.call(obj, colIndex, rowIndex, value); // Keep history records.push(record); // Update all formulas in the chain updateFormulaChain.call(obj, colIndex, rowIndex, records); // Style if (style && style[styleIndex]) { const columnName = getColumnNameFromId([colIndex, rowIndex]); newStyle[columnName] = style[styleIndex]; oldStyle[columnName] = obj.getStyle(columnName); obj.records[rowIndex][colIndex].element.setAttribute('style', style[styleIndex]); styleIndex++; } i++; if (row[i] != null) { if (colIndex >= obj.headers.length - 1) { // If the pasted column is out of range, create it if possible if (obj.options.allowInsertColumn != false) { obj.insertColumn(); // Otherwise skip the pasted data that overflows } else { break; } } colIndex = rightGet.call(obj, colIndex, rowIndex); } } j++; if (data[j]) { if (rowIndex >= obj.rows.length - 1) { // If the pasted row is out of range, create it if possible if (obj.options.allowInsertRow != false) { obj.insertRow(); // Otherwise skip the pasted data that overflows } else { break; } } rowIndex = downGet.call(obj, x, rowIndex); } } // Select the new cells updateSelectionFromCoords.call(obj, x, y, colIndex, rowIndex); // Update history setHistory.call(obj, { action: 'setValue', records: records, selection: obj.selectedCell, newStyle: newStyle, oldStyle: oldStyle, }); // Update table updateTable.call(obj); // Paste event const eventRecords = []; for (let j = 0; j < data.length; j++) { for (let i = 0; i < data[j].length; i++) { eventRecords.push({ x: i + x, y: j + y, value: data[j][i], }); } } dispatch.call(obj, 'onpaste', obj, eventRecords); // On after changes const onafterchangesRecords = records.map(function (record) { return { x: record.x, y: record.y, value: record.value, oldValue: record.oldValue, }; }); dispatch.call(obj, 'onafterchanges', obj, onafterchangesRecords); } removeCopyingSelection(); }; ================================================ FILE: src/utils/data.js ================================================ import { createRow } from './rows.js'; import { updateCell, updateFormulaChain, updateTable } from './internal.js'; import { getIdFromColumnName } from './internalHelpers.js'; import dispatch from './dispatch.js'; import { setHistory } from './history.js'; import { updatePagination } from './pagination.js'; import { setMerge } from './merges.js'; import { getCoordsFromRange } from './helpers.js'; export const setData = function (data) { const obj = this; // Update data if (data) { obj.options.data = data; } // Data if (!obj.options.data) { obj.options.data = []; } // Prepare data if (obj.options.data && obj.options.data[0]) { if (!Array.isArray(obj.options.data[0])) { data = []; for (let j = 0; j < obj.options.data.length; j++) { const row = []; for (let i = 0; i < obj.options.columns.length; i++) { row[i] = obj.options.data[j][obj.options.columns[i].name]; } data.push(row); } obj.options.data = data; } } // Adjust minimal dimensions let j = 0; let i = 0; const size_i = (obj.options.columns && obj.options.columns.length) || 0; const size_j = obj.options.data.length; const min_i = obj.options.minDimensions[0]; const min_j = obj.options.minDimensions[1]; const max_i = min_i > size_i ? min_i : size_i; const max_j = min_j > size_j ? min_j : size_j; for (j = 0; j < max_j; j++) { for (i = 0; i < max_i; i++) { if (obj.options.data[j] == undefined) { obj.options.data[j] = []; } if (obj.options.data[j][i] == undefined) { obj.options.data[j][i] = ''; } } } // Reset containers obj.rows = []; obj.results = null; obj.records = []; obj.history = []; // Reset internal controllers obj.historyIndex = -1; // Reset data obj.tbody.innerHTML = ''; let startNumber; let finalNumber; // Lazy loading if (obj.options.lazyLoading == true) { // Load only 100 records startNumber = 0; finalNumber = obj.options.data.length < 100 ? obj.options.data.length : 100; if (obj.options.pagination) { obj.options.pagination = false; console.error('Jspreadsheet: Pagination will be disable due the lazyLoading'); } } else if (obj.options.pagination) { // Pagination if (!obj.pageNumber) { obj.pageNumber = 0; } var quantityPerPage = obj.options.pagination; startNumber = obj.options.pagination * obj.pageNumber; finalNumber = obj.options.pagination * obj.pageNumber + obj.options.pagination; if (obj.options.data.length < finalNumber) { finalNumber = obj.options.data.length; } } else { startNumber = 0; finalNumber = obj.options.data.length; } // Append nodes to the HTML for (j = 0; j < obj.options.data.length; j++) { // Create row const row = createRow.call(obj, j, obj.options.data[j]); // Append line to the table if (j >= startNumber && j < finalNumber) { obj.tbody.appendChild(row.element); } } if (obj.options.lazyLoading == true) { // Do not create pagination with lazyloading activated } else if (obj.options.pagination) { updatePagination.call(obj); } // Merge cells if (obj.options.mergeCells) { const keys = Object.keys(obj.options.mergeCells); for (let i = 0; i < keys.length; i++) { const num = obj.options.mergeCells[keys[i]]; setMerge.call(obj, keys[i], num[0], num[1], 1); } } // Updata table with custom configurations if applicable updateTable.call(obj); }; /** * Get the value from a cell * * @param object cell * @return string value */ export const getValue = function (cell, processedValue) { const obj = this; let x; let y; if (typeof cell !== 'string') { return null; } cell = getIdFromColumnName(cell, true); x = cell[0]; y = cell[1]; let value = null; if (x != null && y != null) { if (obj.records[y] && obj.records[y][x] && processedValue) { value = obj.records[y][x].element.innerHTML; } else { if (obj.options.data[y] && obj.options.data[y][x] != 'undefined') { value = obj.options.data[y][x]; } } } return value; }; /** * Get the value from a coords * * @param int x * @param int y * @return string value */ export const getValueFromCoords = function (x, y, processedValue) { const obj = this; let value = null; if (x != null && y != null) { if (obj.records[y] && obj.records[y][x] && processedValue) { value = obj.records[y][x].element.innerHTML; } else { if (obj.options.data[y] && obj.options.data[y][x] != 'undefined') { value = obj.options.data[y][x]; } } } return value; }; /** * Set a cell value * * @param mixed cell destination cell * @param string value value * @return void */ export const setValue = function (cell, value, force) { const obj = this; const records = []; if (typeof cell == 'string') { const columnId = getIdFromColumnName(cell, true); const x = columnId[0]; const y = columnId[1]; // Update cell records.push(updateCell.call(obj, x, y, value, force)); // Update all formulas in the chain updateFormulaChain.call(obj, x, y, records); } else { let x = null; let y = null; if (cell && cell.getAttribute) { x = cell.getAttribute('data-x'); y = cell.getAttribute('data-y'); } // Update cell if (x != null && y != null) { records.push(updateCell.call(obj, x, y, value, force)); // Update all formulas in the chain updateFormulaChain.call(obj, x, y, records); } else { const keys = Object.keys(cell); if (keys.length > 0) { for (let i = 0; i < keys.length; i++) { let x, y; if (typeof cell[i] == 'string') { const columnId = getIdFromColumnName(cell[i], true); x = columnId[0]; y = columnId[1]; } else { if (cell[i].x != null && cell[i].y != null) { x = cell[i].x; y = cell[i].y; // Flexible setup if (cell[i].value != null) { value = cell[i].value; } } else { x = cell[i].getAttribute('data-x'); y = cell[i].getAttribute('data-y'); } } // Update cell if (x != null && y != null) { records.push(updateCell.call(obj, x, y, value, force)); // Update all formulas in the chain updateFormulaChain.call(obj, x, y, records); } } } } } // Update history setHistory.call(obj, { action: 'setValue', records: records, selection: obj.selectedCell, }); // Update table with custom configurations if applicable updateTable.call(obj); // On after changes const onafterchangesRecords = records.map(function (record) { return { x: record.x, y: record.y, value: record.value, oldValue: record.oldValue, }; }); dispatch.call(obj, 'onafterchanges', obj, onafterchangesRecords); }; /** * Set a cell value based on coordinates * * @param int x destination cell * @param int y destination cell * @param string value * @return void */ export const setValueFromCoords = function (x, y, value, force) { const obj = this; const records = []; records.push(updateCell.call(obj, x, y, value, force)); // Update all formulas in the chain updateFormulaChain.call(obj, x, y, records); // Update history setHistory.call(obj, { action: 'setValue', records: records, selection: obj.selectedCell, }); // Update table with custom configurations if applicable updateTable.call(obj); // On after changes const onafterchangesRecords = records.map(function (record) { return { x: record.x, y: record.y, value: record.value, oldValue: record.oldValue, }; }); dispatch.call(obj, 'onafterchanges', obj, onafterchangesRecords); }; /** * Get the whole table data * * @param bool get highlighted cells only * @return array data */ export const getData = function (highlighted, processed, delimiter, asJson) { const obj = this; // Control vars const dataset = []; let px = 0; let py = 0; // Column and row length const x = Math.max( ...obj.options.data.map(function (row) { return row.length; }) ); const y = obj.options.data.length; // Go through the columns to get the data for (let j = 0; j < y; j++) { px = 0; for (let i = 0; i < x; i++) { // Cell selected or fullset if (!highlighted || obj.records[j][i].element.classList.contains('highlight')) { // Get value if (!dataset[py]) { dataset[py] = []; } if (processed) { dataset[py][px] = obj.records[j][i].element.innerHTML; } else { dataset[py][px] = obj.options.data[j][i]; } px++; } } if (px > 0) { py++; } } if (delimiter) { return ( dataset .map(function (row) { return row.join(delimiter); }) .join('\r\n') + '\r\n' ); } if (asJson) { return dataset.map(function (row) { const resultRow = {}; row.forEach(function (item, index) { resultRow[index] = item; }); return resultRow; }); } return dataset; }; export const getDataFromRange = function (range, processed) { const obj = this; const coords = getCoordsFromRange(range); const dataset = []; for (let y = coords[1]; y <= coords[3]; y++) { dataset.push([]); for (let x = coords[0]; x <= coords[2]; x++) { if (processed) { dataset[dataset.length - 1].push(obj.records[y][x].element.innerHTML); } else { dataset[dataset.length - 1].push(obj.options.data[y][x]); } } } return dataset; }; ================================================ FILE: src/utils/dispatch.js ================================================ import jSuites from 'jsuites'; /** * Prepare JSON in the correct format */ const prepareJson = function (data) { const obj = this; const rows = []; for (let i = 0; i < data.length; i++) { const x = data[i].x; const y = data[i].y; const k = obj.options.columns[x].name ? obj.options.columns[x].name : x; // Create row if (!rows[y]) { rows[y] = { row: y, data: {}, }; } rows[y].data[k] = data[i].value; } // Filter rows return rows.filter(function (el) { return el != null; }); }; /** * Post json to a remote server */ const save = function (url, data) { const obj = this; // Parse anything in the data before sending to the server const ret = dispatch.call(obj.parent, 'onbeforesave', obj.parent, obj, data); if (ret) { data = ret; } else { if (ret === false) { return false; } } // Remove update jSuites.ajax({ url: url, method: 'POST', dataType: 'json', data: { data: JSON.stringify(data) }, success: function (result) { // Event dispatch.call(obj, 'onsave', obj.parent, obj, data); }, }); }; /** * Trigger events */ const dispatch = function (event) { const obj = this; let ret = null; let spreadsheet = obj.parent ? obj.parent : obj; // Dispatch events if (!spreadsheet.ignoreEvents) { // Call global event if (typeof spreadsheet.config.onevent == 'function') { ret = spreadsheet.config.onevent.apply(this, arguments); } // Call specific events if (typeof spreadsheet.config[event] == 'function') { ret = spreadsheet.config[event].apply(this, Array.prototype.slice.call(arguments, 1)); } if (typeof spreadsheet.plugins === 'object') { const pluginKeys = Object.keys(spreadsheet.plugins); for (let pluginKeyIndex = 0; pluginKeyIndex < pluginKeys.length; pluginKeyIndex++) { const key = pluginKeys[pluginKeyIndex]; const plugin = spreadsheet.plugins[key]; if (typeof plugin.onevent === 'function') { ret = plugin.onevent.apply(this, arguments); } } } } if (event == 'onafterchanges') { const scope = arguments; if (typeof spreadsheet.plugins === 'object') { Object.entries(spreadsheet.plugins).forEach(function ([, plugin]) { if (typeof plugin.persistence === 'function') { plugin.persistence(obj, 'setValue', { data: scope[2] }); } }); } if (obj.options.persistence) { const url = obj.options.persistence == true ? obj.options.url : obj.options.persistence; const data = prepareJson.call(obj, arguments[2]); save.call(obj, url, data); } } return ret; }; export default dispatch; ================================================ FILE: src/utils/download.js ================================================ import { copy } from './copyPaste.js'; /** * Download CSV table * * @return null */ export const download = function (includeHeaders, processed) { const obj = this; if (obj.parent.config.allowExport == false) { console.error('Export not allowed'); } else { // Data let data = ''; // Get data data += copy.call(obj, false, obj.options.csvDelimiter, true, includeHeaders, true, undefined, processed); // Download element const blob = new Blob(['\uFEFF' + data], { type: 'text/csv;charset=utf-8;' }); // IE Compatibility if (window.navigator && window.navigator.msSaveOrOpenBlob) { window.navigator.msSaveOrOpenBlob(blob, (obj.options.csvFileName || obj.options.worksheetName) + '.csv'); } else { // Download element const pom = document.createElement('a'); pom.setAttribute('target', '_top'); const url = URL.createObjectURL(blob); pom.href = url; pom.setAttribute('download', (obj.options.csvFileName || obj.options.worksheetName) + '.csv'); document.body.appendChild(pom); pom.click(); pom.parentNode.removeChild(pom); } } }; ================================================ FILE: src/utils/editor.js ================================================ import jSuites from 'jsuites'; import dispatch from './dispatch.js'; import { getMask, isFormula, updateCell } from './internal.js'; import { setHistory } from './history.js'; /** * Open the editor * * @param object cell * @return void */ export const openEditor = function (cell, empty, e) { const obj = this; // Get cell position const y = cell.getAttribute('data-y'); const x = cell.getAttribute('data-x'); // On edition start dispatch.call(obj, 'oneditionstart', obj, cell, parseInt(x), parseInt(y)); // Overflow if (x > 0) { obj.records[y][x - 1].element.style.overflow = 'hidden'; } // Create editor const createEditor = function (type) { // Cell information const info = cell.getBoundingClientRect(); // Create dropdown const editor = document.createElement(type); editor.style.width = info.width + 'px'; editor.style.height = info.height - 2 + 'px'; editor.style.minHeight = info.height - 2 + 'px'; // Edit cell cell.classList.add('editor'); cell.innerHTML = ''; cell.appendChild(editor); return editor; }; // Readonly if (cell.classList.contains('readonly') == true) { // Do nothing } else { // Holder obj.edition = [obj.records[y][x].element, obj.records[y][x].element.innerHTML, x, y]; // If there is a custom editor for it if (obj.options.columns && obj.options.columns[x] && typeof obj.options.columns[x].type === 'object') { // Custom editors obj.options.columns[x].type.openEditor(cell, obj.options.data[y][x], parseInt(x), parseInt(y), obj, obj.options.columns[x], e); // On edition start dispatch.call(obj, 'oncreateeditor', obj, cell, parseInt(x), parseInt(y), null, obj.options.columns[x]); } else { // Native functions if (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].type == 'hidden') { // Do nothing } else if (obj.options.columns && obj.options.columns[x] && (obj.options.columns[x].type == 'checkbox' || obj.options.columns[x].type == 'radio')) { // Get value const value = cell.children[0].checked ? false : true; // Toogle value obj.setValue(cell, value); // Do not keep edition open obj.edition = null; } else if (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].type == 'dropdown') { // Get current value let value = obj.options.data[y][x]; if (obj.options.columns[x].multiple && !Array.isArray(value)) { value = value.split(';'); } // Create dropdown let source; if (typeof obj.options.columns[x].filter == 'function') { source = obj.options.columns[x].filter(obj.element, cell, x, y, obj.options.columns[x].source); } else { source = obj.options.columns[x].source; } // Do not change the original source const data = []; if (source) { for (let j = 0; j < source.length; j++) { data.push(source[j]); } } // Create editor const editor = createEditor('div'); // On edition start dispatch.call(obj, 'oncreateeditor', obj, cell, parseInt(x), parseInt(y), null, obj.options.columns[x]); const options = { data: data, multiple: obj.options.columns[x].multiple ? true : false, autocomplete: obj.options.columns[x].autocomplete ? true : false, opened: true, value: value, width: '100%', height: editor.style.minHeight, position: obj.options.tableOverflow == true || obj.parent.config.fullscreen == true ? true : false, onclose: function () { closeEditor.call(obj, cell, true); }, }; if (obj.options.columns[x].options && obj.options.columns[x].options.type) { options.type = obj.options.columns[x].options.type; } jSuites.dropdown(editor, options); } else if (obj.options.columns && obj.options.columns[x] && (obj.options.columns[x].type == 'calendar' || obj.options.columns[x].type == 'color')) { // Value const value = obj.options.data[y][x]; // Create editor const editor = createEditor('input'); dispatch.call(obj, 'oncreateeditor', obj, cell, parseInt(x), parseInt(y), null, obj.options.columns[x]); editor.value = value; const options = obj.options.columns[x].options ? { ...obj.options.columns[x].options } : {}; if (obj.options.tableOverflow == true || obj.parent.config.fullscreen == true) { options.position = true; } options.value = obj.options.data[y][x]; options.opened = true; options.onclose = function (el, value) { closeEditor.call(obj, cell, true); }; // Current value if (obj.options.columns[x].type == 'color') { jSuites.color(editor, options); const rect = cell.getBoundingClientRect(); if (options.position) { editor.nextSibling.children[1].style.top = rect.top + rect.height + 'px'; editor.nextSibling.children[1].style.left = rect.left + 'px'; } } else { if (!options.format) { options.format = 'YYYY-MM-DD'; } jSuites.calendar(editor, options); } // Focus on editor editor.focus(); } else if (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].type == 'html') { const value = obj.options.data[y][x]; // Create editor const editor = createEditor('div'); dispatch.call(obj, 'oncreateeditor', obj, cell, parseInt(x), parseInt(y), null, obj.options.columns[x]); editor.style.position = 'relative'; const div = document.createElement('div'); div.classList.add('jss_richtext'); editor.appendChild(div); jSuites.editor(div, { focus: true, value: value, }); const rect = cell.getBoundingClientRect(); const rectContent = div.getBoundingClientRect(); if (window.innerHeight < rect.bottom + rectContent.height) { div.style.top = rect.bottom - (rectContent.height + 2) + 'px'; } else { div.style.top = rect.top + 'px'; } if (window.innerWidth < rect.left + rectContent.width) { div.style.left = rect.right - (rectContent.width + 2) + 'px'; } else { div.style.left = rect.left + 'px'; } } else if (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].type == 'image') { // Value const img = cell.children[0]; // Create editor const editor = createEditor('div'); dispatch.call(obj, 'oncreateeditor', obj, cell, parseInt(x), parseInt(y), null, obj.options.columns[x]); editor.style.position = 'relative'; const div = document.createElement('div'); div.classList.add('jclose'); if (img && img.src) { div.appendChild(img); } editor.appendChild(div); jSuites.image(div, obj.options.columns[x]); const rect = cell.getBoundingClientRect(); const rectContent = div.getBoundingClientRect(); if (window.innerHeight < rect.bottom + rectContent.height) { div.style.top = rect.top - (rectContent.height + 2) + 'px'; } else { div.style.top = rect.top + 'px'; } div.style.left = rect.left + 'px'; } else { // Value const value = empty == true ? '' : obj.options.data[y][x]; // Basic editor let editor; if ( (!obj.options.columns || !obj.options.columns[x] || obj.options.columns[x].wordWrap != false) && (obj.options.wordWrap == true || (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].wordWrap == true)) ) { editor = createEditor('textarea'); } else { editor = createEditor('input'); } dispatch.call(obj, 'oncreateeditor', obj, cell, parseInt(x), parseInt(y), null, obj.options.columns[x]); editor.focus(); editor.value = value; // Column options const options = obj.options.columns && obj.options.columns[x]; // Apply format when is not a formula if (!isFormula(value)) { if (options) { // Format const opt = getMask(options); if (opt) { // Masking if (!options.disabledMaskOnEdition) { if (options.mask) { const m = options.mask.split(';'); editor.setAttribute('data-mask', m[0]); } else if (options.locale) { editor.setAttribute('data-locale', options.locale); } } // Input opt.input = editor; // Configuration editor.mask = opt; // Do not treat the decimals jSuites.mask.render(value, opt, false); } } } editor.onblur = function () { closeEditor.call(obj, cell, true); }; editor.scrollLeft = editor.scrollWidth; } } } }; /** * Close the editor and save the information * * @param object cell * @param boolean save * @return void */ export const closeEditor = function (cell, save) { const obj = this; const x = parseInt(cell.getAttribute('data-x')); const y = parseInt(cell.getAttribute('data-y')); let value; // Get cell properties if (save == true) { // If custom editor if (obj.options.columns && obj.options.columns[x] && typeof obj.options.columns[x].type === 'object') { // Custom editor value = obj.options.columns[x].type.closeEditor(cell, save, parseInt(x), parseInt(y), obj, obj.options.columns[x]); } else { // Native functions if ( obj.options.columns && obj.options.columns[x] && (obj.options.columns[x].type == 'checkbox' || obj.options.columns[x].type == 'radio' || obj.options.columns[x].type == 'hidden') ) { // Do nothing } else if (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].type == 'dropdown') { value = cell.children[0].dropdown.close(true); } else if (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].type == 'calendar') { value = cell.children[0].calendar.close(true); } else if (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].type == 'color') { value = cell.children[0].color.close(true); } else if (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].type == 'html') { value = cell.children[0].children[0].editor.getData(); } else if (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].type == 'image') { const img = cell.children[0].children[0].children[0]; value = img && img.tagName == 'IMG' ? img.src : ''; } else if (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].type == 'numeric') { value = cell.children[0].value; if (('' + value).substr(0, 1) != '=') { if (value == '') { value = obj.options.columns[x].allowEmpty ? '' : 0; } } cell.children[0].onblur = null; } else { value = cell.children[0].value; cell.children[0].onblur = null; // Column options const options = obj.options.columns && obj.options.columns[x]; if (options) { // Format const opt = getMask(options); if (opt) { // Keep numeric in the raw data if (value !== '' && !isFormula(value) && typeof value !== 'number') { const t = jSuites.mask.extract(value, opt, true); if (t && t.value !== '') { value = t.value; } } } } } } // Ignore changes if the value is the same if (obj.options.data[y][x] == value) { cell.innerHTML = obj.edition[1]; } else { obj.setValue(cell, value); } } else { if (obj.options.columns && obj.options.columns[x] && typeof obj.options.columns[x].type === 'object') { // Custom editor obj.options.columns[x].type.closeEditor(cell, save, parseInt(x), parseInt(y), obj, obj.options.columns[x]); } else { if (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].type == 'dropdown') { cell.children[0].dropdown.close(true); } else if (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].type == 'calendar') { cell.children[0].calendar.close(true); } else if (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].type == 'color') { cell.children[0].color.close(true); } else { cell.children[0].onblur = null; } } // Restore value cell.innerHTML = obj.edition && obj.edition[1] ? obj.edition[1] : ''; } // On edition end dispatch.call(obj, 'oneditionend', obj, cell, x, y, value, save); // Remove editor class cell.classList.remove('editor'); // Finish edition obj.edition = null; }; /** * Toogle */ export const setCheckRadioValue = function () { const obj = this; const records = []; const keys = Object.keys(obj.highlighted); for (let i = 0; i < keys.length; i++) { const x = obj.highlighted[i].element.getAttribute('data-x'); const y = obj.highlighted[i].element.getAttribute('data-y'); if (obj.options.columns[x].type == 'checkbox' || obj.options.columns[x].type == 'radio') { // Update cell records.push(updateCell.call(obj, x, y, !obj.options.data[y][x])); } } if (records.length) { // Update history setHistory.call(obj, { action: 'setValue', records: records, selection: obj.selectedCell, }); // On after changes const onafterchangesRecords = records.map(function (record) { return { x: record.x, y: record.y, value: record.value, oldValue: record.oldValue, }; }); dispatch.call(obj, 'onafterchanges', obj, onafterchangesRecords); } }; ================================================ FILE: src/utils/events.js ================================================ import jSuites from 'jsuites'; import { closeEditor, openEditor, setCheckRadioValue } from './editor.js'; import libraryBase from './libraryBase.js'; import { down, first, last, left, right, up } from './keys.js'; import { isColMerged, isRowMerged } from './merges.js'; import { copyData, removeCopySelection, resetSelection, selectAll, updateCornerPosition, updateSelectionFromCoords } from './selection.js'; import { copy, paste } from './copyPaste.js'; import { openFilter } from './filter.js'; import { loadDown, loadUp } from './lazyLoading.js'; import { setWidth } from './columns.js'; import { moveRow, setHeight } from './rows.js'; import version from './version.js'; import { getCellNameFromCoords } from './helpers.js'; const getElement = function (element) { let jssSection = 0; let jssElement = 0; function path(element) { if (element.className) { if (element.classList.contains('jss_container')) { jssElement = element; } if (element.classList.contains('jss_spreadsheet')) { jssElement = element.querySelector(':scope > .jtabs-content > .jtabs-selected'); } } if (element.tagName == 'THEAD') { jssSection = 1; } else if (element.tagName == 'TBODY') { jssSection = 2; } if (element.parentNode) { if (!jssElement) { path(element.parentNode); } } } path(element); return [jssElement, jssSection]; }; const mouseUpControls = function (e) { if (libraryBase.jspreadsheet.current) { // Update cell size if (libraryBase.jspreadsheet.current.resizing) { // Columns to be updated if (libraryBase.jspreadsheet.current.resizing.column) { // New width const newWidth = parseInt( libraryBase.jspreadsheet.current.cols[libraryBase.jspreadsheet.current.resizing.column].colElement.getAttribute('width') ); // Columns const columns = libraryBase.jspreadsheet.current.getSelectedColumns(); if (columns.length > 1) { const currentWidth = []; for (let i = 0; i < columns.length; i++) { currentWidth.push(parseInt(libraryBase.jspreadsheet.current.cols[columns[i]].colElement.getAttribute('width'))); } // Previous width const index = columns.indexOf(parseInt(libraryBase.jspreadsheet.current.resizing.column)); currentWidth[index] = libraryBase.jspreadsheet.current.resizing.width; setWidth.call(libraryBase.jspreadsheet.current, columns, newWidth, currentWidth); } else { setWidth.call( libraryBase.jspreadsheet.current, parseInt(libraryBase.jspreadsheet.current.resizing.column), newWidth, libraryBase.jspreadsheet.current.resizing.width ); } // Remove border libraryBase.jspreadsheet.current.headers[libraryBase.jspreadsheet.current.resizing.column].classList.remove('resizing'); for (let j = 0; j < libraryBase.jspreadsheet.current.records.length; j++) { if (libraryBase.jspreadsheet.current.records[j][libraryBase.jspreadsheet.current.resizing.column]) { libraryBase.jspreadsheet.current.records[j][libraryBase.jspreadsheet.current.resizing.column].element.classList.remove('resizing'); } } } else { // Remove Class libraryBase.jspreadsheet.current.rows[libraryBase.jspreadsheet.current.resizing.row].element.children[0].classList.remove('resizing'); let newHeight = libraryBase.jspreadsheet.current.rows[libraryBase.jspreadsheet.current.resizing.row].element.getAttribute('height'); setHeight.call( libraryBase.jspreadsheet.current, libraryBase.jspreadsheet.current.resizing.row, newHeight, libraryBase.jspreadsheet.current.resizing.height ); // Remove border libraryBase.jspreadsheet.current.resizing.element.classList.remove('resizing'); } // Reset resizing helper libraryBase.jspreadsheet.current.resizing = null; } else if (libraryBase.jspreadsheet.current.dragging) { // Reset dragging helper if (libraryBase.jspreadsheet.current.dragging) { if (libraryBase.jspreadsheet.current.dragging.column) { // Target const columnId = e.target.getAttribute('data-x'); // Remove move style libraryBase.jspreadsheet.current.headers[libraryBase.jspreadsheet.current.dragging.column].classList.remove('dragging'); for (let j = 0; j < libraryBase.jspreadsheet.current.rows.length; j++) { if (libraryBase.jspreadsheet.current.records[j][libraryBase.jspreadsheet.current.dragging.column]) { libraryBase.jspreadsheet.current.records[j][libraryBase.jspreadsheet.current.dragging.column].element.classList.remove('dragging'); } } for (let i = 0; i < libraryBase.jspreadsheet.current.headers.length; i++) { libraryBase.jspreadsheet.current.headers[i].classList.remove('dragging-left'); libraryBase.jspreadsheet.current.headers[i].classList.remove('dragging-right'); } // Update position if (columnId) { if (libraryBase.jspreadsheet.current.dragging.column != libraryBase.jspreadsheet.current.dragging.destination) { libraryBase.jspreadsheet.current.moveColumn( libraryBase.jspreadsheet.current.dragging.column, libraryBase.jspreadsheet.current.dragging.destination ); } } } else { let position; if (libraryBase.jspreadsheet.current.dragging.element.nextSibling) { position = parseInt(libraryBase.jspreadsheet.current.dragging.element.nextSibling.getAttribute('data-y')); if (libraryBase.jspreadsheet.current.dragging.row < position) { position -= 1; } } else { position = parseInt(libraryBase.jspreadsheet.current.dragging.element.previousSibling.getAttribute('data-y')); } if (libraryBase.jspreadsheet.current.dragging.row != libraryBase.jspreadsheet.current.dragging.destination) { moveRow.call(libraryBase.jspreadsheet.current, libraryBase.jspreadsheet.current.dragging.row, position, true); } libraryBase.jspreadsheet.current.dragging.element.classList.remove('dragging'); } libraryBase.jspreadsheet.current.dragging = null; } } else { // Close any corner selection if (libraryBase.jspreadsheet.current.selectedCorner) { libraryBase.jspreadsheet.current.selectedCorner = false; // Data to be copied if (libraryBase.jspreadsheet.current.selection.length > 0) { // Copy data copyData.call( libraryBase.jspreadsheet.current, libraryBase.jspreadsheet.current.selection[0], libraryBase.jspreadsheet.current.selection[libraryBase.jspreadsheet.current.selection.length - 1] ); // Remove selection removeCopySelection.call(libraryBase.jspreadsheet.current); } } } } // Clear any time control if (libraryBase.jspreadsheet.timeControl) { clearTimeout(libraryBase.jspreadsheet.timeControl); libraryBase.jspreadsheet.timeControl = null; } // Mouse up libraryBase.jspreadsheet.isMouseAction = false; }; const mouseDownControls = function (e) { e = e || window.event; let mouseButton; if (e.buttons) { mouseButton = e.buttons; } else if (e.button) { mouseButton = e.button; } else { mouseButton = e.which; } // Get elements const jssTable = getElement(e.target); if (jssTable[0]) { if (libraryBase.jspreadsheet.current != jssTable[0].jssWorksheet) { if (libraryBase.jspreadsheet.current) { if (libraryBase.jspreadsheet.current.edition) { closeEditor.call(libraryBase.jspreadsheet.current, libraryBase.jspreadsheet.current.edition[0], true); } libraryBase.jspreadsheet.current.resetSelection(); } libraryBase.jspreadsheet.current = jssTable[0].jssWorksheet; } } else { if (libraryBase.jspreadsheet.current) { if (libraryBase.jspreadsheet.current.edition) { closeEditor.call(libraryBase.jspreadsheet.current, libraryBase.jspreadsheet.current.edition[0], true); } if (!e.target.classList.contains('jss_object')) { resetSelection.call(libraryBase.jspreadsheet.current, true); libraryBase.jspreadsheet.current = null; } } } if (libraryBase.jspreadsheet.current && mouseButton == 1) { if (e.target.classList.contains('jss_selectall')) { if (libraryBase.jspreadsheet.current) { selectAll.call(libraryBase.jspreadsheet.current); } } else if (e.target.classList.contains('jss_corner')) { if (libraryBase.jspreadsheet.current.options.editable != false) { libraryBase.jspreadsheet.current.selectedCorner = true; } } else { // Header found if (jssTable[1] == 1) { const columnId = e.target.getAttribute('data-x'); if (columnId) { // Update cursor const info = e.target.getBoundingClientRect(); if (libraryBase.jspreadsheet.current.options.columnResize != false && info.width - e.offsetX < 6) { // Resize helper libraryBase.jspreadsheet.current.resizing = { mousePosition: e.pageX, column: columnId, width: info.width, }; // Border indication libraryBase.jspreadsheet.current.headers[columnId].classList.add('resizing'); for (let j = 0; j < libraryBase.jspreadsheet.current.records.length; j++) { if (libraryBase.jspreadsheet.current.records[j][columnId]) { libraryBase.jspreadsheet.current.records[j][columnId].element.classList.add('resizing'); } } } else if (libraryBase.jspreadsheet.current.options.columnDrag != false && info.height - e.offsetY < 6) { if (isColMerged.call(libraryBase.jspreadsheet.current, columnId).length) { console.error('Jspreadsheet: This column is part of a merged cell.'); } else { // Reset selection libraryBase.jspreadsheet.current.resetSelection(); // Drag helper libraryBase.jspreadsheet.current.dragging = { element: e.target, column: columnId, destination: columnId, }; // Border indication libraryBase.jspreadsheet.current.headers[columnId].classList.add('dragging'); for (let j = 0; j < libraryBase.jspreadsheet.current.records.length; j++) { if (libraryBase.jspreadsheet.current.records[j][columnId]) { libraryBase.jspreadsheet.current.records[j][columnId].element.classList.add('dragging'); } } } } else { let o, d; if (libraryBase.jspreadsheet.current.selectedHeader && (e.shiftKey || e.ctrlKey)) { o = libraryBase.jspreadsheet.current.selectedHeader; d = columnId; } else { // Press to rename if ( libraryBase.jspreadsheet.current.selectedHeader == columnId && libraryBase.jspreadsheet.current.options.allowRenameColumn != false ) { libraryBase.jspreadsheet.timeControl = setTimeout(function () { libraryBase.jspreadsheet.current.setHeader(columnId); }, 800); } // Keep track of which header was selected first libraryBase.jspreadsheet.current.selectedHeader = columnId; // Update selection single column o = columnId; d = columnId; } // Update selection updateSelectionFromCoords.call(libraryBase.jspreadsheet.current, o, 0, d, libraryBase.jspreadsheet.current.options.data.length - 1, e); } } else { if (e.target.parentNode.classList.contains('jss_nested')) { let c1, c2; if (e.target.getAttribute('data-column')) { const column = e.target.getAttribute('data-column').split(','); c1 = parseInt(column[0]); c2 = parseInt(column[column.length - 1]); } else { c1 = 0; c2 = libraryBase.jspreadsheet.current.options.columns.length - 1; } updateSelectionFromCoords.call( libraryBase.jspreadsheet.current, c1, 0, c2, libraryBase.jspreadsheet.current.options.data.length - 1, e ); } } } else { libraryBase.jspreadsheet.current.selectedHeader = false; } // Body found if (jssTable[1] == 2) { const rowId = parseInt(e.target.getAttribute('data-y')); if (e.target.classList.contains('jss_row')) { const info = e.target.getBoundingClientRect(); if (libraryBase.jspreadsheet.current.options.rowResize != false && info.height - e.offsetY < 6) { // Resize helper libraryBase.jspreadsheet.current.resizing = { element: e.target.parentNode, mousePosition: e.pageY, row: rowId, height: info.height, }; // Border indication e.target.parentNode.classList.add('resizing'); } else if (libraryBase.jspreadsheet.current.options.rowDrag != false && info.width - e.offsetX < 6) { if (isRowMerged.call(libraryBase.jspreadsheet.current, rowId).length) { console.error('Jspreadsheet: This row is part of a merged cell'); } else if (libraryBase.jspreadsheet.current.options.search == true && libraryBase.jspreadsheet.current.results) { console.error('Jspreadsheet: Please clear your search before perform this action'); } else { // Reset selection libraryBase.jspreadsheet.current.resetSelection(); // Drag helper libraryBase.jspreadsheet.current.dragging = { element: e.target.parentNode, row: rowId, destination: rowId, }; // Border indication e.target.parentNode.classList.add('dragging'); } } else { let o, d; if (libraryBase.jspreadsheet.current.selectedRow != null && (e.shiftKey || e.ctrlKey)) { o = libraryBase.jspreadsheet.current.selectedRow; d = rowId; } else { // Keep track of which header was selected first libraryBase.jspreadsheet.current.selectedRow = rowId; // Update selection single column o = rowId; d = rowId; } // Update selection updateSelectionFromCoords.call(libraryBase.jspreadsheet.current, null, o, null, d, e); } } else { // Jclose if (e.target.classList.contains('jclose') && e.target.clientWidth - e.offsetX < 50 && e.offsetY < 50) { closeEditor.call(libraryBase.jspreadsheet.current, libraryBase.jspreadsheet.current.edition[0], true); } else { const getCellCoords = function (element) { const x = element.getAttribute('data-x'); const y = element.getAttribute('data-y'); if (x && y) { return [x, y]; } else { if (element.parentNode) { return getCellCoords(element.parentNode); } } }; const position = getCellCoords(e.target); if (position) { const columnId = position[0]; const rowId = position[1]; // Close edition if (libraryBase.jspreadsheet.current.edition) { if (libraryBase.jspreadsheet.current.edition[2] != columnId || libraryBase.jspreadsheet.current.edition[3] != rowId) { closeEditor.call(libraryBase.jspreadsheet.current, libraryBase.jspreadsheet.current.edition[0], true); } } if (!libraryBase.jspreadsheet.current.edition) { // Update cell selection if (e.shiftKey) { updateSelectionFromCoords.call( libraryBase.jspreadsheet.current, libraryBase.jspreadsheet.current.selectedCell[0], libraryBase.jspreadsheet.current.selectedCell[1], columnId, rowId, e ); } else { updateSelectionFromCoords.call(libraryBase.jspreadsheet.current, columnId, rowId, undefined, undefined, e); } } // No full row selected libraryBase.jspreadsheet.current.selectedHeader = null; libraryBase.jspreadsheet.current.selectedRow = null; } } } } else { libraryBase.jspreadsheet.current.selectedRow = false; } // Pagination if (e.target.classList.contains('jss_page')) { if (e.target.textContent == '<') { libraryBase.jspreadsheet.current.page(0); } else if (e.target.textContent == '>') { libraryBase.jspreadsheet.current.page(e.target.getAttribute('title') - 1); } else { libraryBase.jspreadsheet.current.page(e.target.textContent - 1); } } } if (libraryBase.jspreadsheet.current.edition) { libraryBase.jspreadsheet.isMouseAction = false; } else { libraryBase.jspreadsheet.isMouseAction = true; } } else { libraryBase.jspreadsheet.isMouseAction = false; } }; // Mouse move controls const mouseMoveControls = function (e) { e = e || window.event; let mouseButton; if (e.buttons) { mouseButton = e.buttons; } else if (e.button) { mouseButton = e.button; } else { mouseButton = e.which; } if (!mouseButton) { libraryBase.jspreadsheet.isMouseAction = false; } if (libraryBase.jspreadsheet.current) { if (libraryBase.jspreadsheet.isMouseAction == true) { // Resizing is ongoing if (libraryBase.jspreadsheet.current.resizing) { if (libraryBase.jspreadsheet.current.resizing.column) { const width = e.pageX - libraryBase.jspreadsheet.current.resizing.mousePosition; if (libraryBase.jspreadsheet.current.resizing.width + width > 0) { const tempWidth = libraryBase.jspreadsheet.current.resizing.width + width; libraryBase.jspreadsheet.current.cols[libraryBase.jspreadsheet.current.resizing.column].colElement.setAttribute('width', tempWidth); updateCornerPosition.call(libraryBase.jspreadsheet.current); } } else { const height = e.pageY - libraryBase.jspreadsheet.current.resizing.mousePosition; if (libraryBase.jspreadsheet.current.resizing.height + height > 0) { const tempHeight = libraryBase.jspreadsheet.current.resizing.height + height; libraryBase.jspreadsheet.current.rows[libraryBase.jspreadsheet.current.resizing.row].element.setAttribute('height', tempHeight); updateCornerPosition.call(libraryBase.jspreadsheet.current); } } } else if (libraryBase.jspreadsheet.current.dragging) { if (libraryBase.jspreadsheet.current.dragging.column) { const columnId = e.target.getAttribute('data-x'); if (columnId) { if (isColMerged.call(libraryBase.jspreadsheet.current, columnId).length) { console.error('Jspreadsheet: This column is part of a merged cell.'); } else { for (let i = 0; i < libraryBase.jspreadsheet.current.headers.length; i++) { libraryBase.jspreadsheet.current.headers[i].classList.remove('dragging-left'); libraryBase.jspreadsheet.current.headers[i].classList.remove('dragging-right'); } if (libraryBase.jspreadsheet.current.dragging.column == columnId) { libraryBase.jspreadsheet.current.dragging.destination = parseInt(columnId); } else { if (e.target.clientWidth / 2 > e.offsetX) { if (libraryBase.jspreadsheet.current.dragging.column < columnId) { libraryBase.jspreadsheet.current.dragging.destination = parseInt(columnId) - 1; } else { libraryBase.jspreadsheet.current.dragging.destination = parseInt(columnId); } libraryBase.jspreadsheet.current.headers[columnId].classList.add('dragging-left'); } else { if (libraryBase.jspreadsheet.current.dragging.column < columnId) { libraryBase.jspreadsheet.current.dragging.destination = parseInt(columnId); } else { libraryBase.jspreadsheet.current.dragging.destination = parseInt(columnId) + 1; } libraryBase.jspreadsheet.current.headers[columnId].classList.add('dragging-right'); } } } } } else { const rowId = e.target.getAttribute('data-y'); if (rowId) { if (isRowMerged.call(libraryBase.jspreadsheet.current, rowId).length) { console.error('Jspreadsheet: This row is part of a merged cell.'); } else { const target = e.target.clientHeight / 2 > e.offsetY ? e.target.parentNode.nextSibling : e.target.parentNode; if (libraryBase.jspreadsheet.current.dragging.element != target) { e.target.parentNode.parentNode.insertBefore(libraryBase.jspreadsheet.current.dragging.element, target); libraryBase.jspreadsheet.current.dragging.destination = Array.prototype.indexOf.call( libraryBase.jspreadsheet.current.dragging.element.parentNode.children, libraryBase.jspreadsheet.current.dragging.element ); } } } } } } else { const x = e.target.getAttribute('data-x'); const y = e.target.getAttribute('data-y'); const rect = e.target.getBoundingClientRect(); if (libraryBase.jspreadsheet.current.cursor) { libraryBase.jspreadsheet.current.cursor.style.cursor = ''; libraryBase.jspreadsheet.current.cursor = null; } if (e.target.parentNode.parentNode && e.target.parentNode.parentNode.className) { if (e.target.parentNode.parentNode.classList.contains('resizable')) { if (e.target && x && !y && rect.width - (e.clientX - rect.left) < 6) { libraryBase.jspreadsheet.current.cursor = e.target; libraryBase.jspreadsheet.current.cursor.style.cursor = 'col-resize'; } else if (e.target && !x && y && rect.height - (e.clientY - rect.top) < 6) { libraryBase.jspreadsheet.current.cursor = e.target; libraryBase.jspreadsheet.current.cursor.style.cursor = 'row-resize'; } } if (e.target.parentNode.parentNode.classList.contains('draggable')) { if (e.target && !x && y && rect.width - (e.clientX - rect.left) < 6) { libraryBase.jspreadsheet.current.cursor = e.target; libraryBase.jspreadsheet.current.cursor.style.cursor = 'move'; } else if (e.target && x && !y && rect.height - (e.clientY - rect.top) < 6) { libraryBase.jspreadsheet.current.cursor = e.target; libraryBase.jspreadsheet.current.cursor.style.cursor = 'move'; } } } } } }; /** * Update copy selection * * @param int x, y * @return void */ const updateCopySelection = function (x3, y3) { const obj = this; // Remove selection removeCopySelection.call(obj); // Get elements first and last const x1 = obj.selectedContainer[0]; const y1 = obj.selectedContainer[1]; const x2 = obj.selectedContainer[2]; const y2 = obj.selectedContainer[3]; if (x3 != null && y3 != null) { let px, ux; if (x3 - x2 > 0) { px = parseInt(x2) + 1; ux = parseInt(x3); } else { px = parseInt(x3); ux = parseInt(x1) - 1; } let py, uy; if (y3 - y2 > 0) { py = parseInt(y2) + 1; uy = parseInt(y3); } else { py = parseInt(y3); uy = parseInt(y1) - 1; } if (ux - px <= uy - py) { px = parseInt(x1); ux = parseInt(x2); } else { py = parseInt(y1); uy = parseInt(y2); } for (let j = py; j <= uy; j++) { for (let i = px; i <= ux; i++) { if (obj.records[j][i] && obj.rows[j].element.style.display != 'none' && obj.records[j][i].element.style.display != 'none') { obj.records[j][i].element.classList.add('selection'); obj.records[py][i].element.classList.add('selection-top'); obj.records[uy][i].element.classList.add('selection-bottom'); obj.records[j][px].element.classList.add('selection-left'); obj.records[j][ux].element.classList.add('selection-right'); // Persist selected elements obj.selection.push(obj.records[j][i].element); } } } } }; const mouseOverControls = function (e) { e = e || window.event; let mouseButton; if (e.buttons) { mouseButton = e.buttons; } else if (e.button) { mouseButton = e.button; } else { mouseButton = e.which; } if (!mouseButton) { libraryBase.jspreadsheet.isMouseAction = false; } if (libraryBase.jspreadsheet.current && libraryBase.jspreadsheet.isMouseAction == true) { // Get elements const jssTable = getElement(e.target); if (jssTable[0]) { // Avoid cross reference if (libraryBase.jspreadsheet.current != jssTable[0].jssWorksheet) { if (libraryBase.jspreadsheet.current) { return false; } } let columnId = e.target.getAttribute('data-x'); const rowId = e.target.getAttribute('data-y'); if (libraryBase.jspreadsheet.current.resizing || libraryBase.jspreadsheet.current.dragging) { // ignore } else { // Header found if (jssTable[1] == 1) { if (libraryBase.jspreadsheet.current.selectedHeader) { columnId = e.target.getAttribute('data-x'); const o = libraryBase.jspreadsheet.current.selectedHeader; const d = columnId; // Update selection updateSelectionFromCoords.call(libraryBase.jspreadsheet.current, o, 0, d, libraryBase.jspreadsheet.current.options.data.length - 1, e); } } // Body found if (jssTable[1] == 2) { if (e.target.classList.contains('jss_row')) { if (libraryBase.jspreadsheet.current.selectedRow != null) { const o = libraryBase.jspreadsheet.current.selectedRow; const d = rowId; // Update selection updateSelectionFromCoords.call( libraryBase.jspreadsheet.current, 0, o, libraryBase.jspreadsheet.current.options.data[0].length - 1, d, e ); } } else { // Do not select edtion is in progress if (!libraryBase.jspreadsheet.current.edition) { if (columnId && rowId) { if (libraryBase.jspreadsheet.current.selectedCorner) { updateCopySelection.call(libraryBase.jspreadsheet.current, columnId, rowId); } else { if (libraryBase.jspreadsheet.current.selectedCell) { updateSelectionFromCoords.call( libraryBase.jspreadsheet.current, libraryBase.jspreadsheet.current.selectedCell[0], libraryBase.jspreadsheet.current.selectedCell[1], columnId, rowId, e ); } } } } } } } } } // Clear any time control if (libraryBase.jspreadsheet.timeControl) { clearTimeout(libraryBase.jspreadsheet.timeControl); libraryBase.jspreadsheet.timeControl = null; } }; const doubleClickControls = function (e) { // Jss is selected if (libraryBase.jspreadsheet.current) { // Corner action if (e.target.classList.contains('jss_corner')) { // Any selected cells if (libraryBase.jspreadsheet.current.highlighted.length > 0) { // Copy from this const x1 = libraryBase.jspreadsheet.current.highlighted[0].element.getAttribute('data-x'); const y1 = parseInt( libraryBase.jspreadsheet.current.highlighted[libraryBase.jspreadsheet.current.highlighted.length - 1].element.getAttribute('data-y') ) + 1; // Until this const x2 = libraryBase.jspreadsheet.current.highlighted[libraryBase.jspreadsheet.current.highlighted.length - 1].element.getAttribute('data-x'); const y2 = libraryBase.jspreadsheet.current.records.length - 1; // Execute copy copyData.call( libraryBase.jspreadsheet.current, libraryBase.jspreadsheet.current.records[y1][x1].element, libraryBase.jspreadsheet.current.records[y2][x2].element ); } } else if (e.target.classList.contains('jss_column_filter')) { // Column const columnId = e.target.getAttribute('data-x'); // Open filter openFilter.call(libraryBase.jspreadsheet.current, columnId); } else { // Get table const jssTable = getElement(e.target); // Double click over header if (jssTable[1] == 1 && libraryBase.jspreadsheet.current.options.columnSorting != false) { // Check valid column header coords const columnId = e.target.getAttribute('data-x'); if (columnId) { libraryBase.jspreadsheet.current.orderBy(parseInt(columnId)); } } // Double click over body if (jssTable[1] == 2 && libraryBase.jspreadsheet.current.options.editable != false) { if (!libraryBase.jspreadsheet.current.edition) { const getCellCoords = function (element) { if (element.parentNode) { const x = element.getAttribute('data-x'); const y = element.getAttribute('data-y'); if (x && y) { return element; } else { return getCellCoords(element.parentNode); } } }; const cell = getCellCoords(e.target); if (cell && cell.classList.contains('highlight')) { openEditor.call(libraryBase.jspreadsheet.current, cell, undefined, e); } } } } } }; const pasteControls = function (e) { if (libraryBase.jspreadsheet.current && libraryBase.jspreadsheet.current.selectedCell) { if (!libraryBase.jspreadsheet.current.edition) { if (libraryBase.jspreadsheet.current.options.editable != false) { if (e && e.clipboardData) { paste.call( libraryBase.jspreadsheet.current, libraryBase.jspreadsheet.current.selectedCell[0], libraryBase.jspreadsheet.current.selectedCell[1], e.clipboardData.getData('text') ); e.preventDefault(); } else if (window.clipboardData) { paste.call( libraryBase.jspreadsheet.current, libraryBase.jspreadsheet.current.selectedCell[0], libraryBase.jspreadsheet.current.selectedCell[1], window.clipboardData.getData('text') ); } } } } }; const getRole = function (element) { if (element.classList.contains('jss_selectall')) { return 'select-all'; } if (element.classList.contains('jss_corner')) { return 'fill-handle'; } let tempElement = element; while (!tempElement.classList.contains('jss_spreadsheet')) { if (tempElement.classList.contains('jss_row')) { return 'row'; } if (tempElement.classList.contains('jss_nested')) { return 'nested'; } if (tempElement.classList.contains('jtabs-headers')) { return 'tabs'; } if (tempElement.classList.contains('jtoolbar')) { return 'toolbar'; } if (tempElement.classList.contains('jss_pagination')) { return 'pagination'; } if (tempElement.tagName === 'TBODY') { return 'cell'; } if (tempElement.tagName === 'TFOOT') { return getElementIndex(element) === 0 ? 'grid' : 'footer'; } if (tempElement.tagName === 'THEAD') { return 'header'; } tempElement = tempElement.parentElement; } return 'applications'; }; const defaultContextMenu = function (worksheet, x, y, role) { const items = []; if (role === 'header') { // Insert a new column if (worksheet.options.allowInsertColumn != false) { items.push({ title: jSuites.translate('Insert a new column before'), onclick: function () { worksheet.insertColumn(1, parseInt(x), 1); }, }); } if (worksheet.options.allowInsertColumn != false) { items.push({ title: jSuites.translate('Insert a new column after'), onclick: function () { worksheet.insertColumn(1, parseInt(x), 0); }, }); } // Delete a column if (worksheet.options.allowDeleteColumn != false) { items.push({ title: jSuites.translate('Delete selected columns'), onclick: function () { worksheet.deleteColumn(worksheet.getSelectedColumns().length ? undefined : parseInt(x)); }, }); } // Rename column if (worksheet.options.allowRenameColumn != false) { items.push({ title: jSuites.translate('Rename this column'), onclick: function () { const oldValue = worksheet.getHeader(x); const newValue = prompt(jSuites.translate('Column name'), oldValue); worksheet.setHeader(x, newValue); }, }); } // Sorting if (worksheet.options.columnSorting != false) { // Line items.push({ type: 'line' }); items.push({ title: jSuites.translate('Order ascending'), onclick: function () { worksheet.orderBy(x, 0); }, }); items.push({ title: jSuites.translate('Order descending'), onclick: function () { worksheet.orderBy(x, 1); }, }); } } if (role === 'row' || role === 'cell') { // Insert new row if (worksheet.options.allowInsertRow != false) { items.push({ title: jSuites.translate('Insert a new row before'), onclick: function () { worksheet.insertRow(1, parseInt(y), 1); }, }); items.push({ title: jSuites.translate('Insert a new row after'), onclick: function () { worksheet.insertRow(1, parseInt(y)); }, }); } if (worksheet.options.allowDeleteRow != false) { items.push({ title: jSuites.translate('Delete selected rows'), onclick: function () { worksheet.deleteRow(worksheet.getSelectedRows().length ? undefined : parseInt(y)); }, }); } } if (role === 'cell') { if (worksheet.options.allowComments != false) { items.push({ type: 'line' }); const title = worksheet.records[y][x].element.getAttribute('title') || ''; items.push({ title: jSuites.translate(title ? 'Edit comments' : 'Add comments'), onclick: function () { const comment = prompt(jSuites.translate('Comments'), title); if (comment) { worksheet.setComments(getCellNameFromCoords(x, y), comment); } }, }); if (title) { items.push({ title: jSuites.translate('Clear comments'), onclick: function () { worksheet.setComments(getCellNameFromCoords(x, y), ''); }, }); } } } // Line if (items.length !== 0) { items.push({ type: 'line' }); } // Copy if (role === 'header' || role === 'row' || role === 'cell') { items.push({ title: jSuites.translate('Copy') + '...', shortcut: 'Ctrl + C', onclick: function () { copy.call(worksheet, true); }, }); // Paste if (navigator && navigator.clipboard) { items.push({ title: jSuites.translate('Paste') + '...', shortcut: 'Ctrl + V', onclick: function () { if (worksheet.selectedCell) { navigator.clipboard.readText().then(function (text) { if (text) { paste.call(worksheet, worksheet.selectedCell[0], worksheet.selectedCell[1], text); } }); } }, }); } } // Save if (worksheet.parent.config.allowExport != false) { items.push({ title: jSuites.translate('Save as') + '...', shortcut: 'Ctrl + S', onclick: function () { worksheet.download(); }, }); } // About if (worksheet.parent.config.about != false) { items.push({ title: jSuites.translate('About'), onclick: function () { if (typeof worksheet.parent.config.about === 'undefined' || worksheet.parent.config.about === true) { alert(version.print()); } else { alert(worksheet.parent.config.about); } }, }); } return items; }; const getElementIndex = function (element) { const parentChildren = element.parentElement.children; for (let i = 0; i < parentChildren.length; i++) { const currentElement = parentChildren[i]; if (element === currentElement) { return i; } } return -1; }; const contextMenuControls = function (e) { e = e || window.event; let mouseButton; if ('buttons' in e) { mouseButton = e.buttons; } else { mouseButton = e.which || e.button; } if (libraryBase.jspreadsheet.current) { const spreadsheet = libraryBase.jspreadsheet.current.parent; if (libraryBase.jspreadsheet.current.edition) { e.preventDefault(); } else { spreadsheet.contextMenu.contextmenu.close(); if (libraryBase.jspreadsheet.current) { const role = getRole(e.target); let x = null, y = null; if (role === 'cell') { let cellElement = e.target; while (cellElement.tagName !== 'TD') { cellElement = cellElement.parentNode; } y = cellElement.getAttribute('data-y'); x = cellElement.getAttribute('data-x'); if ( !libraryBase.jspreadsheet.current.selectedCell || x < parseInt(libraryBase.jspreadsheet.current.selectedCell[0]) || x > parseInt(libraryBase.jspreadsheet.current.selectedCell[2]) || y < parseInt(libraryBase.jspreadsheet.current.selectedCell[1]) || y > parseInt(libraryBase.jspreadsheet.current.selectedCell[3]) ) { updateSelectionFromCoords.call(libraryBase.jspreadsheet.current, x, y, x, y, e); } } else if (role === 'row' || role === 'header') { if (role === 'row') { y = e.target.getAttribute('data-y'); } else { x = e.target.getAttribute('data-x'); } if ( !libraryBase.jspreadsheet.current.selectedCell || x < parseInt(libraryBase.jspreadsheet.current.selectedCell[0]) || x > parseInt(libraryBase.jspreadsheet.current.selectedCell[2]) || y < parseInt(libraryBase.jspreadsheet.current.selectedCell[1]) || y > parseInt(libraryBase.jspreadsheet.current.selectedCell[3]) ) { updateSelectionFromCoords.call(libraryBase.jspreadsheet.current, x, y, x, y, e); } } else if (role === 'nested') { const columns = e.target.getAttribute('data-column').split(','); x = getElementIndex(e.target) - 1; y = getElementIndex(e.target.parentElement); if ( !libraryBase.jspreadsheet.current.selectedCell || columns[0] != parseInt(libraryBase.jspreadsheet.current.selectedCell[0]) || columns[columns.length - 1] != parseInt(libraryBase.jspreadsheet.current.selectedCell[2]) || libraryBase.jspreadsheet.current.selectedCell[1] != null || libraryBase.jspreadsheet.current.selectedCell[3] != null ) { updateSelectionFromCoords.call(libraryBase.jspreadsheet.current, columns[0], null, columns[columns.length - 1], null, e); } } else if (role === 'select-all') { selectAll.call(libraryBase.jspreadsheet.current); } else if (role === 'tabs') { x = getElementIndex(e.target); } else if (role === 'footer') { x = getElementIndex(e.target) - 1; y = getElementIndex(e.target.parentElement); } // Table found let items = defaultContextMenu(libraryBase.jspreadsheet.current, parseInt(x), parseInt(y), role); if (typeof spreadsheet.config.contextMenu === 'function') { const result = spreadsheet.config.contextMenu(libraryBase.jspreadsheet.current, x, y, e, items, role, x, y); if (result) { items = result; } else if (result === false) { return; } } if (typeof spreadsheet.plugins === 'object') { Object.entries(spreadsheet.plugins).forEach(function ([, plugin]) { if (typeof plugin.contextMenu === 'function') { const result = plugin.contextMenu( libraryBase.jspreadsheet.current, x !== null ? parseInt(x) : null, y !== null ? parseInt(y) : null, e, items, role, x !== null ? parseInt(x) : null, y !== null ? parseInt(y) : null ); if (result) { items = result; } } }); } // The id is depending on header and body spreadsheet.contextMenu.contextmenu.open(e, items); // Avoid the real one e.preventDefault(); } } } }; const touchStartControls = function (e) { const jssTable = getElement(e.target); if (jssTable[0]) { if (libraryBase.jspreadsheet.current != jssTable[0].jssWorksheet) { if (libraryBase.jspreadsheet.current) { libraryBase.jspreadsheet.current.resetSelection(); } libraryBase.jspreadsheet.current = jssTable[0].jssWorksheet; } } else { if (libraryBase.jspreadsheet.current) { libraryBase.jspreadsheet.current.resetSelection(); libraryBase.jspreadsheet.current = null; } } if (libraryBase.jspreadsheet.current) { if (!libraryBase.jspreadsheet.current.edition) { const columnId = e.target.getAttribute('data-x'); const rowId = e.target.getAttribute('data-y'); if (columnId && rowId) { updateSelectionFromCoords.call(libraryBase.jspreadsheet.current, columnId, rowId, undefined, undefined, e); libraryBase.jspreadsheet.timeControl = setTimeout(function () { // Keep temporary reference to the element if (libraryBase.jspreadsheet.current.options.columns[columnId].type == 'color') { libraryBase.jspreadsheet.tmpElement = null; } else { libraryBase.jspreadsheet.tmpElement = e.target; } openEditor.call(libraryBase.jspreadsheet.current, e.target, false, e); }, 500); } } } }; const touchEndControls = function (e) { // Clear any time control if (libraryBase.jspreadsheet.timeControl) { clearTimeout(libraryBase.jspreadsheet.timeControl); libraryBase.jspreadsheet.timeControl = null; // Element if (libraryBase.jspreadsheet.tmpElement && libraryBase.jspreadsheet.tmpElement.children[0].tagName == 'INPUT') { libraryBase.jspreadsheet.tmpElement.children[0].focus(); } libraryBase.jspreadsheet.tmpElement = null; } }; export const cutControls = function (e) { if (libraryBase.jspreadsheet.current) { if (!libraryBase.jspreadsheet.current.edition) { copy.call(libraryBase.jspreadsheet.current, true, undefined, undefined, undefined, undefined, true); if (libraryBase.jspreadsheet.current.options.editable != false) { libraryBase.jspreadsheet.current.setValue( libraryBase.jspreadsheet.current.highlighted.map(function (record) { return record.element; }), '' ); } } } }; const copyControls = function (e) { if (libraryBase.jspreadsheet.current && copyControls.enabled) { if (!libraryBase.jspreadsheet.current.edition) { copy.call(libraryBase.jspreadsheet.current, true); } } }; const isMac = function () { return navigator.platform.toUpperCase().indexOf('MAC') >= 0; }; const isCtrl = function (e) { if (isMac()) { return e.metaKey; } else { return e.ctrlKey; } }; const keyDownControls = function (e) { if (libraryBase.jspreadsheet.current) { if (libraryBase.jspreadsheet.current.edition) { if (e.which == 27) { // Escape if (libraryBase.jspreadsheet.current.edition) { // Exit without saving closeEditor.call(libraryBase.jspreadsheet.current, libraryBase.jspreadsheet.current.edition[0], false); } e.preventDefault(); } else if (e.which == 13) { // Enter if ( libraryBase.jspreadsheet.current.options.columns && libraryBase.jspreadsheet.current.options.columns[libraryBase.jspreadsheet.current.edition[2]] && libraryBase.jspreadsheet.current.options.columns[libraryBase.jspreadsheet.current.edition[2]].type == 'calendar' ) { closeEditor.call(libraryBase.jspreadsheet.current, libraryBase.jspreadsheet.current.edition[0], true); } else if ( libraryBase.jspreadsheet.current.options.columns && libraryBase.jspreadsheet.current.options.columns[libraryBase.jspreadsheet.current.edition[2]] && libraryBase.jspreadsheet.current.options.columns[libraryBase.jspreadsheet.current.edition[2]].type == 'dropdown' ) { // Do nothing } else { // Alt enter -> do not close editor if ( (libraryBase.jspreadsheet.current.options.wordWrap == true || (libraryBase.jspreadsheet.current.options.columns && libraryBase.jspreadsheet.current.options.columns[libraryBase.jspreadsheet.current.edition[2]] && libraryBase.jspreadsheet.current.options.columns[libraryBase.jspreadsheet.current.edition[2]].wordWrap == true) || (libraryBase.jspreadsheet.current.options.data[libraryBase.jspreadsheet.current.edition[3]][ libraryBase.jspreadsheet.current.edition[2] ] && libraryBase.jspreadsheet.current.options.data[libraryBase.jspreadsheet.current.edition[3]][ libraryBase.jspreadsheet.current.edition[2] ].length > 200)) && e.altKey ) { // Add new line to the editor const editorTextarea = libraryBase.jspreadsheet.current.edition[0].children[0]; let editorValue = libraryBase.jspreadsheet.current.edition[0].children[0].value; const editorIndexOf = editorTextarea.selectionStart; editorValue = editorValue.slice(0, editorIndexOf) + '\n' + editorValue.slice(editorIndexOf); editorTextarea.value = editorValue; editorTextarea.focus(); editorTextarea.selectionStart = editorIndexOf + 1; editorTextarea.selectionEnd = editorIndexOf + 1; } else { libraryBase.jspreadsheet.current.edition[0].children[0].blur(); } } } else if (e.which == 9) { // Tab if ( libraryBase.jspreadsheet.current.options.columns && libraryBase.jspreadsheet.current.options.columns[libraryBase.jspreadsheet.current.edition[2]] && ['calendar', 'html'].includes(libraryBase.jspreadsheet.current.options.columns[libraryBase.jspreadsheet.current.edition[2]].type) ) { closeEditor.call(libraryBase.jspreadsheet.current, libraryBase.jspreadsheet.current.edition[0], true); } else { libraryBase.jspreadsheet.current.edition[0].children[0].blur(); } } } if (!libraryBase.jspreadsheet.current.edition && libraryBase.jspreadsheet.current.selectedCell) { // Which key if (e.which == 37) { left.call(libraryBase.jspreadsheet.current, e.shiftKey, e.ctrlKey); e.preventDefault(); } else if (e.which == 39) { right.call(libraryBase.jspreadsheet.current, e.shiftKey, e.ctrlKey); e.preventDefault(); } else if (e.which == 38) { up.call(libraryBase.jspreadsheet.current, e.shiftKey, e.ctrlKey); e.preventDefault(); } else if (e.which == 40) { down.call(libraryBase.jspreadsheet.current, e.shiftKey, e.ctrlKey); e.preventDefault(); } else if (e.which == 36) { first.call(libraryBase.jspreadsheet.current, e.shiftKey, e.ctrlKey); e.preventDefault(); } else if (e.which == 35) { last.call(libraryBase.jspreadsheet.current, e.shiftKey, e.ctrlKey); e.preventDefault(); } else if (e.which == 46 || e.which == 8) { // Delete if (libraryBase.jspreadsheet.current.options.editable != false) { if (libraryBase.jspreadsheet.current.selectedRow != null) { if (libraryBase.jspreadsheet.current.options.allowDeleteRow != false) { if (confirm(jSuites.translate('Are you sure to delete the selected rows?'))) { libraryBase.jspreadsheet.current.deleteRow(); } } } else if (libraryBase.jspreadsheet.current.selectedHeader) { if (libraryBase.jspreadsheet.current.options.allowDeleteColumn != false) { if (confirm(jSuites.translate('Are you sure to delete the selected columns?'))) { libraryBase.jspreadsheet.current.deleteColumn(); } } } else { // Change value libraryBase.jspreadsheet.current.setValue( libraryBase.jspreadsheet.current.highlighted.map(function (record) { return record.element; }), '' ); } } } else if (e.which == 13) { // Move cursor if (e.shiftKey) { up.call(libraryBase.jspreadsheet.current); } else { if (libraryBase.jspreadsheet.current.options.allowInsertRow != false) { if (libraryBase.jspreadsheet.current.options.allowManualInsertRow != false) { if (libraryBase.jspreadsheet.current.selectedCell[1] == libraryBase.jspreadsheet.current.options.data.length - 1) { // New record in case selectedCell in the last row libraryBase.jspreadsheet.current.insertRow(); } } } down.call(libraryBase.jspreadsheet.current); } e.preventDefault(); } else if (e.which == 9) { // Tab if (e.shiftKey) { left.call(libraryBase.jspreadsheet.current); } else { if (libraryBase.jspreadsheet.current.options.allowInsertColumn != false) { if (libraryBase.jspreadsheet.current.options.allowManualInsertColumn != false) { if (libraryBase.jspreadsheet.current.selectedCell[0] == libraryBase.jspreadsheet.current.options.data[0].length - 1) { // New record in case selectedCell in the last column libraryBase.jspreadsheet.current.insertColumn(); } } } right.call(libraryBase.jspreadsheet.current); } e.preventDefault(); } else { if ((e.ctrlKey || e.metaKey) && !e.shiftKey) { if (e.which == 65) { // Ctrl + A selectAll.call(libraryBase.jspreadsheet.current); e.preventDefault(); } else if (e.which == 83) { // Ctrl + S libraryBase.jspreadsheet.current.download(); e.preventDefault(); } else if (e.which == 89) { // Ctrl + Y libraryBase.jspreadsheet.current.redo(); e.preventDefault(); } else if (e.which == 90) { // Ctrl + Z libraryBase.jspreadsheet.current.undo(); e.preventDefault(); } else if (e.which == 67) { // Ctrl + C copy.call(libraryBase.jspreadsheet.current, true); e.preventDefault(); } else if (e.which == 88) { // Ctrl + X if (libraryBase.jspreadsheet.current.options.editable != false) { cutControls(); } else { copyControls(); } e.preventDefault(); } else if (e.which == 86) { // Ctrl + V pasteControls(); } } else { if (libraryBase.jspreadsheet.current.selectedCell) { if (libraryBase.jspreadsheet.current.options.editable != false) { const rowId = libraryBase.jspreadsheet.current.selectedCell[1]; const columnId = libraryBase.jspreadsheet.current.selectedCell[0]; // Characters able to start a edition if (e.keyCode == 32) { // Space e.preventDefault(); if ( libraryBase.jspreadsheet.current.options.columns[columnId].type == 'checkbox' || libraryBase.jspreadsheet.current.options.columns[columnId].type == 'radio' ) { setCheckRadioValue.call(libraryBase.jspreadsheet.current); } else { // Start edition openEditor.call( libraryBase.jspreadsheet.current, libraryBase.jspreadsheet.current.records[rowId][columnId].element, true, e ); } } else if (e.keyCode == 113) { // Start edition with current content F2 openEditor.call(libraryBase.jspreadsheet.current, libraryBase.jspreadsheet.current.records[rowId][columnId].element, false, e); } else if ((e.key.length === 1 || e.key === 'Process') && !(e.altKey || isCtrl(e))) { // Start edition openEditor.call(libraryBase.jspreadsheet.current, libraryBase.jspreadsheet.current.records[rowId][columnId].element, true, e); // Prevent entries in the calendar if ( libraryBase.jspreadsheet.current.options.columns && libraryBase.jspreadsheet.current.options.columns[columnId] && libraryBase.jspreadsheet.current.options.columns[columnId].type == 'calendar' ) { e.preventDefault(); } } } } } } } else { if (e.target.classList.contains('jss_search')) { if (libraryBase.jspreadsheet.timeControl) { clearTimeout(libraryBase.jspreadsheet.timeControl); } libraryBase.jspreadsheet.timeControl = setTimeout(function () { libraryBase.jspreadsheet.current.search(e.target.value); }, 200); } } } }; export const wheelControls = function (e) { const obj = this; if (obj.options.lazyLoading == true) { if (libraryBase.jspreadsheet.timeControlLoading == null) { libraryBase.jspreadsheet.timeControlLoading = setTimeout(function () { if (obj.content.scrollTop + obj.content.clientHeight >= obj.content.scrollHeight - 10) { if (loadDown.call(obj)) { if (obj.content.scrollTop + obj.content.clientHeight > obj.content.scrollHeight - 10) { obj.content.scrollTop = obj.content.scrollTop - obj.content.clientHeight; } updateCornerPosition.call(obj); } } else if (obj.content.scrollTop <= obj.content.clientHeight) { if (loadUp.call(obj)) { if (obj.content.scrollTop < 10) { obj.content.scrollTop = obj.content.scrollTop + obj.content.clientHeight; } updateCornerPosition.call(obj); } } libraryBase.jspreadsheet.timeControlLoading = null; }, 100); } } }; let scrollLeft = 0; const updateFreezePosition = function () { const obj = this; scrollLeft = obj.content.scrollLeft; let width = 0; if (scrollLeft > 50) { for (let i = 0; i < obj.options.freezeColumns; i++) { if (i > 0) { // Must check if the previous column is hidden or not to determin whether the width shoule be added or not! if (!obj.options.columns || !obj.options.columns[i - 1] || obj.options.columns[i - 1].type !== 'hidden') { let columnWidth; if (obj.options.columns && obj.options.columns[i - 1] && obj.options.columns[i - 1].width !== undefined) { columnWidth = parseInt(obj.options.columns[i - 1].width); } else { columnWidth = obj.options.defaultColWidth !== undefined ? parseInt(obj.options.defaultColWidth) : 100; } width += parseInt(columnWidth); } } obj.headers[i].classList.add('jss_freezed'); obj.headers[i].style.left = width + 'px'; for (let j = 0; j < obj.rows.length; j++) { if (obj.rows[j] && obj.records[j][i]) { const shifted = scrollLeft + (i > 0 ? obj.records[j][i - 1].element.style.width : 0) - 51 + 'px'; obj.records[j][i].element.classList.add('jss_freezed'); obj.records[j][i].element.style.left = shifted; } } } } else { for (let i = 0; i < obj.options.freezeColumns; i++) { obj.headers[i].classList.remove('jss_freezed'); obj.headers[i].style.left = ''; for (let j = 0; j < obj.rows.length; j++) { if (obj.records[j][i]) { obj.records[j][i].element.classList.remove('jss_freezed'); obj.records[j][i].element.style.left = ''; } } } } // Place the corner in the correct place updateCornerPosition.call(obj); }; export const scrollControls = function (e) { const obj = this; wheelControls.call(obj); if (obj.options.freezeColumns > 0 && obj.content.scrollLeft != scrollLeft) { updateFreezePosition.call(obj); } // Close editor if (obj.options.lazyLoading == true || obj.options.tableOverflow == true) { if (obj.edition && e.target.className.substr(0, 9) != 'jdropdown') { closeEditor.call(obj, obj.edition[0], true); } } }; export const setEvents = function (root) { destroyEvents(root); root.addEventListener('mouseup', mouseUpControls); root.addEventListener('mousedown', mouseDownControls); root.addEventListener('mousemove', mouseMoveControls); root.addEventListener('mouseover', mouseOverControls); root.addEventListener('dblclick', doubleClickControls); root.addEventListener('paste', pasteControls); root.addEventListener('contextmenu', contextMenuControls); root.addEventListener('touchstart', touchStartControls); root.addEventListener('touchend', touchEndControls); root.addEventListener('touchcancel', touchEndControls); root.addEventListener('touchmove', touchEndControls); document.addEventListener('keydown', keyDownControls); }; export const destroyEvents = function (root) { root.removeEventListener('mouseup', mouseUpControls); root.removeEventListener('mousedown', mouseDownControls); root.removeEventListener('mousemove', mouseMoveControls); root.removeEventListener('mouseover', mouseOverControls); root.removeEventListener('dblclick', doubleClickControls); root.removeEventListener('paste', pasteControls); root.removeEventListener('contextmenu', contextMenuControls); root.removeEventListener('touchstart', touchStartControls); root.removeEventListener('touchend', touchEndControls); root.removeEventListener('touchcancel', touchEndControls); document.removeEventListener('keydown', keyDownControls); }; ================================================ FILE: src/utils/factory.js ================================================ import jSuites from 'jsuites'; import libraryBase from './libraryBase.js'; import { setEvents } from './events.js'; import { fullscreen, getWorksheetActive } from './internal.js'; import { hideToolbar, showToolbar, updateToolbar } from './toolbar.js'; import { buildWorksheet, createWorksheetObj, getNextDefaultWorksheetName } from './worksheets.js'; import dispatch from './dispatch.js'; import { createFromTable } from './helpers.js'; import { getSpreadsheetConfig, setConfig } from './config.js'; const factory = function () {}; const createWorksheets = async function (spreadsheet, options, el) { // Create worksheets let o = options.worksheets; if (o) { let tabsOptions = { animation: true, onbeforecreate: function (element, title) { if (title) { return title; } else { return getNextDefaultWorksheetName(spreadsheet); } }, oncreate: function (element, newTabContent) { if (!spreadsheet.creationThroughJss) { const worksheetName = element.tabs.headers.children[element.tabs.headers.children.length - 2].innerHTML; createWorksheetObj.call(spreadsheet.worksheets[0], { minDimensions: [10, 15], worksheetName: worksheetName, }); } else { spreadsheet.creationThroughJss = false; } const newWorksheet = spreadsheet.worksheets[spreadsheet.worksheets.length - 1]; newWorksheet.element = newTabContent; buildWorksheet.call(newWorksheet).then(function () { updateToolbar(newWorksheet); dispatch.call(newWorksheet, 'oncreateworksheet', newWorksheet, options, spreadsheet.worksheets.length - 1); }); }, onchange: function (element, instance, tabIndex) { if (spreadsheet.worksheets.length != 0 && spreadsheet.worksheets[tabIndex]) { updateToolbar(spreadsheet.worksheets[tabIndex]); } }, }; if (options.tabs == true) { tabsOptions.allowCreate = true; } else { tabsOptions.hideHeaders = true; } tabsOptions.data = []; let sheetNumber = 1; for (let i = 0; i < o.length; i++) { if (!o[i].worksheetName) { o[i].worksheetName = 'Sheet' + sheetNumber++; } tabsOptions.data.push({ title: o[i].worksheetName, content: '', }); } el.classList.add('jss_spreadsheet'); el.tabIndex = 0; const tabs = jSuites.tabs(el, tabsOptions); const spreadsheetStyles = options.style; delete options.style; for (let i = 0; i < o.length; i++) { if (o[i].style) { Object.entries(o[i].style).forEach(function ([cellName, value]) { if (typeof value === 'number') { o[i].style[cellName] = spreadsheetStyles[value]; } }); } spreadsheet.worksheets.push({ parent: spreadsheet, element: tabs.content.children[i], options: o[i], filters: [], formula: [], history: [], selection: [], historyIndex: -1, }); await buildWorksheet.call(spreadsheet.worksheets[i]); } } else { throw new Error('JSS: worksheets are not defined'); } }; factory.spreadsheet = async function (el, options, worksheets) { if (el.tagName == 'TABLE') { if (!options) { options = {}; } if (!options.worksheets) { options.worksheets = []; } const tableOptions = createFromTable(el, options.worksheets[0]); options.worksheets[0] = tableOptions; const div = document.createElement('div'); el.parentNode.insertBefore(div, el); el.remove(); el = div; } let spreadsheet = { worksheets: worksheets, config: options, element: el, el, }; // Contextmenu container spreadsheet.contextMenu = document.createElement('div'); spreadsheet.contextMenu.className = 'jss_contextmenu'; spreadsheet.getWorksheetActive = getWorksheetActive.bind(spreadsheet); spreadsheet.fullscreen = fullscreen.bind(spreadsheet); spreadsheet.showToolbar = showToolbar.bind(spreadsheet); spreadsheet.hideToolbar = hideToolbar.bind(spreadsheet); spreadsheet.getConfig = getSpreadsheetConfig.bind(spreadsheet); spreadsheet.setConfig = setConfig.bind(spreadsheet); spreadsheet.setPlugins = function (newPlugins) { if (!spreadsheet.plugins) { spreadsheet.plugins = {}; } if (typeof newPlugins == 'object') { Object.entries(newPlugins).forEach(function ([pluginName, plugin]) { spreadsheet.plugins[pluginName] = plugin.call(libraryBase.jspreadsheet, spreadsheet, {}, spreadsheet.config); }); } }; spreadsheet.setPlugins(options.plugins); // Create as worksheets await createWorksheets(spreadsheet, options, el); spreadsheet.element.appendChild(spreadsheet.contextMenu); // Create element jSuites.contextmenu(spreadsheet.contextMenu, { onclick: function () { spreadsheet.contextMenu.contextmenu.close(false); }, }); // Fullscreen if (spreadsheet.config.fullscreen == true) { spreadsheet.element.classList.add('fullscreen'); } showToolbar.call(spreadsheet); // Build handlers if (options.root) { setEvents(options.root); } else { setEvents(document); } el.spreadsheet = spreadsheet; return spreadsheet; }; factory.worksheet = function (spreadsheet, options, position) { // Worksheet object let w = { // Parent of a worksheet is always the spreadsheet parent: spreadsheet, // Options for this worksheet options: {}, }; // Create the worksheets object if (typeof position === 'undefined') { spreadsheet.worksheets.push(w); } else { spreadsheet.worksheets.splice(position, 0, w); } // Keep configuration used Object.assign(w.options, options); return w; }; export default factory; ================================================ FILE: src/utils/filter.js ================================================ import jSuites from 'jsuites'; import { updateResult } from './internal.js'; import { refreshSelection } from './selection.js'; /** * Open the column filter */ export const openFilter = function (columnId) { const obj = this; if (!obj.options.filters) { console.log('Jspreadsheet: filters not enabled.'); } else { // Make sure is integer columnId = parseInt(columnId); // Reset selection obj.resetSelection(); // Load options let optionsFiltered = []; if (obj.options.columns[columnId].type == 'checkbox') { optionsFiltered.push({ id: 'true', name: 'True' }); optionsFiltered.push({ id: 'false', name: 'False' }); } else { const options = []; let hasBlanks = false; for (let j = 0; j < obj.options.data.length; j++) { const k = obj.options.data[j][columnId]; const v = obj.records[j][columnId].element.innerHTML; if (k && v) { options[k] = v; } else { hasBlanks = true; } } const keys = Object.keys(options); optionsFiltered = []; for (let j = 0; j < keys.length; j++) { optionsFiltered.push({ id: keys[j], name: options[keys[j]] }); } // Has blank options if (hasBlanks) { optionsFiltered.push({ value: '', id: '', name: '(Blanks)' }); } } // Create dropdown const div = document.createElement('div'); obj.filter.children[columnId + 1].innerHTML = ''; obj.filter.children[columnId + 1].appendChild(div); obj.filter.children[columnId + 1].style.paddingLeft = '0px'; obj.filter.children[columnId + 1].style.paddingRight = '0px'; obj.filter.children[columnId + 1].style.overflow = 'initial'; const opt = { data: optionsFiltered, multiple: true, autocomplete: true, opened: true, value: obj.filters[columnId] !== undefined ? obj.filters[columnId] : null, width: '100%', position: obj.options.tableOverflow == true || obj.parent.config.fullscreen == true ? true : false, onclose: function (o) { resetFilters.call(obj); obj.filters[columnId] = o.dropdown.getValue(true); obj.filter.children[columnId + 1].innerHTML = o.dropdown.getText(); obj.filter.children[columnId + 1].style.paddingLeft = ''; obj.filter.children[columnId + 1].style.paddingRight = ''; obj.filter.children[columnId + 1].style.overflow = ''; closeFilter.call(obj, columnId); refreshSelection.call(obj); }, }; // Dynamic dropdown jSuites.dropdown(div, opt); } }; export const closeFilter = function (columnId) { const obj = this; if (!columnId) { for (let i = 0; i < obj.filter.children.length; i++) { if (obj.filters[i]) { columnId = i; } } } // Search filter const search = function (query, x, y) { for (let i = 0; i < query.length; i++) { const value = '' + obj.options.data[y][x]; const label = '' + obj.records[y][x].element.innerHTML; if (query[i] == value || query[i] == label) { return true; } } return false; }; const query = obj.filters[columnId]; obj.results = []; for (let j = 0; j < obj.options.data.length; j++) { if (search(query, columnId, j)) { obj.results.push(j); } } if (!obj.results.length) { obj.results = null; } updateResult.call(obj); }; export const resetFilters = function () { const obj = this; if (obj.options.filters) { for (let i = 0; i < obj.filter.children.length; i++) { obj.filter.children[i].innerHTML = ' '; obj.filters[i] = null; } } obj.results = null; updateResult.call(obj); }; ================================================ FILE: src/utils/footer.js ================================================ import { parseValue } from './internal.js'; export const setFooter = function (data) { const obj = this; if (data) { obj.options.footers = data; } if (obj.options.footers) { if (!obj.tfoot) { obj.tfoot = document.createElement('tfoot'); obj.table.appendChild(obj.tfoot); } for (let j = 0; j < obj.options.footers.length; j++) { let tr; if (obj.tfoot.children[j]) { tr = obj.tfoot.children[j]; } else { tr = document.createElement('tr'); const td = document.createElement('td'); tr.appendChild(td); obj.tfoot.appendChild(tr); } for (let i = 0; i < obj.headers.length; i++) { if (!obj.options.footers[j][i]) { obj.options.footers[j][i] = ''; } let td; if (obj.tfoot.children[j].children[i + 1]) { td = obj.tfoot.children[j].children[i + 1]; } else { td = document.createElement('td'); tr.appendChild(td); // Text align const colAlign = obj.options.columns[i].align || obj.options.defaultColAlign || 'center'; td.style.textAlign = colAlign; } td.textContent = parseValue.call(obj, +obj.records.length + i, j, obj.options.footers[j][i]); // Hide/Show with hideColumn()/showColumn() td.style.display = obj.cols[i].colElement.style.display; } } } }; ================================================ FILE: src/utils/freeze.js ================================================ // Get width of all freezed cells together export const getFreezeWidth = function () { const obj = this; let width = 0; if (obj.options.freezeColumns > 0) { for (let i = 0; i < obj.options.freezeColumns; i++) { let columnWidth; if (obj.options.columns && obj.options.columns[i] && obj.options.columns[i].width !== undefined) { columnWidth = parseInt(obj.options.columns[i].width); } else { columnWidth = obj.options.defaultColWidth !== undefined ? parseInt(obj.options.defaultColWidth) : 100; } width += columnWidth; } } return width; }; ================================================ FILE: src/utils/headers.js ================================================ import { setHistory } from './history.js'; import dispatch from './dispatch.js'; import { getColumnName } from './helpers.js'; /** * Get the column title * * @param column - column number (first column is: 0) * @param title - new column title */ export const getHeader = function (column) { const obj = this; return obj.headers[column].textContent; }; /** * Get the headers * * @param asArray * @return mixed */ export const getHeaders = function (asArray) { const obj = this; const title = []; for (let i = 0; i < obj.headers.length; i++) { title.push(obj.getHeader(i)); } return asArray ? title : title.join(obj.options.csvDelimiter); }; /** * Set the column title * * @param column - column number (first column is: 0) * @param title - new column title */ export const setHeader = function (column, newValue) { const obj = this; if (obj.headers[column]) { const oldValue = obj.headers[column].textContent; const onchangeheaderOldValue = (obj.options.columns && obj.options.columns[column] && obj.options.columns[column].title) || ''; if (!newValue) { newValue = getColumnName(column); } obj.headers[column].textContent = newValue; // Keep the title property obj.headers[column].setAttribute('title', newValue); // Update title if (!obj.options.columns) { obj.options.columns = []; } if (!obj.options.columns[column]) { obj.options.columns[column] = {}; } obj.options.columns[column].title = newValue; setHistory.call(obj, { action: 'setHeader', column: column, oldValue: oldValue, newValue: newValue, }); // On onchange header dispatch.call(obj, 'onchangeheader', obj, parseInt(column), newValue, onchangeheaderOldValue); } }; ================================================ FILE: src/utils/helpers.js ================================================ import { getColumnNameFromId } from './internalHelpers.js'; /** * Get carret position for one element */ export const getCaretIndex = function (e) { let d; if (this.config.root) { d = this.config.root; } else { d = window; } let pos = 0; const s = d.getSelection(); if (s) { if (s.rangeCount !== 0) { const r = s.getRangeAt(0); const p = r.cloneRange(); p.selectNodeContents(e); p.setEnd(r.endContainer, r.endOffset); pos = p.toString().length; } } return pos; }; /** * Invert keys and values */ export const invert = function (o) { const d = []; const k = Object.keys(o); for (let i = 0; i < k.length; i++) { d[o[k[i]]] = k[i]; } return d; }; /** * Get letter based on a number * * @param {number} columnNumber * @return string letter */ export const getColumnName = function (columnNumber) { let dividend = columnNumber + 1; let columnName = ''; let modulo; while (dividend > 0) { modulo = (dividend - 1) % 26; columnName = String.fromCharCode(65 + modulo).toString() + columnName; dividend = parseInt((dividend - modulo) / 26); } return columnName; }; /** * Get column name from coords */ export const getCellNameFromCoords = function (x, y) { return getColumnName(parseInt(x)) + (parseInt(y) + 1); }; export const getCoordsFromCellName = function (columnName) { // Get the letters const t = /^[a-zA-Z]+/.exec(columnName); if (t) { // Base 26 calculation let code = 0; for (let i = 0; i < t[0].length; i++) { code += parseInt(t[0].charCodeAt(i) - 64) * Math.pow(26, t[0].length - 1 - i); } code--; // Make sure jspreadsheet starts on zero if (code < 0) { code = 0; } // Number let number = parseInt(/[0-9]+$/.exec(columnName)) || null; if (number > 0) { number--; } return [code, number]; } }; export const getCoordsFromRange = function (range) { const [start, end] = range.split(':'); return [...getCoordsFromCellName(start), ...getCoordsFromCellName(end)]; }; /** * From stack overflow contributions */ export const parseCSV = function (str, delimiter) { // user-supplied delimeter or default comma delimiter = delimiter || ','; // Remove last line break str = str.replace(/\r?\n$|\r$|\n$/g, ''); const arr = []; let quote = false; // true means we're inside a quoted field // iterate over each character, keep track of current row and column (of the returned array) let maxCol = 0; let row = 0, col = 0; for (let c = 0; c < str.length; c++) { const cc = str[c], nc = str[c + 1]; arr[row] = arr[row] || []; arr[row][col] = arr[row][col] || ''; // If the current character is a quotation mark, and we're inside a quoted field, and the next character is also a quotation mark, add a quotation mark to the current column and skip the next character if (cc == '"' && quote && nc == '"') { arr[row][col] += cc; ++c; continue; } // If it's just one quotation mark, begin/end quoted field if (cc == '"') { quote = !quote; continue; } // If it's a comma and we're not in a quoted field, move on to the next column if (cc == delimiter && !quote) { ++col; continue; } // If it's a newline (CRLF) and we're not in a quoted field, skip the next character and move on to the next row and move to column 0 of that new row if (cc == '\r' && nc == '\n' && !quote) { ++row; maxCol = Math.max(maxCol, col); col = 0; ++c; continue; } // If it's a newline (LF or CR) and we're not in a quoted field, move on to the next row and move to column 0 of that new row if (cc == '\n' && !quote) { ++row; maxCol = Math.max(maxCol, col); col = 0; continue; } if (cc == '\r' && !quote) { ++row; maxCol = Math.max(maxCol, col); col = 0; continue; } // Otherwise, append the current character to the current column arr[row][col] += cc; } // fix array length arr.forEach((row, i) => { for (let i = row.length; i <= maxCol; i++) { row.push(''); } }); return arr; }; export const createFromTable = function (el, options) { if (el.tagName != 'TABLE') { console.log('Element is not a table'); } else { // Configuration if (!options) { options = {}; } options.columns = []; options.data = []; // Colgroup const colgroup = el.querySelectorAll('colgroup > col'); if (colgroup.length) { // Get column width for (let i = 0; i < colgroup.length; i++) { let width = colgroup[i].style.width; if (!width) { width = colgroup[i].getAttribute('width'); } // Set column width if (width) { if (!options.columns[i]) { options.columns[i] = {}; } options.columns[i].width = width; } } } // Parse header const parseHeader = function (header, i) { // Get width information let info = header.getBoundingClientRect(); const width = info.width > 50 ? info.width : 50; // Create column option if (!options.columns[i]) { options.columns[i] = {}; } if (header.getAttribute('data-celltype')) { options.columns[i].type = header.getAttribute('data-celltype'); } else { options.columns[i].type = 'text'; } options.columns[i].width = width + 'px'; options.columns[i].title = header.innerHTML; if (header.style.textAlign) { options.columns[i].align = header.style.textAlign; } if ((info = header.getAttribute('name'))) { options.columns[i].name = info; } if ((info = header.getAttribute('id'))) { options.columns[i].id = info; } if ((info = header.getAttribute('data-mask'))) { options.columns[i].mask = info; } }; // Headers const nested = []; let headers = el.querySelectorAll(':scope > thead > tr'); if (headers.length) { for (let j = 0; j < headers.length - 1; j++) { const cells = []; for (let i = 0; i < headers[j].children.length; i++) { const row = { title: headers[j].children[i].textContent, colspan: headers[j].children[i].getAttribute('colspan') || 1, }; cells.push(row); } nested.push(cells); } // Get the last row in the thead headers = headers[headers.length - 1].children; // Go though the headers for (let i = 0; i < headers.length; i++) { parseHeader(headers[i], i); } } // Content let rowNumber = 0; const mergeCells = {}; const rows = {}; const style = {}; const classes = {}; let content = el.querySelectorAll(':scope > tr, :scope > tbody > tr'); for (let j = 0; j < content.length; j++) { options.data[rowNumber] = []; if (options.parseTableFirstRowAsHeader == true && !headers.length && j == 0) { for (let i = 0; i < content[j].children.length; i++) { parseHeader(content[j].children[i], i); } } else { for (let i = 0; i < content[j].children.length; i++) { // WickedGrid formula compatibility let value = content[j].children[i].getAttribute('data-formula'); if (value) { if (value.substr(0, 1) != '=') { value = '=' + value; } } else { value = content[j].children[i].innerHTML; } options.data[rowNumber].push(value); // Key const cellName = getColumnNameFromId([i, j]); // Classes const tmp = content[j].children[i].getAttribute('class'); if (tmp) { classes[cellName] = tmp; } // Merged cells const mergedColspan = parseInt(content[j].children[i].getAttribute('colspan')) || 0; const mergedRowspan = parseInt(content[j].children[i].getAttribute('rowspan')) || 0; if (mergedColspan || mergedRowspan) { mergeCells[cellName] = [mergedColspan || 1, mergedRowspan || 1]; } // Avoid problems with hidden cells if (content[j].children[i].style && content[j].children[i].style.display == 'none') { content[j].children[i].style.display = ''; } // Get style const s = content[j].children[i].getAttribute('style'); if (s) { style[cellName] = s; } // Bold if (content[j].children[i].classList.contains('styleBold')) { if (style[cellName]) { style[cellName] += '; font-weight:bold;'; } else { style[cellName] = 'font-weight:bold;'; } } } // Row Height if (content[j].style && content[j].style.height) { rows[j] = { height: content[j].style.height }; } // Index rowNumber++; } } // Nested if (Object.keys(nested).length > 0) { options.nestedHeaders = nested; } // Style if (Object.keys(style).length > 0) { options.style = style; } // Merged if (Object.keys(mergeCells).length > 0) { options.mergeCells = mergeCells; } // Row height if (Object.keys(rows).length > 0) { options.rows = rows; } // Classes if (Object.keys(classes).length > 0) { options.classes = classes; } content = el.querySelectorAll('tfoot tr'); if (content.length) { const footers = []; for (let j = 0; j < content.length; j++) { let footer = []; for (let i = 0; i < content[j].children.length; i++) { footer.push(content[j].children[i].textContent); } footers.push(footer); } if (Object.keys(footers).length > 0) { options.footers = footers; } } // TODO: data-hiddencolumns="3,4" // I guess in terms the better column type if (options.parseTableAutoCellType == true) { const pattern = []; for (let i = 0; i < options.columns.length; i++) { let test = true; let testCalendar = true; pattern[i] = []; for (let j = 0; j < options.data.length; j++) { const value = options.data[j][i]; if (!pattern[i][value]) { pattern[i][value] = 0; } pattern[i][value]++; if (value.length > 25) { test = false; } if (value.length == 10) { if (!(value.substr(4, 1) == '-' && value.substr(7, 1) == '-')) { testCalendar = false; } } else { testCalendar = false; } } const keys = Object.keys(pattern[i]).length; if (testCalendar) { options.columns[i].type = 'calendar'; } else if (test == true && keys > 1 && keys <= parseInt(options.data.length * 0.1)) { options.columns[i].type = 'dropdown'; options.columns[i].source = Object.keys(pattern[i]); } } } return options; } }; ================================================ FILE: src/utils/history.js ================================================ import dispatch from './dispatch.js'; import { injectArray } from './internalHelpers.js'; import { updateTableReferences } from './internal.js'; import { setMerge } from './merges.js'; import { updateOrder, updateOrderArrow } from './orderBy.js'; import { conditionalSelectionUpdate } from './selection.js'; /** * Initializes a new history record for undo/redo * * @return null */ export const setHistory = function (changes) { const obj = this; if (obj.ignoreHistory != true) { // Increment and get the current history index const index = ++obj.historyIndex; // Slice the array to discard undone changes obj.history = obj.history = obj.history.slice(0, index + 1); // Keep history obj.history[index] = changes; } }; /** * Process row */ const historyProcessRow = function (type, historyRecord) { const obj = this; const rowIndex = !historyRecord.insertBefore ? historyRecord.rowNumber + 1 : +historyRecord.rowNumber; if (obj.options.search == true) { if (obj.results && obj.results.length != obj.rows.length) { obj.resetSearch(); } } // Remove row if (type == 1) { const numOfRows = historyRecord.numOfRows; // Remove nodes for (let j = rowIndex; j < numOfRows + rowIndex; j++) { obj.rows[j].element.parentNode.removeChild(obj.rows[j].element); } // Remove references obj.records.splice(rowIndex, numOfRows); obj.options.data.splice(rowIndex, numOfRows); obj.rows.splice(rowIndex, numOfRows); conditionalSelectionUpdate.call(obj, 1, rowIndex, numOfRows + rowIndex - 1); } else { // Insert data const records = historyRecord.rowRecords.map((row) => { return [...row]; }); obj.records = injectArray(obj.records, rowIndex, records); const data = historyRecord.rowData.map((row) => { return [...row]; }); obj.options.data = injectArray(obj.options.data, rowIndex, data); obj.rows = injectArray(obj.rows, rowIndex, historyRecord.rowNode); // Insert nodes let index = 0; for (let j = rowIndex; j < historyRecord.numOfRows + rowIndex; j++) { obj.tbody.insertBefore(historyRecord.rowNode[index].element, obj.tbody.children[j]); index++; } } for (let j = rowIndex; j < obj.rows.length; j++) { obj.rows[j].y = j; } for (let j = rowIndex; j < obj.records.length; j++) { for (let i = 0; i < obj.records[j].length; i++) { obj.records[j][i].y = j; } } // Respect pagination if (obj.options.pagination > 0) { obj.page(obj.pageNumber); } updateTableReferences.call(obj); }; /** * Process column */ const historyProcessColumn = function (type, historyRecord) { const obj = this; const columnIndex = !historyRecord.insertBefore ? historyRecord.columnNumber + 1 : historyRecord.columnNumber; // Remove column if (type == 1) { const numOfColumns = historyRecord.numOfColumns; obj.options.columns.splice(columnIndex, numOfColumns); for (let i = columnIndex; i < numOfColumns + columnIndex; i++) { obj.headers[i].parentNode.removeChild(obj.headers[i]); obj.cols[i].colElement.parentNode.removeChild(obj.cols[i].colElement); } obj.headers.splice(columnIndex, numOfColumns); obj.cols.splice(columnIndex, numOfColumns); for (let j = 0; j < historyRecord.data.length; j++) { for (let i = columnIndex; i < numOfColumns + columnIndex; i++) { obj.records[j][i].element.parentNode.removeChild(obj.records[j][i].element); } obj.records[j].splice(columnIndex, numOfColumns); obj.options.data[j].splice(columnIndex, numOfColumns); } // Process footers if (obj.options.footers) { for (let j = 0; j < obj.options.footers.length; j++) { obj.options.footers[j].splice(columnIndex, numOfColumns); } } } else { // Insert data obj.options.columns = injectArray(obj.options.columns, columnIndex, historyRecord.columns); obj.headers = injectArray(obj.headers, columnIndex, historyRecord.headers); obj.cols = injectArray(obj.cols, columnIndex, historyRecord.cols); let index = 0; for (let i = columnIndex; i < historyRecord.numOfColumns + columnIndex; i++) { obj.headerContainer.insertBefore(historyRecord.headers[index], obj.headerContainer.children[i + 1]); obj.colgroupContainer.insertBefore(historyRecord.cols[index].colElement, obj.colgroupContainer.children[i + 1]); index++; } for (let j = 0; j < historyRecord.data.length; j++) { obj.options.data[j] = injectArray(obj.options.data[j], columnIndex, historyRecord.data[j]); obj.records[j] = injectArray(obj.records[j], columnIndex, historyRecord.records[j]); let index = 0; for (let i = columnIndex; i < historyRecord.numOfColumns + columnIndex; i++) { obj.rows[j].element.insertBefore(historyRecord.records[j][index].element, obj.rows[j].element.children[i + 1]); index++; } } // Process footers if (obj.options.footers) { for (let j = 0; j < obj.options.footers.length; j++) { obj.options.footers[j] = injectArray(obj.options.footers[j], columnIndex, historyRecord.footers[j]); } } } for (let i = columnIndex; i < obj.cols.length; i++) { obj.cols[i].x = i; } for (let j = 0; j < obj.records.length; j++) { for (let i = columnIndex; i < obj.records[j].length; i++) { obj.records[j][i].x = i; } } // Adjust nested headers if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0 && obj.options.nestedHeaders[0] && obj.options.nestedHeaders[0][0]) { for (let j = 0; j < obj.options.nestedHeaders.length; j++) { let colspan; if (type == 1) { colspan = parseInt(obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length - 1].colspan) - historyRecord.numOfColumns; } else { colspan = parseInt(obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length - 1].colspan) + historyRecord.numOfColumns; } obj.options.nestedHeaders[j][obj.options.nestedHeaders[j].length - 1].colspan = colspan; obj.thead.children[j].children[obj.thead.children[j].children.length - 1].setAttribute('colspan', colspan); } } updateTableReferences.call(obj); }; /** * Undo last action */ export const undo = function () { const obj = this; // Ignore events and history const ignoreEvents = obj.parent.ignoreEvents ? true : false; const ignoreHistory = obj.ignoreHistory ? true : false; obj.parent.ignoreEvents = true; obj.ignoreHistory = true; // Records const records = []; // Update cells let historyRecord; if (obj.historyIndex >= 0) { // History historyRecord = obj.history[obj.historyIndex--]; if (historyRecord.action == 'insertRow') { historyProcessRow.call(obj, 1, historyRecord); } else if (historyRecord.action == 'deleteRow') { historyProcessRow.call(obj, 0, historyRecord); } else if (historyRecord.action == 'insertColumn') { historyProcessColumn.call(obj, 1, historyRecord); } else if (historyRecord.action == 'deleteColumn') { historyProcessColumn.call(obj, 0, historyRecord); } else if (historyRecord.action == 'moveRow') { obj.moveRow(historyRecord.newValue, historyRecord.oldValue); } else if (historyRecord.action == 'moveColumn') { obj.moveColumn(historyRecord.newValue, historyRecord.oldValue); } else if (historyRecord.action == 'setMerge') { obj.removeMerge(historyRecord.column, historyRecord.data); } else if (historyRecord.action == 'setStyle') { obj.setStyle(historyRecord.oldValue, null, null, 1); } else if (historyRecord.action == 'setWidth') { obj.setWidth(historyRecord.column, historyRecord.oldValue); } else if (historyRecord.action == 'setHeight') { obj.setHeight(historyRecord.row, historyRecord.oldValue); } else if (historyRecord.action == 'setHeader') { obj.setHeader(historyRecord.column, historyRecord.oldValue); } else if (historyRecord.action == 'setComments') { obj.setComments(historyRecord.oldValue); } else if (historyRecord.action == 'orderBy') { let rows = []; for (let j = 0; j < historyRecord.rows.length; j++) { rows[historyRecord.rows[j]] = j; } updateOrderArrow.call(obj, historyRecord.column, historyRecord.order ? 0 : 1); updateOrder.call(obj, rows); } else if (historyRecord.action == 'setValue') { // Redo for changes in cells for (let i = 0; i < historyRecord.records.length; i++) { records.push({ x: historyRecord.records[i].x, y: historyRecord.records[i].y, value: historyRecord.records[i].oldValue, }); if (historyRecord.oldStyle) { obj.resetStyle(historyRecord.oldStyle); } } // Update records obj.setValue(records); // Update selection if (historyRecord.selection) { obj.updateSelectionFromCoords(historyRecord.selection[0], historyRecord.selection[1], historyRecord.selection[2], historyRecord.selection[3]); } } } obj.parent.ignoreEvents = ignoreEvents; obj.ignoreHistory = ignoreHistory; // Events dispatch.call(obj, 'onundo', obj, historyRecord); }; /** * Redo previously undone action */ export const redo = function () { const obj = this; // Ignore events and history const ignoreEvents = obj.parent.ignoreEvents ? true : false; const ignoreHistory = obj.ignoreHistory ? true : false; obj.parent.ignoreEvents = true; obj.ignoreHistory = true; // Records var records = []; // Update cells let historyRecord; if (obj.historyIndex < obj.history.length - 1) { // History historyRecord = obj.history[++obj.historyIndex]; if (historyRecord.action == 'insertRow') { historyProcessRow.call(obj, 0, historyRecord); } else if (historyRecord.action == 'deleteRow') { historyProcessRow.call(obj, 1, historyRecord); } else if (historyRecord.action == 'insertColumn') { historyProcessColumn.call(obj, 0, historyRecord); } else if (historyRecord.action == 'deleteColumn') { historyProcessColumn.call(obj, 1, historyRecord); } else if (historyRecord.action == 'moveRow') { obj.moveRow(historyRecord.oldValue, historyRecord.newValue); } else if (historyRecord.action == 'moveColumn') { obj.moveColumn(historyRecord.oldValue, historyRecord.newValue); } else if (historyRecord.action == 'setMerge') { setMerge.call(obj, historyRecord.column, historyRecord.colspan, historyRecord.rowspan, 1); } else if (historyRecord.action == 'setStyle') { obj.setStyle(historyRecord.newValue, null, null, 1); } else if (historyRecord.action == 'setWidth') { obj.setWidth(historyRecord.column, historyRecord.newValue); } else if (historyRecord.action == 'setHeight') { obj.setHeight(historyRecord.row, historyRecord.newValue); } else if (historyRecord.action == 'setHeader') { obj.setHeader(historyRecord.column, historyRecord.newValue); } else if (historyRecord.action == 'setComments') { obj.setComments(historyRecord.newValue); } else if (historyRecord.action == 'orderBy') { updateOrderArrow.call(obj, historyRecord.column, historyRecord.order); updateOrder.call(obj, historyRecord.rows); } else if (historyRecord.action == 'setValue') { obj.setValue(historyRecord.records); // Redo for changes in cells for (let i = 0; i < historyRecord.records.length; i++) { if (historyRecord.oldStyle) { obj.resetStyle(historyRecord.newStyle); } } // Update selection if (historyRecord.selection) { obj.updateSelectionFromCoords(historyRecord.selection[0], historyRecord.selection[1], historyRecord.selection[2], historyRecord.selection[3]); } } } obj.parent.ignoreEvents = ignoreEvents; obj.ignoreHistory = ignoreHistory; // Events dispatch.call(obj, 'onredo', obj, historyRecord); }; ================================================ FILE: src/utils/internal.js ================================================ import jSuites from 'jsuites'; import formula from '@jspreadsheet/formula'; import dispatch from './dispatch.js'; import { refreshSelection, updateCornerPosition } from './selection.js'; import { getColumnName } from './helpers.js'; import { updateMeta } from './meta.js'; import { getFreezeWidth } from './freeze.js'; import { updatePagination } from './pagination.js'; import { setFooter } from './footer.js'; import { getColumnNameFromId, getIdFromColumnName } from './internalHelpers.js'; export const updateTable = function () { const obj = this; // Check for spare if (obj.options.minSpareRows > 0) { let numBlankRows = 0; for (let j = obj.rows.length - 1; j >= 0; j--) { let test = false; for (let i = 0; i < obj.headers.length; i++) { if (obj.options.data[j][i]) { test = true; } } if (test) { break; } else { numBlankRows++; } } if (obj.options.minSpareRows - numBlankRows > 0) { obj.insertRow(obj.options.minSpareRows - numBlankRows); } } if (obj.options.minSpareCols > 0) { let numBlankCols = 0; for (let i = obj.headers.length - 1; i >= 0; i--) { let test = false; for (let j = 0; j < obj.rows.length; j++) { if (obj.options.data[j][i]) { test = true; } } if (test) { break; } else { numBlankCols++; } } if (obj.options.minSpareCols - numBlankCols > 0) { obj.insertColumn(obj.options.minSpareCols - numBlankCols); } } // Update footers if (obj.options.footers) { setFooter.call(obj); } if (obj.options.columns.length < obj.options.minDimensions[0]) { obj.options.minDimensions[0] = obj.options.columns.length; } // Update corner position setTimeout(function () { updateCornerPosition.call(obj); }, 0); }; /** * Trying to extract a number from a string */ const parseNumber = function (value, columnNumber) { const obj = this; // Decimal point const decimal = columnNumber && obj.options.columns[columnNumber].decimal ? obj.options.columns[columnNumber].decimal : '.'; // Parse both parts of the number let number = '' + value; number = number.split(decimal); number[0] = number[0].match(/[+-]?[0-9]/g); if (number[0]) { number[0] = number[0].join(''); } if (number[1]) { number[1] = number[1].match(/[0-9]*/g).join(''); } // Is a valid number if (number[0] && Number.isInteger(Number(number[0]))) { if (!number[1]) { value = Number(number[0] + '.00'); } else { value = Number(number[0] + '.' + number[1]); } } else { value = null; } return value; }; /** * Parse formulas */ export const executeFormula = function (expression, x, y) { const obj = this; const formulaResults = []; const formulaLoopProtection = []; // Execute formula with loop protection const execute = function (expression, x, y) { // Parent column identification const parentId = getColumnNameFromId([x, y]); // Code protection if (formulaLoopProtection[parentId]) { console.error('Reference loop detected'); return '#ERROR'; } formulaLoopProtection[parentId] = true; // Convert range tokens const tokensUpdate = function (tokens) { for (let index = 0; index < tokens.length; index++) { const f = []; const token = tokens[index].split(':'); const e1 = getIdFromColumnName(token[0], true); const e2 = getIdFromColumnName(token[1], true); let x1, x2; if (e1[0] <= e2[0]) { x1 = e1[0]; x2 = e2[0]; } else { x1 = e2[0]; x2 = e1[0]; } let y1, y2; if (e1[1] <= e2[1]) { y1 = e1[1]; y2 = e2[1]; } else { y1 = e2[1]; y2 = e1[1]; } for (let j = y1; j <= y2; j++) { for (let i = x1; i <= x2; i++) { f.push(getColumnNameFromId([i, j])); } } expression = expression.replace(tokens[index], f.join(',')); } }; // Range with $ remove $ expression = expression.replace(/\$?([A-Z]+)\$?([0-9]+)/g, '$1$2'); let tokens = expression.match(/([A-Z]+[0-9]+):([A-Z]+[0-9]+)/g); if (tokens && tokens.length) { tokensUpdate(tokens); } // Get tokens tokens = expression.match(/([A-Z]+[0-9]+)/g); // Direct self-reference protection if (tokens && tokens.indexOf(parentId) > -1) { console.error('Self Reference detected'); return '#ERROR'; } else { // Expressions to be used in the parsing const formulaExpressions = {}; if (tokens) { for (let i = 0; i < tokens.length; i++) { // Keep chain if (!obj.formula[tokens[i]]) { obj.formula[tokens[i]] = []; } // Is already in the register if (obj.formula[tokens[i]].indexOf(parentId) < 0) { obj.formula[tokens[i]].push(parentId); } // Do not calculate again if (eval('typeof(' + tokens[i] + ') == "undefined"')) { // Coords const position = getIdFromColumnName(tokens[i], 1); // Get value let value; if (typeof obj.options.data[position[1]] != 'undefined' && typeof obj.options.data[position[1]][position[0]] != 'undefined') { value = obj.options.data[position[1]][position[0]]; } else { value = ''; } // Get column data if (('' + value).substr(0, 1) == '=') { if (typeof formulaResults[tokens[i]] !== 'undefined') { value = formulaResults[tokens[i]]; } else { value = execute(value, position[0], position[1]); formulaResults[tokens[i]] = value; } } // Type! if (('' + value).trim() == '') { // Null formulaExpressions[tokens[i]] = null; } else { if (value == Number(value) && obj.parent.config.autoCasting != false) { // Number formulaExpressions[tokens[i]] = Number(value); } else { // Trying any formatted number const number = parseNumber.call(obj, value, position[0]); if (obj.parent.config.autoCasting != false && number) { formulaExpressions[tokens[i]] = number; } else { formulaExpressions[tokens[i]] = '"' + value + '"'; } } } } } } const ret = dispatch.call(obj, 'onbeforeformula', obj, expression, x, y); if (ret === false) { return expression; } else if (ret) { expression = ret; } // Convert formula to javascript let res; try { res = formula(expression.substr(1), formulaExpressions, x, y, obj); if (typeof res === 'function') { res = '#ERROR'; } } catch (e) { res = '#ERROR'; if (obj.parent.config.debugFormulas === true) { console.log(expression.substr(1), formulaExpressions, e); } } return res; } }; return execute(expression, x, y); }; export const parseValue = function (i, j, value, cell) { const obj = this; if (('' + value).substr(0, 1) == '=' && obj.parent.config.parseFormulas != false) { value = executeFormula.call(obj, value, i, j); } // Column options const options = obj.options.columns && obj.options.columns[i]; if (options && !isFormula(value)) { // Mask options let opt = null; if ((opt = getMask(options))) { if (value && value == Number(value)) { value = Number(value); } // Process the decimals to match the mask let masked = jSuites.mask.render(value, opt, true); // Negative indication if (cell) { if (opt.mask) { const t = opt.mask.split(';'); if (t[1]) { const t1 = t[1].match(new RegExp('\\[Red\\]', 'gi')); if (t1) { if (value < 0) { cell.classList.add('red'); } else { cell.classList.remove('red'); } } const t2 = t[1].match(new RegExp('\\(', 'gi')); if (t2) { if (value < 0) { masked = '(' + masked + ')'; } } } } } if (masked) { value = masked; } } } return value; }; /** * Get dropdown value from key */ const getDropDownValue = function (column, key) { const obj = this; const value = []; if (obj.options.columns && obj.options.columns[column] && obj.options.columns[column].source) { // Create array from source const combo = []; const source = obj.options.columns[column].source; for (let i = 0; i < source.length; i++) { if (typeof source[i] == 'object') { combo[source[i].id] = source[i].name; } else { combo[source[i]] = source[i]; } } // Guarantee single multiple compatibility const keys = Array.isArray(key) ? key : ('' + key).split(';'); for (let i = 0; i < keys.length; i++) { if (typeof keys[i] === 'object') { value.push(combo[keys[i].id]); } else { if (combo[keys[i]]) { value.push(combo[keys[i]]); } } } } else { console.error('Invalid column'); } return value.length > 0 ? value.join('; ') : ''; }; const validDate = function (date) { date = '' + date; if (date.substr(4, 1) == '-' && date.substr(7, 1) == '-') { return true; } else { date = date.split('-'); if (date[0].length == 4 && date[0] == Number(date[0]) && date[1].length == 2 && date[1] == Number(date[1])) { return true; } } return false; }; /** * Strip tags */ const stripScript = function (a) { const b = new Option(); b.innerHTML = a; let c = null; for (a = b.getElementsByTagName('script'); (c = a[0]); ) c.parentNode.removeChild(c); return b.innerHTML; }; export const createCell = function (i, j, value) { const obj = this; // Create cell and properties let td = document.createElement('td'); td.setAttribute('data-x', i); td.setAttribute('data-y', j); if (obj.headers[i].style.display === 'none') { td.style.display = 'none'; } // Security if (('' + value).substr(0, 1) == '=' && obj.options.secureFormulas == true) { const val = secureFormula(value); if (val != value) { // Update the data container value = val; } } // Custom column if (obj.options.columns && obj.options.columns[i] && typeof obj.options.columns[i].type === 'object') { if (obj.parent.config.parseHTML === true) { td.innerHTML = value; } else { td.textContent = value; } if (typeof obj.options.columns[i].type.createCell == 'function') { obj.options.columns[i].type.createCell(td, value, parseInt(i), parseInt(j), obj, obj.options.columns[i]); } } else { // Hidden column if (obj.options.columns && obj.options.columns[i] && obj.options.columns[i].type == 'hidden') { td.style.display = 'none'; td.textContent = value; } else if (obj.options.columns && obj.options.columns[i] && (obj.options.columns[i].type == 'checkbox' || obj.options.columns[i].type == 'radio')) { // Create input const element = document.createElement('input'); element.type = obj.options.columns[i].type; element.name = 'c' + i; element.checked = value == 1 || value == true || value == 'true' ? true : false; element.onclick = function () { obj.setValue(td, this.checked); }; if (obj.options.columns[i].readOnly == true || obj.options.editable == false) { element.setAttribute('disabled', 'disabled'); } // Append to the table td.appendChild(element); // Make sure the values are correct obj.options.data[j][i] = element.checked; } else if (obj.options.columns && obj.options.columns[i] && obj.options.columns[i].type == 'calendar') { // Try formatted date let formatted = null; if (!validDate(value)) { const tmp = jSuites.calendar.extractDateFromString( value, (obj.options.columns[i].options && obj.options.columns[i].options.format) || 'YYYY-MM-DD' ); if (tmp) { formatted = tmp; } } // Create calendar cell td.textContent = jSuites.calendar.getDateString( formatted ? formatted : value, obj.options.columns[i].options && obj.options.columns[i].options.format ); } else if (obj.options.columns && obj.options.columns[i] && obj.options.columns[i].type == 'dropdown') { // Create dropdown cell td.classList.add('jss_dropdown'); td.textContent = getDropDownValue.call(obj, i, value); } else if (obj.options.columns && obj.options.columns[i] && obj.options.columns[i].type == 'color') { if (obj.options.columns[i].render == 'square') { const color = document.createElement('div'); color.className = 'color'; color.style.backgroundColor = value; td.appendChild(color); } else { td.style.color = value; td.textContent = value; } } else if (obj.options.columns && obj.options.columns[i] && obj.options.columns[i].type == 'image') { if (value && value.substr(0, 10) == 'data:image') { const img = document.createElement('img'); img.src = value; td.appendChild(img); } } else { if (obj.options.columns && obj.options.columns[i] && obj.options.columns[i].type == 'html') { td.innerHTML = stripScript(parseValue.call(this, i, j, value, td)); } else { if (obj.parent.config.parseHTML === true) { td.innerHTML = stripScript(parseValue.call(this, i, j, value, td)); } else { td.textContent = parseValue.call(this, i, j, value, td); } } } } // Readonly if (obj.options.columns && obj.options.columns[i] && obj.options.columns[i].readOnly == true) { td.className = 'readonly'; } // Text align const colAlign = (obj.options.columns && obj.options.columns[i] && obj.options.columns[i].align) || obj.options.defaultColAlign || 'center'; td.style.textAlign = colAlign; // Wrap option if ( (!obj.options.columns || !obj.options.columns[i] || obj.options.columns[i].wordWrap != false) && (obj.options.wordWrap == true || (obj.options.columns && obj.options.columns[i] && obj.options.columns[i].wordWrap == true) || td.innerHTML.length > 200) ) { td.style.whiteSpace = 'pre-wrap'; } // Overflow if (i > 0) { if (this.options.textOverflow == true) { if (value || td.innerHTML) { obj.records[j][i - 1].element.style.overflow = 'hidden'; } else { if (i == obj.options.columns.length - 1) { td.style.overflow = 'hidden'; } } } } dispatch.call(obj, 'oncreatecell', obj, td, i, j, value); return td; }; /** * Update cell content * * @param object cell * @return void */ export const updateCell = function (x, y, value, force) { const obj = this; let record; // Changing value depending on the column type if (obj.records[y][x].element.classList.contains('readonly') == true && !force) { // Do nothing record = { x: x, y: y, col: x, row: y, }; } else { // Security if (('' + value).substr(0, 1) == '=' && obj.options.secureFormulas == true) { const val = secureFormula(value); if (val != value) { // Update the data container value = val; } } // On change const val = dispatch.call(obj, 'onbeforechange', obj, obj.records[y][x].element, x, y, value); // If you return something this will overwrite the value if (val != undefined) { value = val; } if ( obj.options.columns && obj.options.columns[x] && typeof obj.options.columns[x].type === 'object' && typeof obj.options.columns[x].type.updateCell === 'function' ) { const result = obj.options.columns[x].type.updateCell(obj.records[y][x].element, value, parseInt(x), parseInt(y), obj, obj.options.columns[x]); if (result !== undefined) { value = result; } } // History format record = { x: x, y: y, col: x, row: y, value: value, oldValue: obj.options.data[y][x], }; let editor = obj.options.columns && obj.options.columns[x] && typeof obj.options.columns[x].type === 'object' ? obj.options.columns[x].type : null; if (editor) { // Update data and cell obj.options.data[y][x] = value; if (typeof editor.setValue === 'function') { editor.setValue(obj.records[y][x].element, value); } } else { // Native functions if (obj.options.columns && obj.options.columns[x] && (obj.options.columns[x].type == 'checkbox' || obj.options.columns[x].type == 'radio')) { // Unchecked all options if (obj.options.columns[x].type == 'radio') { for (let j = 0; j < obj.options.data.length; j++) { obj.options.data[j][x] = false; } } // Update data and cell obj.records[y][x].element.children[0].checked = value == 1 || value == true || value == 'true' || value == 'TRUE' ? true : false; obj.options.data[y][x] = obj.records[y][x].element.children[0].checked; } else if (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].type == 'dropdown') { // Update data and cell obj.options.data[y][x] = value; obj.records[y][x].element.textContent = getDropDownValue.call(obj, x, value); } else if (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].type == 'calendar') { // Try formatted date let formatted = null; if (!validDate(value)) { const tmp = jSuites.calendar.extractDateFromString( value, (obj.options.columns[x].options && obj.options.columns[x].options.format) || 'YYYY-MM-DD' ); if (tmp) { formatted = tmp; } } // Update data and cell obj.options.data[y][x] = value; obj.records[y][x].element.textContent = jSuites.calendar.getDateString( formatted ? formatted : value, obj.options.columns[x].options && obj.options.columns[x].options.format ); } else if (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].type == 'color') { // Update color obj.options.data[y][x] = value; // Render if (obj.options.columns[x].render == 'square') { const color = document.createElement('div'); color.className = 'color'; color.style.backgroundColor = value; obj.records[y][x].element.textContent = ''; obj.records[y][x].element.appendChild(color); } else { obj.records[y][x].element.style.color = value; obj.records[y][x].element.textContent = value; } } else if (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].type == 'image') { value = '' + value; obj.options.data[y][x] = value; obj.records[y][x].element.innerHTML = ''; if (value && value.substr(0, 10) == 'data:image') { const img = document.createElement('img'); img.src = value; obj.records[y][x].element.appendChild(img); } } else { // Update data and cell obj.options.data[y][x] = value; // Label if (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].type == 'html') { obj.records[y][x].element.innerHTML = stripScript(parseValue.call(obj, x, y, value)); } else { if (obj.parent.config.parseHTML === true) { obj.records[y][x].element.innerHTML = stripScript(parseValue.call(obj, x, y, value, obj.records[y][x].element)); } else { obj.records[y][x].element.textContent = parseValue.call(obj, x, y, value, obj.records[y][x].element); } } // Handle big text inside a cell if ( (!obj.options.columns || !obj.options.columns[x] || obj.options.columns[x].wordWrap != false) && (obj.options.wordWrap == true || (obj.options.columns && obj.options.columns[x] && obj.options.columns[x].wordWrap == true) || obj.records[y][x].element.innerHTML.length > 200) ) { obj.records[y][x].element.style.whiteSpace = 'pre-wrap'; } else { obj.records[y][x].element.style.whiteSpace = ''; } } } // Overflow if (x > 0) { if (value) { obj.records[y][x - 1].element.style.overflow = 'hidden'; } else { obj.records[y][x - 1].element.style.overflow = ''; } } if (obj.options.columns && obj.options.columns[x] && typeof obj.options.columns[x].render === 'function') { obj.options.columns[x].render( obj.records[y] && obj.records[y][x] ? obj.records[y][x].element : null, value, parseInt(x), parseInt(y), obj, obj.options.columns[x] ); } // On change dispatch.call(obj, 'onchange', obj, obj.records[y] && obj.records[y][x] ? obj.records[y][x].element : null, x, y, value, record.oldValue); } return record; }; /** * The value is a formula */ export const isFormula = function (value) { const v = ('' + value)[0]; return v == '=' || v == '#' ? true : false; }; /** * Get the mask in the jSuites.mask format */ export const getMask = function (o) { if (o.format || o.mask || o.locale) { const opt = {}; if (o.mask) { opt.mask = o.mask; } else if (o.format) { opt.mask = o.format; } else { opt.locale = o.locale; opt.options = o.options; } if (o.decimal) { if (!opt.options) { opt.options = {}; } opt.options = { decimal: o.decimal }; } return opt; } return null; }; /** * Secure formula */ const secureFormula = function (oldValue) { let newValue = ''; let inside = 0; for (let i = 0; i < oldValue.length; i++) { if (oldValue[i] == '"') { if (inside == 0) { inside = 1; } else { inside = 0; } } if (inside == 1) { newValue += oldValue[i]; } else { newValue += oldValue[i].toUpperCase(); } } return newValue; }; /** * Update all related cells in the chain */ let chainLoopProtection = []; export const updateFormulaChain = function (x, y, records) { const obj = this; const cellId = getColumnNameFromId([x, y]); if (obj.formula[cellId] && obj.formula[cellId].length > 0) { if (chainLoopProtection[cellId]) { obj.records[y][x].element.innerHTML = '#ERROR'; obj.formula[cellId] = ''; } else { // Protection chainLoopProtection[cellId] = true; for (let i = 0; i < obj.formula[cellId].length; i++) { const cell = getIdFromColumnName(obj.formula[cellId][i], true); // Update cell const value = '' + obj.options.data[cell[1]][cell[0]]; if (value.substr(0, 1) == '=') { records.push(updateCell.call(obj, cell[0], cell[1], value, true)); } else { // No longer a formula, remove from the chain Object.keys(obj.formula)[i] = null; } updateFormulaChain.call(obj, cell[0], cell[1], records); } } } chainLoopProtection = []; }; /** * Update formula */ export const updateFormula = function (formula, referencesToUpdate) { const testLetter = /[A-Z]/; const testNumber = /[0-9]/; let newFormula = ''; let letter = null; let number = null; let token = ''; for (let index = 0; index < formula.length; index++) { if (testLetter.exec(formula[index])) { letter = 1; number = 0; token += formula[index]; } else if (testNumber.exec(formula[index])) { number = letter ? 1 : 0; token += formula[index]; } else { if (letter && number) { token = referencesToUpdate[token] ? referencesToUpdate[token] : token; } newFormula += token; newFormula += formula[index]; letter = 0; number = 0; token = ''; } } if (token) { if (letter && number) { token = referencesToUpdate[token] ? referencesToUpdate[token] : token; } newFormula += token; } return newFormula; }; /** * Update formulas */ const updateFormulas = function (referencesToUpdate) { const obj = this; // Update formulas for (let j = 0; j < obj.options.data.length; j++) { for (let i = 0; i < obj.options.data[0].length; i++) { const value = '' + obj.options.data[j][i]; // Is formula if (value.substr(0, 1) == '=') { // Replace tokens const newFormula = updateFormula(value, referencesToUpdate); if (newFormula != value) { obj.options.data[j][i] = newFormula; } } } } // Update formula chain const formula = []; const keys = Object.keys(obj.formula); for (let j = 0; j < keys.length; j++) { // Current key and values let key = keys[j]; const value = obj.formula[key]; // Update key if (referencesToUpdate[key]) { key = referencesToUpdate[key]; } // Update values formula[key] = []; for (let i = 0; i < value.length; i++) { let letter = value[i]; if (referencesToUpdate[letter]) { letter = referencesToUpdate[letter]; } formula[key].push(letter); } } obj.formula = formula; }; /** * Update cell references * * @return void */ export const updateTableReferences = function () { const obj = this; if (obj.skipUpdateTableReferences) { return; } // Update headers for (let i = 0; i < obj.headers.length; i++) { const x = obj.headers[i].getAttribute('data-x'); if (x != i) { // Update coords obj.headers[i].setAttribute('data-x', i); // Title if (!obj.headers[i].getAttribute('title')) { obj.headers[i].innerHTML = getColumnName(i); } } } // Update all rows for (let j = 0; j < obj.rows.length; j++) { if (obj.rows[j]) { const y = obj.rows[j].element.getAttribute('data-y'); if (y != j) { // Update coords obj.rows[j].element.setAttribute('data-y', j); obj.rows[j].element.children[0].setAttribute('data-y', j); // Row number obj.rows[j].element.children[0].innerHTML = j + 1; } } } // Regular cells affected by this change const affectedTokens = []; const mergeCellUpdates = []; // Update cell const updatePosition = function (x, y, i, j) { if (x != i) { obj.records[j][i].element.setAttribute('data-x', i); } if (y != j) { obj.records[j][i].element.setAttribute('data-y', j); } // Other updates if (x != i || y != j) { const columnIdFrom = getColumnNameFromId([x, y]); const columnIdTo = getColumnNameFromId([i, j]); affectedTokens[columnIdFrom] = columnIdTo; } }; for (let j = 0; j < obj.records.length; j++) { for (let i = 0; i < obj.records[0].length; i++) { if (obj.records[j][i]) { // Current values const x = obj.records[j][i].element.getAttribute('data-x'); const y = obj.records[j][i].element.getAttribute('data-y'); // Update column if (obj.records[j][i].element.getAttribute('data-merged')) { const columnIdFrom = getColumnNameFromId([x, y]); const columnIdTo = getColumnNameFromId([i, j]); if (mergeCellUpdates[columnIdFrom] == null) { if (columnIdFrom == columnIdTo) { mergeCellUpdates[columnIdFrom] = false; } else { const totalX = parseInt(i - x); const totalY = parseInt(j - y); mergeCellUpdates[columnIdFrom] = [columnIdTo, totalX, totalY]; } } } else { updatePosition(x, y, i, j); } } } } // Update merged if applicable const keys = Object.keys(mergeCellUpdates); if (keys.length) { for (let i = 0; i < keys.length; i++) { if (mergeCellUpdates[keys[i]]) { const info = getIdFromColumnName(keys[i], true); let x = info[0]; let y = info[1]; updatePosition(x, y, x + mergeCellUpdates[keys[i]][1], y + mergeCellUpdates[keys[i]][2]); const columnIdFrom = keys[i]; const columnIdTo = mergeCellUpdates[keys[i]][0]; for (let j = 0; j < obj.options.mergeCells[columnIdFrom][2].length; j++) { x = parseInt(obj.options.mergeCells[columnIdFrom][2][j].getAttribute('data-x')); y = parseInt(obj.options.mergeCells[columnIdFrom][2][j].getAttribute('data-y')); obj.options.mergeCells[columnIdFrom][2][j].setAttribute('data-x', x + mergeCellUpdates[keys[i]][1]); obj.options.mergeCells[columnIdFrom][2][j].setAttribute('data-y', y + mergeCellUpdates[keys[i]][2]); } obj.options.mergeCells[columnIdTo] = obj.options.mergeCells[columnIdFrom]; delete obj.options.mergeCells[columnIdFrom]; } } } // Update formulas updateFormulas.call(obj, affectedTokens); // Update meta data updateMeta.call(obj, affectedTokens); // Refresh selection refreshSelection.call(obj); // Update table with custom configuration if applicable updateTable.call(obj); }; /** * Update scroll position based on the selection */ export const updateScroll = function (direction) { const obj = this; // Jspreadsheet Container information const contentRect = obj.content.getBoundingClientRect(); const x1 = contentRect.left; const y1 = contentRect.top; const w1 = contentRect.width; const h1 = contentRect.height; // Direction Left or Up const reference = obj.records[obj.selectedCell[3]][obj.selectedCell[2]].element; // Reference const referenceRect = reference.getBoundingClientRect(); const x2 = referenceRect.left; const y2 = referenceRect.top; const w2 = referenceRect.width; const h2 = referenceRect.height; let x, y; // Direction if (direction == 0 || direction == 1) { x = x2 - x1 + obj.content.scrollLeft; y = y2 - y1 + obj.content.scrollTop - 2; } else { x = x2 - x1 + obj.content.scrollLeft + w2; y = y2 - y1 + obj.content.scrollTop + h2; } // Top position check if (y > obj.content.scrollTop + 30 && y < obj.content.scrollTop + h1) { // In the viewport } else { // Out of viewport if (y < obj.content.scrollTop + 30) { obj.content.scrollTop = y - h2; } else { obj.content.scrollTop = y - (h1 - 2); } } // Freeze columns? const freezed = getFreezeWidth.call(obj); // Left position check - TODO: change that to the bottom border of the element if (x > obj.content.scrollLeft + freezed && x < obj.content.scrollLeft + w1) { // In the viewport } else { // Out of viewport if (x < obj.content.scrollLeft + 30) { obj.content.scrollLeft = x; if (obj.content.scrollLeft < 50) { obj.content.scrollLeft = 0; } } else if (x < obj.content.scrollLeft + freezed) { obj.content.scrollLeft = x - freezed - 1; } else { obj.content.scrollLeft = x - (w1 - 20); } } }; export const updateResult = function () { const obj = this; let total = 0; let index = 0; // Page 1 if (obj.options.lazyLoading == true) { total = 100; } else if (obj.options.pagination > 0) { total = obj.options.pagination; } else { if (obj.results) { total = obj.results.length; } else { total = obj.rows.length; } } // Reset current nodes while (obj.tbody.firstChild) { obj.tbody.removeChild(obj.tbody.firstChild); } // Hide all records from the table for (let j = 0; j < obj.rows.length; j++) { if (!obj.results || obj.results.indexOf(j) > -1) { if (index < total) { obj.tbody.appendChild(obj.rows[j].element); index++; } obj.rows[j].element.style.display = ''; } else { obj.rows[j].element.style.display = 'none'; } } // Update pagination if (obj.options.pagination > 0) { updatePagination.call(obj); } updateCornerPosition.call(obj); dispatch.call(obj, 'onupdateresult', obj, obj.results); return total; }; /** * Get the cell object * * @param object cell * @return string value */ export const getCell = function (x, y) { const obj = this; if (typeof x === 'string') { // Convert in case name is excel liked ex. A10, BB92 const cell = getIdFromColumnName(x, true); x = cell[0]; y = cell[1]; } return obj.records[y][x].element; }; /** * Get the cell object from coords * * @param object cell * @return string value */ export const getCellFromCoords = function (x, y) { const obj = this; return obj.records[y][x].element; }; /** * Get label * * @param object cell * @return string value */ export const getLabel = function (x, y) { const obj = this; if (typeof x === 'string') { // Convert in case name is excel liked ex. A10, BB92 const cell = getIdFromColumnName(x, true); x = cell[0]; y = cell[1]; } return obj.records[y][x].element.innerHTML; }; /** * Activate/Disable fullscreen * use programmatically : table.fullscreen(); or table.fullscreen(true); or table.fullscreen(false); * @Param {boolean} activate */ export const fullscreen = function (activate) { const spreadsheet = this; // If activate not defined, get reverse options.fullscreen if (activate == null) { activate = !spreadsheet.config.fullscreen; } // If change if (spreadsheet.config.fullscreen != activate) { spreadsheet.config.fullscreen = activate; // Test LazyLoading conflict if (activate == true) { spreadsheet.element.classList.add('fullscreen'); } else { spreadsheet.element.classList.remove('fullscreen'); } } }; /** * Show index column */ export const showIndex = function () { const obj = this; obj.table.classList.remove('jss_hidden_index'); }; /** * Hide index column */ export const hideIndex = function () { const obj = this; obj.table.classList.add('jss_hidden_index'); }; /** * Create a nested header object */ export const createNestedHeader = function (nestedInformation) { const obj = this; const tr = document.createElement('tr'); tr.classList.add('jss_nested'); const td = document.createElement('td'); td.classList.add('jss_selectall'); tr.appendChild(td); // Element nestedInformation.element = tr; let headerIndex = 0; for (let i = 0; i < nestedInformation.length; i++) { // Default values if (!nestedInformation[i].colspan) { nestedInformation[i].colspan = 1; } if (!nestedInformation[i].title) { nestedInformation[i].title = ''; } if (!nestedInformation[i].id) { nestedInformation[i].id = ''; } // Number of columns let numberOfColumns = nestedInformation[i].colspan; // Classes container const column = []; // Header classes for this cell for (let x = 0; x < numberOfColumns; x++) { if (obj.options.columns[headerIndex] && obj.options.columns[headerIndex].type == 'hidden') { numberOfColumns++; } column.push(headerIndex); headerIndex++; } // Created the nested cell const td = document.createElement('td'); td.setAttribute('data-column', column.join(',')); td.setAttribute('colspan', nestedInformation[i].colspan); td.setAttribute('align', nestedInformation[i].align || 'center'); td.setAttribute('id', nestedInformation[i].id); td.textContent = nestedInformation[i].title; tr.appendChild(td); } return tr; }; export const getWorksheetActive = function () { const spreadsheet = this.parent ? this.parent : this; return spreadsheet.element.tabs ? spreadsheet.element.tabs.getActive() : 0; }; export const getWorksheetInstance = function (index) { const spreadsheet = this; const worksheetIndex = typeof index !== 'undefined' ? index : getWorksheetActive.call(spreadsheet); return spreadsheet.worksheets[worksheetIndex]; }; ================================================ FILE: src/utils/internalHelpers.js ================================================ import { getColumnName } from './helpers.js'; /** * Helper injectArray */ export const injectArray = function (o, idx, arr) { if (idx <= o.length) { return o.slice(0, idx).concat(arr).concat(o.slice(idx)); } const array = o.slice(0, o.length); while (idx > array.length) { array.push(undefined); } return array.concat(arr); }; /** * Convert excel like column to jss id * * @param string id * @return string id */ export const getIdFromColumnName = function (id, arr) { // Get the letters const t = /^[a-zA-Z]+/.exec(id); if (t) { // Base 26 calculation let code = 0; for (let i = 0; i < t[0].length; i++) { code += parseInt(t[0].charCodeAt(i) - 64) * Math.pow(26, t[0].length - 1 - i); } code--; // Make sure jss starts on zero if (code < 0) { code = 0; } // Number let number = parseInt(/[0-9]+$/.exec(id)); if (number > 0) { number--; } if (arr == true) { id = [code, number]; } else { id = code + '-' + number; } } return id; }; /** * Convert jss id to excel like column name * * @param string id * @return string id */ export const getColumnNameFromId = function (cellId) { if (!Array.isArray(cellId)) { cellId = cellId.split('-'); } return getColumnName(parseInt(cellId[0])) + (parseInt(cellId[1]) + 1); }; ================================================ FILE: src/utils/keys.js ================================================ import { updateScroll } from './internal.js'; import { loadDown, loadPage, loadUp, loadValidation } from './lazyLoading.js'; const upGet = function (x, y) { const obj = this; x = parseInt(x); y = parseInt(y); for (let j = y - 1; j >= 0; j--) { if (obj.records[j][x].element.style.display != 'none' && obj.rows[j].element.style.display != 'none') { if (obj.records[j][x].element.getAttribute('data-merged')) { if (obj.records[j][x].element == obj.records[y][x].element) { continue; } } y = j; break; } } return y; }; const upVisible = function (group, direction) { const obj = this; let x, y; if (group == 0) { x = parseInt(obj.selectedCell[0]); y = parseInt(obj.selectedCell[1]); } else { x = parseInt(obj.selectedCell[2]); y = parseInt(obj.selectedCell[3]); } if (direction == 0) { for (let j = 0; j < y; j++) { if (obj.records[j][x].element.style.display != 'none' && obj.rows[j].element.style.display != 'none') { y = j; break; } } } else { y = upGet.call(obj, x, y); } if (group == 0) { obj.selectedCell[0] = x; obj.selectedCell[1] = y; } else { obj.selectedCell[2] = x; obj.selectedCell[3] = y; } }; export const up = function (shiftKey, ctrlKey) { const obj = this; if (shiftKey) { if (obj.selectedCell[3] > 0) { upVisible.call(obj, 1, ctrlKey ? 0 : 1); } } else { if (obj.selectedCell[1] > 0) { upVisible.call(obj, 0, ctrlKey ? 0 : 1); } obj.selectedCell[2] = obj.selectedCell[0]; obj.selectedCell[3] = obj.selectedCell[1]; } // Update selection obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]); // Change page if (obj.options.lazyLoading == true) { if (obj.selectedCell[1] == 0 || obj.selectedCell[3] == 0) { loadPage.call(obj, 0); obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]); } else { if (loadValidation.call(obj)) { obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]); } else { const item = parseInt(obj.tbody.firstChild.getAttribute('data-y')); if (obj.selectedCell[1] - item < 30) { loadUp.call(obj); obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]); } } } } else if (obj.options.pagination > 0) { const pageNumber = obj.whichPage(obj.selectedCell[3]); if (pageNumber != obj.pageNumber) { obj.page(pageNumber); } } updateScroll.call(obj, 1); }; export const rightGet = function (x, y) { const obj = this; x = parseInt(x); y = parseInt(y); for (let i = x + 1; i < obj.headers.length; i++) { if (obj.records[y][i].element.style.display != 'none') { if (obj.records[y][i].element.getAttribute('data-merged')) { if (obj.records[y][i].element == obj.records[y][x].element) { continue; } } x = i; break; } } return x; }; const rightVisible = function (group, direction) { const obj = this; let x, y; if (group == 0) { x = parseInt(obj.selectedCell[0]); y = parseInt(obj.selectedCell[1]); } else { x = parseInt(obj.selectedCell[2]); y = parseInt(obj.selectedCell[3]); } if (direction == 0) { for (let i = obj.headers.length - 1; i > x; i--) { if (obj.records[y][i].element.style.display != 'none') { x = i; break; } } } else { x = rightGet.call(obj, x, y); } if (group == 0) { obj.selectedCell[0] = x; obj.selectedCell[1] = y; } else { obj.selectedCell[2] = x; obj.selectedCell[3] = y; } }; export const right = function (shiftKey, ctrlKey) { const obj = this; if (shiftKey) { if (obj.selectedCell[2] < obj.headers.length - 1) { rightVisible.call(obj, 1, ctrlKey ? 0 : 1); } } else { if (obj.selectedCell[0] < obj.headers.length - 1) { rightVisible.call(obj, 0, ctrlKey ? 0 : 1); } obj.selectedCell[2] = obj.selectedCell[0]; obj.selectedCell[3] = obj.selectedCell[1]; } obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]); updateScroll.call(obj, 2); }; export const downGet = function (x, y) { const obj = this; x = parseInt(x); y = parseInt(y); for (let j = y + 1; j < obj.rows.length; j++) { if (obj.records[j][x].element.style.display != 'none' && obj.rows[j].element.style.display != 'none') { if (obj.records[j][x].element.getAttribute('data-merged')) { if (obj.records[j][x].element == obj.records[y][x].element) { continue; } } y = j; break; } } return y; }; const downVisible = function (group, direction) { const obj = this; let x, y; if (group == 0) { x = parseInt(obj.selectedCell[0]); y = parseInt(obj.selectedCell[1]); } else { x = parseInt(obj.selectedCell[2]); y = parseInt(obj.selectedCell[3]); } if (direction == 0) { for (let j = obj.rows.length - 1; j > y; j--) { if (obj.records[j][x].element.style.display != 'none' && obj.rows[j].element.style.display != 'none') { y = j; break; } } } else { y = downGet.call(obj, x, y); } if (group == 0) { obj.selectedCell[0] = x; obj.selectedCell[1] = y; } else { obj.selectedCell[2] = x; obj.selectedCell[3] = y; } }; export const down = function (shiftKey, ctrlKey) { const obj = this; if (shiftKey) { if (obj.selectedCell[3] < obj.records.length - 1) { downVisible.call(obj, 1, ctrlKey ? 0 : 1); } } else { if (obj.selectedCell[1] < obj.records.length - 1) { downVisible.call(obj, 0, ctrlKey ? 0 : 1); } obj.selectedCell[2] = obj.selectedCell[0]; obj.selectedCell[3] = obj.selectedCell[1]; } obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]); // Change page if (obj.options.lazyLoading == true) { if (obj.selectedCell[1] == obj.records.length - 1 || obj.selectedCell[3] == obj.records.length - 1) { loadPage.call(obj, -1); obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]); } else { if (loadValidation.call(obj)) { obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]); } else { const item = parseInt(obj.tbody.lastChild.getAttribute('data-y')); if (item - obj.selectedCell[3] < 30) { loadDown.call(obj); obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]); } } } } else if (obj.options.pagination > 0) { const pageNumber = obj.whichPage(obj.selectedCell[3]); if (pageNumber != obj.pageNumber) { obj.page(pageNumber); } } updateScroll.call(obj, 3); }; const leftGet = function (x, y) { const obj = this; x = parseInt(x); y = parseInt(y); for (let i = x - 1; i >= 0; i--) { if (obj.records[y][i].element.style.display != 'none') { if (obj.records[y][i].element.getAttribute('data-merged')) { if (obj.records[y][i].element == obj.records[y][x].element) { continue; } } x = i; break; } } return x; }; const leftVisible = function (group, direction) { const obj = this; let x, y; if (group == 0) { x = parseInt(obj.selectedCell[0]); y = parseInt(obj.selectedCell[1]); } else { x = parseInt(obj.selectedCell[2]); y = parseInt(obj.selectedCell[3]); } if (direction == 0) { for (let i = 0; i < x; i++) { if (obj.records[y][i].element.style.display != 'none') { x = i; break; } } } else { x = leftGet.call(obj, x, y); } if (group == 0) { obj.selectedCell[0] = x; obj.selectedCell[1] = y; } else { obj.selectedCell[2] = x; obj.selectedCell[3] = y; } }; export const left = function (shiftKey, ctrlKey) { const obj = this; if (shiftKey) { if (obj.selectedCell[2] > 0) { leftVisible.call(obj, 1, ctrlKey ? 0 : 1); } } else { if (obj.selectedCell[0] > 0) { leftVisible.call(obj, 0, ctrlKey ? 0 : 1); } obj.selectedCell[2] = obj.selectedCell[0]; obj.selectedCell[3] = obj.selectedCell[1]; } obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]); updateScroll.call(obj, 0); }; export const first = function (shiftKey, ctrlKey) { const obj = this; if (shiftKey) { if (ctrlKey) { obj.selectedCell[3] = 0; } else { leftVisible.call(obj, 1, 0); } } else { if (ctrlKey) { obj.selectedCell[1] = 0; } else { leftVisible.call(obj, 0, 0); } obj.selectedCell[2] = obj.selectedCell[0]; obj.selectedCell[3] = obj.selectedCell[1]; } // Change page if (obj.options.lazyLoading == true && (obj.selectedCell[1] == 0 || obj.selectedCell[3] == 0)) { loadPage.call(obj, 0); } else if (obj.options.pagination > 0) { const pageNumber = obj.whichPage(obj.selectedCell[3]); if (pageNumber != obj.pageNumber) { obj.page(pageNumber); } } obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]); updateScroll.call(obj, 1); }; export const last = function (shiftKey, ctrlKey) { const obj = this; if (shiftKey) { if (ctrlKey) { obj.selectedCell[3] = obj.records.length - 1; } else { rightVisible.call(obj, 1, 0); } } else { if (ctrlKey) { obj.selectedCell[1] = obj.records.length - 1; } else { rightVisible.call(obj, 0, 0); } obj.selectedCell[2] = obj.selectedCell[0]; obj.selectedCell[3] = obj.selectedCell[1]; } // Change page if (obj.options.lazyLoading == true && (obj.selectedCell[1] == obj.records.length - 1 || obj.selectedCell[3] == obj.records.length - 1)) { loadPage.call(obj, -1); } else if (obj.options.pagination > 0) { const pageNumber = obj.whichPage(obj.selectedCell[3]); if (pageNumber != obj.pageNumber) { obj.page(pageNumber); } } obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]); updateScroll.call(obj, 3); }; ================================================ FILE: src/utils/lazyLoading.js ================================================ /** * Go to a page in a lazyLoading */ export const loadPage = function (pageNumber) { const obj = this; // Search let results; if ((obj.options.search == true || obj.options.filters == true) && obj.results) { results = obj.results; } else { results = obj.rows; } // Per page const quantityPerPage = 100; // pageNumber if (pageNumber == null || pageNumber == -1) { // Last page pageNumber = Math.ceil(results.length / quantityPerPage) - 1; } let startRow = pageNumber * quantityPerPage; let finalRow = pageNumber * quantityPerPage + quantityPerPage; if (finalRow > results.length) { finalRow = results.length; } startRow = finalRow - 100; if (startRow < 0) { startRow = 0; } // Appeding items for (let j = startRow; j < finalRow; j++) { if ((obj.options.search == true || obj.options.filters == true) && obj.results) { obj.tbody.appendChild(obj.rows[results[j]].element); } else { obj.tbody.appendChild(obj.rows[j].element); } if (obj.tbody.children.length > quantityPerPage) { obj.tbody.removeChild(obj.tbody.firstChild); } } }; export const loadValidation = function () { const obj = this; if (obj.selectedCell) { const currentPage = parseInt(obj.tbody.firstChild.getAttribute('data-y')) / 100; const selectedPage = parseInt(obj.selectedCell[3] / 100); const totalPages = parseInt(obj.rows.length / 100); if (currentPage != selectedPage && selectedPage <= totalPages) { if (!Array.prototype.indexOf.call(obj.tbody.children, obj.rows[obj.selectedCell[3]].element)) { obj.loadPage(selectedPage); return true; } } } return false; }; export const loadUp = function () { const obj = this; // Search let results; if ((obj.options.search == true || obj.options.filters == true) && obj.results) { results = obj.results; } else { results = obj.rows; } let test = 0; if (results.length > 100) { // Get the first element in the page let item = parseInt(obj.tbody.firstChild.getAttribute('data-y')); if ((obj.options.search == true || obj.options.filters == true) && obj.results) { item = results.indexOf(item); } if (item > 0) { for (let j = 0; j < 30; j++) { item = item - 1; if (item > -1) { if ((obj.options.search == true || obj.options.filters == true) && obj.results) { obj.tbody.insertBefore(obj.rows[results[item]].element, obj.tbody.firstChild); } else { obj.tbody.insertBefore(obj.rows[item].element, obj.tbody.firstChild); } if (obj.tbody.children.length > 100) { obj.tbody.removeChild(obj.tbody.lastChild); test = 1; } } } } } return test; }; export const loadDown = function () { const obj = this; // Search let results; if ((obj.options.search == true || obj.options.filters == true) && obj.results) { results = obj.results; } else { results = obj.rows; } let test = 0; if (results.length > 100) { // Get the last element in the page let item = parseInt(obj.tbody.lastChild.getAttribute('data-y')); if ((obj.options.search == true || obj.options.filters == true) && obj.results) { item = results.indexOf(item); } if (item < obj.rows.length - 1) { for (let j = 0; j <= 30; j++) { if (item < results.length) { if ((obj.options.search == true || obj.options.filters == true) && obj.results) { obj.tbody.appendChild(obj.rows[results[item]].element); } else { obj.tbody.appendChild(obj.rows[item].element); } if (obj.tbody.children.length > 100) { obj.tbody.removeChild(obj.tbody.firstChild); test = 1; } } item = item + 1; } } } return test; }; ================================================ FILE: src/utils/libraryBase.js ================================================ const lib = { jspreadsheet: {}, }; export default lib; ================================================ FILE: src/utils/merges.js ================================================ import jSuites from 'jsuites'; import { getColumnNameFromId, getIdFromColumnName } from './internalHelpers.js'; import { updateCell } from './internal.js'; import { setHistory } from './history.js'; import dispatch from './dispatch.js'; import { updateSelection } from './selection.js'; /** * Is column merged */ export const isColMerged = function (x, insertBefore) { const obj = this; const cols = []; // Remove any merged cells if (obj.options.mergeCells) { const keys = Object.keys(obj.options.mergeCells); for (let i = 0; i < keys.length; i++) { const info = getIdFromColumnName(keys[i], true); const colspan = obj.options.mergeCells[keys[i]][0]; const x1 = info[0]; const x2 = info[0] + (colspan > 1 ? colspan - 1 : 0); if (insertBefore == null) { if (x1 <= x && x2 >= x) { cols.push(keys[i]); } } else { if (insertBefore) { if (x1 < x && x2 >= x) { cols.push(keys[i]); } } else { if (x1 <= x && x2 > x) { cols.push(keys[i]); } } } } } return cols; }; /** * Is rows merged */ export const isRowMerged = function (y, insertBefore) { const obj = this; const rows = []; // Remove any merged cells if (obj.options.mergeCells) { const keys = Object.keys(obj.options.mergeCells); for (let i = 0; i < keys.length; i++) { const info = getIdFromColumnName(keys[i], true); const rowspan = obj.options.mergeCells[keys[i]][1]; const y1 = info[1]; const y2 = info[1] + (rowspan > 1 ? rowspan - 1 : 0); if (insertBefore == null) { if (y1 <= y && y2 >= y) { rows.push(keys[i]); } } else { if (insertBefore) { if (y1 < y && y2 >= y) { rows.push(keys[i]); } } else { if (y1 <= y && y2 > y) { rows.push(keys[i]); } } } } } return rows; }; /** * Merge cells * @param cellName * @param colspan * @param rowspan * @param ignoreHistoryAndEvents */ export const getMerge = function (cellName) { const obj = this; let data = {}; if (cellName) { if (obj.options.mergeCells && obj.options.mergeCells[cellName]) { data = [obj.options.mergeCells[cellName][0], obj.options.mergeCells[cellName][1]]; } else { data = null; } } else { if (obj.options.mergeCells) { var mergedCells = obj.options.mergeCells; const keys = Object.keys(obj.options.mergeCells); for (let i = 0; i < keys.length; i++) { data[keys[i]] = [obj.options.mergeCells[keys[i]][0], obj.options.mergeCells[keys[i]][1]]; } } } return data; }; /** * Merge cells * @param cellName * @param colspan * @param rowspan * @param ignoreHistoryAndEvents */ export const setMerge = function (cellName, colspan, rowspan, ignoreHistoryAndEvents) { const obj = this; let test = false; if (!cellName) { if (!obj.highlighted.length) { alert(jSuites.translate('No cells selected')); return null; } else { const x1 = parseInt(obj.highlighted[0].getAttribute('data-x')); const y1 = parseInt(obj.highlighted[0].getAttribute('data-y')); const x2 = parseInt(obj.highlighted[obj.highlighted.length - 1].getAttribute('data-x')); const y2 = parseInt(obj.highlighted[obj.highlighted.length - 1].getAttribute('data-y')); cellName = getColumnNameFromId([x1, y1]); colspan = x2 - x1 + 1; rowspan = y2 - y1 + 1; } } else if (typeof cellName !== 'string') { return null; } const cell = getIdFromColumnName(cellName, true); if (obj.options.mergeCells && obj.options.mergeCells[cellName]) { if (obj.records[cell[1]][cell[0]].element.getAttribute('data-merged')) { test = 'Cell already merged'; } } else if ((!colspan || colspan < 2) && (!rowspan || rowspan < 2)) { test = 'Invalid merged properties'; } else { var cells = []; for (let j = cell[1]; j < cell[1] + rowspan; j++) { for (let i = cell[0]; i < cell[0] + colspan; i++) { var columnName = getColumnNameFromId([i, j]); if (obj.records[j][i].element.getAttribute('data-merged')) { test = 'There is a conflict with another merged cell'; } } } } if (test) { alert(jSuites.translate(test)); } else { // Add property if (colspan > 1) { obj.records[cell[1]][cell[0]].element.setAttribute('colspan', colspan); } else { colspan = 1; } if (rowspan > 1) { obj.records[cell[1]][cell[0]].element.setAttribute('rowspan', rowspan); } else { rowspan = 1; } // Keep links to the existing nodes if (!obj.options.mergeCells) { obj.options.mergeCells = {}; } obj.options.mergeCells[cellName] = [colspan, rowspan, []]; // Mark cell as merged obj.records[cell[1]][cell[0]].element.setAttribute('data-merged', 'true'); // Overflow obj.records[cell[1]][cell[0]].element.style.overflow = 'hidden'; // History data const data = []; // Adjust the nodes for (let y = cell[1]; y < cell[1] + rowspan; y++) { for (let x = cell[0]; x < cell[0] + colspan; x++) { if (!(cell[0] == x && cell[1] == y)) { data.push(obj.options.data[y][x]); updateCell.call(obj, x, y, '', true); obj.options.mergeCells[cellName][2].push(obj.records[y][x].element); obj.records[y][x].element.style.display = 'none'; obj.records[y][x].element = obj.records[cell[1]][cell[0]].element; } } } // In the initialization is not necessary keep the history updateSelection.call(obj, obj.records[cell[1]][cell[0]].element); if (!ignoreHistoryAndEvents) { setHistory.call(obj, { action: 'setMerge', column: cellName, colspan: colspan, rowspan: rowspan, data: data, }); dispatch.call(obj, 'onmerge', obj, { [cellName]: [colspan, rowspan] }); } } }; /** * Remove merge by cellname * @param cellName */ export const removeMerge = function (cellName, data, keepOptions) { const obj = this; if (obj.options.mergeCells && obj.options.mergeCells[cellName]) { const beforeMerges = { [cellName]: obj.options.mergeCells[cellName] }; const cell = getIdFromColumnName(cellName, true); obj.records[cell[1]][cell[0]].element.removeAttribute('colspan'); obj.records[cell[1]][cell[0]].element.removeAttribute('rowspan'); obj.records[cell[1]][cell[0]].element.removeAttribute('data-merged'); const info = obj.options.mergeCells[cellName]; let index = 0; let j, i; for (j = 0; j < info[1]; j++) { for (i = 0; i < info[0]; i++) { if (j > 0 || i > 0) { obj.records[cell[1] + j][cell[0] + i].element = info[2][index]; obj.records[cell[1] + j][cell[0] + i].element.style.display = ''; // Recover data if (data && data[index]) { updateCell.call(obj, cell[0] + i, cell[1] + j, data[index]); } index++; } } } // Update selection updateSelection.call(obj, obj.records[cell[1]][cell[0]].element, obj.records[cell[1] + j - 1][cell[0] + i - 1].element); if (!keepOptions) { delete obj.options.mergeCells[cellName]; } dispatch.call(obj, 'onunmerge', obj, cellName, beforeMerges); } }; /** * Remove all merged cells */ export const destroyMerge = function (keepOptions) { const obj = this; // Remove any merged cells if (obj.options.mergeCells) { var mergedCells = obj.options.mergeCells; const keys = Object.keys(obj.options.mergeCells); for (let i = 0; i < keys.length; i++) { removeMerge.call(obj, keys[i], null, keepOptions); } } }; ================================================ FILE: src/utils/meta.js ================================================ import dispatch from './dispatch.js'; /** * Get meta information from cell(s) * * @return integer */ export const getMeta = function (cell, key) { const obj = this; if (!cell) { return obj.options.meta; } else { if (key) { return obj.options.meta && obj.options.meta[cell] && obj.options.meta[cell][key] ? obj.options.meta[cell][key] : null; } else { return obj.options.meta && obj.options.meta[cell] ? obj.options.meta[cell] : null; } } }; /** * Update meta information * * @return integer */ export const updateMeta = function (affectedCells) { const obj = this; if (obj.options.meta) { const newMeta = {}; const keys = Object.keys(obj.options.meta); for (let i = 0; i < keys.length; i++) { if (affectedCells[keys[i]]) { newMeta[affectedCells[keys[i]]] = obj.options.meta[keys[i]]; } else { newMeta[keys[i]] = obj.options.meta[keys[i]]; } } // Update meta information obj.options.meta = newMeta; } }; /** * Set meta information to cell(s) * * @return integer */ export const setMeta = function (o, k, v) { const obj = this; if (!obj.options.meta) { obj.options.meta = {}; } if (k && v) { // Set data value if (!obj.options.meta[o]) { obj.options.meta[o] = {}; } obj.options.meta[o][k] = v; dispatch.call(obj, 'onchangemeta', obj, { [o]: { [k]: v } }); } else { // Apply that for all cells const keys = Object.keys(o); for (let i = 0; i < keys.length; i++) { if (!obj.options.meta[keys[i]]) { obj.options.meta[keys[i]] = {}; } const prop = Object.keys(o[keys[i]]); for (let j = 0; j < prop.length; j++) { obj.options.meta[keys[i]][prop[j]] = o[keys[i]][prop[j]]; } } dispatch.call(obj, 'onchangemeta', obj, o); } }; ================================================ FILE: src/utils/orderBy.js ================================================ import jSuites from 'jsuites'; import { setHistory } from './history.js'; import dispatch from './dispatch.js'; import { updateTableReferences } from './internal.js'; import { loadPage } from './lazyLoading.js'; import { closeFilter } from './filter.js'; /** * Update order arrow */ export const updateOrderArrow = function (column, order) { const obj = this; // Remove order for (let i = 0; i < obj.headers.length; i++) { obj.headers[i].classList.remove('arrow-up'); obj.headers[i].classList.remove('arrow-down'); } // No order specified then toggle order if (order) { obj.headers[column].classList.add('arrow-up'); } else { obj.headers[column].classList.add('arrow-down'); } }; /** * Update rows position */ export const updateOrder = function (rows) { const obj = this; // History let data = []; for (let j = 0; j < rows.length; j++) { data[j] = obj.options.data[rows[j]]; } obj.options.data = data; data = []; for (let j = 0; j < rows.length; j++) { data[j] = obj.records[rows[j]]; for (let i = 0; i < data[j].length; i++) { data[j][i].y = j; } } obj.records = data; data = []; for (let j = 0; j < rows.length; j++) { data[j] = obj.rows[rows[j]]; data[j].y = j; } obj.rows = data; // Update references updateTableReferences.call(obj); // Redo search if (obj.results && obj.results.length) { if (obj.searchInput.value) { obj.search(obj.searchInput.value); } else { closeFilter.call(obj); } } else { // Create page obj.results = null; obj.pageNumber = 0; if (obj.options.pagination > 0) { obj.page(0); } else if (obj.options.lazyLoading == true) { loadPage.call(obj, 0); } else { for (let j = 0; j < obj.rows.length; j++) { obj.tbody.appendChild(obj.rows[j].element); } } } }; /** * Sort data and reload table */ export const orderBy = function (column, order) { const obj = this; if (column >= 0) { // Merged cells if (obj.options.mergeCells && Object.keys(obj.options.mergeCells).length > 0) { if (!confirm(jSuites.translate('This action will destroy any existing merged cells. Are you sure?'))) { return false; } else { // Remove merged cells obj.destroyMerge(); } } // Direction if (order == null) { order = obj.headers[column].classList.contains('arrow-down') ? 1 : 0; } else { order = order ? 1 : 0; } // Test order let temp = []; if ( obj.options.columns && obj.options.columns[column] && (obj.options.columns[column].type == 'number' || obj.options.columns[column].type == 'numeric' || obj.options.columns[column].type == 'percentage' || obj.options.columns[column].type == 'autonumber' || obj.options.columns[column].type == 'color') ) { for (let j = 0; j < obj.options.data.length; j++) { temp[j] = [j, Number(obj.options.data[j][column])]; } } else if ( obj.options.columns && obj.options.columns[column] && (obj.options.columns[column].type == 'calendar' || obj.options.columns[column].type == 'checkbox' || obj.options.columns[column].type == 'radio') ) { for (let j = 0; j < obj.options.data.length; j++) { temp[j] = [j, obj.options.data[j][column]]; } } else { for (let j = 0; j < obj.options.data.length; j++) { temp[j] = [j, obj.records[j][column].element.textContent.toLowerCase()]; } } // Default sorting method if (typeof obj.parent.config.sorting !== 'function') { obj.parent.config.sorting = function (direction) { return function (a, b) { const valueA = a[1]; const valueB = b[1]; if (!direction) { return valueA === '' && valueB !== '' ? 1 : valueA !== '' && valueB === '' ? -1 : valueA > valueB ? 1 : valueA < valueB ? -1 : 0; } else { return valueA === '' && valueB !== '' ? 1 : valueA !== '' && valueB === '' ? -1 : valueA > valueB ? -1 : valueA < valueB ? 1 : 0; } }; }; } temp = temp.sort(obj.parent.config.sorting(order)); // Save history const newValue = []; for (let j = 0; j < temp.length; j++) { newValue[j] = temp[j][0]; } // Save history setHistory.call(obj, { action: 'orderBy', rows: newValue, column: column, order: order, }); // Update order updateOrderArrow.call(obj, column, order); updateOrder.call(obj, newValue); // On sort event dispatch.call( obj, 'onsort', obj, column, order, newValue.map((row) => row) ); return true; } }; ================================================ FILE: src/utils/pagination.js ================================================ import jSuites from 'jsuites'; import dispatch from './dispatch.js'; import { updateCornerPosition } from './selection.js'; /** * Which page the row is */ export const whichPage = function (row) { const obj = this; // Search if ((obj.options.search == true || obj.options.filters == true) && obj.results) { row = obj.results.indexOf(row); } return Math.ceil((parseInt(row) + 1) / parseInt(obj.options.pagination)) - 1; }; /** * Update the pagination */ export const updatePagination = function () { const obj = this; // Reset container obj.pagination.children[0].innerHTML = ''; obj.pagination.children[1].innerHTML = ''; // Start pagination if (obj.options.pagination) { // Searchable let results; if ((obj.options.search == true || obj.options.filters == true) && obj.results) { results = obj.results.length; } else { results = obj.rows.length; } if (!results) { // No records found obj.pagination.children[0].innerHTML = jSuites.translate('No records found'); } else { // Pagination container const quantyOfPages = Math.ceil(results / obj.options.pagination); let startNumber, finalNumber; if (obj.pageNumber < 6) { startNumber = 1; finalNumber = quantyOfPages < 10 ? quantyOfPages : 10; } else if (quantyOfPages - obj.pageNumber < 5) { startNumber = quantyOfPages - 9; finalNumber = quantyOfPages; if (startNumber < 1) { startNumber = 1; } } else { startNumber = obj.pageNumber - 4; finalNumber = obj.pageNumber + 5; } // First if (startNumber > 1) { const paginationItem = document.createElement('div'); paginationItem.className = 'jss_page'; paginationItem.innerHTML = '<'; paginationItem.title = 1; obj.pagination.children[1].appendChild(paginationItem); } // Get page links for (let i = startNumber; i <= finalNumber; i++) { const paginationItem = document.createElement('div'); paginationItem.className = 'jss_page'; paginationItem.innerHTML = i; obj.pagination.children[1].appendChild(paginationItem); if (obj.pageNumber == i - 1) { paginationItem.classList.add('jss_page_selected'); } } // Last if (finalNumber < quantyOfPages) { const paginationItem = document.createElement('div'); paginationItem.className = 'jss_page'; paginationItem.innerHTML = '>'; paginationItem.title = quantyOfPages; obj.pagination.children[1].appendChild(paginationItem); } // Text const format = function (format) { const args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); }; obj.pagination.children[0].innerHTML = format(jSuites.translate('Showing page {0} of {1} entries'), obj.pageNumber + 1, quantyOfPages); } } }; /** * Go to page */ export const page = function (pageNumber) { const obj = this; const oldPage = obj.pageNumber; // Search let results; if ((obj.options.search == true || obj.options.filters == true) && obj.results) { results = obj.results; } else { results = obj.rows; } // Per page const quantityPerPage = parseInt(obj.options.pagination); // pageNumber if (pageNumber == null || pageNumber == -1) { // Last page pageNumber = Math.ceil(results.length / quantityPerPage) - 1; } // Page number obj.pageNumber = pageNumber; let startRow = pageNumber * quantityPerPage; let finalRow = pageNumber * quantityPerPage + quantityPerPage; if (finalRow > results.length) { finalRow = results.length; } if (startRow < 0) { startRow = 0; } // Reset container while (obj.tbody.firstChild) { obj.tbody.removeChild(obj.tbody.firstChild); } // Appeding items for (let j = startRow; j < finalRow; j++) { if ((obj.options.search == true || obj.options.filters == true) && obj.results) { obj.tbody.appendChild(obj.rows[results[j]].element); } else { obj.tbody.appendChild(obj.rows[j].element); } } if (obj.options.pagination > 0) { updatePagination.call(obj); } // Update corner position updateCornerPosition.call(obj); // Events dispatch.call(obj, 'onchangepage', obj, pageNumber, oldPage, obj.options.pagination); }; export const quantiyOfPages = function () { const obj = this; let results; if ((obj.options.search == true || obj.options.filters == true) && obj.results) { results = obj.results.length; } else { results = obj.rows.length; } return Math.ceil(results / obj.options.pagination); }; ================================================ FILE: src/utils/rows.js ================================================ import jSuites from 'jsuites'; import { getNumberOfColumns } from './columns.js'; import { createCell, updateTableReferences } from './internal.js'; import dispatch from './dispatch.js'; import { isRowMerged } from './merges.js'; import { conditionalSelectionUpdate, getSelectedRows, updateCornerPosition } from './selection.js'; import { setHistory } from './history.js'; import { getColumnNameFromId } from './internalHelpers.js'; /** * Create row */ export const createRow = function (j, data) { const obj = this; // Create container if (!obj.records[j]) { obj.records[j] = []; } // Default data if (!data) { data = obj.options.data[j]; } // New line of data to be append in the table const row = { element: document.createElement('tr'), y: j, }; obj.rows[j] = row; row.element.setAttribute('data-y', j); // Index let index = null; // Set default row height if (obj.options.defaultRowHeight) { row.element.style.height = obj.options.defaultRowHeight + 'px'; } // Definitions if (obj.options.rows && obj.options.rows[j]) { if (obj.options.rows[j].height) { row.element.style.height = obj.options.rows[j].height; } if (obj.options.rows[j].title) { index = obj.options.rows[j].title; } } if (!index) { index = parseInt(j + 1); } // Row number label const td = document.createElement('td'); td.innerHTML = index; td.setAttribute('data-y', j); td.className = 'jss_row'; row.element.appendChild(td); const numberOfColumns = getNumberOfColumns.call(obj); // Data columns for (let i = 0; i < numberOfColumns; i++) { // New column of data to be append in the line obj.records[j][i] = { element: createCell.call(this, i, j, data[i]), x: i, y: j, }; // Add column to the row row.element.appendChild(obj.records[j][i].element); if (obj.options.columns && obj.options.columns[i] && typeof obj.options.columns[i].render === 'function') { obj.options.columns[i].render(obj.records[j][i].element, data[i], parseInt(i), parseInt(j), obj, obj.options.columns[i]); } } // Add row to the table body return row; }; /** * Insert a new row * * @param mixed - number of blank lines to be insert or a single array with the data of the new row * @param rowNumber * @param insertBefore * @return void */ export const insertRow = function (mixed, rowNumber, insertBefore) { const obj = this; // Configuration if (obj.options.allowInsertRow != false) { // Records var records = []; // Data to be insert let data = []; // The insert could be lead by number of rows or the array of data let numOfRows; if (!Array.isArray(mixed)) { numOfRows = typeof mixed !== 'undefined' ? mixed : 1; } else { numOfRows = 1; if (mixed) { data = mixed; } } // Direction insertBefore = insertBefore ? true : false; // Current column number const lastRow = obj.options.data.length - 1; if (rowNumber == undefined || rowNumber >= parseInt(lastRow) || rowNumber < 0) { rowNumber = lastRow; } const onbeforeinsertrowRecords = []; for (let row = 0; row < numOfRows; row++) { const newRow = []; for (let col = 0; col < obj.options.columns.length; col++) { newRow[col] = data[col] ? data[col] : ''; } onbeforeinsertrowRecords.push({ row: row + rowNumber + (insertBefore ? 0 : 1), data: newRow, }); } // Onbeforeinsertrow if (dispatch.call(obj, 'onbeforeinsertrow', obj, onbeforeinsertrowRecords) === false) { return false; } // Merged cells if (obj.options.mergeCells && Object.keys(obj.options.mergeCells).length > 0) { if (isRowMerged.call(obj, rowNumber, insertBefore).length) { if (!confirm(jSuites.translate('This action will destroy any existing merged cells. Are you sure?'))) { return false; } else { obj.destroyMerge(); } } } // Clear any search if (obj.options.search == true) { if (obj.results && obj.results.length != obj.rows.length) { if (confirm(jSuites.translate('This action will clear your search results. Are you sure?'))) { obj.resetSearch(); } else { return false; } } obj.results = null; } // Insertbefore const rowIndex = !insertBefore ? rowNumber + 1 : rowNumber; // Keep the current data const currentRecords = obj.records.splice(rowIndex); const currentData = obj.options.data.splice(rowIndex); const currentRows = obj.rows.splice(rowIndex); // Adding lines const rowRecords = []; const rowData = []; const rowNode = []; for (let row = rowIndex; row < numOfRows + rowIndex; row++) { // Push data to the data container obj.options.data[row] = []; for (let col = 0; col < obj.options.columns.length; col++) { obj.options.data[row][col] = data[col] ? data[col] : ''; } // Create row const newRow = createRow.call(obj, row, obj.options.data[row]); // Append node if (currentRows[0]) { if (Array.prototype.indexOf.call(obj.tbody.children, currentRows[0].element) >= 0) { obj.tbody.insertBefore(newRow.element, currentRows[0].element); } } else { if (Array.prototype.indexOf.call(obj.tbody.children, obj.rows[rowNumber].element) >= 0) { obj.tbody.appendChild(newRow.element); } } // Record History rowRecords.push([...obj.records[row]]); rowData.push([...obj.options.data[row]]); rowNode.push(newRow); } // Copy the data back to the main data Array.prototype.push.apply(obj.records, currentRecords); Array.prototype.push.apply(obj.options.data, currentData); Array.prototype.push.apply(obj.rows, currentRows); for (let j = rowIndex; j < obj.rows.length; j++) { obj.rows[j].y = j; } for (let j = rowIndex; j < obj.records.length; j++) { for (let i = 0; i < obj.records[j].length; i++) { obj.records[j][i].y = j; } } // Respect pagination if (obj.options.pagination > 0) { obj.page(obj.pageNumber); } // Keep history setHistory.call(obj, { action: 'insertRow', rowNumber: rowNumber, numOfRows: numOfRows, insertBefore: insertBefore, rowRecords: rowRecords, rowData: rowData, rowNode: rowNode, }); // Remove table references updateTableReferences.call(obj); // Events dispatch.call(obj, 'oninsertrow', obj, onbeforeinsertrowRecords); } }; /** * Move row * * @return void */ export const moveRow = function (o, d, ignoreDom) { const obj = this; if (obj.options.mergeCells && Object.keys(obj.options.mergeCells).length > 0) { let insertBefore; if (o > d) { insertBefore = 1; } else { insertBefore = 0; } if (isRowMerged.call(obj, o).length || isRowMerged.call(obj, d, insertBefore).length) { if (!confirm(jSuites.translate('This action will destroy any existing merged cells. Are you sure?'))) { return false; } else { obj.destroyMerge(); } } } if (obj.options.search == true) { if (obj.results && obj.results.length != obj.rows.length) { if (confirm(jSuites.translate('This action will clear your search results. Are you sure?'))) { obj.resetSearch(); } else { return false; } } obj.results = null; } if (!ignoreDom) { if (Array.prototype.indexOf.call(obj.tbody.children, obj.rows[d].element) >= 0) { if (o > d) { obj.tbody.insertBefore(obj.rows[o].element, obj.rows[d].element); } else { obj.tbody.insertBefore(obj.rows[o].element, obj.rows[d].element.nextSibling); } } else { obj.tbody.removeChild(obj.rows[o].element); } } // Place references in the correct position obj.rows.splice(d, 0, obj.rows.splice(o, 1)[0]); obj.records.splice(d, 0, obj.records.splice(o, 1)[0]); obj.options.data.splice(d, 0, obj.options.data.splice(o, 1)[0]); const firstAffectedIndex = Math.min(o, d); const lastAffectedIndex = Math.max(o, d); for (let j = firstAffectedIndex; j <= lastAffectedIndex; j++) { obj.rows[j].y = j; } for (let j = firstAffectedIndex; j <= lastAffectedIndex; j++) { for (let i = 0; i < obj.records[j].length; i++) { obj.records[j][i].y = j; } } // Respect pagination if (obj.options.pagination > 0 && obj.tbody.children.length != obj.options.pagination) { obj.page(obj.pageNumber); } // Keeping history of changes setHistory.call(obj, { action: 'moveRow', oldValue: o, newValue: d, }); // Update table references updateTableReferences.call(obj); // Events dispatch.call(obj, 'onmoverow', obj, parseInt(o), parseInt(d), 1); }; /** * Delete a row by number * * @param integer rowNumber - row number to be excluded * @param integer numOfRows - number of lines * @return void */ export const deleteRow = function (rowNumber, numOfRows) { const obj = this; // Global Configuration if (obj.options.allowDeleteRow != false) { if (obj.options.allowDeletingAllRows == true || obj.options.data.length > 1) { // Delete row definitions if (rowNumber == undefined) { const number = getSelectedRows.call(obj); if (number.length === 0) { rowNumber = obj.options.data.length - 1; numOfRows = 1; } else { rowNumber = number[0]; numOfRows = number.length; } } // Last column let lastRow = obj.options.data.length - 1; if (rowNumber == undefined || rowNumber > lastRow || rowNumber < 0) { rowNumber = lastRow; } if (!numOfRows) { numOfRows = 1; } // Do not delete more than the number of records if (rowNumber + numOfRows >= obj.options.data.length) { numOfRows = obj.options.data.length - rowNumber; } // Onbeforedeleterow const onbeforedeleterowRecords = []; for (let i = 0; i < numOfRows; i++) { onbeforedeleterowRecords.push(i + rowNumber); } if (dispatch.call(obj, 'onbeforedeleterow', obj, onbeforedeleterowRecords) === false) { return false; } if (parseInt(rowNumber) > -1) { // Merged cells let mergeExists = false; if (obj.options.mergeCells && Object.keys(obj.options.mergeCells).length > 0) { for (let row = rowNumber; row < rowNumber + numOfRows; row++) { if (isRowMerged.call(obj, row, false).length) { mergeExists = true; } } } if (mergeExists) { if (!confirm(jSuites.translate('This action will destroy any existing merged cells. Are you sure?'))) { return false; } else { obj.destroyMerge(); } } // Clear any search if (obj.options.search == true) { if (obj.results && obj.results.length != obj.rows.length) { if (confirm(jSuites.translate('This action will clear your search results. Are you sure?'))) { obj.resetSearch(); } else { return false; } } obj.results = null; } // If delete all rows, and set allowDeletingAllRows false, will stay one row if (obj.options.allowDeletingAllRows != true && lastRow + 1 === numOfRows) { numOfRows--; console.error('Jspreadsheet: It is not possible to delete the last row'); } // Remove node for (let row = rowNumber; row < rowNumber + numOfRows; row++) { if (Array.prototype.indexOf.call(obj.tbody.children, obj.rows[row].element) >= 0) { obj.rows[row].element.className = ''; obj.rows[row].element.parentNode.removeChild(obj.rows[row].element); } } // Remove data const rowRecords = obj.records.splice(rowNumber, numOfRows); const rowData = obj.options.data.splice(rowNumber, numOfRows); const rowNode = obj.rows.splice(rowNumber, numOfRows); for (let j = rowNumber; j < obj.rows.length; j++) { obj.rows[j].y = j; } for (let j = rowNumber; j < obj.records.length; j++) { for (let i = 0; i < obj.records[j].length; i++) { obj.records[j][i].y = j; } } // Respect pagination if (obj.options.pagination > 0 && obj.tbody.children.length != obj.options.pagination) { obj.page(obj.pageNumber); } // Remove selection conditionalSelectionUpdate.call(obj, 1, rowNumber, rowNumber + numOfRows - 1); // Keep history setHistory.call(obj, { action: 'deleteRow', rowNumber: rowNumber, numOfRows: numOfRows, insertBefore: 1, rowRecords: rowRecords, rowData: rowData, rowNode: rowNode, }); // Remove table references updateTableReferences.call(obj); // Events dispatch.call(obj, 'ondeleterow', obj, onbeforedeleterowRecords); } } else { console.error('Jspreadsheet: It is not possible to delete the last row'); } } }; /** * Get the row height * * @param row - row number (first row is: 0) * @return height - current row height */ export const getHeight = function (row) { const obj = this; let data; if (typeof row === 'undefined') { // Get height of all rows data = []; for (let j = 0; j < obj.rows.length; j++) { const h = obj.rows[j].element.style.height; if (h) { data[j] = h; } } } else { // In case the row is an object if (typeof row == 'object') { row = row.getAttribute('data-y'); } data = obj.rows[row].element.style.height; } return data; }; /** * Set the row height * * @param row - row number (first row is: 0) * @param height - new row height * @param oldHeight - old row height */ export const setHeight = function (row, height, oldHeight) { const obj = this; if (height > 0) { // Oldwidth if (!oldHeight) { oldHeight = obj.rows[row].element.getAttribute('height'); if (!oldHeight) { const rect = obj.rows[row].element.getBoundingClientRect(); oldHeight = rect.height; } } // Integer height = parseInt(height); // Set width obj.rows[row].element.style.height = height + 'px'; if (!obj.options.rows) { obj.options.rows = []; } // Keep options updated if (!obj.options.rows[row]) { obj.options.rows[row] = {}; } obj.options.rows[row].height = height; // Keeping history of changes setHistory.call(obj, { action: 'setHeight', row: row, oldValue: oldHeight, newValue: height, }); // On resize column dispatch.call(obj, 'onresizerow', obj, row, height, oldHeight); // Update corner position updateCornerPosition.call(obj); } }; /** * Show row */ export const showRow = function (rowNumber) { const obj = this; if (!Array.isArray(rowNumber)) { rowNumber = [rowNumber]; } rowNumber.forEach(function (rowIndex) { obj.rows[rowIndex].element.style.display = ''; }); }; /** * Hide row */ export const hideRow = function (rowNumber) { const obj = this; if (!Array.isArray(rowNumber)) { rowNumber = [rowNumber]; } rowNumber.forEach(function (rowIndex) { obj.rows[rowIndex].element.style.display = 'none'; }); }; /** * Get a row data by rowNumber */ export const getRowData = function (rowNumber, processed) { const obj = this; if (processed) { return obj.records[rowNumber].map(function (record) { return record.element.innerHTML; }); } else { return obj.options.data[rowNumber]; } }; /** * Set a row data by rowNumber */ export const setRowData = function (rowNumber, data, force) { const obj = this; for (let i = 0; i < obj.headers.length; i++) { // Update cell const columnName = getColumnNameFromId([i, rowNumber]); // Set value if (data[i] != null) { obj.setValue(columnName, data[i], force); } } }; ================================================ FILE: src/utils/search.js ================================================ import { resetFilters } from './filter.js'; import { getIdFromColumnName } from './internalHelpers.js'; import { updateResult } from './internal.js'; import { isRowMerged } from './merges.js'; /** * Search */ export const search = function (query) { const obj = this; // Reset any filter if (obj.options.filters) { resetFilters.call(obj); } // Reset selection obj.resetSelection(); // Total of results obj.pageNumber = 0; obj.results = []; if (query) { if (obj.searchInput.value !== query) { obj.searchInput.value = query; } // Search filter const search = function (item, query, index) { for (let i = 0; i < item.length; i++) { if (('' + item[i]).toLowerCase().search(query) >= 0 || ('' + obj.records[index][i].element.innerHTML).toLowerCase().search(query) >= 0) { return true; } } return false; }; // Result const addToResult = function (k) { if (obj.results.indexOf(k) == -1) { obj.results.push(k); } }; let parsedQuery = query.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); parsedQuery = new RegExp(parsedQuery, 'i'); // Filter obj.options.data.forEach(function (v, k) { if (search(v, parsedQuery, k)) { // Merged rows found const rows = isRowMerged.call(obj, k); if (rows.length) { for (let i = 0; i < rows.length; i++) { const row = getIdFromColumnName(rows[i], true); for (let j = 0; j < obj.options.mergeCells[rows[i]][1]; j++) { addToResult(row[1] + j); } } } else { // Normal row found addToResult(k); } } }); } else { obj.results = null; } updateResult.call(obj); }; /** * Reset search */ export const resetSearch = function () { const obj = this; obj.searchInput.value = ''; obj.search(''); obj.results = null; }; ================================================ FILE: src/utils/selection.js ================================================ import dispatch from './dispatch.js'; import { getFreezeWidth } from './freeze.js'; import { getCellNameFromCoords } from './helpers.js'; import { setHistory } from './history.js'; import { updateCell, updateFormula, updateFormulaChain, updateTable } from './internal.js'; import { getColumnNameFromId, getIdFromColumnName } from './internalHelpers.js'; import { updateToolbar } from './toolbar.js'; export const updateCornerPosition = function () { const obj = this; // If any selected cells if (!obj.highlighted || !obj.highlighted.length) { obj.corner.style.top = '-2000px'; obj.corner.style.left = '-2000px'; } else { // Get last cell const last = obj.highlighted[obj.highlighted.length - 1].element; const lastX = last.getAttribute('data-x'); const contentRect = obj.content.getBoundingClientRect(); const x1 = contentRect.left; const y1 = contentRect.top; const lastRect = last.getBoundingClientRect(); const x2 = lastRect.left; const y2 = lastRect.top; const w2 = lastRect.width; const h2 = lastRect.height; const x = x2 - x1 + obj.content.scrollLeft + w2 - 4; const y = y2 - y1 + obj.content.scrollTop + h2 - 4; // Place the corner in the correct place obj.corner.style.top = y + 'px'; obj.corner.style.left = x + 'px'; if (obj.options.freezeColumns) { const width = getFreezeWidth.call(obj); // Only check if the last column is not part of the merged cells if (lastX > obj.options.freezeColumns - 1 && x2 - x1 + w2 < width) { obj.corner.style.display = 'none'; } else { if (obj.options.selectionCopy != false) { obj.corner.style.display = ''; } } } else { if (obj.options.selectionCopy != false) { obj.corner.style.display = ''; } } } updateToolbar(obj); }; export const resetSelection = function (blur) { const obj = this; let previousStatus; // Remove style if (!obj.highlighted || !obj.highlighted.length) { previousStatus = 0; } else { previousStatus = 1; for (let i = 0; i < obj.highlighted.length; i++) { obj.highlighted[i].element.classList.remove('highlight'); obj.highlighted[i].element.classList.remove('highlight-left'); obj.highlighted[i].element.classList.remove('highlight-right'); obj.highlighted[i].element.classList.remove('highlight-top'); obj.highlighted[i].element.classList.remove('highlight-bottom'); obj.highlighted[i].element.classList.remove('highlight-selected'); const px = parseInt(obj.highlighted[i].element.getAttribute('data-x')); const py = parseInt(obj.highlighted[i].element.getAttribute('data-y')); // Check for merged cells let ux, uy; if (obj.highlighted[i].element.getAttribute('data-merged')) { const colspan = parseInt(obj.highlighted[i].element.getAttribute('colspan')); const rowspan = parseInt(obj.highlighted[i].element.getAttribute('rowspan')); ux = colspan > 0 ? px + (colspan - 1) : px; uy = rowspan > 0 ? py + (rowspan - 1) : py; } else { ux = px; uy = py; } // Remove selected from headers for (let j = px; j <= ux; j++) { if (obj.headers[j]) { obj.headers[j].classList.remove('selected'); } } // Remove selected from rows for (let j = py; j <= uy; j++) { if (obj.rows[j]) { obj.rows[j].element.classList.remove('selected'); } } } } // Reset highlighted cells obj.highlighted = []; // Reset obj.selectedCell = null; // Hide corner obj.corner.style.top = '-2000px'; obj.corner.style.left = '-2000px'; if (blur == true && previousStatus == 1) { dispatch.call(obj, 'onblur', obj); } return previousStatus; }; /** * Update selection based on two cells */ export const updateSelection = function (el1, el2, origin) { const obj = this; const x1 = el1.getAttribute('data-x'); const y1 = el1.getAttribute('data-y'); let x2, y2; if (el2) { x2 = el2.getAttribute('data-x'); y2 = el2.getAttribute('data-y'); } else { x2 = x1; y2 = y1; } updateSelectionFromCoords.call(obj, x1, y1, x2, y2, origin); }; export const removeCopyingSelection = function () { const copying = document.querySelectorAll('.jss_worksheet .copying'); for (let i = 0; i < copying.length; i++) { copying[i].classList.remove('copying'); copying[i].classList.remove('copying-left'); copying[i].classList.remove('copying-right'); copying[i].classList.remove('copying-top'); copying[i].classList.remove('copying-bottom'); } }; export const updateSelectionFromCoords = function (x1, y1, x2, y2, origin) { const obj = this; // select column if (y1 == null) { y1 = 0; y2 = obj.rows.length - 1; if (x1 == null) { return; } } else if (x1 == null) { // select row x1 = 0; x2 = obj.options.data[0].length - 1; } // Same element if (x2 == null) { x2 = x1; } if (y2 == null) { y2 = y1; } // Selection must be within the existing data if (x1 >= obj.headers.length) { x1 = obj.headers.length - 1; } if (y1 >= obj.rows.length) { y1 = obj.rows.length - 1; } if (x2 >= obj.headers.length) { x2 = obj.headers.length - 1; } if (y2 >= obj.rows.length) { y2 = obj.rows.length - 1; } // Limits let borderLeft = null; let borderRight = null; let borderTop = null; let borderBottom = null; // Origin & Destination let px, ux; if (parseInt(x1) < parseInt(x2)) { px = parseInt(x1); ux = parseInt(x2); } else { px = parseInt(x2); ux = parseInt(x1); } let py, uy; if (parseInt(y1) < parseInt(y2)) { py = parseInt(y1); uy = parseInt(y2); } else { py = parseInt(y2); uy = parseInt(y1); } // Verify merged columns for (let i = px; i <= ux; i++) { for (let j = py; j <= uy; j++) { if (obj.records[j][i] && obj.records[j][i].element.getAttribute('data-merged')) { const x = parseInt(obj.records[j][i].element.getAttribute('data-x')); const y = parseInt(obj.records[j][i].element.getAttribute('data-y')); const colspan = parseInt(obj.records[j][i].element.getAttribute('colspan')); const rowspan = parseInt(obj.records[j][i].element.getAttribute('rowspan')); if (colspan > 1) { if (x < px) { px = x; } if (x + colspan > ux) { ux = x + colspan - 1; } } if (rowspan) { if (y < py) { py = y; } if (y + rowspan > uy) { uy = y + rowspan - 1; } } } } } // Vertical limits for (let j = py; j <= uy; j++) { if (obj.rows[j].element.style.display != 'none') { if (borderTop == null) { borderTop = j; } borderBottom = j; } } for (let i = px; i <= ux; i++) { for (let j = py; j <= uy; j++) { // Horizontal limits if (!obj.options.columns || !obj.options.columns[i] || obj.options.columns[i].type != 'hidden') { if (borderLeft == null) { borderLeft = i; } borderRight = i; } } } // Create borders if (!borderLeft) { borderLeft = 0; } if (!borderRight) { borderRight = 0; } const ret = dispatch.call(obj, 'onbeforeselection', obj, borderLeft, borderTop, borderRight, borderBottom, origin); if (ret === false) { return false; } // Reset Selection const previousState = obj.resetSelection(); // Keep selected cell obj.selectedCell = [x1, y1, x2, y2]; // Add selected cell if (obj.records[y1][x1]) { obj.records[y1][x1].element.classList.add('highlight-selected'); } // Redefining styles for (let i = px; i <= ux; i++) { for (let j = py; j <= uy; j++) { if (obj.rows[j].element.style.display != 'none' && obj.records[j][i].element.style.display != 'none') { obj.records[j][i].element.classList.add('highlight'); obj.highlighted.push(obj.records[j][i]); } } } for (let i = borderLeft; i <= borderRight; i++) { if ( (!obj.options.columns || !obj.options.columns[i] || obj.options.columns[i].type != 'hidden') && obj.cols[i].colElement.style && obj.cols[i].colElement.style.display != 'none' ) { // Top border if (obj.records[borderTop] && obj.records[borderTop][i]) { obj.records[borderTop][i].element.classList.add('highlight-top'); } // Bottom border if (obj.records[borderBottom] && obj.records[borderBottom][i]) { obj.records[borderBottom][i].element.classList.add('highlight-bottom'); } // Add selected from headers obj.headers[i].classList.add('selected'); } } for (let j = borderTop; j <= borderBottom; j++) { if (obj.rows[j] && obj.rows[j].element.style.display != 'none') { // Left border obj.records[j][borderLeft].element.classList.add('highlight-left'); // Right border obj.records[j][borderRight].element.classList.add('highlight-right'); // Add selected from rows obj.rows[j].element.classList.add('selected'); } } obj.selectedContainer = [borderLeft, borderTop, borderRight, borderBottom]; // Handle events if (previousState == 0) { dispatch.call(obj, 'onfocus', obj); removeCopyingSelection(); } dispatch.call(obj, 'onselection', obj, borderLeft, borderTop, borderRight, borderBottom, origin); // Find corner cell updateCornerPosition.call(obj); }; /** * Get selected column numbers * * @return array */ export const getSelectedColumns = function (visibleOnly) { const obj = this; if (!obj.selectedCell) { return []; } const result = []; for (let i = Math.min(obj.selectedCell[0], obj.selectedCell[2]); i <= Math.max(obj.selectedCell[0], obj.selectedCell[2]); i++) { if (!visibleOnly || obj.headers[i].style.display != 'none') { result.push(i); } } return result; }; /** * Refresh current selection */ export const refreshSelection = function () { const obj = this; if (obj.selectedCell) { obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]); } }; /** * Remove copy selection * * @return void */ export const removeCopySelection = function () { const obj = this; // Remove current selection for (let i = 0; i < obj.selection.length; i++) { obj.selection[i].classList.remove('selection'); obj.selection[i].classList.remove('selection-left'); obj.selection[i].classList.remove('selection-right'); obj.selection[i].classList.remove('selection-top'); obj.selection[i].classList.remove('selection-bottom'); } obj.selection = []; }; const doubleDigitFormat = function (v) { v = '' + v; if (v.length == 1) { v = '0' + v; } return v; }; /** * Helper function to copy data using the corner icon */ export const copyData = function (o, d) { const obj = this; // Get data from all selected cells const data = obj.getData(true, false); // Selected cells const h = obj.selectedContainer; // Cells const x1 = parseInt(o.getAttribute('data-x')); const y1 = parseInt(o.getAttribute('data-y')); const x2 = parseInt(d.getAttribute('data-x')); const y2 = parseInt(d.getAttribute('data-y')); // Records const records = []; let breakControl = false; let rowNumber, colNumber; if (h[0] == x1) { // Vertical copy if (y1 < h[1]) { rowNumber = y1 - h[1]; } else { rowNumber = 1; } colNumber = 0; } else { if (x1 < h[0]) { colNumber = x1 - h[0]; } else { colNumber = 1; } rowNumber = 0; } // Copy data procedure let posx = 0; let posy = 0; for (let j = y1; j <= y2; j++) { // Skip hidden rows if (obj.rows[j] && obj.rows[j].element.style.display == 'none') { continue; } // Controls if (data[posy] == undefined) { posy = 0; } posx = 0; // Data columns if (h[0] != x1) { if (x1 < h[0]) { colNumber = x1 - h[0]; } else { colNumber = 1; } } // Data columns for (let i = x1; i <= x2; i++) { // Update non-readonly if ( obj.records[j][i] && !obj.records[j][i].element.classList.contains('readonly') && obj.records[j][i].element.style.display != 'none' && breakControl == false ) { // Stop if contains value if (!obj.selection.length) { if (obj.options.data[j][i] != '') { breakControl = true; continue; } } // Column if (data[posy] == undefined) { posx = 0; } else if (data[posy][posx] == undefined) { posx = 0; } // Value let value = data[posy][posx]; if (value && !data[1] && obj.parent.config.autoIncrement != false) { if ( obj.options.columns && obj.options.columns[i] && (!obj.options.columns[i].type || obj.options.columns[i].type == 'text' || obj.options.columns[i].type == 'number') ) { if (('' + value).substr(0, 1) == '=') { const tokens = value.match(/([A-Z]+[0-9]+)/g); if (tokens) { const affectedTokens = []; for (let index = 0; index < tokens.length; index++) { const position = getIdFromColumnName(tokens[index], 1); position[0] += colNumber; position[1] += rowNumber; if (position[1] < 0) { position[1] = 0; } const token = getColumnNameFromId([position[0], position[1]]); if (token != tokens[index]) { affectedTokens[tokens[index]] = token; } } // Update formula if (affectedTokens) { value = updateFormula(value, affectedTokens); } } } else { if (value == Number(value)) { value = Number(value) + rowNumber; } } } else if (obj.options.columns && obj.options.columns[i] && obj.options.columns[i].type == 'calendar') { const date = new Date(value); date.setDate(date.getDate() + rowNumber); value = date.getFullYear() + '-' + doubleDigitFormat(parseInt(date.getMonth() + 1)) + '-' + doubleDigitFormat(date.getDate()) + ' ' + '00:00:00'; } } records.push(updateCell.call(obj, i, j, value)); // Update all formulas in the chain updateFormulaChain.call(obj, i, j, records); } posx++; if (h[0] != x1) { colNumber++; } } posy++; rowNumber++; } // Update history setHistory.call(obj, { action: 'setValue', records: records, selection: obj.selectedCell, }); // Update table with custom configuration if applicable updateTable.call(obj); // On after changes const onafterchangesRecords = records.map(function (record) { return { x: record.x, y: record.y, value: record.value, oldValue: record.oldValue, }; }); dispatch.call(obj, 'onafterchanges', obj, onafterchangesRecords); }; export const hash = function (str) { let hash = 0, i, chr; if (!str || str.length === 0) { return hash; } else { for (i = 0; i < str.length; i++) { chr = str.charCodeAt(i); hash = (hash << 5) - hash + chr; hash |= 0; } } return hash; }; /** * Move coords to A1 in case overlaps with an excluded cell */ export const conditionalSelectionUpdate = function (type, o, d) { const obj = this; if (type == 1) { if (obj.selectedCell && ((o >= obj.selectedCell[1] && o <= obj.selectedCell[3]) || (d >= obj.selectedCell[1] && d <= obj.selectedCell[3]))) { obj.resetSelection(); return; } } else { if (obj.selectedCell && ((o >= obj.selectedCell[0] && o <= obj.selectedCell[2]) || (d >= obj.selectedCell[0] && d <= obj.selectedCell[2]))) { obj.resetSelection(); return; } } }; /** * Get selected rows numbers * * @return array */ export const getSelectedRows = function (visibleOnly) { const obj = this; if (!obj.selectedCell) { return []; } const result = []; for (let i = Math.min(obj.selectedCell[1], obj.selectedCell[3]); i <= Math.max(obj.selectedCell[1], obj.selectedCell[3]); i++) { if (!visibleOnly || obj.rows[i].element.style.display != 'none') { result.push(i); } } return result; }; export const selectAll = function () { const obj = this; if (!obj.selectedCell) { obj.selectedCell = []; } obj.selectedCell[0] = 0; obj.selectedCell[1] = 0; obj.selectedCell[2] = obj.headers.length - 1; obj.selectedCell[3] = obj.records.length - 1; obj.updateSelectionFromCoords(obj.selectedCell[0], obj.selectedCell[1], obj.selectedCell[2], obj.selectedCell[3]); }; export const getSelection = function () { const obj = this; if (!obj.selectedCell) { return null; } return [ Math.min(obj.selectedCell[0], obj.selectedCell[2]), Math.min(obj.selectedCell[1], obj.selectedCell[3]), Math.max(obj.selectedCell[0], obj.selectedCell[2]), Math.max(obj.selectedCell[1], obj.selectedCell[3]), ]; }; export const getSelected = function (columnNameOnly) { const obj = this; const selectedRange = getSelection.call(obj); if (!selectedRange) { return []; } const cells = []; for (let y = selectedRange[1]; y <= selectedRange[3]; y++) { for (let x = selectedRange[0]; x <= selectedRange[2]; x++) { if (columnNameOnly) { cells.push(getCellNameFromCoords(x, y)); } else { cells.push(obj.records[y][x]); } } } return cells; }; export const getRange = function () { const obj = this; const selectedRange = getSelection.call(obj); if (!selectedRange) { return ''; } const start = getCellNameFromCoords(selectedRange[0], selectedRange[1]); const end = getCellNameFromCoords(selectedRange[2], selectedRange[3]); if (start === end) { return obj.options.worksheetName + '!' + start; } return obj.options.worksheetName + '!' + start + ':' + end; }; export const isSelected = function (x, y) { const obj = this; const selection = getSelection.call(obj); return x >= selection[0] && x <= selection[2] && y >= selection[1] && y <= selection[3]; }; export const getHighlighted = function () { const obj = this; const selection = getSelection.call(obj); if (selection) { return [selection]; } return []; }; ================================================ FILE: src/utils/style.js ================================================ import dispatch from './dispatch.js'; import { getColumnNameFromId, getIdFromColumnName } from './internalHelpers.js'; import { setHistory } from './history.js'; /** * Get style information from cell(s) * * @return integer */ export const getStyle = function (cell, key) { const obj = this; // Cell if (!cell) { // Control vars const data = {}; // Column and row length const x = obj.options.data[0].length; const y = obj.options.data.length; // Go through the columns to get the data for (let j = 0; j < y; j++) { for (let i = 0; i < x; i++) { // Value const v = key ? obj.records[j][i].element.style[key] : obj.records[j][i].element.getAttribute('style'); // Any meta data for this column? if (v) { // Column name const k = getColumnNameFromId([i, j]); // Value data[k] = v; } } } return data; } else { cell = getIdFromColumnName(cell, true); return key ? obj.records[cell[1]][cell[0]].element.style[key] : obj.records[cell[1]][cell[0]].element.getAttribute('style'); } }; /** * Set meta information to cell(s) * * @return integer */ export const setStyle = function (o, k, v, force, ignoreHistoryAndEvents) { const obj = this; const newValue = {}; const oldValue = {}; // Apply style const applyStyle = function (cellId, key, value) { // Position const cell = getIdFromColumnName(cellId, true); if (obj.records[cell[1]] && obj.records[cell[1]][cell[0]] && (obj.records[cell[1]][cell[0]].element.classList.contains('readonly') == false || force)) { // Current value const currentValue = obj.records[cell[1]][cell[0]].element.style[key]; // Change layout if (currentValue == value && !force) { value = ''; obj.records[cell[1]][cell[0]].element.style[key] = ''; } else { obj.records[cell[1]][cell[0]].element.style[key] = value; } // History if (!oldValue[cellId]) { oldValue[cellId] = []; } if (!newValue[cellId]) { newValue[cellId] = []; } oldValue[cellId].push([key + ':' + currentValue]); newValue[cellId].push([key + ':' + value]); } }; if (k && v) { // Get object from string if (typeof o == 'string') { applyStyle(o, k, v); } } else { const keys = Object.keys(o); for (let i = 0; i < keys.length; i++) { let style = o[keys[i]]; if (typeof style == 'string') { style = style.split(';'); } for (let j = 0; j < style.length; j++) { if (typeof style[j] == 'string') { style[j] = style[j].split(':'); } // Apply value if (style[j][0].trim()) { applyStyle(keys[i], style[j][0].trim(), style[j][1]); } } } } let keys = Object.keys(oldValue); for (let i = 0; i < keys.length; i++) { oldValue[keys[i]] = oldValue[keys[i]].join(';'); } keys = Object.keys(newValue); for (let i = 0; i < keys.length; i++) { newValue[keys[i]] = newValue[keys[i]].join(';'); } if (!ignoreHistoryAndEvents) { // Keeping history of changes setHistory.call(obj, { action: 'setStyle', oldValue: oldValue, newValue: newValue, }); } dispatch.call(obj, 'onchangestyle', obj, newValue); }; export const resetStyle = function (o, ignoreHistoryAndEvents) { const obj = this; const keys = Object.keys(o); for (let i = 0; i < keys.length; i++) { // Position const cell = getIdFromColumnName(keys[i], true); if (obj.records[cell[1]] && obj.records[cell[1]][cell[0]]) { obj.records[cell[1]][cell[0]].element.setAttribute('style', ''); } } obj.setStyle(o, null, null, null, ignoreHistoryAndEvents); }; ================================================ FILE: src/utils/toolbar.js ================================================ import jSuites from 'jsuites'; import { getCellNameFromCoords } from './helpers.js'; import { getWorksheetInstance } from './internal.js'; const setItemStatus = function (toolbarItem, worksheet) { if (worksheet.options.editable != false) { toolbarItem.classList.remove('jtoolbar-disabled'); } else { toolbarItem.classList.add('jtoolbar-disabled'); } }; export const getDefault = function () { const items = []; const spreadsheet = this; const getActive = function () { return getWorksheetInstance.call(spreadsheet); }; items.push({ content: 'undo', onclick: function () { const worksheet = getActive(); worksheet.undo(); }, }); items.push({ content: 'redo', onclick: function () { const worksheet = getActive(); worksheet.redo(); }, }); items.push({ content: 'save', onclick: function () { const worksheet = getActive(); if (worksheet) { worksheet.download(); } }, }); items.push({ type: 'divisor', }); items.push({ type: 'select', width: '120px', options: ['Default', 'Verdana', 'Arial', 'Courier New'], render: function (e) { return '' + e + ''; }, onchange: function (a, b, c, d, e) { const worksheet = getActive(); let cells = worksheet.getSelected(true); if (cells) { let value = !e ? '' : d; worksheet.setStyle( Object.fromEntries( cells.map(function (cellName) { return [cellName, 'font-family: ' + value]; }) ) ); } }, updateState: function (a, b, toolbarItem) { setItemStatus(toolbarItem, getActive()); }, }); items.push({ type: 'select', width: '48px', content: 'format_size', options: ['x-small', 'small', 'medium', 'large', 'x-large'], render: function (e) { return '' + e + ''; }, onchange: function (a, b, c, value) { const worksheet = getActive(); let cells = worksheet.getSelected(true); if (cells) { worksheet.setStyle( Object.fromEntries( cells.map(function (cellName) { return [cellName, 'font-size: ' + value]; }) ) ); } }, updateState: function (a, b, toolbarItem) { setItemStatus(toolbarItem, getActive()); }, }); items.push({ type: 'select', options: ['left', 'center', 'right', 'justify'], render: function (e) { return 'format_align_' + e + ''; }, onchange: function (a, b, c, value) { const worksheet = getActive(); let cells = worksheet.getSelected(true); if (cells) { worksheet.setStyle( Object.fromEntries( cells.map(function (cellName) { return [cellName, 'text-align: ' + value]; }) ) ); } }, updateState: function (a, b, toolbarItem) { setItemStatus(toolbarItem, getActive()); }, }); items.push({ content: 'format_bold', onclick: function () { const worksheet = getActive(); let cells = worksheet.getSelected(true); if (cells) { worksheet.setStyle( Object.fromEntries( cells.map(function (cellName) { return [cellName, 'font-weight:bold']; }) ) ); } }, updateState: function (a, b, toolbarItem) { setItemStatus(toolbarItem, getActive()); }, }); items.push({ type: 'color', content: 'format_color_text', k: 'color', updateState: function (a, b, toolbarItem) { setItemStatus(toolbarItem, getActive()); }, }); items.push({ type: 'color', content: 'format_color_fill', k: 'background-color', updateState: function (a, b, toolbarItem, d) { setItemStatus(toolbarItem, getActive()); }, }); let verticalAlign = ['top', 'middle', 'bottom']; items.push({ type: 'select', options: ['vertical_align_top', 'vertical_align_center', 'vertical_align_bottom'], render: function (e) { return '' + e + ''; }, value: 1, onchange: function (a, b, c, d, value) { const worksheet = getActive(); let cells = worksheet.getSelected(true); if (cells) { worksheet.setStyle( Object.fromEntries( cells.map(function (cellName) { return [cellName, 'vertical-align: ' + verticalAlign[value]]; }) ) ); } }, updateState: function (a, b, toolbarItem) { setItemStatus(toolbarItem, getActive()); }, }); items.push({ content: 'web', tooltip: jSuites.translate('Merge the selected cells'), onclick: function () { const worksheet = getActive(); if (worksheet.selectedCell && confirm(jSuites.translate('The merged cells will retain the value of the top-left cell only. Are you sure?'))) { const selectedRange = [ Math.min(worksheet.selectedCell[0], worksheet.selectedCell[2]), Math.min(worksheet.selectedCell[1], worksheet.selectedCell[3]), Math.max(worksheet.selectedCell[0], worksheet.selectedCell[2]), Math.max(worksheet.selectedCell[1], worksheet.selectedCell[3]), ]; let cell = getCellNameFromCoords(selectedRange[0], selectedRange[1]); if (worksheet.records[selectedRange[1]][selectedRange[0]].element.getAttribute('data-merged')) { worksheet.removeMerge(cell); } else { let colspan = selectedRange[2] - selectedRange[0] + 1; let rowspan = selectedRange[3] - selectedRange[1] + 1; if (colspan !== 1 || rowspan !== 1) { worksheet.setMerge(cell, colspan, rowspan); } } } }, updateState: function (a, b, toolbarItem) { setItemStatus(toolbarItem, getActive()); }, }); items.push({ type: 'select', options: [ 'border_all', 'border_outer', 'border_inner', 'border_horizontal', 'border_vertical', 'border_left', 'border_top', 'border_right', 'border_bottom', 'border_clear', ], columns: 5, render: function (e) { return '' + e + ''; }, right: true, onchange: function (a, b, c, d) { const worksheet = getActive(); if (worksheet.selectedCell) { const selectedRange = [ Math.min(worksheet.selectedCell[0], worksheet.selectedCell[2]), Math.min(worksheet.selectedCell[1], worksheet.selectedCell[3]), Math.max(worksheet.selectedCell[0], worksheet.selectedCell[2]), Math.max(worksheet.selectedCell[1], worksheet.selectedCell[3]), ]; let type = d; if (selectedRange) { // Default options let thickness = b.thickness || 1; let color = b.color || 'black'; const borderStyle = b.style || 'solid'; if (borderStyle === 'double') { thickness += 2; } let style = {}; // Matrix let px = selectedRange[0]; let py = selectedRange[1]; let ux = selectedRange[2]; let uy = selectedRange[3]; const setBorder = function (columnName, i, j) { let border = ['', '', '', '']; if ( ((type === 'border_top' || type === 'border_outer') && j === py) || ((type === 'border_inner' || type === 'border_horizontal') && j > py) || type === 'border_all' ) { border[0] = 'border-top: ' + thickness + 'px ' + borderStyle + ' ' + color; } else { border[0] = 'border-top: '; } if ((type === 'border_all' || type === 'border_right' || type === 'border_outer') && i === ux) { border[1] = 'border-right: ' + thickness + 'px ' + borderStyle + ' ' + color; } else { border[1] = 'border-right: '; } if ((type === 'border_all' || type === 'border_bottom' || type === 'border_outer') && j === uy) { border[2] = 'border-bottom: ' + thickness + 'px ' + borderStyle + ' ' + color; } else { border[2] = 'border-bottom: '; } if ( ((type === 'border_left' || type === 'border_outer') && i === px) || ((type === 'border_inner' || type === 'border_vertical') && i > px) || type === 'border_all' ) { border[3] = 'border-left: ' + thickness + 'px ' + borderStyle + ' ' + color; } else { border[3] = 'border-left: '; } style[columnName] = border.join(';'); }; for (let j = selectedRange[1]; j <= selectedRange[3]; j++) { // Row - py - uy for (let i = selectedRange[0]; i <= selectedRange[2]; i++) { // Col - px - ux setBorder(getCellNameFromCoords(i, j), i, j); if (worksheet.records[j][i].element.getAttribute('data-merged')) { setBorder(getCellNameFromCoords(selectedRange[0], selectedRange[1]), i, j); } } } if (Object.keys(style)) { worksheet.setStyle(style); } } } }, onload: function (a, b) { // Border color let container = document.createElement('div'); let div = document.createElement('div'); container.appendChild(div); let colorPicker = jSuites.color(div, { closeOnChange: false, onchange: function (o, v) { o.parentNode.children[1].style.color = v; b.color = v; }, }); let i = document.createElement('i'); i.classList.add('material-icons'); i.innerHTML = 'color_lens'; i.onclick = function () { colorPicker.open(); }; container.appendChild(i); a.children[1].appendChild(container); div = document.createElement('div'); jSuites.picker(div, { type: 'select', data: [1, 2, 3, 4, 5], render: function (e) { return '
'; }, onchange: function (a, k, c, d) { b.thickness = d; }, width: '50px', }); a.children[1].appendChild(div); const borderStylePicker = document.createElement('div'); jSuites.picker(borderStylePicker, { type: 'select', data: ['solid', 'dotted', 'dashed', 'double'], render: function (e) { if (e === 'double') { return '
'; } return '
'; }, onchange: function (a, k, c, d) { b.style = d; }, width: '50px', }); a.children[1].appendChild(borderStylePicker); div = document.createElement('div'); div.style.flex = '1'; a.children[1].appendChild(div); }, updateState: function (a, b, toolbarItem) { setItemStatus(toolbarItem, getActive()); }, }); items.push({ type: 'divisor', }); items.push({ content: 'fullscreen', tooltip: 'Toggle Fullscreen', onclick: function (a, b, c) { if (c.children[0].textContent === 'fullscreen') { spreadsheet.fullscreen(true); c.children[0].textContent = 'fullscreen_exit'; } else { spreadsheet.fullscreen(false); c.children[0].textContent = 'fullscreen'; } }, updateState: function (a, b, c, d) { if (d.parent.config.fullscreen === true) { c.children[0].textContent = 'fullscreen_exit'; } else { c.children[0].textContent = 'fullscreen'; } }, }); return items; }; const adjustToolbarSettingsForJSuites = function (toolbar) { const spreadsheet = this; const items = toolbar.items; for (let i = 0; i < items.length; i++) { // Tooltip if (items[i].tooltip) { items[i].title = items[i].tooltip; delete items[i].tooltip; } if (items[i].type == 'select') { if (items[i].options) { items[i].data = items[i].options; delete items[i].options; } else { items[i].data = items[i].v; delete items[i].v; if (items[i].k && !items[i].onchange) { items[i].onchange = function (el, config, value) { const worksheet = getWorksheetInstance.call(spreadsheet); const cells = worksheet.getSelected(true); worksheet.setStyle( Object.fromEntries( cells.map(function (cellName) { return [cellName, items[i].k + ': ' + value]; }) ) ); }; } } } else if (items[i].type == 'color') { items[i].type = 'i'; items[i].onclick = function (a, b, c) { if (!c.color) { jSuites.color(c, { onchange: function (o, v) { const worksheet = getWorksheetInstance.call(spreadsheet); const cells = worksheet.getSelected(true); worksheet.setStyle( Object.fromEntries( cells.map(function (cellName) { return [cellName, items[i].k + ': ' + v]; }) ) ); }, onopen: function (o) { o.color.select(''); }, }); c.color.open(); } }; } } }; /** * Create toolbar */ export const createToolbar = function (toolbar) { const spreadsheet = this; const toolbarElement = document.createElement('div'); toolbarElement.classList.add('jss_toolbar'); adjustToolbarSettingsForJSuites.call(spreadsheet, toolbar); if (typeof spreadsheet.plugins === 'object') { Object.entries(spreadsheet.plugins).forEach(function ([, plugin]) { if (typeof plugin.toolbar === 'function') { const result = plugin.toolbar(toolbar); if (result) { toolbar = result; } } }); } jSuites.toolbar(toolbarElement, toolbar); return toolbarElement; }; export const updateToolbar = function (worksheet) { if (worksheet.parent.toolbar) { worksheet.parent.toolbar.toolbar.update(worksheet); } }; export const showToolbar = function () { const spreadsheet = this; if (spreadsheet.config.toolbar && !spreadsheet.toolbar) { let toolbar; if (Array.isArray(spreadsheet.config.toolbar)) { toolbar = { items: spreadsheet.config.toolbar, }; } else if (typeof spreadsheet.config.toolbar === 'object') { toolbar = spreadsheet.config.toolbar; } else { toolbar = { items: getDefault.call(spreadsheet), }; if (typeof spreadsheet.config.toolbar === 'function') { toolbar = spreadsheet.config.toolbar(toolbar); } } spreadsheet.toolbar = spreadsheet.element.insertBefore(createToolbar.call(spreadsheet, toolbar), spreadsheet.element.children[1]); } }; export const hideToolbar = function () { const spreadsheet = this; if (spreadsheet.toolbar) { spreadsheet.toolbar.parentNode.removeChild(spreadsheet.toolbar); delete spreadsheet.toolbar; } }; ================================================ FILE: src/utils/version.js ================================================ // Basic version information export default { version: '5.0.0', host: 'https://bossanova.uk/jspreadsheet', license: 'MIT', print: function () { return [['Jspreadsheet CE', this.version, this.host, this.license].join('\r\n')]; }, }; ================================================ FILE: src/utils/worksheets.js ================================================ import jSuites from 'jsuites'; import libraryBase from './libraryBase.js'; import { parseCSV } from './helpers.js'; import { createCellHeader, deleteColumn, getColumnData, getNumberOfColumns, getWidth, hideColumn, insertColumn, moveColumn, setColumnData, setWidth, showColumn, } from './columns.js'; import { getData, getDataFromRange, getValue, getValueFromCoords, setData, setValue, setValueFromCoords } from './data.js'; import { cutControls, scrollControls, wheelControls } from './events.js'; import { getHighlighted, getRange, getSelected, getSelectedColumns, getSelectedRows, getSelection, isSelected, resetSelection, selectAll, updateSelectionFromCoords, } from './selection.js'; import { deleteRow, getHeight, getRowData, hideRow, insertRow, moveRow, setHeight, setRowData, showRow } from './rows.js'; import { destroyMerge, getMerge, removeMerge, setMerge } from './merges.js'; import { resetSearch, search } from './search.js'; import { getHeader, getHeaders, setHeader } from './headers.js'; import { getStyle, resetStyle, setStyle } from './style.js'; import { page, quantiyOfPages, whichPage } from './pagination.js'; import { download } from './download.js'; import { down, first, last, left, right, up } from './keys.js'; import { createNestedHeader, executeFormula, getCell, getCellFromCoords, getLabel, getWorksheetActive, hideIndex, showIndex } from './internal.js'; import { getComments, setComments } from './comments.js'; import { orderBy } from './orderBy.js'; import { getWorksheetConfig, setConfig } from './config.js'; import { getMeta, setMeta } from './meta.js'; import { closeEditor, openEditor } from './editor.js'; import dispatch from './dispatch.js'; import { getIdFromColumnName } from './internalHelpers.js'; import { copy, paste } from './copyPaste.js'; import { isReadOnly, setReadOnly } from './cells.js'; import { openFilter, resetFilters } from './filter.js'; import { redo, undo } from './history.js'; const setWorksheetFunctions = function (worksheet) { for (let i = 0; i < worksheetPublicMethodsLength; i++) { const [methodName, method] = worksheetPublicMethods[i]; worksheet[methodName] = method.bind(worksheet); } }; const createTable = function () { let obj = this; setWorksheetFunctions(obj); // Elements obj.table = document.createElement('table'); obj.thead = document.createElement('thead'); obj.tbody = document.createElement('tbody'); // Create headers controllers obj.headers = []; obj.cols = []; // Create table container obj.content = document.createElement('div'); obj.content.classList.add('jss_content'); obj.content.onscroll = function (e) { scrollControls.call(obj, e); }; obj.content.onwheel = function (e) { wheelControls.call(obj, e); }; // Search const searchContainer = document.createElement('div'); const searchLabel = document.createElement('label'); searchLabel.innerHTML = jSuites.translate('Search') + ': '; searchContainer.appendChild(searchLabel); obj.searchInput = document.createElement('input'); obj.searchInput.classList.add('jss_search'); searchLabel.appendChild(obj.searchInput); obj.searchInput.onfocus = function () { obj.resetSelection(); }; // Pagination select option const paginationUpdateContainer = document.createElement('div'); if (obj.options.pagination > 0 && obj.options.paginationOptions && obj.options.paginationOptions.length > 0) { obj.paginationDropdown = document.createElement('select'); obj.paginationDropdown.classList.add('jss_pagination_dropdown'); obj.paginationDropdown.onchange = function () { obj.options.pagination = parseInt(this.value); obj.page(0); }; for (let i = 0; i < obj.options.paginationOptions.length; i++) { const temp = document.createElement('option'); temp.value = obj.options.paginationOptions[i]; temp.innerHTML = obj.options.paginationOptions[i]; obj.paginationDropdown.appendChild(temp); } // Set initial pagination value obj.paginationDropdown.value = obj.options.pagination; paginationUpdateContainer.appendChild(document.createTextNode(jSuites.translate('Show '))); paginationUpdateContainer.appendChild(obj.paginationDropdown); paginationUpdateContainer.appendChild(document.createTextNode(jSuites.translate('entries'))); } // Filter and pagination container const filter = document.createElement('div'); filter.classList.add('jss_filter'); filter.appendChild(paginationUpdateContainer); filter.appendChild(searchContainer); // Colsgroup obj.colgroupContainer = document.createElement('colgroup'); let tempCol = document.createElement('col'); tempCol.setAttribute('width', '50'); obj.colgroupContainer.appendChild(tempCol); // Nested if (obj.options.nestedHeaders && obj.options.nestedHeaders.length > 0 && obj.options.nestedHeaders[0] && obj.options.nestedHeaders[0][0]) { for (let j = 0; j < obj.options.nestedHeaders.length; j++) { obj.thead.appendChild(createNestedHeader.call(obj, obj.options.nestedHeaders[j])); } } // Row obj.headerContainer = document.createElement('tr'); tempCol = document.createElement('td'); tempCol.classList.add('jss_selectall'); obj.headerContainer.appendChild(tempCol); const numberOfColumns = getNumberOfColumns.call(obj); for (let i = 0; i < numberOfColumns; i++) { // Create header createCellHeader.call(obj, i); // Append cell to the container obj.headerContainer.appendChild(obj.headers[i]); obj.colgroupContainer.appendChild(obj.cols[i].colElement); } obj.thead.appendChild(obj.headerContainer); // Filters if (obj.options.filters == true) { obj.filter = document.createElement('tr'); const td = document.createElement('td'); obj.filter.appendChild(td); for (let i = 0; i < obj.options.columns.length; i++) { const td = document.createElement('td'); td.innerHTML = ' '; td.setAttribute('data-x', i); td.className = 'jss_column_filter'; if (obj.options.columns[i].type == 'hidden') { td.style.display = 'none'; } obj.filter.appendChild(td); } obj.thead.appendChild(obj.filter); } // Content table obj.table = document.createElement('table'); obj.table.classList.add('jss_worksheet'); obj.table.setAttribute('cellpadding', '0'); obj.table.setAttribute('cellspacing', '0'); obj.table.setAttribute('unselectable', 'yes'); //obj.table.setAttribute('onselectstart', 'return false'); obj.table.appendChild(obj.colgroupContainer); obj.table.appendChild(obj.thead); obj.table.appendChild(obj.tbody); if (!obj.options.textOverflow) { obj.table.classList.add('jss_overflow'); } // Spreadsheet corner obj.corner = document.createElement('div'); obj.corner.className = 'jss_corner'; obj.corner.setAttribute('unselectable', 'on'); obj.corner.setAttribute('onselectstart', 'return false'); if (obj.options.selectionCopy == false) { obj.corner.style.display = 'none'; } // Textarea helper obj.textarea = document.createElement('textarea'); obj.textarea.className = 'jss_textarea'; obj.textarea.id = 'jss_textarea'; obj.textarea.tabIndex = '-1'; obj.textarea.ariaHidden = 'true'; // Powered by Jspreadsheet const ads = document.createElement('a'); ads.setAttribute('href', 'https://bossanova.uk/jspreadsheet/'); obj.ads = document.createElement('div'); obj.ads.className = 'jss_about'; const span = document.createElement('span'); span.innerHTML = 'Jspreadsheet CE'; ads.appendChild(span); obj.ads.appendChild(ads); // Create table container TODO: frozen columns const container = document.createElement('div'); container.classList.add('jss_table'); // Pagination obj.pagination = document.createElement('div'); obj.pagination.classList.add('jss_pagination'); const paginationInfo = document.createElement('div'); const paginationPages = document.createElement('div'); obj.pagination.appendChild(paginationInfo); obj.pagination.appendChild(paginationPages); // Hide pagination if not in use if (!obj.options.pagination) { obj.pagination.style.display = 'none'; } // Append containers to the table if (obj.options.search == true) { obj.element.appendChild(filter); } // Elements obj.content.appendChild(obj.table); obj.content.appendChild(obj.corner); obj.content.appendChild(obj.textarea); obj.element.appendChild(obj.content); obj.element.appendChild(obj.pagination); obj.element.appendChild(obj.ads); obj.element.classList.add('jss_container'); obj.element.jssWorksheet = obj; obj.element.jspreadsheet = obj; // Overflow if (obj.options.tableOverflow == true) { if (obj.options.tableHeight) { obj.content.style['overflow-y'] = 'auto'; obj.content.style['box-shadow'] = 'rgb(221 221 221) 2px 2px 5px 0.1px'; obj.content.style.maxHeight = typeof obj.options.tableHeight === 'string' ? obj.options.tableHeight : obj.options.tableHeight + 'px'; } if (obj.options.tableWidth) { obj.content.style['overflow-x'] = 'auto'; obj.content.style.width = typeof obj.options.tableWidth === 'string' ? obj.options.tableWidth : obj.options.tableWidth + 'px'; } } // With toolbars if (obj.options.tableOverflow != true && obj.parent.config.toolbar) { obj.element.classList.add('with-toolbar'); } // Actions if (obj.options.columnDrag != false) { obj.thead.classList.add('draggable'); } if (obj.options.columnResize != false) { obj.thead.classList.add('resizable'); } if (obj.options.rowDrag != false) { obj.tbody.classList.add('draggable'); } if (obj.options.rowResize != false) { obj.tbody.classList.add('resizable'); } // Load data obj.setData.call(obj); // Style if (obj.options.style) { obj.setStyle(obj.options.style, null, null, 1, 1); delete obj.options.style; } Object.defineProperty(obj.options, 'style', { enumerable: true, configurable: true, get() { return obj.getStyle(); }, }); if (obj.options.comments) { obj.setComments(obj.options.comments); } // Classes if (obj.options.classes) { const k = Object.keys(obj.options.classes); for (let i = 0; i < k.length; i++) { const cell = getIdFromColumnName(k[i], true); obj.records[cell[1]][cell[0]].element.classList.add(obj.options.classes[k[i]]); } } }; /** * Prepare the jspreadsheet table * * @Param config */ const prepareTable = function () { const obj = this; // Lazy loading if (obj.options.lazyLoading == true && obj.options.tableOverflow != true && obj.parent.config.fullscreen != true) { console.error('Jspreadsheet: The lazyloading only works when tableOverflow = yes or fullscreen = yes'); obj.options.lazyLoading = false; } if (!obj.options.columns) { obj.options.columns = []; } // Number of columns let size = obj.options.columns.length; let keys; if (obj.options.data && typeof obj.options.data[0] !== 'undefined') { if (!Array.isArray(obj.options.data[0])) { // Data keys keys = Object.keys(obj.options.data[0]); if (keys.length > size) { size = keys.length; } } else { const numOfColumns = obj.options.data[0].length; if (numOfColumns > size) { size = numOfColumns; } } } // Minimal dimensions if (!obj.options.minDimensions) { obj.options.minDimensions = [0, 0]; } if (obj.options.minDimensions[0] > size) { size = obj.options.minDimensions[0]; } // Requests const multiple = []; // Preparations for (let i = 0; i < size; i++) { // Default column description if (!obj.options.columns[i]) { obj.options.columns[i] = {}; } if (!obj.options.columns[i].name && keys && keys[i]) { obj.options.columns[i].name = keys[i]; } // Pre-load initial source for json dropdown if (obj.options.columns[i].type == 'dropdown') { // if remote content if (obj.options.columns[i].url) { multiple.push({ url: obj.options.columns[i].url, index: i, method: 'GET', dataType: 'json', success: function (data) { if (!obj.options.columns[this.index].source) { obj.options.columns[this.index].source = []; } for (let i = 0; i < data.length; i++) { obj.options.columns[this.index].source.push(data[i]); } }, }); } } } // Create the table when is ready if (!multiple.length) { createTable.call(obj); } else { jSuites.ajax(multiple, function () { createTable.call(obj); }); } }; export const getNextDefaultWorksheetName = function (spreadsheet) { const defaultWorksheetNameRegex = /^Sheet(\d+)$/; let largestWorksheetNumber = 0; spreadsheet.worksheets.forEach(function (worksheet) { const regexResult = defaultWorksheetNameRegex.exec(worksheet.options.worksheetName); if (regexResult) { largestWorksheetNumber = Math.max(largestWorksheetNumber, parseInt(regexResult[1])); } }); return 'Sheet' + (largestWorksheetNumber + 1); }; export const buildWorksheet = async function () { const obj = this; const el = obj.element; const spreadsheet = obj.parent; if (typeof spreadsheet.plugins === 'object') { Object.entries(spreadsheet.plugins).forEach(function ([, plugin]) { if (typeof plugin.beforeinit === 'function') { plugin.beforeinit(obj); } }); } libraryBase.jspreadsheet.current = obj; const promises = []; // Load the table data based on an CSV file if (obj.options.csv) { const promise = new Promise((resolve) => { // Load CSV file jSuites.ajax({ url: obj.options.csv, method: 'GET', dataType: 'text', success: function (result) { // Convert data const newData = parseCSV(result, obj.options.csvDelimiter); // Headers if (obj.options.csvHeaders == true && newData.length > 0) { const headers = newData.shift(); if (headers.length > 0) { if (!obj.options.columns) { obj.options.columns = []; } for (let i = 0; i < headers.length; i++) { if (!obj.options.columns[i]) { obj.options.columns[i] = {}; } // Precedence over pre-configurated titles if (typeof obj.options.columns[i].title === 'undefined') { obj.options.columns[i].title = headers[i]; } } } } // Data obj.options.data = newData; // Prepare table prepareTable.call(obj); resolve(); }, }); }); promises.push(promise); } else if (obj.options.url) { const promise = new Promise((resolve) => { jSuites.ajax({ url: obj.options.url, method: 'GET', dataType: 'json', success: function (result) { // Data obj.options.data = result.data ? result.data : result; // Prepare table prepareTable.call(obj); resolve(); }, }); }); promises.push(promise); } else { // Prepare table prepareTable.call(obj); } await Promise.all(promises); if (typeof spreadsheet.plugins === 'object') { Object.entries(spreadsheet.plugins).forEach(function ([, plugin]) { if (typeof plugin.init === 'function') { plugin.init(obj); } }); } }; export const createWorksheetObj = function (options) { const obj = this; const spreadsheet = obj.parent; if (!options.worksheetName) { options.worksheetName = getNextDefaultWorksheetName(obj.parent); } const newWorksheet = { parent: spreadsheet, options: options, filters: [], formula: [], history: [], selection: [], historyIndex: -1, }; spreadsheet.config.worksheets.push(newWorksheet.options); spreadsheet.worksheets.push(newWorksheet); return newWorksheet; }; export const createWorksheet = function (options) { const obj = this; const spreadsheet = obj.parent; spreadsheet.creationThroughJss = true; createWorksheetObj.call(obj, options); spreadsheet.element.tabs.create(options.worksheetName); }; export const openWorksheet = function (position) { const obj = this; const spreadsheet = obj.parent; spreadsheet.element.tabs.open(position); }; export const deleteWorksheet = function (position) { const obj = this; obj.parent.element.tabs.remove(position); const removedWorksheet = obj.parent.worksheets.splice(position, 1)[0]; dispatch.call(obj.parent, 'ondeleteworksheet', removedWorksheet, position); }; const worksheetPublicMethods = [ ['selectAll', selectAll], [ 'updateSelectionFromCoords', function (x1, y1, x2, y2) { return updateSelectionFromCoords.call(this, x1, y1, x2, y2); }, ], [ 'resetSelection', function () { return resetSelection.call(this); }, ], ['getSelection', getSelection], ['getSelected', getSelected], ['getSelectedColumns', getSelectedColumns], ['getSelectedRows', getSelectedRows], ['getData', getData], ['setData', setData], ['getValue', getValue], ['getValueFromCoords', getValueFromCoords], ['setValue', setValue], ['setValueFromCoords', setValueFromCoords], ['getWidth', getWidth], [ 'setWidth', function (column, width) { return setWidth.call(this, column, width); }, ], ['insertRow', insertRow], [ 'moveRow', function (rowNumber, newPositionNumber) { return moveRow.call(this, rowNumber, newPositionNumber); }, ], ['deleteRow', deleteRow], ['hideRow', hideRow], ['showRow', showRow], ['getRowData', getRowData], ['setRowData', setRowData], ['getHeight', getHeight], [ 'setHeight', function (row, height) { return setHeight.call(this, row, height); }, ], ['getMerge', getMerge], [ 'setMerge', function (cellName, colspan, rowspan) { return setMerge.call(this, cellName, colspan, rowspan); }, ], [ 'destroyMerge', function () { return destroyMerge.call(this); }, ], [ 'removeMerge', function (cellName, data) { return removeMerge.call(this, cellName, data); }, ], ['search', search], ['resetSearch', resetSearch], ['getHeader', getHeader], ['getHeaders', getHeaders], ['setHeader', setHeader], ['getStyle', getStyle], [ 'setStyle', function (cell, property, value, forceOverwrite) { return setStyle.call(this, cell, property, value, forceOverwrite); }, ], ['resetStyle', resetStyle], ['insertColumn', insertColumn], ['moveColumn', moveColumn], ['deleteColumn', deleteColumn], ['getColumnData', getColumnData], ['setColumnData', setColumnData], ['whichPage', whichPage], ['page', page], ['download', download], ['getComments', getComments], ['setComments', setComments], ['orderBy', orderBy], ['undo', undo], ['redo', redo], ['getCell', getCell], ['getCellFromCoords', getCellFromCoords], ['getLabel', getLabel], ['getConfig', getWorksheetConfig], ['setConfig', setConfig], [ 'getMeta', function (cell) { return getMeta.call(this, cell); }, ], ['setMeta', setMeta], ['showColumn', showColumn], ['hideColumn', hideColumn], ['showIndex', showIndex], ['hideIndex', hideIndex], ['getWorksheetActive', getWorksheetActive], ['openEditor', openEditor], ['closeEditor', closeEditor], ['createWorksheet', createWorksheet], ['openWorksheet', openWorksheet], ['deleteWorksheet', deleteWorksheet], [ 'copy', function (cut) { if (cut) { cutControls(); } else { copy.call(this, true); } }, ], ['paste', paste], ['executeFormula', executeFormula], ['getDataFromRange', getDataFromRange], ['quantiyOfPages', quantiyOfPages], ['getRange', getRange], ['isSelected', isSelected], ['setReadOnly', setReadOnly], ['isReadOnly', isReadOnly], ['getHighlighted', getHighlighted], ['dispatch', dispatch], ['down', down], ['first', first], ['last', last], ['left', left], ['right', right], ['up', up], ['openFilter', openFilter], ['resetFilters', resetFilters], ]; const worksheetPublicMethodsLength = worksheetPublicMethods.length; ================================================ FILE: src/webcomponent.js ================================================ class Jspreadsheet extends HTMLElement { constructor() { super(); } init() { // Shadow root const shadowRoot = this.attachShadow({ mode: 'open' }); // Style const css = document.createElement('link'); css.rel = 'stylesheet'; css.type = 'text/css'; css.href = 'https://cdn.jsdelivr.net/npm/jspreadsheet-ce/dist/jspreadsheet.min.css'; shadowRoot.appendChild(css); const cssJsuites = document.createElement('link'); cssJsuites.rel = 'stylesheet'; cssJsuites.type = 'text/css'; cssJsuites.href = 'https://cdn.jsdelivr.net/npm/jsuites/dist/jsuites.min.css'; shadowRoot.appendChild(cssJsuites); const cssMaterial = document.createElement('link'); cssMaterial.rel = 'stylesheet'; cssMaterial.type = 'text/css'; cssMaterial.href = 'https://fonts.googleapis.com/css?family=Material+Icons'; shadowRoot.appendChild(cssMaterial); // JSS container const container = document.createElement('div'); shadowRoot.appendChild(container); // Properties const toolbar = this.getAttribute('toolbar') == 'true' ? true : false; // Create jexcel element this.el = jspreadsheet(container, { tabs: true, toolbar: toolbar, root: shadowRoot, worksheets: [ { filters: true, minDimensions: [6, 6], }, ], }); } connectedCallback() { this.init(this); } disconnectedCallback() {} attributeChangedCallback() {} } window.customElements.define('j-spreadsheet-ce', Jspreadsheet); ================================================ FILE: test/calculations.js ================================================ const { expect } = require('chai'); const jspreadsheet = require('../dist/index.js'); describe('Calculations', () => { it('Testing formula chain', () => { let test = jspreadsheet(root, { worksheets: [ { data: [ ['1', ''], ['', ''], ['', ''], ['', ''], ['', ''], ], }, ], }); test[0].setValue('B5', '=B3+A1'); test[0].setValue('B3', '=A1+1'); test[0].setValue('A1', '2'); expect(test[0].getValue('B5', true)).to.equal('5'); }); describe('Test updating formulas when adding new rows', () => { it('1', () => { let test = jspreadsheet(root, { worksheets: [ { data: [ ['1', '2', '3', '=SUM(A2:C2)'], ['4', '5', '6', '=SUM(A2:C2)'], ['7', '8', '9', '=SUM(A2:C2)'], ], worksheetName: 'sheet1', }, ], }); test[0].insertRow(1, 1, true); expect(test[0].getValue('D1')).to.equal('=SUM(A3:C3)'); expect(test[0].getValue('D2')).to.equal(''); expect(test[0].getValue('D3')).to.equal('=SUM(A3:C3)'); expect(test[0].getValue('D4')).to.equal('=SUM(A3:C3)'); }); it('2', () => { let test = jspreadsheet(root, { worksheets: [ { data: [ ['1', '2', '3', '=SUM(A2:C3)'], ['4', '5', '6', '=SUM(A2:C3)'], ['7', '8', '9', '=SUM(A2:C3)'], ['10', '11', '12', '=SUM(A2:C3)'], ], worksheetName: 'sheet1', }, ], }); test[0].insertRow(1, 1, true); expect(test[0].getValue('D1')).to.equal('=SUM(A3:C4)'); expect(test[0].getValue('D2')).to.equal(''); expect(test[0].getValue('D3')).to.equal('=SUM(A3:C4)'); expect(test[0].getValue('D4')).to.equal('=SUM(A3:C4)'); expect(test[0].getValue('D5')).to.equal('=SUM(A3:C4)'); }); it('3', () => { let test = jspreadsheet(root, { worksheets: [ { data: [ ['1', '2', '3', '=SUM(A2:C3)'], ['4', '5', '6', '=SUM(A2:C3)'], ['7', '8', '9', '=SUM(A2:C3)'], ['10', '11', '12', '=SUM(A2:C3)'], ], worksheetName: 'sheet1', }, ], }); test[0].insertRow(1, 1, false); expect(test[0].getValue('D1')).to.equal('=SUM(A2:C4)'); expect(test[0].getValue('D2')).to.equal('=SUM(A2:C4)'); expect(test[0].getValue('D3')).to.equal(''); expect(test[0].getValue('D4')).to.equal('=SUM(A2:C4)'); expect(test[0].getValue('D5')).to.equal('=SUM(A2:C4)'); }); it('4', () => { let test = jspreadsheet(root, { worksheets: [ { data: [ ['1', '2', '3', '=SUM(A2:C3)'], ['4', '5', '6', '=SUM(A2:C3)'], ['7', '8', '9', '=SUM(A2:C3)'], ['10', '11', '12', '=SUM(A2:C3)'], ], worksheetName: 'sheet1', }, ], }); test[0].insertRow(1, 2, true); expect(test[0].getValue('D1')).to.equal('=SUM(A2:C4)'); expect(test[0].getValue('D2')).to.equal('=SUM(A2:C4)'); expect(test[0].getValue('D3')).to.equal(''); expect(test[0].getValue('D4')).to.equal('=SUM(A2:C4)'); expect(test[0].getValue('D5')).to.equal('=SUM(A2:C4)'); }); it('5', () => { let test = jspreadsheet(root, { worksheets: [ { data: [ ['1', '2', '3', '=SUM(A2:C3)'], ['4', '5', '6', '=SUM(A2:C3)'], ['7', '8', '9', '=SUM(A2:C3)'], ['10', '11', '12', '=SUM(A2:C3)'], ], worksheetName: 'sheet1', }, ], }); test[0].insertRow(1, 2, false); expect(test[0].getValue('D1')).to.equal('=SUM(A2:C3)'); expect(test[0].getValue('D2')).to.equal('=SUM(A2:C3)'); expect(test[0].getValue('D3')).to.equal('=SUM(A2:C3)'); expect(test[0].getValue('D4')).to.equal(''); expect(test[0].getValue('D5')).to.equal('=SUM(A2:C3)'); }); }); describe('Test updating formulas when adding new columns', () => { it('1', () => { let test = jspreadsheet(root, { worksheets: [ { data: [ ['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9'], ['=SUM(B1:B3)', '=SUM(B1:B3)', '=SUM(B1:B3)'], ], worksheetName: 'sheet1', }, ], }); test[0].insertColumn(1, 1, true); expect(test[0].getValue('A4')).to.equal('=SUM(C1:C3)'); expect(test[0].getValue('B4')).to.equal(''); expect(test[0].getValue('C4')).to.equal('=SUM(C1:C3)'); expect(test[0].getValue('D4')).to.equal('=SUM(C1:C3)'); }); it('2', () => { let test = jspreadsheet(root, { worksheets: [ { data: [ ['1', '2', '3', '4'], ['5', '6', '7', '8'], ['9', '10', '11', '12'], ['=SUM(B1:C3)', '=SUM(B1:C3)', '=SUM(B1:C3)', '=SUM(B1:C3)'], ], worksheetName: 'sheet1', }, ], }); test[0].insertColumn(1, 1, true); expect(test[0].getValue('A4')).to.equal('=SUM(C1:D3)'); expect(test[0].getValue('B4')).to.equal(''); expect(test[0].getValue('C4')).to.equal('=SUM(C1:D3)'); expect(test[0].getValue('D4')).to.equal('=SUM(C1:D3)'); expect(test[0].getValue('E4')).to.equal('=SUM(C1:D3)'); }); it('3', () => { let test = jspreadsheet(root, { worksheets: [ { data: [ ['1', '2', '3', '4'], ['5', '6', '7', '8'], ['9', '10', '11', '12'], ['=SUM(B1:C3)', '=SUM(B1:C3)', '=SUM(B1:C3)', '=SUM(B1:C3)'], ], worksheetName: 'sheet1', }, ], }); test[0].insertColumn(1, 1, false); expect(test[0].getValue('A4')).to.equal('=SUM(B1:D3)'); expect(test[0].getValue('B4')).to.equal('=SUM(B1:D3)'); expect(test[0].getValue('C4')).to.equal(''); expect(test[0].getValue('D4')).to.equal('=SUM(B1:D3)'); expect(test[0].getValue('E4')).to.equal('=SUM(B1:D3)'); }); it('4', () => { let test = jspreadsheet(root, { worksheets: [ { data: [ ['1', '2', '3', '4'], ['5', '6', '7', '8'], ['9', '10', '11', '12'], ['=SUM(B1:C3)', '=SUM(B1:C3)', '=SUM(B1:C3)', '=SUM(B1:C3)'], ], worksheetName: 'sheet1', }, ], }); test[0].insertColumn(1, 2, true); expect(test[0].getValue('A4')).to.equal('=SUM(B1:D3)'); expect(test[0].getValue('B4')).to.equal('=SUM(B1:D3)'); expect(test[0].getValue('C4')).to.equal(''); expect(test[0].getValue('D4')).to.equal('=SUM(B1:D3)'); expect(test[0].getValue('E4')).to.equal('=SUM(B1:D3)'); }); it('5', () => { let test = jspreadsheet(root, { worksheets: [ { data: [ ['1', '2', '3', '4'], ['5', '6', '7', '8'], ['9', '10', '11', '12'], ['=SUM(B1:C3)', '=SUM(B1:C3)', '=SUM(B1:C3)', '=SUM(B1:C3)'], ], worksheetName: 'sheet1', }, ], }); test[0].insertColumn(1, 2, false); expect(test[0].getValue('A4')).to.equal('=SUM(B1:C3)'); expect(test[0].getValue('B4')).to.equal('=SUM(B1:C3)'); expect(test[0].getValue('C4')).to.equal('=SUM(B1:C3)'); expect(test[0].getValue('D4')).to.equal(''); expect(test[0].getValue('E4')).to.equal('=SUM(B1:C3)'); }); it('6', () => { let test = jspreadsheet(root, { worksheets: [ { data: [[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], ['=SUM(A1:A4)']], minDimensions: [5, 5], }, ], }); test[0].deleteRow(1); expect(test[0].getValue('A5')).to.equal('=SUM(A1:A3)'); }); }); }); ================================================ FILE: test/columns.js ================================================ const { expect } = require('chai'); const jspreadsheet = require('../dist/index.js'); describe('Use the columns method', () => { it('insertColumn and column is inserted in the position 0', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ [9, 9, 9, 9, 9, 9], [9, 9, 9, 9, 9, 9], ], }, ], }); let table = root.querySelector('tbody'); let rows = table.children; expect(rows[0].children[1].innerHTML).to.include(9); // Insert [3, 3] column in the first column instance[0].insertColumn([3, 3], 0, 1); expect(rows[0].children[1].innerHTML).to.include(3); // Insert [2, 2] column in the first column instance[0].insertColumn([2, 2], 0, 1); expect(rows[0].children[1].innerHTML).to.include(2); }); it('insertColumn and column is inserted in the position 1', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ [9, 9, 9, 9, 9, 9], [9, 9, 9, 9, 9, 9], ], }, ], }); let table = root.querySelector('tbody'); let rows = table.children; expect(rows[0].children[2].innerHTML).to.include(9); // Insert [3, 3] column in the second column instance[0].insertColumn([3, 3], 0); expect(rows[0].children[1].innerHTML).to.include(9); expect(rows[0].children[2].innerHTML).to.include(3); // Insert [2, 2] column in the second column instance[0].insertColumn([2, 2], 0); expect(rows[0].children[1].innerHTML).to.include(9); expect(rows[0].children[2].innerHTML).to.include(2); }); it('deleteColumn and column is removed in the given index', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ [3, 6, 9, 9, 9, 9], [3, 6, 9, 9, 9, 9], ], }, ], }); let table = root.querySelector('tbody'); let rows = table.children; expect(rows[0].children[1].innerHTML).to.include(3); expect(rows[0].children[2].innerHTML).to.include(6); expect(rows[0].children[3].innerHTML).to.include(9); // Delete first column instance[0].deleteColumn(0); expect(rows[0].children[1].innerHTML).to.include(6); expect(rows[0].children[2].innerHTML).to.include(9); expect(rows[0].children[3].innerHTML).to.include(9); // Delete first column instance[0].deleteColumn(0); expect(rows[0].children[1].innerHTML).to.include(9); expect(rows[0].children[2].innerHTML).to.include(9); expect(rows[0].children[3].innerHTML).to.include(9); }); it('deleteColumn and multiple column are removed starting from the given index', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ [3, 6, 9, 12, 15, 18], [3, 6, 9, 12, 15, 18], ], }, ], }); let table = root.querySelector('tbody'); let rows = table.children; expect(rows[0].children[1].innerHTML).to.include(3); expect(rows[0].children[2].innerHTML).to.include(6); expect(rows[0].children[3].innerHTML).to.include(9); // Delete first two columns instance[0].deleteColumn(0, 2); expect(rows[0].children[1].innerHTML).to.include(9); expect(rows[0].children[2].innerHTML).to.include(12); expect(rows[0].children[3].innerHTML).to.include(15); // Delete first two columns instance[0].deleteColumn(0, 2); expect(rows[0].children[1].innerHTML).to.include(15); expect(rows[0].children[2].innerHTML).to.include(18); }); it('hideColumn and showColumn', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ [3, 6, 9, 12, 15, 18], [3, 6, 9, 12, 15, 18], ], }, ], }); let table = root.querySelector('thead'); let headers = table.children[0].children; expect(headers[1].innerHTML).to.include('A'); expect(window.getComputedStyle(headers[1]).display).not.to.include('none'); // Hides the first column 'A' instance[0].hideColumn(0); expect(headers[1].innerHTML).to.include('A'); expect(window.getComputedStyle(headers[1]).display).to.include('none'); expect(headers[2].innerHTML).to.include('B'); expect(window.getComputedStyle(headers[2]).display).not.to.include('none'); // Shows the column that was hidden instance[0].showColumn(0); expect(headers[1].innerHTML).to.include('A'); expect(window.getComputedStyle(headers[1]).display).not.to.include('none'); expect(headers[2].innerHTML).to.include('B'); expect(window.getComputedStyle(headers[2]).display).not.to.include('none'); }); it('insertColumn history', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ [9, 9, 9, 9, 9, 9], [9, 9, 9, 9, 9, 9], ], }, ], }); let table = root.querySelector('tbody'); let rows = table.children; expect(rows[0].children[1].innerHTML).to.include(9); // Insert [3, 3] column in the first column instance[0].insertColumn([3, 3], 0, 1); expect(rows[0].children[1].innerHTML).to.include(3); instance[0].undo(); expect(rows[0].children[1].innerHTML).to.include(9); instance[0].redo(); expect(rows[0].children[1].innerHTML).to.include(3); }); it('deleteColumn history', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ [1, 2, 3, 4], [5, 6, 7, 8], ], }, ], }); let table = root.querySelector('tbody'); let rows = table.children; expect(rows[0].children[1].innerHTML).to.include(1); expect(rows[0].children[2].innerHTML).to.include(2); expect(rows[0].children[3].innerHTML).to.include(3); // Delete first column instance[0].deleteColumn(0); expect(rows[0].children[1].innerHTML).to.include(2); expect(rows[0].children[2].innerHTML).to.include(3); expect(rows[0].children[3].innerHTML).to.include(4); instance[0].undo(0); expect(rows[0].children[1].innerHTML).to.include(1); expect(rows[0].children[2].innerHTML).to.include(2); expect(rows[0].children[3].innerHTML).to.include(3); instance[0].redo(0); expect(rows[0].children[1].innerHTML).to.include(2); expect(rows[0].children[2].innerHTML).to.include(3); expect(rows[0].children[3].innerHTML).to.include(4); }); }); ================================================ FILE: test/comments.js ================================================ const { expect } = require('chai'); const jspreadsheet = require('../dist/index.js'); describe('Comment tests', () => { it('Set comment', () => { const instance = jspreadsheet(root, { worksheets: [ { data: [ ['US', 'Cheese', '2019-02-12'], ['CA', 'Apples', '2019-03-01'], ['CA', 'Carrots', '2018-11-10'], ['BR', 'Oranges', '2019-01-12'], ], columns: [{ width: '300px' }, { width: '200px' }, { width: '200px' }], allowComments: true, }, ], }); const table = root.querySelector('tbody'); const rows = table.children; instance[0].setComments('C2', 'Test'); expect(rows[1].children[3].getAttribute('title')).to.equal('Test'); instance[0].setComments('C2', ''); expect(rows[1].children[3].getAttribute('title')).to.equal(''); }); it('Get comment', () => { const instance = jspreadsheet(root, { worksheets: [ { data: [ ['US', 'Cheese', '2019-02-12'], ['CA', 'Apples', '2019-03-01'], ['CA', 'Carrots', '2018-11-10'], ['BR', 'Oranges', '2019-01-12'], ], columns: [{ width: '300px' }, { width: '200px' }, { width: '200px' }], allowComments: true, }, ], }); instance[0].setComments('B3', 'something'); expect(instance[0].getComments('B3')).to.equal('something'); }); it('setComments history', () => { const instance = jspreadsheet(root, { worksheets: [ { data: [ ['US', 'Cheese', '2019-02-12'], ['CA', 'Apples', '2019-03-01'], ['CA', 'Carrots', '2018-11-10'], ['BR', 'Oranges', '2019-01-12'], ], columns: [{ width: '300px' }, { width: '200px' }, { width: '200px' }], allowComments: true, }, ], }); const table = root.querySelector('tbody'); const rows = table.children; instance[0].setComments('C2', 'Test'); expect(rows[1].children[3].getAttribute('title')).to.equal('Test'); instance[0].undo(); expect(rows[1].children[3].getAttribute('title')).to.equal(''); instance[0].redo(); expect(rows[1].children[3].getAttribute('title')).to.equal('Test'); }); }); ================================================ FILE: test/data.js ================================================ const { expect } = require('chai'); const jspreadsheet = require('../dist/index.js'); describe('Use the data method', () => { it('getData and it returns the data properly', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ [1, 2, 3], [3, 2, 1], [4, 5, 6], [6, 5, 4], [9, 12, 15], ], worksheetName: 'Countries', }, ], }); const data = instance[0].getData(); expect(data.length).to.eq(7); expect(data[0].length).to.eq(7); expect(data[0][0]).to.eq(1); expect(data[0][1]).to.eq(2); expect(data[0][2]).to.eq(3); expect(data[1][0]).to.eq(3); expect(data[1][1]).to.eq(2); expect(data[1][2]).to.eq(1); expect(data[2][0]).to.eq(4); expect(data[2][1]).to.eq(5); expect(data[2][2]).to.eq(6); expect(data[3][0]).to.eq(6); expect(data[3][1]).to.eq(5); expect(data[3][2]).to.eq(4); expect(data[4][0]).to.eq(9); expect(data[4][1]).to.eq(12); expect(data[4][2]).to.eq(15); }); it('setData and it sets data properly', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], worksheetName: 'Countries', }, ], }); instance[0].setData([ ['Hello', 'World'], ['Testing', 'CE'], ]); const table = root.querySelector('tbody'); const rows = table.children; const firstRow = rows[0]; const secondRow = rows[1]; expect(firstRow.children[1].innerHTML).to.include('Hello'); expect(firstRow.children[1].innerHTML).not.to.include('World'); expect(firstRow.children[2].innerHTML).to.include('World'); expect(firstRow.children[2].innerHTML).not.to.include('Hello'); expect(secondRow.children[1].innerHTML).to.include('Testing'); expect(secondRow.children[1].innerHTML).not.to.include('CE'); expect(secondRow.children[2].innerHTML).to.include('CE'); expect(secondRow.children[2].innerHTML).not.to.include('Testing'); }); it('setValue and it sets the value of a cell', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ ['Hello', 'World'], ['Testing', 'CE'], ], worksheetName: 'Countries', }, ], }); const table = root.querySelector('tbody'); const rows = table.children; const firstRow = rows[0]; const secondRow = rows[1]; expect(firstRow.children[1].innerHTML).to.include('Hello'); expect(firstRow.children[2].innerHTML).to.include('World'); expect(secondRow.children[1].innerHTML).to.include('Testing'); expect(secondRow.children[2].innerHTML).to.include('CE'); instance[0].setValue('A1', 'New Value'); expect(firstRow.children[1].innerHTML).to.include('New Value'); expect(firstRow.children[2].innerHTML).to.include('World'); instance[0].setValue('A1', 'olleH'); instance[0].setValue('B1', 'dlroW'); expect(firstRow.children[1].innerHTML).to.include('olleH'); expect(firstRow.children[2].innerHTML).to.include('dlroW'); instance[0].setValue('B2', 'TESTING'); expect(secondRow.children[1].innerHTML).to.include('Testing'); expect(secondRow.children[2].innerHTML).to.include('TESTING'); }); it('getValue and it gets the value from the cell', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ ['Hello', 'World'], ['Testing', 'CE'], ], worksheetName: 'Countries', }, ], }); expect(instance[0].getValue('A1')).to.include('Hello'); expect(instance[0].getValue('B1')).to.include('World'); expect(instance[0].getValue('A2')).to.include('Testing'); expect(instance[0].getValue('B2')).to.include('CE'); }); it('getValueFromCoords and it gets the not processed cell value', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ ['=1+1', '=2+2'], ['Testing', 'CE'], ], worksheetName: 'Countries', }, ], }); expect(instance[0].getValueFromCoords(0, 0)).to.include('=1+1'); expect(instance[0].getValueFromCoords(1, 0)).to.include('=2+2'); expect(instance[0].getValueFromCoords(0, 1)).to.include('Testing'); expect(instance[0].getValueFromCoords(1, 1)).to.include('CE'); }); it('getValueFromCoords and it gets the processed cell value', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ ['=1+1', '=2+2'], ['Testing', 'CE'], ], worksheetName: 'Countries', }, ], }); expect(instance[0].getValueFromCoords(0, 0, true)).to.include('2'); expect(instance[0].getValueFromCoords(1, 0, true)).to.include('4'); expect(instance[0].getValueFromCoords(0, 1, true)).to.include('Testing'); expect(instance[0].getValueFromCoords(1, 1, true)).to.include('CE'); }); it('setValueFromCoords and it sets the value of a cell', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ ['Hello', 'World'], ['Testing', 'CE'], ], worksheetName: 'Countries', }, ], }); const table = root.querySelector('tbody'); const rows = table.children; const firstRow = rows[0]; const secondRow = rows[1]; expect(firstRow.children[1].innerHTML).to.include('Hello'); expect(firstRow.children[2].innerHTML).to.include('World'); expect(secondRow.children[1].innerHTML).to.include('Testing'); expect(secondRow.children[2].innerHTML).to.include('CE'); instance[0].setValueFromCoords(0, 0, 'New Value'); expect(firstRow.children[1].innerHTML).to.include('New Value'); expect(firstRow.children[2].innerHTML).to.include('World'); instance[0].setValueFromCoords(0, 0, 'olleH'); instance[0].setValueFromCoords(1, 0, 'dlroW'); expect(firstRow.children[1].innerHTML).to.include('olleH'); expect(firstRow.children[2].innerHTML).to.include('dlroW'); instance[0].setValueFromCoords(1, 1, 'TESTING'); expect(secondRow.children[1].innerHTML).to.include('Testing'); expect(secondRow.children[2].innerHTML).to.include('TESTING'); }); it('setValueFromCoords history', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ ['Hello', 'World'], ['Testing', 'CE'], ], worksheetName: 'Countries', }, ], }); const table = root.querySelector('tbody'); const rows = table.children; const firstRow = rows[0]; instance[0].setValueFromCoords(0, 0, 'New Value'); expect(firstRow.children[1].innerHTML).to.include('New Value'); instance[0].undo(); expect(firstRow.children[1].innerHTML).to.include('Hello'); instance[0].redo(); expect(firstRow.children[1].innerHTML).to.include('New Value'); }); }); ================================================ FILE: test/footer.js ================================================ const { expect } = require('chai'); const jspreadsheet = require('../dist/index.js'); describe('Use footers', () => { it('Start the worksheet with a footer', () => { jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], freezeColumns: 2, data: [ ['Hello', 'World'], ['Testing', 'CE'], ], footers: [ ['a', 'b', 'c'], [1, 2, 3], ], }, ], }); const footerTag = root.querySelector('tfoot'); const firstRow = footerTag.children[0]; expect(firstRow.children[1].innerHTML).to.equal('a'); expect(firstRow.children[2].innerHTML).to.equal('b'); expect(firstRow.children[3].innerHTML).to.equal('c'); const secondRow = footerTag.children[1]; expect(secondRow.children[1].innerHTML).to.equal('1'); expect(secondRow.children[2].innerHTML).to.equal('2'); expect(secondRow.children[3].innerHTML).to.equal('3'); }); }); ================================================ FILE: test/headers.js ================================================ const { expect } = require('chai'); const jspreadsheet = require('../dist/index.js'); describe('Use the headers method', () => { it('setHeader and header title is changed', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ [3, 6, 9, 12, 15, 18], [3, 6, 9, 12, 15, 18], ], }, ], }); let table = root.querySelector('thead'); let headers = table.children[0].children; expect(headers[1].innerHTML).to.include('A'); expect(headers[2].innerHTML).to.include('B'); instance[0].setHeader(0, 'Produtos'); expect(headers[1].innerHTML).to.include('Produtos'); expect(headers[2].innerHTML).to.include('B'); instance[0].setHeader(1, 'Quantidade'); expect(headers[1].innerHTML).to.include('Produtos'); expect(headers[2].innerHTML).to.include('Quantidade'); }); it('getHeader and header title is retrieved', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ [3, 6, 9, 12, 15, 18], [3, 6, 9, 12, 15, 18], ], }, ], }); let table = root.querySelector('thead'); let headers = table.children[0].children; expect(headers[1].innerHTML).to.include('A'); expect(headers[2].innerHTML).to.include('B'); expect(instance[0].getHeader(0)).to.include('A'); expect(instance[0].getHeader(1)).to.include('B'); expect(instance[0].getHeader(2)).to.include('C'); }); it('getHeaders and header titles are retrieved', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ [3, 6, 9, 12, 15, 18], [3, 6, 9, 12, 15, 18], ], }, ], }); let table = root.querySelector('thead'); let headers = table.children[0].children; expect(headers[1].innerHTML).to.include('A'); expect(headers[2].innerHTML).to.include('B'); let h = instance[0].getHeaders(); expect(h).to.include('A'); expect(h).to.include('B'); expect(h).to.include('C'); expect(h).to.include('D'); expect(h).to.include('E'); expect(h).to.include('F'); }); it('setHeader history', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ [3, 6, 9, 12, 15, 18], [3, 6, 9, 12, 15, 18], ], }, ], }); let table = root.querySelector('thead'); let headers = table.children[0].children; expect(headers[1].innerHTML).to.equal('A'); expect(headers[2].innerHTML).to.equal('B'); instance[0].setHeader(0, 'Products'); expect(headers[1].innerHTML).to.equal('Products'); expect(headers[2].innerHTML).to.equal('B'); instance[0].undo(); expect(headers[1].innerHTML).to.equal('A'); expect(headers[2].innerHTML).to.equal('B'); instance[0].redo(); expect(headers[1].innerHTML).to.equal('Products'); expect(headers[2].innerHTML).to.equal('B'); }); }); ================================================ FILE: test/instance.js ================================================ const { expect } = require('chai'); const jspreadsheet = require('../dist/index.js'); describe('Create a jspreadsheet instance', () => { it('and the dimensions are applied correctly', () => { jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [10, 10], data: [[10, 20, 30]], worksheetName: 'Countries', }, ], }); const table = root.querySelector('tbody'); const rows = table.children; const firstRow = rows[0]; // check that the amount of rows displayed is 10 expect(rows.length).to.eq(10); // check that the amount of columns displayed is 10 + 1 (one from the row header) expect(firstRow.children.length).to.eq(10 + 1); }); it('and the data is displayed correctly', () => { jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [10, 10], data: [ [10, 20, 30], [40, 50, 60], ], worksheetName: 'Countries', }, ], }); const table = root.querySelector('tbody'); const rows = table.children; const firstRow = rows[0]; const secondRow = rows[1]; // check that [A1, B1, C1] received the data value expect(firstRow.children[1].innerHTML).to.include(10); expect(firstRow.children[1].innerHTML).not.to.include(20); expect(firstRow.children[1].innerHTML).not.to.include(30); expect(firstRow.children[2].innerHTML).to.include(20); expect(firstRow.children[2].innerHTML).not.to.include(10); expect(firstRow.children[2].innerHTML).not.to.include(30); expect(firstRow.children[3].innerHTML).to.include(30); expect(firstRow.children[3].innerHTML).not.to.include(10); expect(firstRow.children[3].innerHTML).not.to.include(20); // check that [A2, B2, C2] received the data value expect(secondRow.children[1].innerHTML).to.include(40); expect(secondRow.children[1].innerHTML).not.to.include(50); expect(secondRow.children[1].innerHTML).not.to.include(60); expect(secondRow.children[2].innerHTML).to.include(50); expect(secondRow.children[2].innerHTML).not.to.include(40); expect(secondRow.children[2].innerHTML).not.to.include(60); expect(secondRow.children[3].innerHTML).to.include(60); expect(secondRow.children[3].innerHTML).not.to.include(40); expect(secondRow.children[3].innerHTML).not.to.include(50); }); it('and the worksheet names are displayed correctly', () => { jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [10, 10], worksheetName: 'Countries' }, { minDimensions: [10, 10], worksheetName: 'Employees' }, ], }); const headerWorksheets = root.querySelector('.jtabs-headers'); expect(headerWorksheets.children[0].innerHTML).to.include('Countries'); expect(headerWorksheets.children[0].innerHTML).not.to.include('Employees'); expect(headerWorksheets.children[1].innerHTML).to.include('Employees'); expect(headerWorksheets.children[1].innerHTML).not.to.include('Countries'); }); }); ================================================ FILE: test/merges.js ================================================ const { expect } = require('chai'); const jspreadsheet = require('../dist/index.js'); describe('Merge tests', () => { describe('Get merge', () => { it('Worksheet started with a merge', () => { const instance = jspreadsheet(root, { toolbar: true, worksheets: [ { data: [ ['Mazda', 2001, 2000, '2006-01-01 12:00:00'], ['Peugeot', 2010, 5000, '2005-01-01 13:00:00'], ['Honda Fit', 2009, 3000, '2004-01-01 14:01:00'], ['Honda CRV', 2010, 6000, '2003-01-01 23:30:00'], ], minDimensions: [10, 15], columns: [ { width: '300px', }, { width: '80px', }, { width: '100px', }, { width: '150px', }, ], mergeCells: { C1: [1, 2], }, }, ], }); expect(instance[0].getMerge('C1')).to.eql([1, 2]); expect(instance[0].getMerge('C2')).to.equal(null); expect(instance[0].getMerge()).to.eql({ C1: [1, 2] }); }); it('Worksheet started without merges', () => { const instance = jspreadsheet(root, { toolbar: true, worksheets: [ { data: [ ['Mazda', 2001, 2000, '2006-01-01 12:00:00'], ['Peugeot', 2010, 5000, '2005-01-01 13:00:00'], ['Honda Fit', 2009, 3000, '2004-01-01 14:01:00'], ['Honda CRV', 2010, 6000, '2003-01-01 23:30:00'], ], minDimensions: [10, 15], columns: [ { width: '300px', }, { width: '80px', }, { width: '100px', }, { width: '150px', }, ], }, ], }); expect(instance[0].getMerge('C1')).to.equal(null); expect(instance[0].getMerge()).to.eql({}); }); }); it('Set merge', () => { const instance = jspreadsheet(root, { toolbar: true, worksheets: [ { data: [ ['Mazda', 2001, 2000, '2006-01-01 12:00:00'], ['Peugeot', 2010, 5000, '2005-01-01 13:00:00'], ['Honda Fit', 2009, 3000, '2004-01-01 14:01:00'], ['Honda CRV', 2010, 6000, '2003-01-01 23:30:00'], ], minDimensions: [10, 15], columns: [ { width: '300px', }, { width: '80px', }, { width: '100px', }, { width: '150px', }, ], }, ], }); instance[0].setMerge('A3', 2, 3); const table = root.querySelector('tbody'); const rows = table.children; expect(rows[2].children[1].getAttribute('colspan')).to.equal('2'); expect(rows[2].children[1].getAttribute('rowspan')).to.equal('3'); }); it('Remove merge', () => { const instance = jspreadsheet(root, { toolbar: true, worksheets: [ { data: [ ['Mazda', 2001, 2000, '2006-01-01 12:00:00'], ['Peugeot', 2010, 5000, '2005-01-01 13:00:00'], ['Honda Fit', 2009, 3000, '2004-01-01 14:01:00'], ['Honda CRV', 2010, 6000, '2003-01-01 23:30:00'], ], minDimensions: [10, 15], columns: [ { width: '300px', }, { width: '80px', }, { width: '100px', }, { width: '150px', }, ], mergeCells: { A1: [2, 2], E5: [3, 2], }, }, ], }); const table = root.querySelector('tbody'); const rows = table.children; instance[0].removeMerge('A1'); expect(rows[0].children[1].getAttribute('colspan')).to.equal(null); expect(rows[0].children[1].getAttribute('rowspan')).to.equal(null); expect(rows[4].children[5].getAttribute('colspan')).to.equal('3'); expect(rows[4].children[5].getAttribute('rowspan')).to.equal('2'); }); it('Remove all merge', () => { const instance = jspreadsheet(root, { toolbar: true, worksheets: [ { data: [ ['Mazda', 2001, 2000, '2006-01-01 12:00:00'], ['Peugeot', 2010, 5000, '2005-01-01 13:00:00'], ['Honda Fit', 2009, 3000, '2004-01-01 14:01:00'], ['Honda CRV', 2010, 6000, '2003-01-01 23:30:00'], ], minDimensions: [10, 15], columns: [ { width: '300px', }, { width: '80px', }, { width: '100px', }, { width: '150px', }, ], mergeCells: { A1: [2, 2], E5: [3, 2], }, }, ], }); const table = root.querySelector('tbody'); const rows = table.children; instance[0].destroyMerge(); expect(rows[0].children[1].getAttribute('colspan')).to.equal(null); expect(rows[0].children[1].getAttribute('rowspan')).to.equal(null); expect(rows[4].children[5].getAttribute('colspan')).to.equal(null); expect(rows[4].children[5].getAttribute('rowspan')).to.equal(null); }); it('setMerge history', () => { const instance = jspreadsheet(root, { toolbar: true, worksheets: [ { data: [ ['Mazda', 2001, 2000, '2006-01-01 12:00:00'], ['Peugeot', 2010, 5000, '2005-01-01 13:00:00'], ['Honda Fit', 2009, 3000, '2004-01-01 14:01:00'], ['Honda CRV', 2010, 6000, '2003-01-01 23:30:00'], ], minDimensions: [10, 15], columns: [ { width: '300px', }, { width: '80px', }, { width: '100px', }, { width: '150px', }, ], }, ], }); instance[0].setMerge('A3', 2, 3); const table = root.querySelector('tbody'); const rows = table.children; expect(rows[2].children[1].getAttribute('colspan')).to.equal('2'); expect(rows[2].children[1].getAttribute('rowspan')).to.equal('3'); instance[0].undo(); expect(rows[0].children[1].getAttribute('colspan')).to.equal(null); expect(rows[0].children[1].getAttribute('rowspan')).to.equal(null); instance[0].redo(); expect(rows[2].children[1].getAttribute('colspan')).to.equal('2'); expect(rows[2].children[1].getAttribute('rowspan')).to.equal('3'); }); it('removeMerge event', () => { let eventCalled = false; let eventCellName = null; let eventInstance = null; let eventBeforeMerges = null; const instance = jspreadsheet(root, { toolbar: true, worksheets: [ { data: [ ['Mazda', 2001, 2000, '2006-01-01 12:00:00'], ['Peugeot', 2010, 5000, '2005-01-01 13:00:00'], ['Honda Fit', 2009, 3000, '2004-01-01 14:01:00'], ['Honda CRV', 2010, 6000, '2003-01-01 23:30:00'], ], minDimensions: [10, 15], columns: [ { width: '300px', }, { width: '80px', }, { width: '100px', }, { width: '150px', }, ], mergeCells: { A1: [2, 2], E5: [3, 2], }, }, ], onunmerge: (worksheetInstance, cellName, beforeMerges) => { eventCalled = true; eventCellName = cellName; eventInstance = worksheetInstance; eventBeforeMerges = beforeMerges; }, }); instance[0].removeMerge('A1'); instance[0].getMerge(); expect(eventCalled).to.equal(true); expect(eventCellName).to.equal('A1'); expect(eventInstance).to.equal(instance[0]); expect(eventBeforeMerges['A1'][0]).to.eql(2); expect(eventBeforeMerges['A1'][1]).to.eql(2); expect(Object.keys(eventBeforeMerges).length).to.eql(1); expect(instance[0].getMerge('A1')).to.equal(null); }); }); ================================================ FILE: test/meta.js ================================================ const { expect } = require('chai'); const jspreadsheet = require('../dist/index.js'); describe('Meta tests', () => { describe('Set meta information', () => { it('Set meta information using an object', () => { const instance = jspreadsheet(root, { worksheets: [ { data: [ ['US', 'Apples', 'Yes', '2019-02-12'], ['UK', 'Carrots', 'Yes', '2019-03-01'], ['CA', 'Oranges', 'No', '2018-11-10'], ['BR', 'Coconuts', 'Yes', '2019-01-12'], ], }, ], }); instance[0].setMeta({ B1: { id: '1', y: '2019' }, C2: { test: '2' } }); expect(instance[0].options.meta).to.eql({ B1: { id: '1', y: '2019' }, C2: { test: '2' }, }); instance[0].setMeta({ C2: { something: '35' } }); expect(instance[0].options.meta).to.eql({ B1: { id: '1', y: '2019' }, C2: { test: '2', something: '35' }, }); }); it('Set meta information using strings', () => { const instance = jspreadsheet(root, { worksheets: [ { data: [ ['US', 'Apples', 'Yes', '2019-02-12'], ['UK', 'Carrots', 'Yes', '2019-03-01'], ['CA', 'Oranges', 'No', '2018-11-10'], ['BR', 'Coconuts', 'Yes', '2019-01-12'], ], }, ], }); instance[0].setMeta('A1', 'myMeta', 'this is just a test'); instance[0].setMeta('A1', 'otherMetaInformation', 'other test'); instance[0].setMeta('D2', 'info', 'test'); expect(instance[0].options.meta).to.eql({ A1: { myMeta: 'this is just a test', otherMetaInformation: 'other test', }, D2: { info: 'test' }, }); instance[0].setMeta('D2', 'myMetaData', 'something'); expect(instance[0].options.meta).to.eql({ A1: { myMeta: 'this is just a test', otherMetaInformation: 'other test', }, D2: { info: 'test', myMetaData: 'something' }, }); }); }); it('Get meta information', () => { const instance = jspreadsheet(root, { worksheets: [ { data: [ ['US', 'Apples', 'Yes', '2019-02-12'], ['UK', 'Carrots', 'Yes', '2019-03-01'], ['CA', 'Oranges', 'No', '2018-11-10'], ['BR', 'Coconuts', 'Yes', '2019-01-12'], ], meta: { A1: { myMeta: 'this is just a test', otherMetaInformation: 'other test', }, D2: { info: 'test' }, }, }, ], }); expect(instance[0].getMeta()).to.eql({ A1: { myMeta: 'this is just a test', otherMetaInformation: 'other test' }, D2: { info: 'test' }, }); expect(instance[0].getMeta('A1')).to.eql({ myMeta: 'this is just a test', otherMetaInformation: 'other test', }); expect(instance[0].getMeta('D2')).to.eql({ info: 'test' }); expect(instance[0].getMeta('A2')).to.equal(null); }); }); ================================================ FILE: test/orderBy.js ================================================ const { expect } = require('chai'); const jspreadsheet = require('../dist/index.js'); describe('Sorting tests', () => { it('Default sorting', () => { const instance = jspreadsheet(root, { worksheets: [ { data: [ ['Mazda', 2001, 2000, '2006-01-01', '453.00', '2', '=E1*F1'], ['Peugeot', 2010, 5000, '2005-01-01', '23.00', '5', '=E2*F2'], ['Honda Fit', 2009, 3000, '2004-01-01', '214.00', '3', '=E3*F3'], ['Honda CRV', 2010, 6000, '2003-01-01', '56.11', '2', '=E4*F4'], ], }, ], }); instance[0].orderBy(5); expect(instance[0].options.data).to.eql([ ['Mazda', 2001, 2000, '2006-01-01', '453.00', '2', '=E1*F1'], ['Honda CRV', 2010, 6000, '2003-01-01', '56.11', '2', '=E2*F2'], ['Honda Fit', 2009, 3000, '2004-01-01', '214.00', '3', '=E3*F3'], ['Peugeot', 2010, 5000, '2005-01-01', '23.00', '5', '=E4*F4'], ]); instance[0].orderBy(5); expect(instance[0].options.data).to.eql([ ['Peugeot', 2010, 5000, '2005-01-01', '23.00', '5', '=E1*F1'], ['Honda Fit', 2009, 3000, '2004-01-01', '214.00', '3', '=E2*F2'], ['Mazda', 2001, 2000, '2006-01-01', '453.00', '2', '=E3*F3'], ['Honda CRV', 2010, 6000, '2003-01-01', '56.11', '2', '=E4*F4'], ]); }); it('Custom sorting', () => { const instance = jspreadsheet(root, { worksheets: [ { data: [ ['Mazda', 2001, 2000, '2006-01-01', '453.00', '2', '=E1*F1'], ['Peugeot', 1800, 5000, '2005-01-01', '23.00', '5', '=E2*F2'], ['test', 2009, 3000, '2004-01-01', '214.00', '3', '=E3*F3'], ['Honda CRV', 1900, 6000, '2003-01-01', '56.11', '2', '=E4*F4'], ], sorting: function (direction) { return function (a, b) { let valueA = a[1]; let valueB = b[1]; if (valueA === 'test') { return direction ? 1 : -1; } if (valueB === 'test') { return direction ? -1 : 1; } // Consider blank rows in the sorting if (!direction) { return valueA > valueB ? 1 : valueA < valueB ? -1 : 0; } else { return valueA > valueB ? -1 : valueA < valueB ? 1 : 0; } }; }, }, ], }); instance[0].orderBy(0, 1); expect(instance[0].options.data).to.eql([ ['test', 2009, 3000, '2004-01-01', '214.00', '3', '=E1*F1'], ['Peugeot', 1800, 5000, '2005-01-01', '23.00', '5', '=E2*F2'], ['Mazda', 2001, 2000, '2006-01-01', '453.00', '2', '=E3*F3'], ['Honda CRV', 1900, 6000, '2003-01-01', '56.11', '2', '=E4*F4'], ]); }); it('orderBy history', () => { const instance = jspreadsheet(root, { worksheets: [ { data: [ ['Mazda', 2001, 2000, '2006-01-01', '453.00', '2', '=E1*F1'], ['Peugeot', 2010, 5000, '2005-01-01', '23.00', '5', '=E2*F2'], ['Honda Fit', 2009, 3000, '2004-01-01', '214.00', '3', '=E3*F3'], ['Honda CRV', 2010, 6000, '2003-01-01', '56.11', '2', '=E4*F4'], ], }, ], }); instance[0].orderBy(5); expect(instance[0].options.data).to.eql([ ['Mazda', 2001, 2000, '2006-01-01', '453.00', '2', '=E1*F1'], ['Honda CRV', 2010, 6000, '2003-01-01', '56.11', '2', '=E2*F2'], ['Honda Fit', 2009, 3000, '2004-01-01', '214.00', '3', '=E3*F3'], ['Peugeot', 2010, 5000, '2005-01-01', '23.00', '5', '=E4*F4'], ]); instance[0].undo(5); expect(instance[0].options.data).to.eql([ ['Mazda', 2001, 2000, '2006-01-01', '453.00', '2', '=E1*F1'], ['Peugeot', 2010, 5000, '2005-01-01', '23.00', '5', '=E2*F2'], ['Honda Fit', 2009, 3000, '2004-01-01', '214.00', '3', '=E3*F3'], ['Honda CRV', 2010, 6000, '2003-01-01', '56.11', '2', '=E4*F4'], ]); instance[0].redo(5); expect(instance[0].options.data).to.eql([ ['Mazda', 2001, 2000, '2006-01-01', '453.00', '2', '=E1*F1'], ['Honda CRV', 2010, 6000, '2003-01-01', '56.11', '2', '=E2*F2'], ['Honda Fit', 2009, 3000, '2004-01-01', '214.00', '3', '=E3*F3'], ['Peugeot', 2010, 5000, '2005-01-01', '23.00', '5', '=E4*F4'], ]); }); }); ================================================ FILE: test/pagination.js ================================================ const { expect } = require('chai'); const jspreadsheet = require('../dist/index.js'); describe('Use pagination', () => { it('Start the worksheet with pagination', () => { const instance = jspreadsheet(root, { worksheets: [ { minDimensions: [7, 7], data: [ [1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20], [21, 22, 23, 24, 25], [26, 27, 28, 29, 30], [31, 32, 33, 34, 35], [36, 37, 38, 39, 40], [41, 42, 43, 44, 45], [46, 47, 48, 49, 50], ], pagination: 3, }, ], }); expect(instance[0].quantiyOfPages()).to.equal(4); const bodyTag = root.querySelector('tbody'); expect(bodyTag.children.length).to.equal(3); expect(bodyTag.children[0].getAttribute('data-y')).to.equal('0'); expect(bodyTag.children[1].getAttribute('data-y')).to.equal('1'); expect(bodyTag.children[2].getAttribute('data-y')).to.equal('2'); }); it('page method', () => { const instance = jspreadsheet(root, { worksheets: [ { minDimensions: [7, 7], data: [ [1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20], [21, 22, 23, 24, 25], [26, 27, 28, 29, 30], [31, 32, 33, 34, 35], [36, 37, 38, 39, 40], [41, 42, 43, 44, 45], [46, 47, 48, 49, 50], ], pagination: 3, }, ], }); instance[0].page(2); expect(instance[0].quantiyOfPages()).to.equal(4); const bodyTag = root.querySelector('tbody'); expect(bodyTag.children.length).to.equal(3); expect(bodyTag.children[0].getAttribute('data-y')).to.equal('6'); expect(bodyTag.children[1].getAttribute('data-y')).to.equal('7'); expect(bodyTag.children[2].getAttribute('data-y')).to.equal('8'); }); }); ================================================ FILE: test/paste.js ================================================ const { expect } = require('chai'); const jspreadsheet = require('../dist/index.js'); global.document.execCommand = function execCommandMock() {}; const fixtureData = () => [ ['Mazda', 2001, 2000, 1], ['Peugeot', 2010, 5000, '=B2+C2'], ['Honda Fit', 2009, 3000, '=B3+C3'], ['Honda CRV', 2010, 6000, '=B4+C4'], ]; describe('Paste', () => { it('no expand', () => { let sheet = jspreadsheet(root, { worksheets: [ { data: fixtureData(), }, ], })[0]; const pasteText = '0-0\t0-1\t0-2\t0-3\n1-0\t1-1\t1-2\t1-3\n2-0\t2-1\t2-2\t2-3\n3-0\t3-1\t3-2\t3-3'; sheet.updateSelectionFromCoords(0, 0, 0, 0); sheet.paste(sheet.selectedCell[0], sheet.selectedCell[1], pasteText); expect(sheet.getData()).to.eql([ ['0-0', '0-1', '0-2', '0-3'], ['1-0', '1-1', '1-2', '1-3'], ['2-0', '2-1', '2-2', '2-3'], ['3-0', '3-1', '3-2', '3-3'], ]); }); it('expand', () => { let sheet = jspreadsheet(root, { worksheets: [ { data: fixtureData(), }, ], })[0]; const pasteText = '0-0\t0-1\t0-2\t0-3\n1-0\t1-1\t1-2\t1-3\n2-0\t2-1\t2-2\t2-3\n3-0\t3-1\t3-2\t3-3'; sheet.updateSelectionFromCoords(3, 3, 3, 3); sheet.paste(sheet.selectedCell[0], sheet.selectedCell[1], pasteText); expect(sheet.getData()).to.eql([ ['Mazda', 2001, 2000, 1, '', '', ''], ['Peugeot', 2010, 5000, '=B2+C2', '', '', ''], ['Honda Fit', 2009, 3000, '=B3+C3', '', '', ''], ['Honda CRV', 2010, 6000, '0-0', '0-1', '0-2', '0-3'], ['', '', '', '1-0', '1-1', '1-2', '1-3'], ['', '', '', '2-0', '2-1', '2-2', '2-3'], ['', '', '', '3-0', '3-1', '3-2', '3-3'], ]); }); it('repeat horizontal', () => { let sheet = jspreadsheet(root, { worksheets: [ { data: fixtureData(), }, ], })[0]; const pasteText = '0-0\t0-1'; sheet.updateSelectionFromCoords(0, 0, 4, 0); sheet.paste(sheet.selectedCell[0], sheet.selectedCell[1], pasteText); expect(sheet.getData()).to.eql([ ['0-0', '0-1', '0-0', '0-1'], ['Peugeot', 2010, 5000, '=B2+C2'], ['Honda Fit', 2009, 3000, '=B3+C3'], ['Honda CRV', 2010, 6000, '=B4+C4'], ]); }); it('repeat vertical', () => { let sheet = jspreadsheet(root, { worksheets: [ { data: fixtureData(), }, ], })[0]; const pasteText = '0-0\n1-0'; sheet.updateSelectionFromCoords(0, 0, 0, 4); sheet.paste(sheet.selectedCell[0], sheet.selectedCell[1], pasteText); expect(sheet.getData()).to.eql([ ['0-0', 2001, 2000, 1], ['1-0', 2010, 5000, '=B2+C2'], ['0-0', 2009, 3000, '=B3+C3'], ['1-0', 2010, 6000, '=B4+C4'], ]); }); it('repeat rectangle', () => { let sheet = jspreadsheet(root, { worksheets: [ { data: fixtureData(), }, ], })[0]; const pasteText = '0-0\t0-1\n1-0\t1-1'; sheet.updateSelectionFromCoords(1, 0, 1, 3); sheet.paste(sheet.selectedCell[0], sheet.selectedCell[1], pasteText); expect(sheet.getData()).to.eql([ ['Mazda', '0-0', '0-1', 1], ['Peugeot', '1-0', '1-1', '=B2+C2'], ['Honda Fit', '0-0', '0-1', '=B3+C3'], ['Honda CRV', '1-0', '1-1', '=B4+C4'], ]); }); it('skip hidden column', () => { let sheet = jspreadsheet(root, { worksheets: [ { columns: [ { type: 'text' }, { type: 'text' }, { type: 'hidden' }, // paste is skipped. { type: 'text' }, ], data: fixtureData(), }, ], })[0]; const pasteText = '0-0\t0-1\n1-0\t1-1'; sheet.updateSelectionFromCoords(1, 0, 1, 0); sheet.paste(sheet.selectedCell[0], sheet.selectedCell[1], pasteText); expect(sheet.getData()).to.eql([ ['Mazda', '0-0', 2000, '0-1'], ['Peugeot', '1-0', 5000, '1-1'], ['Honda Fit', 2009, 3000, '=B3+C3'], ['Honda CRV', 2010, 6000, '=B4+C4'], ]); }); it('skip hidden row', () => { let sheet = jspreadsheet(root, { worksheets: [ { data: fixtureData(), }, ], })[0]; const pasteText = '0-0\t0-1\n1-0\t1-1'; sheet.hideRow(1); sheet.updateSelectionFromCoords(1, 0, 1, 0); sheet.paste(sheet.selectedCell[0], sheet.selectedCell[1], pasteText); expect(sheet.getData()).to.eql([ ['Mazda', '0-0', '0-1', 1], ['Peugeot', 2010, 5000, '=B2+C2'], ['Honda Fit', '1-0', '1-1', '=B3+C3'], ['Honda CRV', 2010, 6000, '=B4+C4'], ]); }); it('see https://github.com/jspreadsheet/ce/pull/1717#issuecomment-2576060698', () => { let sheet = jspreadsheet(root, { worksheets: [ { minDimensions: [4, 4], data: [ [1, 2], [3, 4], ], }, ], })[0]; sheet.updateSelectionFromCoords(0, 0, 1, 1); sheet.copy(); sheet.hideRow(0); sheet.hideColumn(0); sheet.updateSelectionFromCoords(2, 2, 2, 2); sheet.paste(sheet.selectedCell[0], sheet.selectedCell[1], sheet.data); expect(sheet.getData()).to.eql([ [1, 2, '', ''], [3, 4, '', ''], ['', '', '1', '2'], ['', '', '3', '4'], ]); }); it('see https://github.com/jspreadsheet/ce/pull/1717#issuecomment-2580087207', () => { let sheet = jspreadsheet(root, { worksheets: [ { minDimensions: [10, 4], data: [ [1, 2, 3], [4, 5, 6], ], }, ], })[0]; sheet.updateSelectionFromCoords(0, 0, 2, 1); sheet.copy(); sheet.hideColumn(8); sheet.hideColumn(9); sheet.hideRow(3); sheet.updateSelectionFromCoords(7, 2, 7, 2); expect(sheet.getData()).to.eql([ [1, 2, 3, '', '', '', '', '', '', ''], [4, 5, 6, '', '', '', '', '', '', ''], ['', '', '', '', '', '', '', '', '', ''], ['', '', '', '', '', '', '', '', '', ''], ]); sheet.paste(7, 2, sheet.data); expect(sheet.getData()).to.eql([ [1, 2, 3, '', '', '', '', '', '', '', '', ''], [4, 5, 6, '', '', '', '', '', '', '', '', ''], ['', '', '', '', '', '', '', '1', '', '', '2', '3'], ['', '', '', '', '', '', '', '', '', '', '', ''], ['', '', '', '', '', '', '', '4', '', '', '5', '6'], ]); }); it('large data paste', () => { let count = {}; let sheet = jspreadsheet(root, { worksheets: [ { data: fixtureData(), }, ], onevent: (event) => { count[event] = (count[event] ?? 0) + 1; }, })[0]; const pasteText = new Array(1000) .fill(0) .map((v, i) => new Array(20) .fill(0) .map((v2, i2) => `${i}-${i2}`) .join('\t') ) .join('\n'); sheet.updateSelectionFromCoords(3, 3, 3, 3); sheet.paste(sheet.selectedCell[0], sheet.selectedCell[1], pasteText); expect(count.onbeforechange).to.eql(20000); expect(count.onbeforeinsertcolumn).to.eql(1); expect(count.onbeforeinsertrow).to.eql(1); expect(count.onchange).to.eql(20000); expect(count.oninsertcolumn).to.eql(1); expect(count.oninsertrow).to.eql(1); expect(count.onselection).to.eql(3); }).timeout(10 * 1000); it('expand with hidden cells', () => { let count = {}; let sheet = jspreadsheet(root, { worksheets: [ { columns: [ { type: 'text' }, { type: 'text' }, { type: 'hidden' }, // paste is skipped. { type: 'text' }, ], data: fixtureData(), }, ], onevent: (event) => { count[event] = (count[event] ?? 0) + 1; }, })[0]; const pasteText = '0-0\t0-1\t0-2\t0-3\n1-0\t1-1\t1-2\t1-3\n2-0\t2-1\t2-2\t2-3\n3-0\t3-1\t3-2\t3-3'; sheet.hideRow(2); sheet.updateSelectionFromCoords(1, 1, 1, 1); sheet.paste(sheet.selectedCell[0], sheet.selectedCell[1], pasteText); expect(sheet.getData()).to.eql([ ['Mazda', 2001, 2000, 1, '', ''], ['Peugeot', '0-0', 5000, '0-1', '0-2', '0-3'], ['Honda Fit', 2009, 3000, '=B3+C3', '', ''], ['Honda CRV', '1-0', 6000, '1-1', '1-2', '1-3'], ['', '2-0', '', '2-1', '2-2', '2-3'], ['', '3-0', '', '3-1', '3-2', '3-3'], ]); expect(count.onbeforeinsertcolumn).to.eql(1); expect(count.onbeforeinsertrow).to.eql(1); }).timeout(10 * 1000); it('copy and paste with style', () => { let sheet = jspreadsheet(root, { worksheets: [ { data: fixtureData(), }, ], })[0]; sheet.setStyle('A1', 'color', 'red'); sheet.updateSelectionFromCoords(0, 0, 1, 1); sheet.copy(); sheet.paste(2, 2, sheet.data); expect(sheet.getData()).to.eql([ ['Mazda', 2001, 2000, 1], ['Peugeot', 2010, 5000, '=B2+C2'], ['Honda Fit', 2009, 'Mazda', '2001'], ['Honda CRV', 2010, 'Peugeot', '2010'], ]); expect(sheet.getStyle('A1', 'color')).to.eql('red'); expect(sheet.getStyle('C3', 'color')).to.eql('red'); }); it('copy and repeat paste with style', () => { global.document.execCommand = function execCommandMock() {}; let sheet = jspreadsheet(root, { worksheets: [ { data: fixtureData(), }, ], })[0]; sheet.setStyle('A1', 'color', 'red'); sheet.updateSelectionFromCoords(0, 0, 1, 0); sheet.copy(); sheet.updateSelectionFromCoords(0, 2, 4, 4); sheet.paste(0, 2, sheet.data); expect(sheet.getData()).to.eql([ ['Mazda', 2001, 2000, 1], ['Peugeot', 2010, 5000, '=B2+C2'], ['Mazda', '2001', 'Mazda', '2001'], ['Mazda', '2001', 'Mazda', '2001'], ]); // expect(sheet.getStyle('A1', 'color')).to.eql('red'); // expect(sheet.getStyle('A3', 'color')).to.eql('red'); expect(sheet.getStyle('B3', 'color')).to.eql(''); expect(sheet.getStyle('C3', 'color')).to.eql('red'); expect(sheet.getStyle('D3', 'color')).to.eql(''); // expect(sheet.getStyle('A4', 'color')).to.eql('red'); expect(sheet.getStyle('B4', 'color')).to.eql(''); expect(sheet.getStyle('C4', 'color')).to.eql('red'); expect(sheet.getStyle('D4', 'color')).to.eql(''); }); it('copy and paste to another sheet', async () => { global.document.execCommand = function execCommandMock() {}; let isLoaded = false; let sheets = jspreadsheet(root, { tabs: true, worksheets: [ { data: fixtureData(), worksheetName: 'Sheet1', }, { data: fixtureData(), worksheetName: 'Sheet2', }, ], onload: () => { isLoaded = true; }, }); const awaitLoop = (resolve) => { setTimeout(() => { if (isLoaded) { resolve(); } else { resolve(awaitLoop); } }, 100); }; // NOTE: jpreadsheet constructor is acutally async. So it waits for load events in await. await new Promise(awaitLoop); const from = sheets[0]; from.setStyle('A1', 'color', 'red'); from.updateSelectionFromCoords(0, 0, 1, 0); from.copy(); const to = sheets[1]; to.updateSelectionFromCoords(0, 2, 4, 4); to.paste(0, 2, from.data); expect(to.getData()).to.eql([ ['Mazda', 2001, 2000, 1], ['Peugeot', 2010, 5000, '=B2+C2'], ['Mazda', '2001', 'Mazda', '2001'], ['Mazda', '2001', 'Mazda', '2001'], ]); }); it('fix - u0000 is pasted, when the last cell ends in a tab', () => { let sheet = jspreadsheet(root, { worksheets: [ { data: fixtureData(), }, ], })[0]; const pasteText = '0-0\t0-1\t0-2\t0-3\n1-0\t1-1\t1-2\t1-3\n2-0\t2-1\t2-2\t2-3\n3-0\t3-1\t3-2\t'; sheet.updateSelectionFromCoords(0, 0, 0, 0); sheet.paste(sheet.selectedCell[0], sheet.selectedCell[1], pasteText); expect(sheet.getData()).to.eql([ ['0-0', '0-1', '0-2', '0-3'], ['1-0', '1-1', '1-2', '1-3'], ['2-0', '2-1', '2-2', '2-3'], ['3-0', '3-1', '3-2', ''], ]); }); it('paste lacked columns data', () => { let sheet = jspreadsheet(root, { worksheets: [ { data: fixtureData(), }, ], })[0]; const pasteText = '0-0\t\n' + '1-0\t1-1\t1-2\t1-3\n' + '2-0\n' + '3-0\t3-1\t3-2\t'; sheet.updateSelectionFromCoords(0, 0, 0, 0); sheet.paste(sheet.selectedCell[0], sheet.selectedCell[1], pasteText); expect(sheet.getData()).to.eql([ ['0-0', '', '', ''], ['1-0', '1-1', '1-2', '1-3'], ['2-0', '', '', ''], ['3-0', '3-1', '3-2', ''], ]); }); }); ================================================ FILE: test/redo.js ================================================ const { expect } = require('chai'); const jspreadsheet = require('../dist/index.js'); describe('Use the redo method', () => { it('.undo', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ ['Hello', 'World'], ['Testing', 'CE'], ], worksheetName: 'Countries', }, ], }); const table = root.querySelector('tbody'); const rows = table.children; const firstRow = rows[0]; const secondRow = rows[1]; expect(firstRow.children[1].innerHTML).to.include('Hello'); expect(firstRow.children[2].innerHTML).to.include('World'); expect(secondRow.children[1].innerHTML).to.include('Testing'); expect(secondRow.children[2].innerHTML).to.include('CE'); instance[0].setValueFromCoords(0, 0, 'New Value'); instance[0].setValueFromCoords(1, 0, 'TESTING'); expect(firstRow.children[1].innerHTML).to.include('New Value'); expect(firstRow.children[2].innerHTML).to.include('TESTING'); instance[0].undo(); expect(firstRow.children[1].innerHTML).to.include('New Value'); expect(firstRow.children[2].innerHTML).to.include('World'); instance[0].undo(); expect(firstRow.children[1].innerHTML).to.include('Hello'); expect(firstRow.children[2].innerHTML).to.include('World'); }); it('.redo after undo something', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ ['Hello', 'World'], ['Testing', 'CE'], ], worksheetName: 'Countries', }, ], }); const table = root.querySelector('tbody'); const rows = table.children; const firstRow = rows[0]; const secondRow = rows[1]; expect(firstRow.children[1].innerHTML).to.include('Hello'); expect(firstRow.children[2].innerHTML).to.include('World'); expect(secondRow.children[1].innerHTML).to.include('Testing'); expect(secondRow.children[2].innerHTML).to.include('CE'); instance[0].setValueFromCoords(0, 0, 'New Value'); instance[0].setValueFromCoords(1, 0, 'TESTING'); expect(firstRow.children[1].innerHTML).to.include('New Value'); expect(firstRow.children[2].innerHTML).to.include('TESTING'); instance[0].undo(); expect(firstRow.children[1].innerHTML).to.include('New Value'); expect(firstRow.children[2].innerHTML).to.include('World'); instance[0].undo(); expect(firstRow.children[1].innerHTML).to.include('Hello'); expect(firstRow.children[2].innerHTML).to.include('World'); instance[0].redo(); expect(firstRow.children[1].innerHTML).to.include('New Value'); expect(firstRow.children[2].innerHTML).to.include('World'); instance[0].redo(); expect(firstRow.children[1].innerHTML).to.include('New Value'); expect(firstRow.children[2].innerHTML).to.include('TESTING'); }); }); ================================================ FILE: test/rows.js ================================================ const { expect } = require('chai'); const jspreadsheet = require('../dist/index.js'); describe('Use the rows method', () => { it('deleteRow and a row is removed', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ [1, 2, 3], [4, 5, 6], ], worksheetName: 'Countries', }, ], }); let table = root.querySelector('tbody'); let rows = table.children; let firstRow = rows[0]; // Check that first row has the value of [1, 2, 3] expect(firstRow.children[1].innerHTML).to.include(1); expect(firstRow.children[2].innerHTML).to.include(2); expect(firstRow.children[3].innerHTML).to.include(3); instance[0].deleteRow(0); table = root.querySelector('tbody'); rows = table.children; firstRow = rows[0]; // Check that the value of the first row now is [4, 5, 6] since the first one got removed expect(firstRow.children[1].innerHTML).to.include(4); expect(firstRow.children[2].innerHTML).to.include(5); expect(firstRow.children[3].innerHTML).to.include(6); }); it('insertRow and a row is added', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ [1, 2, 3], [4, 5, 6], ], worksheetName: 'Countries', }, ], }); let table = root.querySelector('tbody'); let rows = table.children; let firstRow = rows[0]; // Check that first row has the value of [1, 2, 3] expect(firstRow.children[1].innerHTML).to.include(1); expect(firstRow.children[2].innerHTML).to.include(2); expect(firstRow.children[3].innerHTML).to.include(3); instance[0].insertRow([9, 9, 9], 0, 1); table = root.querySelector('tbody'); rows = table.children; firstRow = rows[0]; // Check that the value of the first row now is [9, 9, 9] expect(firstRow.children[1].innerHTML).to.include(9); expect(firstRow.children[2].innerHTML).to.include(9); expect(firstRow.children[3].innerHTML).to.include(9); }); it('moveRow and the row is moved', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ [1, 2, 3], [4, 5, 6], ], }, ], }); let table = root.querySelector('tbody'); let rows = table.children; let firstRow = rows[0]; let secondRow = rows[1]; let A1 = firstRow.children[1]; let A2 = secondRow.children[1]; expect(A1.innerHTML).to.include(1); expect(A2.innerHTML).to.include(4); instance[0].moveRow(0, 1); table = root.querySelector('tbody'); rows = table.children; firstRow = rows[0]; secondRow = rows[1]; A1 = firstRow.children[1]; A2 = secondRow.children[1]; expect(A1.innerHTML).to.include(4); expect(A2.innerHTML).to.include(1); }); it('deleteRow history', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ [1, 2, 3], [4, 5, 6], ], worksheetName: 'Countries', }, ], }); let table = root.querySelector('tbody'); let rows = table.children; expect(rows[0].children[1].innerHTML).to.include(1); expect(rows[0].children[2].innerHTML).to.include(2); expect(rows[0].children[3].innerHTML).to.include(3); instance[0].deleteRow(0); expect(rows[0].children[1].innerHTML).to.include(4); expect(rows[0].children[2].innerHTML).to.include(5); expect(rows[0].children[3].innerHTML).to.include(6); instance[0].undo(); expect(rows[0].children[1].innerHTML).to.include(1); expect(rows[0].children[2].innerHTML).to.include(2); expect(rows[0].children[3].innerHTML).to.include(3); instance[0].redo(); expect(rows[0].children[1].innerHTML).to.include(4); expect(rows[0].children[2].innerHTML).to.include(5); expect(rows[0].children[3].innerHTML).to.include(6); }); it('insertRow history', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ [1, 2, 3], [4, 5, 6], ], worksheetName: 'Countries', }, ], }); let table = root.querySelector('tbody'); let rows = table.children; expect(rows[0].children[1].innerHTML).to.include(1); expect(rows[0].children[2].innerHTML).to.include(2); expect(rows[0].children[3].innerHTML).to.include(3); instance[0].insertRow([9, 9, 9], 0, 1); expect(rows[0].children[1].innerHTML).to.include(9); expect(rows[0].children[2].innerHTML).to.include(9); expect(rows[0].children[3].innerHTML).to.include(9); instance[0].undo(); expect(rows[0].children[1].innerHTML).to.include(1); expect(rows[0].children[2].innerHTML).to.include(2); expect(rows[0].children[3].innerHTML).to.include(3); instance[0].redo(); expect(rows[0].children[1].innerHTML).to.include(9); expect(rows[0].children[2].innerHTML).to.include(9); expect(rows[0].children[3].innerHTML).to.include(9); }); it('moveRow history', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { minDimensions: [7, 7], data: [ [1, 2, 3], [4, 5, 6], ], }, ], }); let table = root.querySelector('tbody'); expect(table.children[0].children[1].innerHTML).to.include(1); expect(table.children[1].children[1].innerHTML).to.include(4); instance[0].moveRow(0, 1); expect(table.children[0].children[1].innerHTML).to.include(4); expect(table.children[1].children[1].innerHTML).to.include(1); instance[0].undo(); expect(table.children[0].children[1].innerHTML).to.include(1); expect(table.children[1].children[1].innerHTML).to.include(4); instance[0].redo(); expect(table.children[0].children[1].innerHTML).to.include(4); expect(table.children[1].children[1].innerHTML).to.include(1); }); }); ================================================ FILE: test/search.js ================================================ const { expect } = require('chai'); const jspreadsheet = require('../dist/index.js'); describe('Use search', () => { it('search and resetSearch methods', () => { const instance = jspreadsheet(root, { tabs: true, worksheets: [ { search: true, minDimensions: [7, 7], data: [ ['Mazda', 2001, 2000, '2006-01-01 12:00:00'], ['Peugeot', 2010, 5000, '2005-01-01 13:00:00'], ['Honda Fit', 2009, 3000, '2004-01-01 14:01:00'], ['Honda CRV', 2010, 6000, '2003-01-01 23:30:00'], ], }, ], }); instance[0].search('Honda'); expect(instance[0].searchInput.value).to.equal('Honda'); const bodyTag = root.querySelector('tbody'); expect(bodyTag.children.length).to.equal(2); expect(bodyTag.children[0].getAttribute('data-y')).to.equal('2'); expect(bodyTag.children[1].getAttribute('data-y')).to.equal('3'); instance[0].resetSearch(); expect(instance[0].searchInput.value).to.equal(''); expect(bodyTag.children[0].getAttribute('data-y')).to.equal('0'); expect(bodyTag.children[1].getAttribute('data-y')).to.equal('1'); }); }); ================================================ FILE: webpack.config.js ================================================ const path = require('path'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); class MyPlugin { apply(compiler) { compiler.hooks.emit.tap('MyPlugin', (compilation) => { // Get the bundled file name const fileName = Object.keys(compilation.assets)[0]; // Get the bundled file content const fileContent = compilation.assets[fileName].source(); const header = `if (! jSuites && typeof(require) === 'function') { var jSuites = require('jsuites'); } if (! formula && typeof(require) === 'function') { var formula = require('@jspreadsheet/formula'); } ;(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : global.jspreadsheet = factory(); }(this, (function () {`; const footer = ` return jspreadsheet; })));`; // Updated file content with custom content added const updatedFileContent = header + '\n\n' + fileContent + '\n\n' + footer; // Replace the bundled file content with updated content compilation.assets[fileName] = { source: () => updatedFileContent, size: () => updatedFileContent.length, }; }); } } let isProduction = process.env.NODE_ENV === 'production'; const webpack = { target: ['web', 'es5'], entry: isProduction ? './src/index' : './src/test.js', mode: isProduction ? 'production' : 'development', externals: {}, output: { filename: 'index.js', path: path.resolve(__dirname, 'dist'), library: 'jspreadsheet', libraryExport: 'default', }, optimization: { minimize: true, }, devServer: { static: { directory: path.join(__dirname, '/public'), }, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', 'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization', }, port: 8000, devMiddleware: { publicPath: 'https://localhost:3000/', }, hot: 'only', }, plugins: [], module: { rules: [ isProduction ? { test: /\.js$/, use: [ { loader: path.resolve('build.cjs'), options: {}, }, ], } : null, { test: /\.css$/, use: [isProduction ? MiniCssExtractPlugin.loader : 'style-loader', 'css-loader'], }, ], }, stats: { warnings: false, }, }; if (isProduction) { webpack.plugins.push(new MyPlugin()); webpack.plugins.push( new MiniCssExtractPlugin({ filename: 'jspreadsheet.css', }) ); } module.exports = webpack;