Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapter.remove API improvements #290

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ Below is the list of invocable methods of the Adapter API with description and l
|[clip](https://dhilt.github.io/ngx-ui-scroll/#adapter#clip)|(options: {<br>&nbsp;&nbsp;forwardOnly?:&nbsp;boolean,<br>&nbsp;&nbsp;backwardOnly?:&nbsp;boolean<br>})|Removes out-of-viewport items on demand. The direction in which invisible items should be clipped can be specified by passing an options object. If no options is passed (or both properties are set to _true_), clipping will occur in both directions. |
|[append](https://dhilt.github.io/ngx-ui-scroll/#adapter#append-prepend)|(options: {<br>&nbsp;&nbsp;items:&nbsp;MyItem[],<br>&nbsp;&nbsp;eof?:&nbsp;boolean<br>&nbsp;&nbsp;decrease?:&nbsp;boolean<br>}) <br><br> (items:&nbsp;MyItem&nbsp;&vert;&nbsp;MyItem[], eof?:&nbsp;boolean) &#42;<br><sub>&#42; old signature, deprecated</sub>|Adds items to the end of the Scroller's dataset. Indexes increase by default. If _decrease_ is set to true, indexes are decremented. If _eof_ parameter is not set, items will be added and rendered immediately, they will be placed right after the last item in the Scroller's buffer. If _eof_ is set to true, items will be added and rendered only if the end of the dataset is reached; otherwise, these items will be virtualized (see also [bof/eof](https://dhilt.github.io/ngx-ui-scroll/#adapter#bof-eof)). |
|[prepend](https://dhilt.github.io/ngx-ui-scroll/#adapter#append-prepend)|(options: {<br>&nbsp;&nbsp;items:&nbsp;MyItem[],<br>&nbsp;&nbsp;bof?:&nbsp;boolean<br>&nbsp;&nbsp;increase?:&nbsp;boolean<br>}) <br><br> (items:&nbsp;MyItem&nbsp;&vert;&nbsp;MyItem[], bof?:&nbsp;boolean) &#42;<br><sub>&#42; old signature, deprecated</sub>|Adds items to the beginning of the Scroller's dataset. Indexes decrease by default. If _increase_ is set to true, indexes are incremented. If _bof_ parameter is not set, items will be added and rendered immediately, they will be placed right before the first item in the Scroller's buffer. If _bof_ is set to true, items will be added and rendered only if the beginning of the dataset is reached; otherwise, these items will be virtualized (see also [bof/eof](https://dhilt.github.io/ngx-ui-scroll/#adapter#bof-eof)). |
|[remove](https://dhilt.github.io/ngx-ui-scroll/#adapter#remove)|(options: {<br>&nbsp;&nbsp;predicate?:&nbsp;ItemsPredicate,<br>&nbsp;&nbsp;indexes?:&nbsp;number[],<br>&nbsp;&nbsp;increase?:&nbsp;boolean<br>}) <br><br>(func:&nbsp;ItemsPredicate) &#42;<br><sub>&#42; old signature, deprecated</sub><br><br> type&nbsp;ItemsPredicate&nbsp;=<br>&nbsp;&nbsp;(item: ItemAdapter)&nbsp;=><br>&nbsp;&nbsp;&nbsp;&nbsp;boolean|Removes items form buffer and/or virtually. Predicate is a function to be applied to every item presently in the buffer. Predicate must return a boolean value. If predicate's return value is true, the item will be removed. Only a continuous series of items can be removed at a time using _predicate_. Alternatively, if _indexes_ array is passed, the items whose indexes match the list will be removed. Only one of the _predicate_ and _indexes_ options is allowed. In case of _indexes_, the deletion is performed also virtually. By default, indexes of the items following the deleted ones are decremented. Instead, if _increase_ is set to _true_, the indexes of the items before the removed ones are incremented. |
|[remove](https://dhilt.github.io/ngx-ui-scroll/#adapter#remove)|(options: {<br>&nbsp;&nbsp;predicate?:&nbsp;ItemsPredicate,<br>&nbsp;&nbsp;indexes?:&nbsp;number[],<br>&nbsp;&nbsp;increase?:&nbsp;boolean<br>}) <br><br>(func:&nbsp;ItemsPredicate) &#42;<br><sub>&#42; old signature, deprecated</sub><br><br> type&nbsp;ItemsPredicate&nbsp;=<br>&nbsp;&nbsp;(item: ItemAdapter)&nbsp;=><br>&nbsp;&nbsp;&nbsp;&nbsp;boolean|Removes items form buffer and/or virtually. Predicate is a function to be applied to every item presently in the buffer. Predicate must return a boolean value. If predicate's return value is true, the item will be removed. Alternatively, if _indexes_ array is passed, the items whose indexes match the list will be removed. Only one of the _predicate_ and _indexes_ options is allowed. In case of _indexes_, the deletion is performed also virtually. By default, indexes of the items following the deleted ones are decremented. Instead, if _increase_ is set to _true_, the indexes of the items before the removed ones are incremented. |
|[insert](https://dhilt.github.io/ngx-ui-scroll/#adapter#insert)|(options: {<br>&nbsp;&nbsp;items:&nbsp;MyItem[],<br>&nbsp;&nbsp;before?:&nbsp;ItemsPredicate,<br>&nbsp;&nbsp;after?:&nbsp;ItemsPredicate,<br>&nbsp;&nbsp;decrease?:&nbsp;boolean<br>})|Inserts items _before_ or _after_ the one that presents in the buffer and satisfies the predicate condition. Only one of the _before_ and _after_ options is allowed. Indexes increase by default. Decreasing strategy can be enabled via _decrease_ option. |
|[replace](https://dhilt.github.io/ngx-ui-scroll/#adapter#replace)|(options: {<br>&nbsp;&nbsp;predicate:&nbsp;ItemsPredicate,<br>&nbsp;&nbsp;items:&nbsp;MyItem[],<br>&nbsp;&nbsp;fixRight?:&nbsp;boolean<br>})|Replaces items that continuously match the _predicate_ with an array of new _items_. Indexes are maintained on the assumption that the left border of the dataset is fixed. To release the left border and fix the right one the _fixRight_ option should be set to _true_. |
|[update](https://dhilt.github.io/ngx-ui-scroll/#adapter#update)|(options: {<br>&nbsp;&nbsp;predicate:&nbsp;BufferUpdater,<br>&nbsp;&nbsp;fixRight?:&nbsp;boolean<br>}) <br><br> type&nbsp;BufferUpdater&nbsp;=<br>&nbsp;&nbsp;(item: ItemAdapter)&nbsp;=><br>&nbsp;&nbsp;&nbsp;&nbsp;unknown|Updates existing items by running the _predicate_ function over the Scroller's buffer. The return value of the _predicate_ determines the operation: falsy or empty array to remove, truthy or array with only 1 current item to keep unchanged, non-empty array to replace/insert. Indexes are maintained on the assumption that the left border of the dataset is fixed. To release the left border and fix the right one the _fixRight_ option should be set to _true_. |
Expand Down
18 changes: 6 additions & 12 deletions demo/app/samples/adapter/remove.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,21 @@
<p>
The <em>predicate</em> option is a function applying to each item in the buffer.
If the return value is true, the item will be removed.
The argument of the predicate has <em>ItemAdapter</em> type, and
in this demo we have two versions of <em>predicate</em> using for
The argument of the predicate is of <em>ItemAdapter</em> type, and
in this demo we have two versions of <em>predicate</em> for
removing by id and by index:
</p>
<pre>{{predicateDescription}}</pre>
<p>
Indexes are adjusted each time we perform a removing via Adapter.
It is impossible to remove item with id = 5 twice,
Indexes are adjusted each time a delete is performed via Adapter.
For example, it's impossible to remove item with id = 5 twice,
because there is only one item with id = 5.
But we may remove item with index = 5 as many times
as many items we have after this index.
</p>
<p>
Only a continuous series of items can be removed at a time using <em>predicate</em>.
So, if we want to remove items 1, 2, 3 and 5 with <em>predicate</em>,
we need to call this method twice: once for 1, 2, 3 and then for 5.
as many indexes we have after this one.
</p>
<p>
Instead of <em>predicate</em> running over buffered items,
it is possible to remove items by <em>indexes</em>.
It has no sequence limitation.
This method works with both buffered and virtual items.
For example, we have [1..10] buffered items (they are rendered)
and [11..100] virtual items (emulated via forward padding element
Expand All @@ -64,7 +58,7 @@
that we want to increase indexes of the items before the removed one(s).
</p>
<p>
The very important thing is to synchronize the datasource with the changes
The very important point is that we need to synchronize the datasource with the changes
we are making over the Scroller's buffer via <em>Adapter.remove</em>.
Generally this is the App component responsibility,
and in this demo it is done by the <em>removeFromDatasource</em> method.
Expand Down
2 changes: 1 addition & 1 deletion package-dist.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
},
"dependencies": {
"tslib": "^1.9.0",
"vscroll": "^1.3.2"
"vscroll": "^1.3.4"
},
"keywords": [
"angular",
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"ngx-bootstrap": "^5.6.1",
"rxjs": "~6.6.1",
"tslib": "^1.13.0",
"vscroll": "^1.3.2",
"vscroll": "^1.3.4",
"zone.js": "~0.11.4"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions tests/_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const testContext = require.context(
//(direction-priority)\.spec\.ts/
//(dynamic-height-reload)\.spec\.ts/
//(dynamic-height-scroll)\.spec\.ts/
//(dynamic-height-update)\.spec\.ts/
//(dynamic-size\.average)\.spec\.ts/
//(dynamic-size\.frequent)\.spec\.ts/
//(dynamic-size\.zero)\.spec\.ts/
Expand Down
155 changes: 113 additions & 42 deletions tests/adapter.remove.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { ItemsPredicate, SizeStrategy } from './miscellaneous/vscroll';

import { makeTest, TestBedConfig, ItFuncConfig, ItFunc } from './scaffolding/runner';
import { DatasourceRemover, getDatasourceClassForRemovals } from './scaffolding/datasources/class';
import { Misc } from './miscellaneous/misc';
import { IndexedItem, removeItems } from './miscellaneous/items';
import { getDynamicSumSize, getDynamicSizeByIndex } from './miscellaneous/dynamicSize';
import { ItemsPredicate, SizeStrategy } from './miscellaneous/vscroll';

interface ICustom {
remove: number[];
Expand All @@ -13,7 +11,6 @@ interface ICustom {
}

interface ICustomCommon {
interrupted?: number[];
remove?: number[];
removeBwd?: number[];
removeFwd?: number[];
Expand All @@ -36,39 +33,43 @@ interface ICustomBreak {
predicate: unknown;
}

const baseConfig: TestBedConfig = {
datasourceSettings: {},
datasourceName: 'limited--99-100-processor',
templateSettings: { viewportHeight: 100 }
};
interface ICustomBoth {
text: string;
toRemove: number[];
increase?: boolean;
result: {
min: number;
max: number;
};
}

const configList: TestBedConfig<ICustom>[] = [{
...baseConfig,
datasourceSettings: { startIndex: 1, bufferSize: 5, padding: 0.2, itemSize: 20 },
custom: { remove: [3, 4, 5] }
}, {
...baseConfig,
datasourceSettings: { startIndex: 55, bufferSize: 8, padding: 1, itemSize: 20 },
custom: { remove: [54, 55, 56, 57, 58] }
}, {
...baseConfig,
datasourceSettings: { startIndex: 10, bufferSize: 5, padding: 0.2, itemSize: 20 },
custom: { remove: [7, 8, 9] }
}];

configList.forEach(config => config.datasourceSettings.adapter = true);
}].map(config => ({
...config,
templateSettings: { viewportHeight: 100 },
datasourceClass: getDatasourceClassForRemovals({
settings: config.datasourceSettings,
common: { limits: { min: -99, max: 100 } },
})
}));

const configListInterrupted: TestBedConfig<ICustomCommon>[] = [{
...configList[0],
custom: {
remove: [2, 3],
interrupted: [2, 3, 5, 6]
remove: [2, 3, 5, 6]
}
}, {
...configList[1],
custom: {
remove: [54],
interrupted: [54, 56, 57, 58]
remove: [54, 56, 57, 58]
}
}];

Expand Down Expand Up @@ -118,6 +119,9 @@ const baseConfigOut = {
maxIndex: 100
}
};
baseConfigOut.datasourceClass = getDatasourceClassForRemovals({
settings: baseConfigOut.datasourceSettings
});

const configListOutFixed: TestBedConfig<ICustomCommon>[] = [{
...baseConfigOut, custom: {
Expand Down Expand Up @@ -163,7 +167,7 @@ const configListDynamicBuffer: TestBedConfig<ICustomCommon>[] = [{
}].map(config => ({
...config,
templateSettings: { viewportHeight: 100, dynamicSize: 'size' },
datasourceClass: getDatasourceClassForRemovals(config.datasourceSettings)
datasourceClass: getDatasourceClassForRemovals({ settings: config.datasourceSettings })
}));

const configListDynamicVirtual: TestBedConfig<ICustomCommon>[] = [{
Expand Down Expand Up @@ -193,7 +197,10 @@ const configListDynamicVirtual: TestBedConfig<ICustomCommon>[] = [{
}].map(config => ({
...config,
templateSettings: { viewportHeight: 100, dynamicSize: 'size' },
datasourceClass: getDatasourceClassForRemovals(config.datasourceSettings, config.datasourceDevSettings)
datasourceClass: getDatasourceClassForRemovals({
settings: config.datasourceSettings,
devSettings: config.datasourceDevSettings
})
}));

const configListFlush: TestBedConfig<ICustomFlush>[] = [{
Expand All @@ -217,29 +224,79 @@ const configListFlush: TestBedConfig<ICustomFlush>[] = [{
}].map(config => ({
...config,
templateSettings: { viewportHeight: 100 },
datasourceClass: getDatasourceClassForRemovals(config.datasourceSettings)
datasourceClass: getDatasourceClassForRemovals({ settings: config.datasourceSettings })
}));

const configListBufferAndVirtual: TestBedConfig<ICustomBoth>[] = [{
datasourceSettings: { startIndex: 1, minIndex: 1, maxIndex: 20, bufferSize: 1, padding: 0.3 },
custom: {
text: 'the bottom virtual item is not reached',
toRemove: [1, 20], increase: false, result: { min: 1, max: 18 }
}
}, {
datasourceSettings: { startIndex: 1, minIndex: 1, maxIndex: 20, bufferSize: 1, padding: 0.3 },
custom: {
text: 'the bottom virtual item is not reached (increase)',
toRemove: [1, 20], increase: true, result: { min: 3, max: 20 }
}
}, {
datasourceSettings: { startIndex: 20, minIndex: 1, maxIndex: 20, bufferSize: 1, padding: 0.3 },
custom: {
text: 'the top virtual item is not reached',
toRemove: [1, 20], increase: false, result: { min: 1, max: 18 }
}
}, {
datasourceSettings: { startIndex: 20, minIndex: 1, maxIndex: 20, bufferSize: 1, padding: 0.3 },
custom: {
text: 'the top virtual item is not reached (increase)',
toRemove: [1, 20], increase: true, result: { min: 3, max: 20 }
}
}, {
datasourceSettings: { startIndex: 1, minIndex: 1, maxIndex: 20, bufferSize: 1, padding: 0.3 },
custom: {
text: 'the bottom virtual item is not reached, v2',
toRemove: [2, 3, 17, 18], increase: false, result: { min: 1, max: 16 }
}
}, {
datasourceSettings: { startIndex: 1, minIndex: 1, maxIndex: 20, bufferSize: 1, padding: 0.3 },
custom: {
text: 'the bottom virtual item is not reached, v2 (increase)',
toRemove: [2, 3, 17, 18], increase: true, result: { min: 5, max: 20 }
}
}, {
datasourceSettings: { startIndex: 20, minIndex: 1, maxIndex: 20, bufferSize: 1, padding: 0.3 },
custom: {
text: 'the top virtual item is not reached, v2',
toRemove: [2, 3, 17, 18], increase: false, result: { min: 1, max: 16 }
}
}, {
datasourceSettings: { startIndex: 20, minIndex: 1, maxIndex: 20, bufferSize: 1, padding: 0.3 },
custom: {
text: 'the top virtual item is not reached (increase)',
toRemove: [2, 3, 17, 18], increase: true, result: { min: 5, max: 20 }
}
}].map(config => ({
...config,
templateSettings: { viewportHeight: 100 },
datasourceClass: getDatasourceClassForRemovals({ settings: config.datasourceSettings })
}));

const doRemove = async (config: TestBedConfig<ICustomCommon>, misc: Misc, byId = false) => {
const { increase, useIndexes, remove, removeBwd, removeFwd } = config.custom;
const indexList = remove || [...(removeBwd || []), ...(removeFwd || [])];
const indexListInterrupted = config.custom.interrupted;
const ds = misc.datasource as DatasourceRemover;
// remove item from the original datasource
misc.setDatasourceProcessor((result: IndexedItem[]) =>
[removeBwd, removeFwd, remove].forEach(list =>
list && removeItems(result, list, -99, 100, increase)
)
);
ds.remove(indexList, !!increase);
// remove items from the UiScroll
if (useIndexes) {
await misc.adapter.remove({
indexes: indexListInterrupted || indexList,
indexes: indexList,
increase
});
} else {
await misc.adapter.remove({
predicate: item =>
(indexListInterrupted || indexList).some((i: number) =>
indexList.some((i: number) =>
i === (byId ? item.data.id : item.$index)
),
increase
Expand Down Expand Up @@ -275,7 +332,7 @@ const shouldRemove = (config: TestBedConfig<ICustomCommon>, byId = false): ItFun
const { firstIndex, lastIndex, items } = misc.scroller.buffer;
if (!isNaN(firstIndex) && !isNaN(lastIndex)) {
// check all items contents
(items as IndexedItem[]).forEach(({ $index, data: { id } }) => {
items.forEach(({ $index, data: { id } }) => {
const diff = indexList.reduce((acc: number, index: number) =>
acc + (increase ? (id < index ? -1 : 0) : (id > index ? 1 : 0)), 0
);
Expand Down Expand Up @@ -349,14 +406,6 @@ const shouldRemoveVirtual: ItFuncConfig<ICustomCommon> = config => misc => async
done();
};

const scrollDownToIndex = async (misc: Misc, index: number) => {
const { adapter } = misc;
while (adapter.bufferInfo.lastIndex < index) {
adapter.fix({ scrollToItem: ({ $index }) => $index === adapter.bufferInfo.lastIndex });
await misc.relaxNext();
}
};

const shouldRemoveDynamicSize: ItFuncConfig<ICustomCommon> = config => misc => async done => {
const { indexToReload, size, increase } = config.custom;
const indexToRemove = config.custom.indexToRemove as number;
Expand All @@ -366,7 +415,7 @@ const shouldRemoveDynamicSize: ItFuncConfig<ICustomCommon> = config => misc => a
const finalSize = getDynamicSumSize(minIndex, maxIndex) - getDynamicSizeByIndex(indexToRemove);

// set DS sizes
ds.data.forEach(item => item.size = getDynamicSizeByIndex(item.id));
ds.setSizes(getDynamicSizeByIndex);
ds.data[indexToRemove - minIndex].size = size;
await misc.relaxNext();

Expand All @@ -392,7 +441,7 @@ const shouldRemoveDynamicSize: ItFuncConfig<ICustomCommon> = config => misc => a
}

await misc.scrollMinRelax();
await scrollDownToIndex(misc, maxIndex - (increase ? 0 : 1));
await misc.scrollToIndexRecursively(maxIndex - (increase ? 0 : 1));
expect(misc.getScrollableSize()).toBe(finalSize);

done();
Expand All @@ -415,6 +464,18 @@ const shouldFlush: ItFuncConfig<ICustomFlush> = config => misc => async done =>
done();
};

const shouldRemoveInBufferAndVirtual: ItFuncConfig<ICustomBoth> = config => misc => async done => {
const { adapter, scroller: { buffer } } = misc;
const { toRemove, increase, result } = config.custom;
const ds = misc.datasource as DatasourceRemover;
await misc.relaxNext();
ds.remove(toRemove, !!increase);
await adapter.remove({ indexes: toRemove, increase });
expect(buffer.absMinIndex).toEqual(result.min);
expect(buffer.absMaxIndex).toEqual(result.max);
done();
};

describe('Adapter Remove Spec', () => {

describe('Buffer', () => {
Expand All @@ -437,7 +498,7 @@ describe('Adapter Remove Spec', () => {
configListInterrupted.forEach(config =>
makeTest({
config,
title: 'should remove only first uninterrupted portion',
title: 'should remove portion of non-continuous indexes',
it: shouldRemove(config)
})
);
Expand Down Expand Up @@ -515,4 +576,14 @@ describe('Adapter Remove Spec', () => {
);
});

describe('Buffer and Virtual', () => {
configListBufferAndVirtual.forEach(config =>
makeTest({
config,
title: `should remove in-buffer and virtual items when ${config.custom.text}`,
it: shouldRemoveInBufferAndVirtual(config)
})
);
});

});
Loading