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

<iframe id="ss-demo" src="/online/webapp/spreadsheet" width="100%" height="600" frameborder="0" style="border:1px solid #ccc; border-radius:4px;"></iframe>
<script>window._wsConnect('ss-demo', 'ssSocket');</script>

Embed

<iframe src="https://sgapps.io/online/webapp/spreadsheet"
    width="100%" height="600" frameborder="0"></iframe>

Open with a CSV File

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
&#x22;editor&#x22; shown shown shown enabled Full spreadsheet application
&#x22;viewer&#x22; hidden hidden shown disabled Displaying data, reports, dashboards
&#x22;minimal&#x22; hidden shown hidden enabled Compact data-entry forms, embedded grids

socket.fire("webapp::instance::request", "getMode",
    function (err, mode) { console.log("Mode:", mode); });

Events Reference

Cell Operations

setValue -- Set Cell Value

socket.fire("webapp::instance::request", "setValue", "A1", "Hello World",
    function (err) { console.log(err || "Set"); });

<button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','setValue','A1','Sales Data',function(){window.ssSocket.fire('webapp::instance::request','setValue','A2','100',function(){window.ssSocket.fire('webapp::instance::request','setValue','A3','250',function(){window.ssSocket.fire('webapp::instance::request','setValue','B1','Region',function(){window.ssSocket.fire('webapp::instance::request','setValue','B2','North',function(){window.ssSocket.fire('webapp::instance::request','setValue','B3','South')})})})})})">Try: Fill Sample Data</button>

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.

// 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)");

<button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','setFormula','A4','SUM(A2:A3)')">Try: Set SUM Formula in A4</button>

getFormula -- Get Cell Formula

socket.fire("webapp::instance::request", "getFormula", "A4",
    function (err, expr) { console.log("Formula:", expr); });

<button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','getFormula','A4',function(e,f){alert('A4 formula: '+(f||'(none)'))})">Try: Get A4 Formula</button>

setCellStyle -- Style a Cell

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 &#x22;Arial&#x22; Font family name
fontSize number 11 Font size in points
color string &#x22;#000000&#x22; Text color (hex)
bg string null Background / fill color (hex). null = no fill
align string &#x22;left&#x22; Horizontal: &#x22;left&#x22;, &#x22;center&#x22;, &#x22;right&#x22;
valign string &#x22;bottom&#x22; Vertical: &#x22;top&#x22;, &#x22;middle&#x22;, &#x22;bottom&#x22;
wrap boolean false Wrap text within cell
numFmt string &#x22;General&#x22; Number format string (see below)
borderTop / borderRight / borderBottom / borderLeft Object null &#x7B;style, width, color&#x7D; -- style is &#x22;solid&#x22;, &#x22;dashed&#x22;, or &#x22;dotted&#x22;

// 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" }
});

<button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','setCellStyle','A1',{bold:true,fontSize:14,bg:'#4a6cf7',color:'#ffffff'})">Try: Style A1 (bold, blue bg)</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','setCellStyle','A1:F1',{bold:true,bg:'#1a73e8',color:'#ffffff',align:'center',fontSize:12})">Try: Style header row A1:F1</button>

Number Formats (numFmt)

The numFmt property controls how a cell value is displayed. Common format strings:

Format Example Description
&#x22;General&#x22; 1234.5 Default — no special formatting
&#x22;0&#x22; 1235 Integer (rounded)
&#x22;0.00&#x22; 1234.50 Fixed 2 decimal places
&#x22;#,##0&#x22; 1,235 Thousands separator
&#x22;#,##0.00&#x22; 1,234.50 Thousands + 2 decimals
&#x22;&#x24;#,##0.00&#x22; &#x24;1,234.50 US currency
&#x22;0%&#x22; 12% Percentage (value × 100)
&#x22;0.00%&#x22; 12.35% Percentage with decimals
&#x22;0.00E+0&#x22; 1.23E+3 Scientific notation
&#x22;yyyy-mm-dd&#x22; 2026-04-04 ISO date
&#x22;mm/dd/yyyy&#x22; 04/04/2026 US date
&#x22;hh:MM:ss&#x22; 14:30:00 Time
&#x22;@&#x22; (text) Text — no number conversion

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

socket.fire("webapp::instance::request", "getValue", "A1",
    function (err, cell) { console.log("Value:", cell); });

<button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','getValue','A1',function(e,c){alert('A1 value: '+JSON.stringify(c))})">Try: Get A1 Value</button>


Sheet Management

// 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) {});

<button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','addSheet','Sheet 2')">Try: Add Sheet</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','renameSheet',0,'Summary')">Try: Rename First Sheet</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','activeSheet',function(e,i){alert('Active sheet idx: '+i)})">Try: Get Active Sheet</button>


Row & Column Operations

// 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) {});

<button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','insertRow',1,1)">Try: Insert Row at index 1</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','insertCol',1,1)">Try: Insert Column at index 1</button>


Selection

// 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);
    });

<button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','setSelection','A1:C5')">Try: Select A1:C5</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','getSelection',function(e,s){alert('Selection: '+JSON.stringify(s,null,2))})">Try: Get Selection</button>


Sort, Filter, Find & Replace

// 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); });

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

<button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','sort','A1:F10',[{col:0,ascending:true}])">Try: Sort A1:F10 by col A</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','filter')">Try: Toggle Filter</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','find','Total',{},function(e,m){alert('Matches: '+(m?m.length:0))})">Try: Find "Total"</button>


Clipboard

// 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");

<button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','copy','A1:B5')">Try: Copy A1:B5</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','paste','D1','all')">Try: Paste at D1</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','copyRange','A1:B5','F1')">Try: Copy A1:B5 → F1</button>


Merge, Freeze & Layout

// 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);

<button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','merge','A1:C1')">Try: Merge A1:C1</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','unmerge','A1:C1')">Try: Unmerge A1:C1</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','freeze',1,1)">Try: Freeze Row 1 + Col A</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','freeze',0,0)">Try: Unfreeze</button>


Charts

Charts render as inline SVG and can be dragged/resized after insertion. Supported chart types: &#x22;bar&#x22;, &#x22;line&#x22;, &#x22;pie&#x22;, &#x22;area&#x22;, &#x22;scatter&#x22;, &#x22;combo&#x22;.

// 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" });

<button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','insertChart','A1:F4','bar',{title:'Sample Chart'})">Try: Insert Bar Chart</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','insertChart','A1:B5','pie',{title:'Sample Pie'})">Try: Insert Pie Chart</button>


Named Ranges

Define names for cells or ranges to use them in formulas:

// 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.

// 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" }
});

Import / Export

// 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"); });

<button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','exportCSV',function(e,csv){alert('CSV:\n\n'+(csv||'empty'))})">Try: Export CSV</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','toJSON',function(e,d){alert('JSON keys: '+Object.keys(d||{}).join(', '))})">Try: Export JSON</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','exportXLSX',function(e,b){alert('XLSX blob size: '+(b&&b.size||0)+' bytes')})">Try: Export XLSX (Blob)</button>

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:

{
    "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
}
Tip: the engine recalculates all formulas when you call fromJSON, so cells with v: null, f: &#x22;...&#x22; are filled in automatically. You don't need to persist computed values.
// 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

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

<button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','zoom',1.25)">Try: Zoom 125%</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','zoom',1)">Try: Zoom 100%</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','undo')">Try: Undo</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','redo')">Try: Redo</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','print')">Try: Print</button>


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.

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

<button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','showFindDialog',true)">Try: Find & Replace Dialog</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','showFormatCellsDialog')">Try: Format Cells Dialog</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','showFunctionWizard')">Try: Function Wizard</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','showDataValidationDialog')">Try: Data Validation</button><button onclick="window._ws('ssSocket')&&window.ssSocket.fire('webapp::instance::request','showConditionalFormatDialog')">Try: Conditional Format</button>

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:

// 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" }

Listening to Editor Events

The Spreadsheet forwards internal events to the embedder via api-event::instance:&#x3C;eventName&#x3E;. The first element of the args array is the event payload.

Event Payload Description
event:ready -- Spreadsheet fully initialized and rendered
event:cell-change &#x7B;ref, oldValue, newValue, sheet&#x7D; A cell value or formula was modified
event:recalculate -- Formulas were recalculated
event:sheet-change &#x7B;index, name&#x7D; The active sheet was switched (or a sheet was added/deleted/renamed)
event:selection-change &#x7B;start: &#x7B;row, col&#x7D;, end: &#x7B;row, col&#x7D;, sheet&#x7D; 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.
// 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 =):

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")');

Formula error values

Error Meaning
#REF! Invalid cell reference
#VALUE! Wrong value type
#DIV/0! Division by zero
#NAME&#x3F; Unrecognized function or name
#N/A Value not available
#NULL! Invalid range intersection
#NUM! Invalid numeric value
#CIRC! Circular reference detected


Complete Example

<iframe id="sheet" src="https://sgapps.io/online/webapp/spreadsheet"
    width="100%" height="500" frameborder="0"></iframe>

<script src="https://sgapps.io/components/application-prototype/ApplicationPrototype.js"></script>
<script src="https://sgapps.io/components/window-socket/index.js"></script>
<script>
    var socket = new WindowSocket();
    socket.start();
    socket.on("webapp::connection::ping", function () {
        socket.fire("webapp::instance::embed-mode", true);

        // Listen for changes
        socket.on("api-event::instance:event:cell-change", function (args) {
            console.log("Cell changed:", args[0]);
        });

        // Fill a data table
        var data = [
            ["Product", "Q1", "Q2", "Q3", "Q4"],
            ["Widget A", 120, 150, 180, 200],
            ["Widget B", 80, 95, 110, 130],
            ["Widget C", 200, 220, 250, 280]
        ];
        data.forEach(function (row, r) {
            row.forEach(function (val, c) {
                var ref = String.fromCharCode(65 + c) + (r + 1);
                socket.fire("webapp::instance::request", "setValue", ref, String(val));
            });
        });

        // Add SUM formulas (no leading "=" with setFormula)
        socket.fire("webapp::instance::request", "setFormula", "F2", "SUM(B2:E2)");
        socket.fire("webapp::instance::request", "setFormula", "F3", "SUM(B3:E3)");
        socket.fire("webapp::instance::request", "setFormula", "F4", "SUM(B4:E4)");
        socket.fire("webapp::instance::request", "setValue", "F1", "Total");

        // Style header row
        ["A1","B1","C1","D1","E1","F1"].forEach(function (ref) {
            socket.fire("webapp::instance::request", "setCellStyle", ref, {
                bold: true, bg: "#4a6cf7", color: "#fff", align: "center"
            });
        });

        // Freeze the header row
        socket.fire("webapp::instance::request", "freeze", 1, 0);

        // Apply a number format to data cells
        ["B2","C2","D2","E2","F2","B3","C3","D3","E3","F3","B4","C4","D4","E4","F4"].forEach(function (ref) {
            socket.fire("webapp::instance::request", "setCellStyle", ref, { numFmt: "#,##0" });
        });
    });
</script>
sgapps.io