Commit pirate JS files

This commit is contained in:
2026-04-02 17:39:02 -06:00
parent 7b15a0eb9c
commit d287f8a443
49 changed files with 19149 additions and 0 deletions

679
js/TabularData.js Normal file
View File

@@ -0,0 +1,679 @@
/*
Copyright (c) 2023-forever Douglas Malnati. All rights reserved.
See the /faq/tos page for details.
(If this generated header is stamped on a file which is a 3rd party file or under a different license or copyright, then ignore this copyright statement and use that file's terms.)
*/
export class TabularData
{
constructor(dataTable)
{
this.dataTable = dataTable;
this.col__idx = new Map();
this.col__metaData = new Map();
this.row__metaData = new WeakMap();
this.#CacheHeaderLocations();
}
// create a new set of rows with copies of the values
// duplicate the metadata.
// metadata row keys will be new (objects), values will be copied
// metadata col keys will be copied (strings), values will be copied
Clone()
{
// prepare new objects
let dataTableNew = [];
let tdNew = new TabularData(dataTableNew);
// make new rows, with copies of data (including header)
for (let rowCur of this.dataTable)
{
let rowNew = [... rowCur];
dataTableNew.push(rowNew);
// copy any row meta data if any
if (this.row__metaData.has(rowCur))
{
tdNew.row__metaData.set(rowNew, this.row__metaData.get(rowCur));
}
}
// col meta data by big copy, keys are strings, so ok to do
// without tying to some object
tdNew.col__metaData = new Map(this.col__metaData);
// update internal data structure
tdNew.#CacheHeaderLocations();
return tdNew;
}
// will only set the col metadata if it's a real column.
// this data is destroyed if the column is destroyed.
SetColMetaData(col, metaData)
{
let idx = this.Idx(col);
if (idx != undefined)
{
this.col__metaData.set(col, metaData);
}
}
// for valid columns, return metadata, creating if needed.
// for invalid columns, undefined
GetColMetaData(col)
{
let retVal = undefined;
let idx = this.Idx(col);
if (idx != undefined)
{
if (this.col__metaData.has(col) == false)
{
this.col__metaData.set(col, {});
}
retVal = this.col__metaData.get(col);
}
return retVal;
}
// will set the row metadata if an object or idx in range,
// discard if numerically out of range.
// all row metadata survives rows being moved around.
// this data is destroyed if the row is destroyed.
SetRowMetaData(row, metaData)
{
row = this.#GetRow(row);
if (row != undefined)
{
this.row__metaData.set(row, metaData);
}
}
// will get the row metadata if an object or idx in range, creating if needed,
// undefined if numerically out of range.
GetRowMetaData(row)
{
let retVal = undefined;
row = this.#GetRow(row);
if (row != undefined)
{
if (this.row__metaData.has(row) == false)
{
this.row__metaData.set(row, {});
}
retVal = this.row__metaData.get(row);
}
return retVal;
}
GetDataTable()
{
return this.dataTable;
}
GetHeaderList()
{
let retVal = [];
if (this.dataTable.length)
{
// prevent caller from modifying column names directly
retVal = [... this.dataTable[0]];
}
return retVal;
}
GetColCount()
{
return this.GetHeaderList().length;
}
// return the number of data rows
Length()
{
let retVal = 0;
if (this.dataTable.length)
{
retVal = this.dataTable.length - 1;
}
return retVal;
}
#CacheHeaderLocations()
{
if (this.dataTable && this.dataTable.length)
{
this.col__idx = new Map();
const headerRow = this.dataTable[0];
for (let i = 0; i < headerRow.length; ++i)
{
const col = headerRow[i];
this.col__idx.set(col, i);
}
}
}
Idx(col)
{
// undefined if no present
return this.col__idx.get(col);
}
// if given a row (array) object, return that object.
// if given a numeric index, return the row in the table at that logical index.
#GetRow(row)
{
if (row == undefined || row == null) { return undefined; }
let retVal = undefined;
if (typeof row == "object")
{
retVal = row;
}
else
{
if (row + 1 < this.dataTable.length)
{
retVal = this.dataTable[row + 1];
}
}
return retVal;
}
// if given a row (array) object, return the value in the specified column.
// if given a numeric index, return the value in the specified column.
Get(row, col)
{
let retVal = undefined;
row = this.#GetRow(row);
if (row)
{
retVal = row[this.Idx(col)];
}
return retVal;
}
// if given a row (array) object, return the value in the specified column.
// if given a numeric index, return the value in the specified column.
Set(row, col, val)
{
if (typeof row == "object")
{
row[this.Idx(col)] = val;
}
else
{
this.dataTable[row + 1][this.Idx(col)] = val;
}
}
// idx of data, not including header
DeleteRowList(idxList)
{
// put in descending order so we don't need to recalculate indices after each delete
idxList.sort((a, b) => (a - b));
idxList.reverse();
for (let idx of idxList)
{
this.DeleteRow(idx);
}
}
// idx of data, not including header
DeleteRow(idx)
{
this.dataTable.splice(idx + 1, 1);
}
// create a new row, with empty values.
// row will have the same number of elements as the header.
// the row is returned to the caller and is appropriate for use with
// the Get() and Set() API.
AddRow()
{
let row = new Array(this.GetColCount());
this.dataTable.push(row);
return row;
}
RenameColumn(colOld, colNew)
{
this.dataTable[0][this.Idx(colOld)] = colNew;
this.#CacheHeaderLocations();
if (colOld != colNew)
{
this.col__metaData.set(colNew, this.col__metaData.get(colOld));
this.col__metaData.delete(colOld);
}
}
DeleteColumn(col)
{
let idx = this.Idx(col);
if (idx != undefined)
{
for (let row of this.dataTable)
{
row.splice(idx, 1);
}
}
this.#CacheHeaderLocations();
this.col__metaData.delete(col);
}
DeleteColumnList(colList)
{
for (let col of colList)
{
this.DeleteColumn(col);
}
}
DeleteEmptyColumns()
{
let colList = [];
for (let i = 0; i < this.dataTable[0].length; ++i)
{
let col = this.dataTable[0][i];
let allBlank = true;
for (let j = 1; j < this.dataTable.length; ++j)
{
let val = this.dataTable[j][i];
if (val != "" && val != null)
{
allBlank = false;
}
}
if (allBlank)
{
colList.push(col);
}
}
this.DeleteColumnList(colList);
}
MakeDataTableFromRowList(rowList)
{
let dataTable = [[... this.dataTable[0]]];
for (let row of rowList)
{
dataTable.push([... row]);
}
return dataTable;
}
MakeDataTableFromRow(row)
{
return this.MakeDataTableFromRowList([row]);
}
Extract(headerList)
{
const headerRow = this.dataTable[0];
let idxList = [];
for (const header of headerList)
{
let idx = headerRow.indexOf(header);
idxList.push(idx);
}
// build new data table
let dataTableNew = [];
for (const row of this.dataTable)
{
let rowNew = [];
for (const idx of idxList)
{
rowNew.push(row[idx]);
}
dataTableNew.push(rowNew);
}
return dataTableNew;
}
ExtractDataOnly(headerList)
{
let dataTable = this.Extract(headerList);
return dataTable.slice(1);
}
DeepCopy()
{
return this.Extract(this.dataTable[0]);
}
ForEach(fnEachRow, reverseOrder)
{
if (reverseOrder == undefined) { reverseOrder = false; }
if (reverseOrder == false)
{
for (let i = 1; i < this.dataTable.length; ++i)
{
let row = this.dataTable[i];
fnEachRow(row, i - 1);
}
}
else
{
for (let i = this.dataTable.length - 1; i >= 1; --i)
{
let row = this.dataTable[i];
fnEachRow(row, i - 1);
}
}
}
AppendGeneratedColumns(colHeaderList, fnEachRow, reverseOrder)
{
if (reverseOrder == undefined) { reverseOrder = false; }
this.dataTable[0].push(... colHeaderList);
if (reverseOrder == false)
{
for (let i = 1; i < this.dataTable.length; ++i)
{
let row = this.dataTable[i];
row.push(... fnEachRow(row));
}
}
else
{
for (let i = this.dataTable.length - 1; i >= 1; --i)
{
let row = this.dataTable[i];
row.push(... fnEachRow(row));
}
}
this.#CacheHeaderLocations();
}
PrependGeneratedColumns(colHeaderList, fnEachRow, reverseOrder)
{
if (reverseOrder == undefined) { reverseOrder = false; }
this.dataTable[0].unshift(... colHeaderList);
if (reverseOrder == false)
{
for (let i = 1; i < this.dataTable.length; ++i)
{
let row = this.dataTable[i];
row.unshift(... fnEachRow(row));
}
}
else
{
for (let i = this.dataTable.length - 1; i >= 1; --i)
{
let row = this.dataTable[i];
row.unshift(... fnEachRow(row));
}
}
this.#CacheHeaderLocations();
}
GenerateModifiedColumn(colHeaderList, fnEachRow, reverseOrder)
{
if (reverseOrder == undefined) { reverseOrder = false; }
let col = colHeaderList[0];
let colIdx = this.Idx(col);
if (colIdx == undefined)
{
return;
}
let rowList = [];
if (reverseOrder == false)
{
// build new values
for (let i = 1; i < this.dataTable.length; ++i)
{
let row = this.dataTable[i];
let rowNew = fnEachRow(row, i - 1);
rowList.push(rowNew[0]);
}
// update table
for (let i = 1; i < this.dataTable.length; ++i)
{
let row = this.dataTable[i];
row[colIdx] = rowList[i - 1];
}
}
else
{
for (let i = this.dataTable.length - 1; i >= 1; --i)
{
let row = this.dataTable[i];
let rowNew = fnEachRow(row, i - 1);
// row[this.Idx(col)] = rowNew[0];
rowList.push(rowNew[0]);
}
for (let i = this.dataTable.length - 1; i >= 1; --i)
{
let row = this.dataTable[i];
row[colIdx] = rowList[rowList.length - i];
}
}
}
// rearranges columns, leaving row objects intact (in-place operation).
// specified columns which don't exist will start to exist, and undefined values
// will be present in the cells.
SetColumnOrder(colList)
{
let colListNewSet = new Set(colList);
let colListOldSet = new Set(this.GetHeaderList());
// figure out which old columns are no longer present
let colListDelSet = colListOldSet.difference(colListNewSet);
// modify each row, in place, to have only the column values
for (let i = 1; i < this.dataTable.length; ++i)
{
let row = this.#GetRow(i - 1);
// build a new row of values.
// undefined for any invalid columns.
let rowNew = [];
for (let col of colList)
{
rowNew.push(this.Get(row, col));
}
// wipe out contents of existing row, but keep row object
row.length = 0;
// add new values into the row, in order
row.push(... rowNew);
}
// update headers in place
this.dataTable[0].length = 0;
this.dataTable[0].push(... colList);
// delete metadata from destroyed columns
for (let col of colList)
{
this.col__metaData.delete(col);
}
// update column index
this.#CacheHeaderLocations();
}
// Will put specified columns in the front, in this order, if they exist.
// Columns not specified will retain their order.
PrioritizeColumnOrder(colHeaderList)
{
// get reference of existing columns
let remainingColSet = new Set(this.GetHeaderList());
// get list of existing priority columns
let priorityColSet = new Set();
for (let col of colHeaderList)
{
if (remainingColSet.has(col))
{
remainingColSet.delete(col);
priorityColSet.add(col);
}
}
// now we have two lists of columns:
// - priorityColSet - existing columns in the order specified
// - remainingColSet - every other existing column other than priority column set,
// in original order
// now arrange columns
let colHeaderListNew = [... priorityColSet.values(), ... remainingColSet.values()];
this.SetColumnOrder(colHeaderListNew);
}
FillUp(col, defaultVal)
{
defaultVal = defaultVal | "";
let idx = this.Idx(col);
for (let i = this.dataTable.length - 1; i >= 1; --i)
{
const row = this.dataTable[i];
let val = row[idx];
if (val == null)
{
if (i == this.dataTable.length - 1)
{
val = defaultVal;
}
else
{
val = this.dataTable[i + 1][idx];
}
row[idx] = val;
}
}
}
FillDown(col, defaultVal, reverseOrder)
{
defaultVal = defaultVal | "";
let idx = this.Idx(col);
for (let i = 1; i < this.dataTable.length; ++i)
{
const row = this.dataTable[i];
let val = row[idx];
if (val == null)
{
if (i == 1)
{
val = defaultVal;
}
else
{
val = this.dataTable[i - 1][idx];
}
row[idx] = val;
}
}
}
GetDataForCol(col)
{
let valList = [];
if (this.dataTable && this.dataTable.length && this.Idx(col) != undefined)
{
for (let i = 0; i < this.Length(); ++i)
{
valList.push(this.Get(i, col));
}
}
return valList;
}
Reverse()
{
// reverse the whole things
this.dataTable.reverse();
// swap the header (now at the bottom) to the top
this.dataTable.unshift(this.dataTable.pop());
}
}