Tables (ListObject) and Comments — creation, totals row, structured references, memos

level: intermediate / verified: 2026-10-01 / tabs: Node.js・Python・Java・WebAssembly / 日本語

What you want to do

Turn a data range into an Excel "table" (ListObject) and work with its name, column names, style and totals row. Use "structured references" to refer to table columns from formulas, and add, read and remove cell comments (modern threaded comments) and memos (legacy notes).

Classes used

Get tables from ws.getListObjects() and comments from ws.getComments().

ListObjects (collection of tables)

MethodDescription
addList(name, A1C1, useFirstRowAsHeader, insertTotals)Creates a table from an existing cell range and returns the Table.
addListFromRange(name, topA1, copyFromA1C1, useFirstRowAsHeader, insertTotals)Creates a table by copying the data to topA1. copyFromA1C1 may point to another sheet, like Sheet2!A1:C1.
getList(name)Gets a table. Throws a table is not found exception if it does not exist.
removeList(name, deleteData?)Removes a table. deleteData defaults to true (the data is deleted too). Pass false to keep it.

Table (a single table)

GroupMethods
RangesgetAllRange() / getHeaderRowRange() / getDataBodyRange() / getTotalRowRange() (all return a Range; getTotalRowRange() throws totalRow is not set when there is no totals row)
NamegetName() / changeTableName(name)
Column namesgetColumnName() / setColumnName([...]) (also changes what the header cells display)
StylegetTableStyleName() / setBuiltinStyleName(enums.XlDefaultTableStyle.Xxx, clearFormat) / setCustomTableStyleName(name, clearFormat)
Totals rowisShowTotals() / setShowTotals(bool) / getTotalRowFunction(col) / setTotalRowFunction(col, enums.XlTotalsCalculation.Xxx) / setCustomRowFunction(col, formula, isArray?) / getTotalRowLabel(col) / setTotalRowLabel(col, text)
Filter / displaygetAutoFilter() / removeAutoFilter() / isShowAutoFilter() / isShowHeaders() / setShowHeaders(bool) / isShowTableStyleRowStripes() and more

Comments (cell comments)

Excel has two comment systems: modern comments (threads, replies, done flag) and legacy memos (callout notes).

MethodSystemDescription
setComment(A1, commentObject)modernAdds one comment. On a cell that already has comments this appends (automatically as a reply).
setCommentThread(A1, [commentObject...])modernSets a whole thread (overwrites). The first entry is the body, the rest are replies.
getComment(A1)modernReturns Array<dto.CommentObject>. Throws comment not found if there is none.
removeComment(A1)modernRemoves modern comments.
setMemo(A1, author, text, fontObject, visible?, rowColumnsNum?, colColumnsNum?)legacySets a callout memo. fontObject can be new dto.FontObject().
getMemoText(A1) / getMemoAuthor(A1)legacyGets the memo text / author. Throws memo not found if there is none.
removeMemo(A1)legacyRemoves a memo.

dto.CommentObject has setAuthor / setContent / setParentId (parent ID of a reply) / setDone (done flag) plus the corresponding getters. Once set, an ID is automatically assigned as a UUID in {...} form.

Totals functions enums.XlTotalsCalculation

MemberValueMeaning
TotalsCalculationNone0No totals
TotalsCalculationSum1Sum
TotalsCalculationAverage2Average
TotalsCalculationCount3Count
TotalsCalculationCountNums4Count of numbers
TotalsCalculationMin / TotalsCalculationMax5 / 6Min / Max
TotalsCalculationStdDev / TotalsCalculationVar7 / 8StdDev / Var
TotalsCalculationCustom9Custom formula (set with setCustomRowFunction)

Code

Output:

// tables-comments.js — table (ListObject) creation, totals row, structured references, cell comments
const { nodeosbxl, enums, dto } = require("nodeosbxl");

const app = new nodeosbxl.App();
const wb = app.createWorkBook("tables-comments.xlsx");
const ws = wb.openWorkSheet("Sheet1");

// ---------- fill in the data (1 header row + 3 data rows) ----------
const rows = [
    ["Product", "Unit Price", "Qty"],
    ["Apple", 120, 3],
    ["Orange", 80, 5],
    ["Grape", 300, 2],
];
const values = [];
rows.forEach((row, ri) => row.forEach((cell, ci) => {
    const a1 = app.convertFromRowColNumber(ri + 1, ci + 1);
    const o = new dto.InputValueObject();
    if (typeof cell === "number") o.setNumberValue(a1, cell);
    else o.setStringValue(a1, cell);
    values.push(o);
}));
ws.setValueArray(values);

// ---------- 1. create the table ----------
const lo = ws.getListObjects();
const table = lo.addList("Sales", "A1:C4", true, true); // useFirstRowAsHeader, insertTotals
console.log("[1] Table created");
console.log(`    name       = ${table.getName()}`);
console.log(`    columns    = ${JSON.stringify(table.getColumnName())}`);
console.log(`    style      = ${table.getTableStyleName()}`);
console.log(`    full range = ${table.getAllRange().getAddress()}`);
console.log(`    header     = ${table.getHeaderRowRange().getAddress()}`);
console.log(`    data body  = ${table.getDataBodyRange().getAddress()}`);
console.log(`    totals row = ${table.getTotalRowRange().getAddress()}`);

// ---------- 2. totals row ----------
table.setTotalRowLabel(1, "Total");
table.setTotalRowFunction(2, enums.XlTotalsCalculation.TotalsCalculationSum);
table.setTotalRowFunction(3, enums.XlTotalsCalculation.TotalsCalculationSum);
console.log("[2] Totals row");
console.log(`    label (col 1) = ${JSON.stringify(table.getTotalRowLabel(1))}`);
console.log(`    formula       = ${JSON.stringify(ws.getRange("B5:C5").getFormula())}`);
console.log(`    value         = ${JSON.stringify(ws.getRange("B5:C5").getValue(true))}`);

// ---------- 3. change the table style ----------
table.setBuiltinStyleName(enums.XlDefaultTableStyle.TableStyleMedium9, true);
console.log(`[3] style after change = ${table.getTableStyleName()}`);

// ---------- 4. structured reference (formula referring to a table column) ----------
ws.getRange("E1:E1").setFormula("SUM(Sales[Unit Price])"); // no leading "="
console.log("[4] Structured reference");
console.log(`    E1 formula = ${JSON.stringify(ws.getRange("E1:E1").getFormula())}`);
console.log(`    E1 value   = ${JSON.stringify(ws.getRange("E1:E1").getValue(true))}`);

// ---------- 5. comments (modern) and memos (legacy) ----------
const cm = ws.getComments();
const mkComment = (author, content) => {
    const c = new dto.CommentObject();
    c.setAuthor(author);
    c.setContent(content);
    return c;
};

// modern comment (thread): body + reply = 2 entries -> B2 (kept in the file after save)
cm.setCommentThread("B2", [mkComment("alice", "Please check this number"), mkComment("bob", "Checked")]);
// single modern comment -> D2 (used to demonstrate removal)
cm.setComment("D2", mkComment("dave", "Comment to delete later"));
// legacy memo (callout) -> A2
cm.setMemo("A2", "Carol", "Top-priority product", new dto.FontObject());

const thread = cm.getComment("B2");
console.log("[5] Comments");
console.log(`    B2 thread count = ${thread.length}`);
thread.forEach((c, i) => console.log(`      [${i}] ${c.getAuthor()}: ${c.getContent()}${c.getParentId() ? " (reply)" : ""}`));
console.log(`    D2 comment count = ${cm.getComment("D2").length}`);
console.log(`    A2 memo = ${JSON.stringify(cm.getMemoText("A2"))} / author ${JSON.stringify(cm.getMemoAuthor("A2"))}`);

// removal: removeComment for modern, removeMemo for legacy (reading after removal throws "not found")
cm.removeComment("D2");
cm.removeMemo("A2");
const tryRead = (label, fn) => {
    try { fn(); console.log(`    after removal ${label}: no exception`); }
    catch (e) { console.log(`    after removal ${label}: exception ${e.message}`); }
};
tryRead("D2 comment", () => cm.getComment("D2"));
tryRead("A2 memo", () => cm.getMemoText("A2"));
console.log(`    B2 is kept, so ${cm.getComment("B2").length} comments remain after save`);

const total = ws.getRange("B5:B5").getValue(true)["B5"];
const e1 = ws.getRange("E1:E1").getValue(true)["E1"];
console.log(`[Check] table=${table.getName()} total(Unit Price)=${total} structuredRef(E1)=${e1}`);

wb.save();
wb.close();
[1] Table created
    name       = Sales
    columns    = ["Product","Unit Price","Qty"]
    style      = TableStyleMedium2
    full range = A1:C5
    header     = A1:C1
    data body  = A2:C4
    totals row = A5:C5
[2] Totals row
    label (col 1) = "Total"
    formula       = {"B5":"SUBTOTAL(109,Sales[Unit Price])","C5":"SUBTOTAL(109,Sales[Qty])"}
    value         = {"B5":"500","C5":"10"}
[3] style after change = TableStyleMedium9
[4] Structured reference
    E1 formula = {"E1":"SUM(Sales[Unit Price])"}
    E1 value   = {"E1":"500"}
[5] Comments
    B2 thread count = 2
      [0] alice: Please check this number
      [1] bob: Checked (reply)
    D2 comment count = 1
    A2 memo = "Top-priority product" / author "Carol"
    after removal D2 comment: exception comment not found
    after removal A2 memo: exception memo not found
    B2 is kept, so 2 comments remain after save
[Check] table=Sales total(Unit Price)=500 structuredRef(E1)=500

Notes

Caveats

See also