Skip to content

Instantly share code, notes, and snippets.

@wilsoncook
Last active May 9, 2018 09:42
Show Gist options
  • Save wilsoncook/0d5e2bb6f3d778640c3c97d807a043e0 to your computer and use it in GitHub Desktop.
Save wilsoncook/0d5e2bb6f3d778640c3c97d807a043e0 to your computer and use it in GitHub Desktop.
Recursive a array, support addson operations: filter, map, restore
/**
* Recursively iterate table data rows, if iterator() returns false, then will stop iterating
* @param rows Data list which may contain children
* @param iterator Iterator, return false to stop iterating
* @param childrenField Specify the children field's key
* @param _parent [Private] The parent of the rows, null by default, used for iterator() ONLY
* @return If iterated all items, this method will return true
*/
function recurseRows<T>(
rows: T[],
iterator: (row: T, index: number, rows: T[], parent: T) => boolean | any,
childrenField = 'children',
_parent: T = null
) {
if (!rows) { return false; }
for (let i = 0, len = rows.length; i < len; i++) {
const row = rows[i];
if (iterator(row, i, rows, _parent) === false) { return false; }
if (row[childrenField] && recurseRows(row[childrenField], iterator, childrenField, row) === false) {
return false;
}
}
return true;
}
/**
* Returns a new array which only contains the row that filtered (Will keep the tree structure)
* [NOTE] All tree array(children) will be modified, ONLY the row object will be kept as original
* @param rows
* @param filter Return `true` to keep this row
* @param options.childrenField
* @param options.backupChildren The backup field for children, stored in row, if not specified, will not do backup
*/
function recurseFilterRows<T>(rows: T[], filter: (row: T) => boolean, options?: RecurseFilterRowsOptions) {
options = Object.assign({
childrenField: 'children',
backupChildren: null
}, options);
if (!rows) { return rows; }
const copyRows: T[] = [], { childrenField, backupChildren } = options;
for (let i = 0, len = rows.length; i < len; i++) {
// const row = Object.assign({}, rows[i]);
const row = rows[i];
if (backupChildren) { row[backupChildren] = row[childrenField]; } // Backup
let filterHasChildren = false;
if (row[childrenField]) {
row[childrenField] = this.recurseFilterRows(row[childrenField], filter, options);
filterHasChildren = row[childrenField].length > 0;
}
if (filterHasChildren || filter(row)) {
copyRows.push(row);
}
}
return copyRows;
}
// Restore children of the rows that being backup by recurseFilterRows()
function recurseRestoreChildren<T>(rows: T[], backupField: string, childrenField = 'children') {
this.recurseRows(rows, (row) => {
if (typeof row[backupField] !== 'undefined') {
row[childrenField] = row[backupField];
row[backupField] = undefined;
}
});
return rows;
}
/**
* Recursively map table data rows
* @param rows Data list which may contain children
* @param callback Map iterator, MUST return an object, [NOTE] the "obj[childrenField]" will be overriden
* @param options.childrenField Specify the children field's key, this field MUST be an array
* @param options.toChildrenField Use new field key for children, if null, will use options.childrenField by default
*/
function recurseMap<T>(rows: T[], callback: (row: T, index: number, rows: T[]) => any, options?: RecurseMapOptions) {
options = Object.assign({
childrenField: 'children',
toChildrenField: null,
holdEmptyChildren: false,
}, options);
if (!options.toChildrenField) { options.toChildrenField = options.childrenField; }
return rows.map((row, index, arr) => {
const resultRow = callback(row, index, arr);
if (Array.isArray(row[options.childrenField])) {
resultRow[options.toChildrenField] = this.recurseMap(row[options.childrenField], callback, options);
} else if (options.holdEmptyChildren) {
resultRow[options.toChildrenField] = [];
}
return resultRow;
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment