After figuring out the solution myself.
It is possible to create a class that shares the current row index between two proxies:
interface IProxyHandler
{
get(obj, prop, receiver);
}
declare class Proxy
{
public constructor(obj, handler_callback: IProxyHandler);
}
interface SomeTable
{
col1: string;
col2: number;
col3: Date;
}
export class table<T>
{
public obj: T[];
//public columns: map<string, number>;
public columns: { [key: string]: number };
protected i: number;
protected accessor: Proxy;
//get bar(): boolean
//{
// return null; // this._bar;
//}
//set bar(theBar: boolean)
//{
// //this._bar = theBar;
//}
public row(index:number): T
{
this.i = index;
return <T><any>this.accessor;
}
public rows :T[];
constructor(rows: any[][], columnNames:string[])
{
this.obj = <any>rows;
this.i = 0;
// this.columns = columnNames;
this.columns = {}; // "associative array" or Object
for (let i = 0; i < columnNames.length; ++i)
{
this.columns[columnNames[i]] = i;
}
let handler: IProxyHandler = {
get: function (obj, prop, receiver)
{
return this.obj[this.i][this.columns[prop]];
}
};
handler.get = handler.get.bind(this);
this.row = this.row.bind(this);
this.accessor = new Proxy(this.obj, handler);
let handler2: IProxyHandler = {
get: function (obj, prop, receiver)
{
return this.row(prop);
}
};
handler2.get = handler2.get.bind(this);
this.rows = <any> new Proxy(this.obj, handler2);
}
}
// https://caniuse.com/#feat=proxy
// Sorry, your browser is no longer supported.
// If you want this program to support IE11, develop a proxy-polyfill for IE11.
// Hint from Babel-docs: ES2015-Proxies requires support on the engine level;
// it is thus not possible to polyfill Proxy in ES5.
export function testTable()
{
let columns = ["col1", "col2", "col3"];
let rows = [
["row 1 col 1", "row 1 col 2", "row 1 col 3"]
, ["row 2 col 1", "row 2 col 2", "row 2 col 3"]
, ["row 3 col 1", "row 3 col 2", "row 3 col 3"]
, ["row 4 col 1", "row 4 col 2", "row 4 col 3"]
, ["row 5 col 1", "row 5 col 2", "row 5 col 3"]
];
let x = new table<SomeTable>(rows, columns);
console.log(x.rows[0].col1);
// console.log(x.row(1).col1);
// console.log(x.obj[0][0]);
}
PS:
If I were to use a function, I'd need to do this:
public rowz(index: number, colName: string): any
{
this.i = index;
return this.obj[index][this.columns[colName]];
}
or this
public rowz(index: number): any
{
this.i = index;
return function (colName)
{
this.obj[index][this.columns[colName]];
};
}
This has the disadvanages that I would lose compile-time type-safety, both with the columnName as well as with the return type. Or I would need to copy the entiry row into an object, which duplicates data/memory-consumption.
Another option, which is compatible with ES5/IE11, is using object-properties:
interface ITestTable1
{
col1:number;
col2:number;
}
interface ITestTable2
{
a:number;
b:number;
c:number;
}
export class TableWrapper<T>
{
public rows:any[][];
protected m_accessor:object;
protected m_i:number;
protected m_columnMap: { [columnName: string]: number; };
protected m_columns: string[];
protected m_columnLength:number;
public get rowCount(): number
{
return this.rows.length;
}
public get columnCount(): number
{
return this.m_columns.length;
}
get columns(): string[]
{
return this.m_columns;
}
protected setColumns(cols: string[])
{
this.m_columnLength = cols.length;
this.m_columnMap = null;
this.m_columnMap = {};
for (let i = 0; i < this.m_columnLength; ++i)
{
this.m_columnMap[cols[i]] = i;
}
this.m_columns = cols;
} // End Sub setColumns
public row(i:number):T
{
this.m_i = i;
return <T><any>this.m_accessor;
}
public getIndex(name:string):number
{
return this.m_columnMap[name];
}
public addRow(dat:any[])
{
this.rows.push(dat);
return this;
}
public removeRow(i:number)
{
this.rows.splice(i, 1);
return this;
}
constructor(columns: string[], data: Array<any[]>, ignoreCase?:boolean)
{
if (ignoreCase == null)
ignoreCase = true;
for (let i = 0; i< columns.length; ++i)
{
columns[i] = columns[i].toLowerCase();
} // Next i
let that = this;
this.getIndex.bind(this);
this.setColumns.bind(this);
this.row.bind(this);
this.addRow.bind(this);
this.removeRow.bind(this);
this.rows = data;
this.setColumns(columns);
this.m_accessor = { }; // Creates a new object
for (let i = 0; i < columns.length; ++i)
{
let propName = columns[i];
Object.defineProperty(this.m_accessor, propName, {
// Using shorthand method names (ES2015 feature).
// get() { return bValue;},
// set(value) { bValue = value;},
// This is equivalent to:
// get: function() { return bValue; },
// set: function(value) { bValue = value; },
// And could be written as (getter = getter.bind(this))
// get: getter,
// set: setter,
get: function ()
{
let currentRow = <any> that.rows[that.m_i];
return currentRow == null ? currentRow : currentRow[i];
},
set: function(value:any)
{
let currentRow = <any> that.rows[that.m_i];
if (currentRow!= null )
currentRow[i] = value;
},
enumerable: true,
configurable: true
});
} // Next i
}
}
let tab: TableWrapper<ITestTable1> = new TableWrapper<ITestTable1>(["col1","col2"], [[1,2], [3,4]]);
// tab.columns=["col1","col2", "col3"];
let hi :TableWrapper<ITestTable2>= new TableWrapper<ITestTable2>(["a","b","c"], [[1,2,3],[4,5,6] ]);
console.log(tab.row(0).col1);
console.log(hi.row(0).a);
console.log(hi.row(1).b);
console.log(hi.row(0).c);
hi.row(0).a = 123;
for (let i = 0; i< hi.rowCount; ++i)
{
for (let j=0; j < hi.columnCount; ++j)
{
console.log(hi.rows[i][j]);
console.log(hi.row(i).a);
console.log(hi.row(i).b);
console.log(hi.row(i).c);
console.log((<any>hi.row(i))[hi.columns[j]]);
console.log((<any>hi.row(i))[hi.columns[j]]);
console.log((<any>hi.row(i))[hi.columns[j]]);
}
}