You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I have been trying to create a more comprehensive example that includes one-to-one (easy), one-to-many, and many-to-one relationships between signals.
For this example I came up with the following two data tables, where new rows can be added and existing rows can be removed at any time. The empty fields are all computed, the rest of the fields are a signal.
And this is the code I came up with. It just feels complicated or too verbose and not in the style of the simplicity of this library. My biggest issue is with how to best track a list of dicts for added, removed and changed items? I'm certain there is a better way than what I did.
Maybe someone has a better way to solve these dependencies, so the fields are computed when a signal changes or a row is added or removed. Thank you!
import{root,signal,computed,effect,tick,WriteSignal,ReadSignal,Effect,peek}from'./maverick-js';typeCustomDict<KTextendsstring|number|symbol,VT>={[keyinKT]: VT;};interfaceIRowBase{id: WriteSignal<number>;parentList: WriteSignal<any>;}classBaseRows<RTextendsIRowBase>{protected_rows: Array<RT>=[];protected_rowIdxById: CustomDict<number,number>={};protected_rowIdEffectsById: CustomDict<number,Effect>={};publicrowCount: WriteSignal<number>=signal(this._rows.length);constructor(){}addRow(row: RT){letrowId=row.id();// Only allow to add the same row once.if(this._rowIdxById[rowId]>=0){return;}this._rows.push(row);this._rowIdxById[rowId]=this._rows.length-1;this._rowIdEffectsById[rowId]=effect(()=>{// TODO: Is there a way to access the previous value without resorting to keeping it in the outer scope?// Handle an "id" change.constnewRowId=row.id();if(rowId!==newRowId){this._rowIdxById[newRowId]=this._rowIdxById[rowId];this._rowIdEffectsById[newRowId]=this._rowIdEffectsById[rowId];deletethis._rowIdxById[rowId];deletethis._rowIdEffectsById[rowId];rowId=newRowId;}},{id: '_rowIdEffect'});this.rowCount.set(this._rows.length);row.parentList.set(this);}removeRowByRow(row: RT): RT{constidx=this._rows.findIndex((value)=>peek(value.id)==peek(row.id));returnthis.removeRowByIdx(idx);}removeRowByIdx(idx: number): RT{letrow=undefined,rowId;if(idx>=0&&idx<this._rows.length){row=this._rows[idx];rowId=row.id();this._rows.splice(idx,1);deletethis._rowIdxById[rowId];// Stop the effect and remove the entry.this._rowIdEffectsById[rowId]();deletethis._rowIdEffectsById[rowId];this.rowCount.set(this._rows.length);row.parentList.set(undefined);}returnrow;}findRowById(id: number): RT{constidx=this._rowIdxById[id];if(idx>=0&&idx<this._rows.length){returnthis._rows[idx];}returnundefined;}iterateRows(callbackFn: (value: RT,index: number)=>void,thisArg?: any){this._rows.forEach(callbackFn,thisArg);}}classBuildingRowimplementsIRowBase{private__roomRows: Array<RoomRow>=[];id: WriteSignal<number>;room_count: WriteSignal<number>;total_floor_area: ReadSignal<number>;parentList: WriteSignal<BuildingRows>=signal(undefined);constructor(dataRow: CustomDict<string,any>,publicdataRoot: DataRoot){this.id=signal(dataRow['id']);this.room_count=signal(0);this.total_floor_area=computed<number>(()=>{this.room_count();returnthis.__roomRows.reduce((previousValue,currentValue,currentIndex,array)=>previousValue+currentValue.area(),0);});// Check if there are any rooms that belong to the current building.constroomRowsSource=this.dataRoot.roomRows;roomRowsSource.iterateRows((roomRow)=>{if(peek(roomRow.building_id)==peek(this.id)){this.__roomRows.push(roomRow);}this.room_count.set(this.__roomRows.length);});}attachRoomRow(roomRow: RoomRow){// Do not allow to add the same row twice.constroomRowId=peek(roomRow.id);constidx=this.__roomRows.findIndex(v=>peek(v.id)==roomRowId);if(idx==-1){this.__roomRows.push(roomRow);this.room_count.set(this.__roomRows.length);}}detachRoomRow(roomRow: RoomRow){constroomRowId=peek(roomRow.id);constidx=this.__roomRows.findIndex(v=>peek(v.id)==roomRowId);if(idx>-1){this.__roomRows.splice(idx,1);this.room_count.set(this.__roomRows.length);}}}classBuildingRowsextendsBaseRows<BuildingRow>{}classRoomRowimplementsIRowBase{id: WriteSignal<number>;building_id: WriteSignal<number>;length: WriteSignal<number>;width: WriteSignal<number>;area: ReadSignal<number>;building_area_percent: ReadSignal<number>;parentList: WriteSignal<BuildingRows>=signal(undefined);// Helper computes...buildingRow: ReadSignal<BuildingRow>;private_prevBuildingRow: BuildingRow;constructor(dataRow: CustomDict<string,any>,publicdataRoot: DataRoot){constthis_=this;this.id=signal(dataRow['id']);this.building_id=signal(dataRow['building_id']);this.length=signal(dataRow['length']);this.width=signal(dataRow['width']);this.area=computed<number>(()=>{returnthis_.length()*this_.width();});this.building_area_percent=computed<number>(()=>{constbldgRow=this_.buildingRow();if(bldgRow&&bldgRow.total_floor_area()>0){returnthis.area()*100/bldgRow.total_floor_area();}return0;});// Helper computes...this.buildingRow=computed<BuildingRow>(()=>{constbuildingId=this.building_id();letresultingBuildingRow=null;if(!isNaN(buildingId)){constbuildingRow=this.dataRoot.buildingRows.findRowById(buildingId);// buildingRow.parentList() returns "undefined" as soon as the building row has been removed from the rows.// Calling parentList() allows us to keep track of that.// Also track the id of the building row, since that id might change as well.if(buildingRow&&buildingRow.parentList()!=undefined&&buildingRow.id()==buildingId){buildingRow.attachRoomRow(this);resultingBuildingRow=buildingRow;}else{// Introduce this dependency so the computed function is re-run when the buildingRow might have been// added to the list.// TODO: This is not very nice.. is it possible to solve this another way?this.dataRoot.buildingRows.rowCount();}}if(this._prevBuildingRow){this._prevBuildingRow.detachRoomRow(this);}this._prevBuildingRow=resultingBuildingRow;returnresultingBuildingRow;});effect(()=>{constparentList=this.parentList();if(parentList){this.buildingRow();}elseif(parentList==undefined&&this.buildingRow()){this.buildingRow().detachRoomRow(this);}});}}classRoomRowsextendsBaseRows<RoomRow>{}classDataRoot{buildingRows: BuildingRows=newBuildingRows();roomRows: RoomRows=newRoomRows();constructor(){}}root(()=>{constdataRoot=newDataRoot();constroom1=newRoomRow({id: 101,name: 'R1',building_id: 1,length: 5,width: 4},dataRoot);constroom2=newRoomRow({id: 102,name: 'R2',building_id: 1,length: 2,width: 2},dataRoot);constroom3=newRoomRow({id: 104,name: 'R3',building_id: 1,length: 3,width: 3},dataRoot);dataRoot.roomRows.addRow(room1);dataRoot.roomRows.addRow(room2);console.log(room2.building_area_percent());constbldg1=newBuildingRow({id: 1,name: 'Building 1'},dataRoot);dataRoot.buildingRows.addRow(bldg1);console.log(room1.area());console.log(bldg1.room_count());console.log(room2.building_area_percent());dataRoot.roomRows.addRow(room3);tick();console.log(room2.building_area_percent());dataRoot.roomRows.removeRowByRow(room3);tick();console.log(room2.building_area_percent());});
The text was updated successfully, but these errors were encountered:
I have been trying to create a more comprehensive example that includes one-to-one (easy), one-to-many, and many-to-one relationships between signals.
For this example I came up with the following two data tables, where new rows can be added and existing rows can be removed at any time. The empty fields are all
computed
, the rest of the fields are asignal
.And this is the code I came up with. It just feels complicated or too verbose and not in the style of the simplicity of this library. My biggest issue is with how to best track a list of dicts for added, removed and changed items? I'm certain there is a better way than what I did.
Maybe someone has a better way to solve these dependencies, so the fields are computed when a signal changes or a row is added or removed. Thank you!
The text was updated successfully, but these errors were encountered: