Skip to content

Commit

Permalink
Merge pull request #230 from angular-ui/immutable-top
Browse files Browse the repository at this point in the history
immutableTop option
  • Loading branch information
dhilt authored Aug 17, 2019
2 parents 41e4c4f + 07eb2c3 commit 0182953
Show file tree
Hide file tree
Showing 12 changed files with 501 additions and 202 deletions.
6 changes: 2 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ cache:
addons:
firefox: latest

before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- sleep 3
services:
- xvfb

script:
- npm run build
Expand Down
25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,13 +229,7 @@ exactly `count` elements unless it hit eof/bof.

* Properties `minIndex` and `maxIndex`

As the scroller receives the items requested by the `get` method, the value of minimum and maximum values of the item index are placed in the `minIndex` and `maxIndex` properties respectively. These values are used to maintain the appearance of the scrollbar. The values of the properties can be internaly changed by the ui-scroll engine in three cases:
* reset both properties in response to a call to the adapter `reload` method;
* increment `minIndex` in response to deleting the topmost element via adapter `applyUpdates` method;
* decrement `maxIndex` in response to deleting anything other than the topmost element via adapter `applyUpdates` method.

Values of the properties can be assigned programmatically. If the range of the index values is known in advance, assigning them programmatically would improve the usability of the scrollBar.

If the boundaries of the dataset are known, we may virtualize all the dataset by assigning appropriate values to `minIndex` and `maxIndex` datasource properties. This would improve the usability of the scroll bar: the uiScroll will maintain forward and backward padding elements of the viewport assuming the dataset consists of (maxIndex - minIndex) items. So it will be possible to jump to any position immediately.

### Adapter

Expand Down Expand Up @@ -286,21 +280,26 @@ Adapter object implements the following methods

* Method `applyUpdates`

applyUpdates(index, newItems)
applyUpdates(index, newItems, options)

Replaces the item in the buffer at the given index with the new items.

Parameters
* **index** provides position of the item to be affected in the dataset (not in the buffer). If the item with the given index currently is not in the buffer no updates will be applied. `$index` property of the item $scope can be used to access the index value for a given item
* **newItems** is an array of items to replace the affected item. If the array is empty (`[]`) the item will be deleted, otherwise the items in the array replace the item. If the newItem array contains the old item, the old item stays in place.

applyUpdates(updater)
applyUpdates(updater, options)

Updates scroller content as determined by the updater function

Parameters
* **updater** is a function to be applied to every item currently in the buffer. The function will receive 3 parameters: `item`, `scope`, and `element`. Here `item` is the item to be affected, `scope` is the item $scope, and `element` is the html element for the item. The return value of the function should be an array of items. Similarly to the `newItem` parameter (see above), if the array is empty(`[]`), the item is deleted, otherwise the item is replaced by the items in the array. If the return value is not an array, the item remains unaffected, unless some updates were made to the item in the updater function. This can be thought of as in place update.

Options for both signatures, an object with following fields
* **immutableTop** is a boolean flag with `false` defalt value. This option has an impact on removing/inserting items procedure. If it's `false`, deleting the topmost item will lead to incrementing min index, also inserting new item(s) before the topmost one will lead to decrementing min index. If it's `true`, min index will not be affected, max index will be shifted instead. If it's `true`, no matter which item is going to be removed/inserted, max index will be reduced/increased respectively.

Let's discuss a little sample. We have `{{$index}}: {{item}}` template and three rows: `1: item1`, `2: item2`, `3: item3`. Then we want to remove the first item. Without `immutableTop` we'll get `2: item2`, `3: item3`. With `immutableTop` we'll get `1: item2`, `2: item3`. The same for inserting, say, `item0` before `item1`. Without `immutableTop` we'll get `0: item0`, `1: item1`, `2: item2`, `3: item3`. With `immutableTop` we'll get `1: item0`, `2: item1`, `3: item2`, `4: item3`.

* Method `append`

append(newItems)
Expand All @@ -312,12 +311,13 @@ Adapter object implements the following methods

* Method `prepend`

prepend(newItems)
prepend(newItems, options)

Adds new items before the first item in the buffer.
Adds new items before the first item in the buffer. Works exactly as inserting new item(s) before the topmost one via `applyUpdates` method.

Parameters
* **newItems** provides an array of items to be prepended.
* **options** the same object as the last argument of `applyUpdates` method; `options.immutableTop` set to `true` will make min index unchangeable, max index will be increased. Otherwise (`options.immutableTop = false`, the default case), min index will be increased.

#### Manipulating the scroller content with the adapter methods
Adapter methods `applyUpdates`, `append` and `prepend` provide a way to update the scroller content without full reload of the content from the datasource. The updates are performed by changing the items in the scroller internal buffer after they are loaded from the datasource. Items in the buffer can be deleted or replaced with one or more items.
Expand Down Expand Up @@ -477,6 +477,9 @@ Pull Rerquest should include source code (./scr) changes, may include tests (./t

## Change log

### v1.7.6
* Added immutableTop option for applyUpdates and prepend Adapter methods.

### v1.7.5
* Added bufferFirst, bufferLast, bufferLength read-only properties to the Adapter.
* Fixed reload unsubscribe issue [226](https://github.com/angular-ui/ui-scroll/issues/226).
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "angular-ui-scroll",
"description": "AngularJS infinite scrolling module",
"version": "1.7.5",
"version": "1.7.6",
"main": "./dist/ui-scroll.js",
"homepage": "https://github.com/angular-ui/ui-scroll.git",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion dist/ui-scroll-grid.js

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

37 changes: 24 additions & 13 deletions dist/ui-scroll.js

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

2 changes: 1 addition & 1 deletion dist/ui-scroll.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/ui-scroll.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/ui-scroll.min.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "angular-ui-scroll",
"description": "AngularJS infinite scrolling module",
"version": "1.7.5",
"version": "1.7.6",
"src": "./src/",
"public": "./dist/",
"main": "./dist/ui-scroll.js",
Expand Down
28 changes: 14 additions & 14 deletions src/modules/adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,45 +114,45 @@ class Adapter {
this.viewport.clipBottom();
}

prepend(newItems) {
this.buffer.prepend(newItems);
prepend(newItems, options = {}) {
this.buffer.prepend(newItems, options.immutableTop);
this.doAdjust();
this.viewport.clipTop();
this.viewport.clipBottom();
}

applyUpdates(arg1, arg2) {
applyUpdates(arg1, arg2, arg3) {
if (typeof arg1 === 'function') {
this.applyUpdatesFunc(arg1);
this.applyUpdatesFunc(arg1, arg2);
} else {
this.applyUpdatesIndex(arg1, arg2);
this.applyUpdatesIndex(arg1, arg2, arg3);
}
this.doAdjust();
}

applyUpdatesFunc(cb) {
applyUpdatesFunc(cb, options = {}) {
this.buffer.slice(0).forEach((wrapper) => {
// we need to do it on the buffer clone, because buffer content
// may change as we iterate through
this.applyUpdate(wrapper, cb(wrapper.item, wrapper.scope, wrapper.element));
this.applyUpdate(wrapper, cb(wrapper.item, wrapper.scope, wrapper.element), options);
});
}

applyUpdatesIndex(index, newItems) {
applyUpdatesIndex(index, newItems, options = {}) {
if (index % 1 !== 0) {
throw new Error('applyUpdates - ' + index + ' is not a valid index (should be an integer)');
}
const _index = index - this.buffer.first;

// apply updates only within buffer
if (_index >= 0 && _index < this.buffer.length) {
this.applyUpdate(this.buffer[_index], newItems);
this.applyUpdate(this.buffer[_index], newItems, options);
}
// out-of-buffer case: deletion may affect Paddings
else if(index >= this.buffer.getAbsMinIndex() && index <= this.buffer.getAbsMaxIndex()) {
if(angular.isArray(newItems) && !newItems.length) {
this.viewport.removeCacheItem(index, index === this.buffer.minIndex);
if(index === this.buffer.getAbsMinIndex()) {
this.viewport.removeCacheItem(index, !options.immutableTop && index === this.buffer.minIndex);
if (!options.immutableTop && index === this.buffer.getAbsMinIndex()) {
this.buffer.incrementMinIndex();
}
else {
Expand All @@ -162,14 +162,14 @@ class Adapter {
}
}

applyUpdate(wrapper, newItems) {
applyUpdate(wrapper, newItems, options = {}) {
if (!angular.isArray(newItems)) {
return;
}
let position = this.buffer.indexOf(wrapper);
if (!newItems.reverse().some(newItem => newItem === wrapper.item)) {
wrapper.op = 'remove';
if(position === 0 && !newItems.length) {
if (!options.immutableTop && position === 0 && !newItems.length) {
wrapper._op = 'isTop'; // to catch "first" edge case on remove
}
}
Expand All @@ -178,7 +178,7 @@ class Adapter {
position--;
} else {
// 3 parameter (isTop) is to catch "first" edge case on insert
this.buffer.insert(position + 1, newItem, position === -1);
this.buffer.insert(position + 1, newItem, !options.immutableTop && position === -1);
}
});
}
Expand Down
9 changes: 7 additions & 2 deletions src/modules/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,14 @@ export default function ScrollBuffer(elementRoutines, bufferSize, startIndex) {
buffer.maxIndex = buffer.eof ? buffer.next - 1 : Math.max(buffer.next - 1, buffer.maxIndex);
},

prepend(items) {
prepend(items, immutableTop) {
items.reverse().forEach((item) => {
--buffer.first;
if (immutableTop) {
++buffer.next;
}
else {
--buffer.first;
}
buffer.insert('prepend', item);
});
buffer.minIndex = buffer.bof ? buffer.minIndex = buffer.first : Math.min(buffer.first, buffer.minIndex);
Expand Down
Loading

0 comments on commit 0182953

Please sign in to comment.