```
================================================
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
`,
})
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: `
`,
})
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
`,
})
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: `
`
})
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', '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'],
],
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
},
],
style: {
A1:'background-color: orange;',
B1:'background-color: orange;',
},
});
}
}
```
{.pro}
> **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:

**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
Creado
Creado por
N
Nombre
Nombre
Tipo
Calle
Poblacin
Nombre SAP
Cdigo Producto
Respuesta
2019-04-02
ocastellanos
30616
GRUPO UVESCO, S.A. "NETTO"
GRANDE
Supermercado grande
CTRA. S-20. BARRIO MONTE, S/N
SANTANDER
HAMBURGUESA NATURAL
206774
NO EST EN SURTIDO
2019-04-02
ocastellanos
30616
GRUPO UVESCO, S.A. "NETTO"
GRANDE
Supermercado grande
CTRA. S-20. BARRIO MONTE, S/N
SANTANDER
HAMBURGUESA BARBACOA
206775
NO EST EN SURTIDO
2019-04-02
ocastellanos
30616
GRUPO UVESCO, S.A. "NETTO"
GRANDE
Supermercado grande
CTRA. S-20. BARRIO MONTE, S/N
SANTANDER
NUGGETS
206776
ROTURA
2019-04-02
ocastellanos
30616
GRUPO UVESCO, S.A. "NETTO"
GRANDE
Supermercado grande
CTRA. S-20. BARRIO MONTE, S/N
SANTANDER
ESCALOPA
206777
PRESENTE
2019-04-02
ocastellanos
30716
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
PEQUEO
Supermercado medianos
JUAN XXIII, 12
TORRELAVEGA
HAMBURGUESA NATURAL
206774
NO EST EN SURTIDO
2019-04-02
ocastellanos
30716
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
PEQUEO
Supermercado medianos
JUAN XXIII, 12
TORRELAVEGA
ESCALOPA
206777
NO EST EN SURTIDO
2019-04-02
ocastellanos
30716
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
PEQUEO
Supermercado medianos
JUAN XXIII, 12
TORRELAVEGA
HAMBURGUESA BARBACOA
206775
NO EST EN SURTIDO
2019-04-02
ocastellanos
30716
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
PEQUEO
Supermercado medianos
JUAN XXIII, 12
TORRELAVEGA
NUGGETS
206776
NO EST EN SURTIDO
2019-04-03
ocastellanos
46825
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
PEQUEO
Supermercado medianos
Barrio la Cruz S/N
Liencres
HAMBURGUESA NATURAL
206774
NO EST EN SURTIDO
2019-04-03
ocastellanos
46825
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
PEQUEO
Supermercado medianos
Barrio la Cruz S/N
Liencres
HAMBURGUESA BARBACOA
206775
NO EST EN SURTIDO
2019-04-03
ocastellanos
46825
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
PEQUEO
Supermercado medianos
Barrio la Cruz S/N
Liencres
ESCALOPA
206777
NO EST EN SURTIDO
2019-04-03
ocastellanos
46825
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
PEQUEO
Supermercado medianos
Barrio la Cruz S/N
Liencres
NUGGETS
206776
PRESENTE
2019-04-05
ocastellanos
30676
GRUPO UVESCO, S.A. "NORCASH"
PEQUEO
Supermercado pequeo
P ALISAS, S/N
SOLARES
NUGGETS
206776
PRESENTE
2019-04-05
ocastellanos
30676
GRUPO UVESCO, S.A. "NORCASH"
PEQUEO
Supermercado pequeo
P ALISAS, S/N
SOLARES
HAMBURGUESA BARBACOA
206775
NO EST EN SURTIDO
2019-04-05
ocastellanos
30676
GRUPO UVESCO, S.A. "NORCASH"
PEQUEO
Supermercado pequeo
P ALISAS, S/N
SOLARES
HAMBURGUESA NATURAL
206774
NO EST EN SURTIDO
2019-04-05
ocastellanos
30676
GRUPO UVESCO, S.A. "NORCASH"
PEQUEO
Supermercado pequeo
P ALISAS, S/N
SOLARES
ESCALOPA
206777
NO EST EN SURTIDO
2019-04-05
ocastellanos
30296
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
1
Supermercado grande
LEONARDO RUCABADO, 26
CASTRO URDIALES
HAMBURGUESA BARBACOA
206775
NO EST EN SURTIDO
2019-04-05
ocastellanos
30296
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
1
Supermercado grande
LEONARDO RUCABADO, 26
CASTRO URDIALES
HAMBURGUESA NATURAL
206774
NO EST EN SURTIDO
2019-04-05
ocastellanos
30296
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
1
Supermercado grande
LEONARDO RUCABADO, 26
CASTRO URDIALES
NUGGETS
206776
PRESENTE
2019-04-05
ocastellanos
30296
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
1
Supermercado grande
LEONARDO RUCABADO, 26
CASTRO URDIALES
ESCALOPA
206777
PRESENTE
2019-04-15
ocastellanos
30296
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
1
Supermercado grande
LEONARDO RUCABADO, 26
CASTRO URDIALES
HAMBURGUESA NATURAL
206774
NO EST EN SURTIDO
2019-04-15
ocastellanos
30296
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
1
Supermercado grande
LEONARDO RUCABADO, 26
CASTRO URDIALES
HAMBURGUESA BARBACOA
206775
NO EST EN SURTIDO
2019-04-15
ocastellanos
30296
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
1
Supermercado grande
LEONARDO RUCABADO, 26
CASTRO URDIALES
ESCALOPA
206777
PRESENTE
2019-04-15
ocastellanos
30296
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
1
Supermercado grande
LEONARDO RUCABADO, 26
CASTRO URDIALES
NUGGETS
206776
PRESENTE
2019-04-12
ocastellanos
30596
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
PEQUEO
Supermercado grande
RUBEN DARIO, 2
SANTANDER
HAMBURGUESA BARBACOA
206775
NO EST EN SURTIDO
2019-04-12
ocastellanos
30596
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
PEQUEO
Supermercado grande
RUBEN DARIO, 2
SANTANDER
HAMBURGUESA NATURAL
206774
NO EST EN SURTIDO
2019-04-12
ocastellanos
30596
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
PEQUEO
Supermercado grande
RUBEN DARIO, 2
SANTANDER
NUGGETS
206776
ROTURA
2019-04-12
ocastellanos
30596
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
PEQUEO
Supermercado grande
RUBEN DARIO, 2
SANTANDER
ESCALOPA
206777
NO EST EN SURTIDO
2019-04-15
ocastellanos
30596
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
PEQUEO
Supermercado grande
RUBEN DARIO, 2
SANTANDER
HAMBURGUESA BARBACOA
206775
NO EST EN SURTIDO
2019-04-15
ocastellanos
30596
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
PEQUEO
Supermercado grande
RUBEN DARIO, 2
SANTANDER
HAMBURGUESA NATURAL
206774
NO EST EN SURTIDO
2019-04-15
ocastellanos
30596
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
PEQUEO
Supermercado grande
RUBEN DARIO, 2
SANTANDER
NUGGETS
206776
ROTURA
2019-04-15
ocastellanos
30596
GRUPO UVESCO, S.A. "BM SUPERMERCADOS"
PEQUEO
Supermercado grande
RUBEN DARIO, 2
SANTANDER
ESCALOPA
206777
NO EST EN SURTIDO
2019-04-15
ocastellanos
30676
GRUPO UVESCO, S.A. "NORCASH"
PEQUEO
Supermercado pequeo
P ALISAS, S/N
SOLARES
ESCALOPA
206777
NO EST EN SURTIDO
2019-04-15
ocastellanos
30676
GRUPO UVESCO, S.A. "NORCASH"
PEQUEO
Supermercado pequeo
P ALISAS, S/N
SOLARES
NUGGETS
206776
PRESENTE
2019-04-15
ocastellanos
30676
GRUPO UVESCO, S.A. "NORCASH"
PEQUEO
Supermercado pequeo
P ALISAS, S/N
SOLARES
HAMBURGUESA BARBACOA
206775
NO EST EN SURTIDO
2019-04-15
ocastellanos
30676
GRUPO UVESCO, S.A. "NORCASH"
PEQUEO
Supermercado pequeo
P ALISAS, S/N
SOLARES
HAMBURGUESA NATURAL
206774
NO 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.

[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 (
);
}
```
================================================
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.
23k+
Weekly downloads
6700+
GitHub stars
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.
A complete solution to make rich and user-friendly web applications
Fully customizable JavaScript spreadsheet library, offering various components to enhance web development projects.
```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
Make rich and user-friendly web interfaces and applications
User-Friendly Inputs
You can easily handle complicated data inputs in a way users are used to
Enhanced Experience
Improve your user software experience
Beautiful CRUDs
Create rich CRUDS and beautiful UI
Compatibility with excel spreadsheets
Users can move data around with common copy and paste shortcuts
Easy Customization
Easy customizations with easy third-party plugin integrations
Fast and Simple
Lean, fast and simple to use
Faster Data Entry
Speed up your work dealing with difficult data entry in a web-based software
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
Enterprise JavaScript data grid component to integrate spreadsheet UI into your web-based application.
Intrasheets
Collaborate with ease using Intrasheets, an intuitive tool for managing spreadsheets across teams, ensuring that everyone stays on the same page.
Jsuites
Comprehensive JavaScript plugins and web components for diverse web-based applications and requirements.
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;