# Spreadsheet Full spreadsheet editor with formulas, cell styling, multi-sheet support, charts, conditional formatting, named ranges, sorting, filtering, freeze panes, clipboard operations, find/replace, and import/export to XLSX/CSV/ODS/PDF. ## Live Demo ## Embed ```html ``` ### Open with a CSV File ```js var csvUrl = "https://example.com/data.csv"; var src = "https://sgapps.io/online/webapp/spreadsheet/url/" + btoa(csvUrl); ``` --- ## Display Modes The editor supports three pre-configured embedding modes (set via the `mode` constructor option). The wrapping window currently launches in `editor` mode by default but the same instance can be queried via `getMode`: | Mode | Toolbar | Formula Bar | Sheet Tabs | Editing | Best For | |---|---|---|---|---|---| | `"editor"` | shown | shown | shown | enabled | Full spreadsheet application | | `"viewer"` | hidden | hidden | shown | disabled | Displaying data, reports, dashboards | | `"minimal"` | hidden | shown | hidden | enabled | Compact data-entry forms, embedded grids | ```js socket.fire("webapp::instance::request", "getMode", function (err, mode) { console.log("Mode:", mode); }); ``` --- ## Events Reference ### Cell Operations #### `setValue` -- Set Cell Value ```js socket.fire("webapp::instance::request", "setValue", "A1", "Hello World", function (err) { console.log(err || "Set"); }); ``` #### `setFormula` -- Set Cell Formula The formula expression is **without** the leading `=` (the editor stores the raw expression). If you'd rather use the `=` prefix, just call `setValue` -- it auto-detects values starting with `=` as formulas. ```js // Direct formula (no leading =) socket.fire("webapp::instance::request", "setFormula", "A4", "SUM(A2:A3)", function (err) { console.log(err || "Formula set"); }); // Or via setValue with = prefix socket.fire("webapp::instance::request", "setValue", "A4", "=SUM(A2:A3)"); ``` #### `getFormula` -- Get Cell Formula ```js socket.fire("webapp::instance::request", "getFormula", "A4", function (err, expr) { console.log("Formula:", expr); }); ``` #### `setCellStyle` -- Style a Cell ```js socket.fire("webapp::instance::request", "setCellStyle", "A1", { bold: true, fontSize: 14, bg: "#4a6cf7", color: "#ffffff" }); ``` The style object uses these properties (any subset is allowed): | Property | Type | Default | Description | |---|---|---|---| | `bold` | `boolean` | `false` | Bold text | | `italic` | `boolean` | `false` | Italic text | | `underline` | `boolean` | `false` | Underlined text | | `strikethrough` | `boolean` | `false` | Strikethrough text | | `fontFamily` | `string` | `"Arial"` | Font family name | | `fontSize` | `number` | `11` | Font size in points | | `color` | `string` | `"#000000"` | Text color (hex) | | `bg` | `string` | `null` | Background / fill color (hex). `null` = no fill | | `align` | `string` | `"left"` | Horizontal: `"left"`, `"center"`, `"right"` | | `valign` | `string` | `"bottom"` | Vertical: `"top"`, `"middle"`, `"bottom"` | | `wrap` | `boolean` | `false` | Wrap text within cell | | `numFmt` | `string` | `"General"` | Number format string (see below) | | `borderTop` / `borderRight` / `borderBottom` / `borderLeft` | `Object` | `null` | `{style, width, color}` -- style is `"solid"`, `"dashed"`, or `"dotted"` | ```js // Style a header row with bold blue background and a thick bottom border socket.fire("webapp::instance::request", "setCellStyle", "A1:F1", { bold: true, bg: "#1a73e8", color: "#ffffff", align: "center", fontSize: 12, borderBottom: { style: "solid", width: 2, color: "#0d47a1" } }); ``` #### Number Formats (`numFmt`) The `numFmt` property controls how a cell value is displayed. Common format strings: | Format | Example | Description | |---|---|---| | `"General"` | `1234.5` | Default — no special formatting | | `"0"` | `1235` | Integer (rounded) | | `"0.00"` | `1234.50` | Fixed 2 decimal places | | `"#,##0"` | `1,235` | Thousands separator | | `"#,##0.00"` | `1,234.50` | Thousands + 2 decimals | | `"$#,##0.00"` | `$1,234.50` | US currency | | `"0%"` | `12%` | Percentage (value × 100) | | `"0.00%"` | `12.35%` | Percentage with decimals | | `"0.00E+0"` | `1.23E+3` | Scientific notation | | `"yyyy-mm-dd"` | `2026-04-04` | ISO date | | `"mm/dd/yyyy"` | `04/04/2026` | US date | | `"hh:MM:ss"` | `14:30:00` | Time | | `"@"` | `(text)` | Text — no number conversion | ```js socket.fire("webapp::instance::request", "setCellStyle", "B2:B100", { numFmt: "$#,##0.00" }); // Currency socket.fire("webapp::instance::request", "setCellStyle", "C2:C100", { numFmt: "0.0%" }); // Percentage socket.fire("webapp::instance::request", "setCellStyle", "D2:D100", { numFmt: "yyyy-mm-dd" }); // Date ``` #### `getValue` -- Get Cell Value ```js socket.fire("webapp::instance::request", "getValue", "A1", function (err, cell) { console.log("Value:", cell); }); ``` --- ### Sheet Management ```js // Add a new sheet socket.fire("webapp::instance::request", "addSheet", "Data", function (err) {}); // Switch to sheet (0-based) socket.fire("webapp::instance::request", "activeSheet", 1, function (err) {}); // Read current active sheet socket.fire("webapp::instance::request", "activeSheet", function (err, idx) { console.log("Active:", idx); }); // Rename sheet socket.fire("webapp::instance::request", "renameSheet", 0, "Summary", function (err) {}); // Delete sheet socket.fire("webapp::instance::request", "deleteSheet", 1, function (err) {}); ``` --- ### Row & Column Operations ```js // Insert 2 rows starting at row index 5 socket.fire("webapp::instance::request", "insertRow", 5, 2, function (err) {}); // Insert a column at column 0 (leftmost) socket.fire("webapp::instance::request", "insertCol", 0, 1, function (err) {}); // Delete 3 rows starting at index 10 socket.fire("webapp::instance::request", "deleteRow", 10, 3, function (err) {}); // Delete a single column socket.fire("webapp::instance::request", "deleteCol", 4, function (err) {}); ``` --- ### Selection ```js // Select a single cell socket.fire("webapp::instance::request", "setSelection", "B2"); // Select a range socket.fire("webapp::instance::request", "setSelection", "A1:D10"); // Read current selection socket.fire("webapp::instance::request", "getSelection", function (err, sel) { console.log(sel.rangeStr); // e.g. "A1:D10" console.log(sel.activeCell); }); ``` --- ### Sort, Filter, Find & Replace ```js // Sort range A1:D100 by column 0 ascending, then by column 2 descending socket.fire("webapp::instance::request", "sort", "A1:D100", [{col: 0, ascending: true}, {col: 2, ascending: false}], function (err) {}); // Toggle auto-filter for current selection socket.fire("webapp::instance::request", "filter", function (err) {}); // Find all matches socket.fire("webapp::instance::request", "find", "Total", { matchCase: false, wholeCell: false, regex: false }, function (err, matches) { // matches: [{ ref: "A2", sheet: 0, value: "Total" }, ...] console.log("Found", matches.length, "matches"); }); // Find with a regex (treats query as a regular expression) socket.fire("webapp::instance::request", "find", "\\d{4}-\\d{2}-\\d{2}", { regex: true }); // Replace socket.fire("webapp::instance::request", "replace", "old", "new", { matchCase: true }, function (err, count) { console.log("Replaced", count); }); ``` **Find / Replace options:** | Option | Type | Default | Description | |---|---|---|---| | `matchCase` | `boolean` | `false` | Case-sensitive search | | `wholeCell` | `boolean` | `false` | Match entire cell content (not substring) | | `regex` | `boolean` | `false` | Treat the query as a regular expression | | `sheet` | `number` | all | Search only in a specific sheet index | --- ### Clipboard ```js // Copy current selection socket.fire("webapp::instance::request", "copy"); // Or specify a range socket.fire("webapp::instance::request", "copy", "A1:B5"); // Cut socket.fire("webapp::instance::request", "cut", "A1:B5"); // Paste at active cell socket.fire("webapp::instance::request", "paste"); // Paste mode: "all" | "values" | "formats" | "formulas" | "transpose" socket.fire("webapp::instance::request", "paste", "D1", "values"); // Copy from one place to another in one call socket.fire("webapp::instance::request", "copyRange", "A1:B5", "D1"); ``` --- ### Merge, Freeze & Layout ```js // Merge cells socket.fire("webapp::instance::request", "merge", "A1:C1"); // Unmerge socket.fire("webapp::instance::request", "unmerge", "A1:C1"); // Freeze first row + first 2 columns socket.fire("webapp::instance::request", "freeze", 1, 2); // Unfreeze socket.fire("webapp::instance::request", "freeze", 0, 0); ``` --- ### Charts Charts render as inline SVG and can be dragged/resized after insertion. Supported chart types: `"bar"`, `"line"`, `"pie"`, `"area"`, `"scatter"`, `"combo"`. ```js // Bar chart socket.fire("webapp::instance::request", "insertChart", "A1:B7", "bar", { title: "Sales by Region", legend: "right", width: 400, height: 300 }); // Line chart socket.fire("webapp::instance::request", "insertChart", "A1:C12", "line", { title: "Monthly Trend" }); // Pie chart socket.fire("webapp::instance::request", "insertChart", "A1:B5", "pie", { title: "Market Share" }); // Scatter socket.fire("webapp::instance::request", "insertChart", "A1:B50", "scatter", { title: "Correlation" }); // Area socket.fire("webapp::instance::request", "insertChart", "A1:D12", "area", { title: "Revenue Breakdown" }); ``` --- ### Named Ranges Define names for cells or ranges to use them in formulas: ```js // Define socket.fire("webapp::instance::request", "namedRange", "tax_rate", "B1"); socket.fire("webapp::instance::request", "namedRange", "prices", "Sheet1!B2:B100"); // Use in a formula socket.fire("webapp::instance::request", "setValue", "C2", "=B2*tax_rate"); socket.fire("webapp::instance::request", "setValue", "D1", "=SUM(prices)"); // Read a named range socket.fire("webapp::instance::request", "namedRange", "tax_rate", function (err, ref) { console.log(ref); }); ``` --- ### Conditional Formatting Apply visual rules to highlight cells based on their values. Each rule is an object with a `type` discriminator. ```js // Highlight values greater than 1000 in red socket.fire("webapp::instance::request", "conditionalFormat", "B2:B100", { type: "cellIs", operator: "greaterThan", values: [1000], style: { bg: "#fce4ec", color: "#c62828" } }); // 3-color scale (green -> yellow -> red) socket.fire("webapp::instance::request", "conditionalFormat", "C2:C100", { type: "colorScale", colors: ["#4caf50", "#ffeb3b", "#f44336"] }); // Data bars socket.fire("webapp::instance::request", "conditionalFormat", "D2:D50", { type: "dataBar", color: "#2196f3" }); // Icon sets: 'arrows3', 'traffic3', 'stars3', 'flags3' socket.fire("webapp::instance::request", "conditionalFormat", "E2:E50", { type: "iconSet", icons: "arrows3" }); // Formula-based rule (alternate row shading) socket.fire("webapp::instance::request", "conditionalFormat", "A2:F100", { type: "expression", formula: "=MOD(ROW(),2)=0", style: { bg: "#f5f5f5" } }); ``` **Rule types:** `"cellIs"`, `"colorScale"`, `"dataBar"`, `"iconSet"`, `"expression"` **Operators (for `cellIs`):** `"greaterThan"`, `"lessThan"`, `"equal"`, `"notEqual"`, `"greaterThanOrEqual"`, `"lessThanOrEqual"`, `"between"`, `"notBetween"` --- ### Import / Export ```js // Export as CSV (active sheet) socket.fire("webapp::instance::request", "exportCSV", function (err, csv) { console.log(csv); }); // Export sheet by index socket.fire("webapp::instance::request", "exportCSV", 0, function (err, csv) { console.log(csv); }); // Export as XLSX (Promise -> Blob) socket.fire("webapp::instance::request", "exportXLSX", function (err, blob) { var url = URL.createObjectURL(blob); // download or display }); // Export as ODS / PDF socket.fire("webapp::instance::request", "exportODS", function (err, blob) {}); socket.fire("webapp::instance::request", "exportPDF", function (err, blob) {}); // Full state as JSON socket.fire("webapp::instance::request", "toJSON", function (err, data) { console.log(data); }); // Restore state from JSON socket.fire("webapp::instance::request", "fromJSON", savedData); // Import a file (XLSX, CSV, ODS, ...) var file = /* a File or Blob */; socket.fire("webapp::instance::request", "importFile", file, function (err) { if (!err) console.log("Imported"); }); ``` #### JSON data model `toJSON` returns -- and `fromJSON` accepts -- this workbook structure. You can save it to localStorage / IndexedDB / a server, then restore it later in any spreadsheet instance: ```json { "name": "Workbook", "sheets": [ { "name": "Sheet1", "tabColor": null, "rows": { "5": { "height": 30, "hidden": false } }, "cols": { "2": { "width": 150, "hidden": false } }, "cells": { "A1": { "v": "Revenue", "s": { "bold": true, "bg": "#4a6cf7", "color": "#fff" } }, "B1": { "v": 50000, "f": null, "s": { "numFmt": "$#,##0" } }, "B2": { "v": null, "f": "B1*1.1", "s": {} } }, "merges": ["A1:C1"], "freeze": { "row": 1, "col": 0 }, "filters": null, "charts": [], "conditionalFormats": [], "namedRanges": { "total": "B10" }, "validations": [] } ], "activeSheet": 0 } ``` **Cell object schema:** `v` = raw value, `f` = formula expression (without `=`, or `null`), `s` = style object (see [Cell Styles](#setcellstyle--style-a-cell)). > **Tip:** the engine recalculates all formulas when you call `fromJSON`, so cells with `v: null, f: "..."` are filled in automatically. You don't need to persist computed values. ```js // Save to localStorage on every cell change socket.on("api-event::instance:event:cell-change", function () { socket.fire("webapp::instance::request", "toJSON", function (err, data) { if (!err) localStorage.setItem("myWorkbook", JSON.stringify(data)); }); }); // Restore on page load var saved = localStorage.getItem("myWorkbook"); if (saved) { socket.fire("webapp::instance::request", "fromJSON", JSON.parse(saved)); } ``` --- ### View Controls ```js socket.fire("webapp::instance::request", "zoom", 1.5); // 150% socket.fire("webapp::instance::request", "readOnly", true); // read-only mode socket.fire("webapp::instance::request", "freeze", 1, 1); // freeze first row + col socket.fire("webapp::instance::request", "print"); // open print dialog ``` --- ### Built-in Dialogs The Spreadsheet ships with 9 dialogs that can be opened remotely. Each one is a fully interactive form -- the user fills it in and the changes are applied automatically. ```js socket.fire("webapp::instance::request", "showFormatCellsDialog"); socket.fire("webapp::instance::request", "showSortDialog"); socket.fire("webapp::instance::request", "showFindDialog", true); // true = find/replace socket.fire("webapp::instance::request", "showConditionalFormatDialog"); socket.fire("webapp::instance::request", "showDataValidationDialog"); socket.fire("webapp::instance::request", "showNamedRangeDialog"); socket.fire("webapp::instance::request", "showChartDialog"); socket.fire("webapp::instance::request", "showFunctionWizard"); socket.fire("webapp::instance::request", "showPrintDialog"); ``` | Dialog | Description | |---|---| | **Format Cells** (`Ctrl+1`) | 5 tabs: Number, Alignment, Font, Border, Fill | | **Sort** | Multi-level sort with column/direction selectors | | **Find & Replace** (`Ctrl+F`/`Ctrl+H`) | Search with match case, whole cell, regex options | | **Conditional Format** | Rule builder: cell value, formula, color scale, data bar, icon set | | **Data Validation** | 3 tabs: Settings (type/operator/values), Input Message, Error Alert | | **Named Range Manager** | List, add, edit, delete named ranges | | **Insert Chart** | Chart type grid with preview, title/legend settings | | **Function Wizard** | Category filter, function list, argument builder (also reachable from the `fx` button in the formula bar) | | **Print** (`Ctrl+P`) | Print area, orientation, margins, scale, headers/footers | #### Data Validation rule shape Inside the Data Validation dialog (or for users embedding the editor and calling validation programmatically through their own code) the rule shape is: ```js // Dropdown list { type: "list", list: ["High", "Medium", "Low"], message: "Select a priority" } // Number range { type: "number", operator: "between", value1: 0, value2: 100, message: "0 to 100" } // Date range { type: "date", operator: "greaterThan", value1: "2026-01-01" } // Text length { type: "textLength", operator: "lessThanOrEqual", value1: 50 } // Custom formula { type: "custom", formula: "=AND(F2>=0, MOD(F2,1)=0)", message: "Positive integer" } ``` **Validation types:** `"list"`, `"number"`, `"date"`, `"textLength"`, `"custom"` **Validation operators:** `"between"`, `"notBetween"`, `"equal"`, `"notEqual"`, `"greaterThan"`, `"lessThan"`, `"greaterThanOrEqual"`, `"lessThanOrEqual"` --- ### Listening to Editor Events The Spreadsheet forwards internal events to the embedder via `api-event::instance:`. The first element of the `args` array is the event payload. | Event | Payload | Description | |---|---|---| | `event:ready` | -- | Spreadsheet fully initialized and rendered | | `event:cell-change` | `{ref, oldValue, newValue, sheet}` | A cell value or formula was modified | | `event:recalculate` | -- | Formulas were recalculated | | `event:sheet-change` | `{index, name}` | The active sheet was switched (or a sheet was added/deleted/renamed) | | `event:selection-change` | `{start: {row, col}, end: {row, col}, sheet}` | Selection range changed | > **Editor-only events:** the underlying library also emits `event:before-edit`, `event:after-edit`, `event:context-menu`, `event:scroll`, `event:zoom`, `event:import` and `event:export`. These are not currently forwarded by the wrapper but can be added on request. ```js // Cell value or formula changed socket.on("api-event::instance:event:cell-change", function (args) { var data = args[0]; console.log("Cell " + data.ref + " : " + data.oldValue + " -> " + data.newValue); }); // Recalculation finished socket.on("api-event::instance:event:recalculate", function () { console.log("Formulas recalculated"); }); // Sheet added/deleted/renamed socket.on("api-event::instance:event:sheet-change", function (args) { var s = args[0]; console.log("Active sheet:", s.index, s.name); }); // Selection moved socket.on("api-event::instance:event:selection-change", function (args) { var sel = args[0]; console.log("From", sel.start, "to", sel.end); }); // Editor finished initial load socket.on("api-event::instance:event:ready", function () { console.log("Spreadsheet ready"); }); ``` --- ## Keyboard Shortcuts Whenever the spreadsheet has focus, the following keyboard shortcuts are available: | Shortcut | Action | |---|---| | `Enter` | Confirm edit, move down | | `Tab` / `Shift+Tab` | Confirm edit, move right / left | | `Escape` | Cancel edit | | `F2` | Enter edit mode on selected cell | | `Delete` | Clear cell content | | `Ctrl+Z` | Undo | | `Ctrl+Y` / `Ctrl+Shift+Z` | Redo | | `Ctrl+C` / `Ctrl+X` / `Ctrl+V` | Copy / Cut / Paste | | `Ctrl+D` | Fill down | | `Ctrl+R` | Fill right | | `Ctrl+B` / `Ctrl+I` / `Ctrl+U` | Bold / Italic / Underline | | `Ctrl+A` | Select all cells | | `Ctrl+F` | Open Find dialog | | `Ctrl+H` | Open Find & Replace dialog | | `Ctrl+G` | Go to cell dialog | | `Ctrl+Home` / `Ctrl+End` | Navigate to A1 / last used cell | | `Ctrl+;` / `Ctrl+Shift+;` | Insert current date / time | | `Ctrl+1` | Open Format Cells dialog | | `Ctrl+Shift+L` | Toggle auto-filter | | `Arrow keys` | Move selection | | `Ctrl+Arrow` | Jump to edge of data region | | `Shift+Arrow` | Extend selection | | `Shift+Click` / `Ctrl+Click` | Extend selection / multi-selection | | `Page Up` / `Page Down` | Scroll by one page | | `Ctrl+P` | Print current sheet | | `Alt+Enter` | Insert new line inside a cell | --- ## Formula Functions The formula engine supports 200+ functions. Use them with `setValue` (with leading `=`) or `setFormula` (without leading `=`): ```js socket.fire("webapp::instance::request", "setValue", "D2", "=B2*C2"); socket.fire("webapp::instance::request", "setValue", "D10", "=SUM(D2:D9)"); socket.fire("webapp::instance::request", "setValue", "E2", '=IF(D2>1000,"High","Low")'); socket.fire("webapp::instance::request", "setValue", "F2", "=VLOOKUP(A2,Sheet2!A:B,2,FALSE)"); socket.fire("webapp::instance::request", "setValue", "G2", '=TEXT(B2,"$#,##0.00")'); socket.fire("webapp::instance::request", "setValue", "H2", '=IFERROR(B2/C2,"N/A")'); ``` **Available categories:** - **Math:** `SUM`, `AVERAGE`, `MIN`, `MAX`, `COUNT`, `COUNTA`, `ROUND`, `CEILING`, `FLOOR`, `POWER`, `SQRT`, `MOD`, `RAND`, `RANDBETWEEN`, `PI`, `LOG`, `LN`, `EXP`, `INT`, `SIGN`, `TRUNC`, `PRODUCT`, `SUMPRODUCT`, `SIN`, `COS`, `TAN`, `ASIN`, `ACOS`, `ATAN`, `ATAN2`, `DEGREES`, `RADIANS`, `ABS` - **Conditional:** `SUMIF`, `SUMIFS`, `COUNTIF`, `COUNTIFS`, `AVERAGEIF`, `AVERAGEIFS` - **Text:** `CONCAT`, `CONCATENATE`, `LEFT`, `RIGHT`, `MID`, `LEN`, `UPPER`, `LOWER`, `PROPER`, `TRIM`, `SUBSTITUTE`, `REPLACE`, `FIND`, `SEARCH`, `TEXT`, `VALUE`, `REPT`, `CHAR`, `CODE`, `EXACT`, `T`, `TEXTJOIN` - **Logical:** `IF`, `AND`, `OR`, `NOT`, `XOR`, `IFERROR`, `IFNA`, `IFS`, `SWITCH`, `TRUE`, `FALSE` - **Lookup:** `VLOOKUP`, `HLOOKUP`, `INDEX`, `MATCH`, `OFFSET`, `INDIRECT`, `ROW`, `COLUMN`, `ROWS`, `COLUMNS`, `ADDRESS`, `CHOOSE` - **Date / Time:** `NOW`, `TODAY`, `DATE`, `YEAR`, `MONTH`, `DAY`, `HOUR`, `MINUTE`, `SECOND`, `DATEVALUE`, `DAYS`, `EDATE`, `EOMONTH`, `WEEKDAY`, `WEEKNUM` - **Statistical:** `MEDIAN`, `MODE`, `STDEV`, `VAR`, `LARGE`, `SMALL`, `RANK`, `PERCENTILE`, `QUARTILE`, `CORREL`, `FORECAST` - **Financial:** `PMT`, `FV`, `PV`, `NPV`, `IRR`, `NPER` - **Info:** `ISBLANK`, `ISERROR`, `ISNUMBER`, `ISTEXT`, `ISLOGICAL`, `ISNA`, `TYPE`, `NA`, `ERROR.TYPE` #### Formula error values | Error | Meaning | |---|---| | `#REF!` | Invalid cell reference | | `#VALUE!` | Wrong value type | | `#DIV/0!` | Division by zero | | `#NAME?` | Unrecognized function or name | | `#N/A` | Value not available | | `#NULL!` | Invalid range intersection | | `#NUM!` | Invalid numeric value | | `#CIRC!` | Circular reference detected | --- ## Complete Example ```html ```