diff --git a/README.md b/README.md index 1221db67..93749549 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,14 @@ [![Build Status](https://travis-ci.com/dhilt/ngx-ui-scroll.svg?branch=master)](https://travis-ci.com/dhilt/ngx-ui-scroll) -[![npm version](https://badge.fury.io/js/ngx-ui-scroll.svg)](https://www.npmjs.com/package/ngx-ui-scroll) +[![npm version](https://badge.fury.io/js/ngx-ui-scroll.svg)](https://www.npmjs.com/package/ngx-ui-scroll) # NgxUiScroll Unlimited bidirectional scrolling over limited viewport. A directive for [Angular](https://angular.io/) framework. Built with [angular-library-starter](https://github.com/robisim74/angular-library-starter). Inspired by [angular-ui-scroll](https://github.com/angular-ui/ui-scroll) (AngularJS, since 2013). Demo is available at [dhilt.github.io/ngx-ui-scroll](https://dhilt.github.io/ngx-ui-scroll/). +

+can donate? go here 👉
make open-source world better
+

+ - [Motivation](#motivation) - [Features](#features) - [Getting](#getting) @@ -13,11 +17,13 @@ Unlimited bidirectional scrolling over limited viewport. A directive for [Angula - [Adapter API](#adapter-api) - [Development](#development) +
+ ### Motivation -Scrolling large data sets may cause performance issues. Many DOM elements, many data-bindings, many event listeners... The common way to improve this case is to render only a small portion of the data set visible to a user. Other data set elements that are not visible to a user are virtualized with upward and downward empty padding elements which should give us a consistent viewport with consistent scrollbar parameters. +Scrolling large datasets may cause performance issues. Many DOM elements, many data-bindings, many event listeners... The common way to improve the performance is to render only a small portion of the dataset visible to a user. Other dataset elements that are not visible to a user are virtualized with upward and downward empty padding elements which should provide a consistent viewport with consistent scrollbar parameters. -The \*uiScroll is structural directive that works like \*ngFor and renders a templated element once per item from a collection. By requesting the external Datasource (the implementation of which is a developer responsibility) the \*uiScroll directive fetches necessary portion of the data set and renders corresponded elements until the visible part of the viewport is filled out. It starts to retrieve new data to render new elements again if a user scrolls to the edge of visible element list. It dynamically destroys elements as they become invisible and recreates them if they become visible again. +The \*uiScroll is a structural directive that works like \*ngFor and renders a templated element once per item from a collection. By requesting the external Datasource (the implementation of which is a developer responsibility) the \*uiScroll directive fetches necessary portion of the dataset and renders corresponded elements until the visible part of the viewport is filled out. It starts to retrieve new data to render new elements again if a user scrolls to the edge of visible element list. It dynamically destroys elements as they become invisible and recreates them if they become visible again.

@@ -27,11 +33,11 @@ The \*uiScroll is structural directive that works like \*ngFor and renders a tem - unlimited bidirectional virtual scroll - lots of virtualization settings - super easy templating - - infinite mode, [demo](https://dhilt.github.io/ngx-ui-scroll/#settings#infinite-mode) - - horizontal mode, [demo](https://dhilt.github.io/ngx-ui-scroll/#settings#horizontal-mode) - - entire window scrollable, [demo](https://dhilt.github.io/ngx-ui-scroll/#settings#window-viewport) - - items with non-constant heights, [demo](https://dhilt.github.io/ngx-ui-scroll/#settings#different-item-heights) - - API Adapter object to manipulate and assess the scroller, [demos](https://dhilt.github.io/ngx-ui-scroll/#/adapter) + - infinite mode, [demo](https://dhilt.github.io/ngx-ui-scroll/settings#infinite-mode) + - horizontal mode, [demo](https://dhilt.github.io/ngx-ui-scroll/settings#horizontal-mode) + - entire window scrollable, [demo](https://dhilt.github.io/ngx-ui-scroll/settings#window-viewport) + - items with non-constant heights, [demo](https://dhilt.github.io/ngx-ui-scroll/settings#different-item-heights) + - API Adapter object to manipulate and assess the scroller, [demos](https://dhilt.github.io/ngx-ui-scroll/adapter) ### Getting @@ -80,7 +86,7 @@ where the viewport is a scrollable area of finite height: } ``` -If the height of the viewport is not constrained, it will pull the entire content of the datasource and no scrollbar will appear. Previous versions of the library (prior to 1.6.4) had the requirement that the value of "overflow-anchor" css property should be set to "none" for the viewport element. +If the height of the viewport is not constrained, it will pull the entire content of the datasource and no scrollbar will appear. \*uiScroll acts like \*ngFor, but the datasource is an object of special type (IDatasource). It implements method _get_ to be used by the \*uiScroll directive to access the data by _index_ and _count_ parameters. The directive calls `Datasource.get` method each time a user scrolls to the edge of visible element list. That's the API provided by the \*uiScroll. @@ -111,7 +117,7 @@ _Datasource.get_ has 3 signatures: callback based, Promise based and Observable }; ``` -More details could be found on the [Datasource demo page](https://dhilt.github.io/ngx-ui-scroll/#/datasource). +More details could be found on the [Datasource demo page](https://dhilt.github.io/ngx-ui-scroll/datasource). ### Settings @@ -132,18 +138,18 @@ Settings are being applied during the uiScroll initialization and have an impact |Name|Type|Default|Description| |:--|:----:|:----------:|:----------| -|[bufferSize](https://dhilt.github.io/ngx-ui-scroll/#settings#buffer-size)|number,
integer|5| Fixes minimal size of the pack of the datasource items to be requested per single _Datasource.get_ call. Can't be less than 1. | -|[padding](https://dhilt.github.io/ngx-ui-scroll/#settings#padding)|number,
float|0.5| Determines viewport outlets relative to the viewport's size that need to be filled. For example, 0.5 means that we'll have as many items at a moment as needed to fill out 100% of the visible part of the viewport, + 50% of the viewport size in backward direction and + 50% in forward direction. The value can't be less than 0.01. | -|[startIndex](https://dhilt.github.io/ngx-ui-scroll/#settings#start-index)|number,
integer|1| Specifies item index to be requested/rendered first. Can be any, but real datasource boundaries should be taken into account. | -|[minIndex](https://dhilt.github.io/ngx-ui-scroll/#settings#min-max-indexes)|number,
integer|-Infinity| Fixes absolute minimal index of the data set. The datasource left boundary. | -|[maxIndex](https://dhilt.github.io/ngx-ui-scroll/#settings#min-max-indexes)|number,
integer|+Infinity| Fixes absolute maximal index of the data set. The datasource right boundary. | -|[infinite](https://dhilt.github.io/ngx-ui-scroll/#settings#infinite-mode)|boolean|false| Allows to run "infinite" mode, when items rendered once are never removed. | -|[horizontal](https://dhilt.github.io/ngx-ui-scroll/#settings#horizontal-mode)|boolean|false| Allows to run "horizontal" mode, when the viewport's orientation is horizontal. | -|[windowViewport](https://dhilt.github.io/ngx-ui-scroll/#settings#window-viewport)|boolean|false| Allows to run "entire window scrollable" mode, when the entire window becomes the scrollable viewport. | +|[bufferSize](https://dhilt.github.io/ngx-ui-scroll/settings#buffer-size)|number,
integer|5| Fixes minimal size of the pack of the datasource items to be requested per single _Datasource.get_ call. Can't be less than 1. | +|[padding](https://dhilt.github.io/ngx-ui-scroll/settings#padding)|number,
float|0.5| Determines viewport outlets relative to the viewport's size that need to be filled. For example, 0.5 means that we'll have as many items at a moment as needed to fill out 100% of the visible part of the viewport, + 50% of the viewport size in backward direction and + 50% in forward direction. The value can't be less than 0.01. | +|[startIndex](https://dhilt.github.io/ngx-ui-scroll/settings#start-index)|number,
integer|1| Specifies item index to be requested/rendered first. Can be any, but real datasource boundaries should be taken into account. | +|[minIndex](https://dhilt.github.io/ngx-ui-scroll/settings#min-max-indexes)|number,
integer|-Infinity| Fixes absolute minimal index of the dataset. The datasource left boundary. | +|[maxIndex](https://dhilt.github.io/ngx-ui-scroll/settings#min-max-indexes)|number,
integer|+Infinity| Fixes absolute maximal index of the dataset. The datasource right boundary. | +|[infinite](https://dhilt.github.io/ngx-ui-scroll/settings#infinite-mode)|boolean|false| Allows to run "infinite" mode, when items rendered once are never removed. | +|[horizontal](https://dhilt.github.io/ngx-ui-scroll/settings#horizontal-mode)|boolean|false| Allows to run "horizontal" mode, when the viewport's orientation is horizontal. | +|[windowViewport](https://dhilt.github.io/ngx-ui-scroll/settings#window-viewport)|boolean|false| Allows to run "entire window scrollable" mode, when the entire window becomes the scrollable viewport. | ### Adapter API -The uiScroll has API to assess its parameters and provide some manipulations run-time. This API is available via special Adapter object. The datasource needs to be instantiated via operator "new" for the Adapter object to be added to it: +The uiScroll has API to assess its parameters and provide some manipulations at runtime. This API is available via special Adapter object. The datasource needs to be instantiated via operator "new" for the Adapter object to be added to it: ```javascript import { Datasource } from 'ngx-ui-scroll'; @@ -159,35 +165,36 @@ Then `this.datasource.adapter.version`, `this.datasource.adapter.reload()` and o |Name|Type|Description| |:--|:----|:----------| |version|string|Current version of ngx-ui-scroll library| -|[isLoading](https://dhilt.github.io/ngx-ui-scroll/#/adapter#is-loading)|boolean|Indicates whether the uiScroll is working ot not. | -|[isLoading$](https://dhilt.github.io/ngx-ui-scroll/#/adapter#is-loading)|Subject<boolean>|An Observable version of "isLoading" property. | -|[itemsCount](https://dhilt.github.io/ngx-ui-scroll/#/adapter#items-count)|number|A number of items that are rendered in the viewport at a moment.| -|[bof](https://dhilt.github.io/ngx-ui-scroll/#/adapter#bof-eof)|boolean|Indicates whether the beginning of the dataset is reached or not.| -|[bof$](https://dhilt.github.io/ngx-ui-scroll/#/adapter#bof-eof)|Subject<boolean>|An Observable version of "bof" property.| -|[eof](https://dhilt.github.io/ngx-ui-scroll/#/adapter#bof-eof)|boolean|Indicates whether the end of the dataset is reached or not.| -|[eof$](https://dhilt.github.io/ngx-ui-scroll/#/adapter#bof-eof)|Subject<boolean>|An Observable version of "eof" property.| -|[firstVisible](https://dhilt.github.io/ngx-ui-scroll/#/adapter#first-last-visible-items)|ItemAdapter {
  $index: number;
  data: any;
  element?: HTMLElement;
}|Object of ItemAdapter type containing information about first visible item, where "$index" corresponds to the datasource item index value, "data" is exactly the item's content, "element" is a link to DOM element which is relevant to the item. | -|[firstVisible$](https://dhilt.github.io/ngx-ui-scroll/#/adapter#first-last-visible-items)|BehaviorSubject
<ItemAdapter>|An observable version of "firstVisible" property. | -|[lastVisible](https://dhilt.github.io/ngx-ui-scroll/#/adapter#first-last-visible-items)|ItemAdapter {
  $index: number;
  data: any;
  element?: HTMLElement;
}|Object of ItemAdapter type containing information about last visible item. | -|[lastVisible$](https://dhilt.github.io/ngx-ui-scroll/#/adapter#first-last-visible-items)|BehaviorSubject
<ItemAdapter>|An observable version of "lastVisible" property. | +|[isLoading](https://dhilt.github.io/ngx-ui-scroll/adapter#is-loading)|boolean|Indicates whether the uiScroll is working ot not. | +|[isLoading$](https://dhilt.github.io/ngx-ui-scroll/adapter#is-loading)|Subject<boolean>|An Observable version of "isLoading" property. | +|[itemsCount](https://dhilt.github.io/ngx-ui-scroll/adapter#items-count)|number|A number of items that are rendered in the viewport at a moment.| +|[bof](https://dhilt.github.io/ngx-ui-scroll/adapter#bof-eof)|boolean|Indicates whether the beginning of the dataset is reached or not.| +|[bof$](https://dhilt.github.io/ngx-ui-scroll/adapter#bof-eof)|Subject<boolean>|An Observable version of "bof" property.| +|[eof](https://dhilt.github.io/ngx-ui-scroll/adapter#bof-eof)|boolean|Indicates whether the end of the dataset is reached or not.| +|[eof$](https://dhilt.github.io/ngx-ui-scroll/adapter#bof-eof)|Subject<boolean>|An Observable version of "eof" property.| +|[firstVisible](https://dhilt.github.io/ngx-ui-scroll/adapter#first-last-visible-items)|ItemAdapter {
  $index: number;
  data: any;
  element?: HTMLElement;
}|Object of ItemAdapter type containing information about first visible item, where "$index" corresponds to the datasource item index value, "data" is exactly the item's content, "element" is a link to DOM element which is relevant to the item. | +|[firstVisible$](https://dhilt.github.io/ngx-ui-scroll/adapter#first-last-visible-items)|BehaviorSubject
<ItemAdapter>|An observable version of "firstVisible" property. | +|[lastVisible](https://dhilt.github.io/ngx-ui-scroll/adapter#first-last-visible-items)|ItemAdapter {
  $index: number;
  data: any;
  element?: HTMLElement;
}|Object of ItemAdapter type containing information about last visible item. | +|[lastVisible$](https://dhilt.github.io/ngx-ui-scroll/adapter#first-last-visible-items)|BehaviorSubject
<ItemAdapter>|An observable version of "lastVisible" property. | Below is the list of invocable methods of the Adapter API with description and links to demos. |Name|Parameters|Description| |:--|:----|:----------| -|[relax](https://dhilt.github.io/ngx-ui-scroll/#/adapter#relax)|(callback?: Function)|Resolves asynchronously when there are no pending processes. If the _callback_ is set, it will be executed right before resolving. Basically, it needs to protect with the _relax_ every piece of the App logic, that might be sensitive to the uiScroll internal processes, to avoid interference and race conditions. | -|[reload](https://dhilt.github.io/ngx-ui-scroll/#/adapter#reload)|(startIndex?: number)|Resets the items buffer, resets the viewport params and starts fetching items from _startIndex_ (if set). | -|[reset](https://dhilt.github.io/ngx-ui-scroll/#/adapter#reset)|(datasource?: IDatasource)|Performs hard reset of the uiScroll internal state by re-instantiating all its entities (instead of reusing them when _reload_). If _datasource_ argument is passed, it will be treated as new Datasource. All props of the _datasource_ are optional and the result Datasource will be a combination (merge) of the original one and the one passed as an argument. | -|[append](https://dhilt.github.io/ngx-ui-scroll/#/adapter#append-prepend)|(options: {
  items: any[],
  eof?: boolean
})

(items: any | any[], eof?: boolean) *
* old signature, deprecated|Adds items to the end of the uiScroll dataset. If eof parameter is not set, items will be added and rendered immediately, they will be placed right after the last item in the uiScroll buffer. If eof parameter 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) demo. | -|[prepend](https://dhilt.github.io/ngx-ui-scroll/#/adapter#append-prepend)|(options: {
  items: any[],
  bof?: boolean
})

(items: any | any[], bof?: boolean) *
* old signature, deprecated|Adds items to the beginning of the uiScroll dataset. If bof parameter is not set, items will be added and rendered immediately, they will be placed right before the first item in the uiScroll buffer. If bof parameter 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) demo. | -|[check](https://dhilt.github.io/ngx-ui-scroll/#/adapter#check-size)| |Checks if any of current items changed it's size and runs a procedure to provide internal consistency and new items fetching if needed. | -|[remove](https://dhilt.github.io/ngx-ui-scroll/#/adapter#remove)|(options: {
  predicate?: ItemsPredicate,
  indexes?: number[],
  increase?: boolean
})

(func: ItemsPredicate) *
* old signature, deprecated

type ItemsPredicate =
  (item: ItemAdapter) =>
    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_, indexes of the items before the removed ones will be increased. | -|[clip](https://dhilt.github.io/ngx-ui-scroll/#/adapter#clip)|(options: {
  forwardOnly?: boolean,
  backwardOnly?: boolean
})|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. | -|[insert](https://dhilt.github.io/ngx-ui-scroll/#/adapter#insert)|(options: {
  items: any[],
  before?: ItemsPredicate,
  after?: ItemsPredicate,
  decrease?: boolean
})|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. | +|[relax](https://dhilt.github.io/ngx-ui-scroll/adapter#relax)|(callback?: Function)|Resolves asynchronously when there are no pending processes. If the _callback_ is set, it will be executed right before resolving. Basically, it needs to protect with the _relax_ every piece of the App logic, that might be sensitive to the uiScroll internal processes, to avoid interference and race conditions. | +|[reload](https://dhilt.github.io/ngx-ui-scroll/adapter#reload)|(startIndex?: number)|Resets the items buffer, resets the viewport params and starts fetching items from _startIndex_ (if set). | +|[reset](https://dhilt.github.io/ngx-ui-scroll/adapter#reset)|(datasource?: IDatasource)|Performs hard reset of the uiScroll internal state by re-instantiating all its entities (instead of reusing them when _reload_). If _datasource_ argument is passed, it will be treated as new Datasource. All props of the _datasource_ are optional and the result Datasource will be a combination (merge) of the original one and the one passed as an argument. | +|[check](https://dhilt.github.io/ngx-ui-scroll/adapter#check-size)| |Checks if any of current items changed it's size and runs a procedure to provide internal consistency and new items fetching if needed. | +|[clip](https://dhilt.github.io/ngx-ui-scroll/adapter#clip)|(options: {
  forwardOnly?: boolean,
  backwardOnly?: boolean
})|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: {
  items: any[],
  eof?: boolean
})

(items: any | any[], eof?: boolean) *
* old signature, deprecated|Adds items to the end of the uiScroll dataset. If eof parameter is not set, items will be added and rendered immediately, they will be placed right after the last item in the uiScroll buffer. If eof parameter 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) demo. | +|[prepend](https://dhilt.github.io/ngx-ui-scroll/adapter#append-prepend)|(options: {
  items: any[],
  bof?: boolean
})

(items: any | any[], bof?: boolean) *
* old signature, deprecated|Adds items to the beginning of the uiScroll dataset. If bof parameter is not set, items will be added and rendered immediately, they will be placed right before the first item in the uiScroll buffer. If bof parameter 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) demo. | +|[remove](https://dhilt.github.io/ngx-ui-scroll/adapter#remove)|(options: {
  predicate?: ItemsPredicate,
  indexes?: number[],
  increase?: boolean
})

(func: ItemsPredicate) *
* old signature, deprecated

type ItemsPredicate =
  (item: ItemAdapter) =>
    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_, indexes of the items before the removed ones will be increased. | +|[insert](https://dhilt.github.io/ngx-ui-scroll/adapter#insert)|(options: {
  items: any[],
  before?: ItemsPredicate,
  after?: ItemsPredicate,
  decrease?: boolean
})|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: {
  predicate: ItemsPredicate,
  items: any[],
  fixRight?: boolean
})|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_. | -Along with the documented API there are some undocumented features that can be treated as experimental. They are not tested enough and might change over time. Some of them can be found on the [experimental tab](https://dhilt.github.io/ngx-ui-scroll/#/experimental) of the demo app. +Along with the documented API there are some undocumented features that can be treated as experimental. They are not tested enough and might change over time. Some of them can be found on the [experimental tab](https://dhilt.github.io/ngx-ui-scroll/experimental) of the demo app. -All of the Adapter methods return Promise resolving at the moment when the scroller terminates its internal processes triggered by the invocation of correspondent Adapter method. It is called [Adapter Return API](https://dhilt.github.io/ngx-ui-scroll/#/adapter#return-value). This promise has exactly the same nature as the promise of the [relax method](https://dhilt.github.io/ngx-ui-scroll/#/experimental#adapter-relax). Both "Relax" and "Return API" are the instruments of the App-Scroller processes normalization. It might be quite important to run some logic after the Scroller finishes its job and relaxes. Below is an example of how an explicit sequence of the Adapter methods can be safely implemented: +All of the Adapter methods return Promise resolving at the moment when the scroller terminates its internal processes triggered by the invocation of correspondent Adapter method. It is called [Adapter Return API](https://dhilt.github.io/ngx-ui-scroll/adapter#return-value). This promise has exactly the same nature as the promise of the [relax method](https://dhilt.github.io/ngx-ui-scroll/experimental#adapter-relax). Both "Relax" and "Return API" are the instruments of the App-Scroller processes normalization. It might be quite important to run some logic after the Scroller finishes its job and relaxes. Below is an example of how an explicit sequence of the Adapter methods can be safely implemented: ```js const { adapter } = this.datasource; @@ -198,7 +205,7 @@ await adapter.insert({ items: [itemToReplace], before: predicate }); console.log('Replacement done'); ``` -For more information, see [Adapter demo page](https://dhilt.github.io/ngx-ui-scroll/#/adapter). +For more information, see [Adapter demo page](https://dhilt.github.io/ngx-ui-scroll/adapter). ### Development @@ -226,7 +233,7 @@ import { Datasource } from 'ngx-ui-scroll'; }); ``` -Like the experimental features, the development settings are not documented. Information about them can be obtained directly from the [source code](https://github.com/dhilt/ngx-ui-scroll/blob/master/src/component/classes/settings.ts). The uiScroll has "debug" mode with powerful logging which can be enabled via `devSettings.debug = true`. Also, with `devSettings.immediateLog = false` the console logging will be postponed until the undocumented Adapter method `showLog` is called (`datasource.adapter.showLog()`). This case could be important from the performance view: there might be too many logs and pushing them to the console output immediately could slow down the App. +The development settings are not documented. Information about them can be obtained directly from the [source code](https://github.com/dhilt/ngx-ui-scroll/blob/master/src/component/classes/settings.ts). The uiScroll has "debug" mode with powerful logging which can be enabled via `devSettings.debug = true`. Also, with `devSettings.immediateLog = false` the console logging will be postponed until the undocumented Adapter method `showLog` is called (`datasource.adapter.showLog()`). This case could be important from the performance view: there might be too many logs and pushing them to the console output immediately could slow down the App. At last, any participation is welcome, so feel free to submit new [Issues](https://github.com/dhilt/ngx-ui-scroll/issues) and open [Pull Requests](https://github.com/dhilt/ngx-ui-scroll/pulls). diff --git a/demo/app/app-routing.module.ts b/demo/app/app-routing.module.ts index 2f48cbb0..80a50d85 100644 --- a/demo/app/app-routing.module.ts +++ b/demo/app/app-routing.module.ts @@ -21,7 +21,7 @@ const routes: Routes = [ ]; @NgModule({ - imports: [RouterModule.forRoot(routes, { useHash: true })], + imports: [RouterModule.forRoot(routes, { useHash: false })], exports: [RouterModule] }) export class AppRoutingModule { diff --git a/demo/app/app.component.ts b/demo/app/app.component.ts index beeef8ff..8a5a3b3b 100644 --- a/demo/app/app.component.ts +++ b/demo/app/app.component.ts @@ -27,6 +27,9 @@ export class AppComponent implements AfterViewInit, OnDestroy { } else { document.body.classList.remove('entire-window'); } + if (!url.includes('#')) { + window.scrollTo(0, 0); + } }) ); if ('scrollRestoration' in history) { diff --git a/demo/app/demos.ts b/demo/app/demos.ts index fb5538f5..a30f7203 100644 --- a/demo/app/demos.ts +++ b/demo/app/demos.ts @@ -31,6 +31,7 @@ import { DemoIsLoadingExtendedComponent } from './samples/adapter/is-loading-ext import { DemoInsertComponent } from './samples/adapter/insert.component'; import { DemoCheckSizeComponent } from './samples/adapter/check-size.component'; import { DemoRemoveComponent } from './samples/adapter/remove.component'; +import { DemoReplaceComponent } from './samples/adapter/replace.component'; import { DemoClipComponent } from './samples/adapter/clip.component'; import { DemoViewportElementSettingComponent } from './samples/experimental/viewportElement-setting.component'; @@ -78,6 +79,7 @@ const adapter = [ DemoInsertComponent, DemoCheckSizeComponent, DemoRemoveComponent, + DemoReplaceComponent, DemoClipComponent, ]; diff --git a/demo/app/routes.ts b/demo/app/routes.ts new file mode 100644 index 00000000..d24a56d3 --- /dev/null +++ b/demo/app/routes.ts @@ -0,0 +1,288 @@ +interface IScope { + id: string; + name: string; +} + +interface IDemo extends IScope { + scope: string; +} + +interface ScopeDemo extends IScope { + map: { + [key in string]: IDemo + }; +} + +type Demos = { + [key in string]: ScopeDemo +}; + +const globalScope = { + datasource: { + id: 'datasource', + name: 'Datasource' + }, + settings: { + id: 'settings', + name: 'Settings' + }, + adapter: { + id: 'adapter', + name: 'Adapter' + }, + experimental: { + id: 'experimental', + name: 'Experimental' + }, +}; + +const datasourceScope = { + datasourceGetSignatures: { + id: 'datasource-get-signatures', + name: 'Get-method signatures', + scope: globalScope.datasource.id + }, + unlimitedBidirectional: { + id: 'unlimited-bidirectional', + name: 'Unlimited bidirectional datasource', + scope: globalScope.datasource.id + }, + limited: { + id: 'limited', + name: 'Limited datasource', + scope: globalScope.datasource.id + }, + positiveLimitedIndexes: { + id: 'positive-limited-indexes', + name: 'Positive limited datasource', + scope: globalScope.datasource.id + }, + remote: { + id: 'remote', + name: 'Remote datasource', + scope: globalScope.datasource.id + }, + invertedIndexes: { + id: 'inverted-indexes', + name: 'Inverted datasource', + scope: globalScope.datasource.id + }, + pages: { + id: 'pages', + name: 'Pages datasource', + scope: globalScope.datasource.id + }, +}; + +const settingsScope = { + noSettings: { + id: 'no-settings', + name: 'No settings', + scope: globalScope.settings.id + }, + bufferSize: { + id: 'buffer-size', + name: 'bufferSize setting', + scope: globalScope.settings.id + }, + padding: { + id: 'padding', + name: 'padding setting', + scope: globalScope.settings.id + }, + itemSize: { + id: 'item-size', + name: 'itemSize setting', + scope: globalScope.settings.id + }, + startIndex: { + id: 'start-index', + name: 'startIndex setting', + scope: globalScope.settings.id + }, + minMaxIndexes: { + id: 'min-max-indexes', + name: 'minIndex / maxIndex', + scope: globalScope.settings.id + }, + infiniteMode: { + id: 'infinite-mode', + name: 'Infinite mode', + scope: globalScope.settings.id + }, + horizontalMode: { + id: 'horizontal-mode', + name: 'Horizontal mode', + scope: globalScope.settings.id + }, + differentItemHeights: { + id: 'different-item-heights', + name: 'Different item heights', + scope: globalScope.settings.id + }, + windowViewport: { + id: 'window-viewport', + name: 'Entire window scrollable', + scope: globalScope.settings.id + }, +}; + +const adapterScope = { + returnValue: { + id: 'return-value', + name: 'Return value', + scope: globalScope.adapter.id + }, + relax: { + id: 'relax', + name: 'Relax', + scope: globalScope.adapter.id + }, + reload: { + id: 'reload', + name: 'Reload', + scope: globalScope.adapter.id + }, + reset: { + id: 'reset', + name: 'Reset', + scope: globalScope.adapter.id + }, + isLoading: { + id: 'is-loading', + name: 'Is loading?', + scope: globalScope.adapter.id + }, + isLoadingAdvanced: { + id: 'is-loading-advanced', + name: 'Is loading, advanced', + scope: globalScope.adapter.id + }, + itemsCount: { + id: 'items-count', + name: 'Buffer items counter', + scope: globalScope.adapter.id + }, + bofEof: { + id: 'bof-eof', + name: 'Begin/end of file', + scope: globalScope.adapter.id + }, + firstLastVisible: { + id: 'first-last-visible-items', + name: 'First and last visible items', + scope: globalScope.adapter.id + }, + check: { + id: 'check-size', + name: 'Check size', + scope: globalScope.adapter.id + }, + clip: { + id: 'clip', + name: 'Clip', + scope: globalScope.adapter.id + }, + appendPrepend: { + id: 'append-prepend', + name: 'Append / prepend', + scope: globalScope.adapter.id + }, + appendPrependSync: { + id: 'append-prepend-sync', + name: 'Append / prepend sync', + scope: globalScope.adapter.id + }, + remove: { + id: 'remove', + name: 'Remove', + scope: globalScope.adapter.id + }, + insert: { + id: 'insert', + name: 'Insert', + scope: globalScope.adapter.id + }, + replace: { + id: 'replace', + name: 'Replace', + scope: globalScope.adapter.id + }, +}; + +const experimentalScope = { + viewportElementSetting: { + id: 'viewportElement-setting', + name: 'viewportElement setting', + scope: globalScope.experimental.id + }, + inverseSetting: { + id: 'inverse-setting', + name: 'inverse setting', + scope: globalScope.experimental.id + }, + adapterFixPosition: { + id: 'adapter-fix-position', + name: 'Adapter fix-position method', + scope: globalScope.experimental.id + }, + adapterFixUpdater: { + id: 'adapter-fix-updater', + name: 'Adapter fix-updater method', + scope: globalScope.experimental.id + }, + adapterFixScrollToItem: { + id: 'adapter-fix-scrollToItem', + name: 'Adapter fix-scrollToItem method', + scope: globalScope.experimental.id + }, + onBeforeClipSetting: { + id: 'onBeforeClip-setting', + name: 'onBeforeClip setting', + scope: globalScope.experimental.id + }, +}; + +const demos = { + datasource: { + ...globalScope.datasource, + map: datasourceScope + }, + settings: { + ...globalScope.settings, + map: settingsScope + }, + adapter: { + ...globalScope.adapter, + map: adapterScope + }, + experimental: { + ...globalScope.experimental, + map: experimentalScope + } +}; + +const demoList = Object.values(demos).map(scope => ({ + ...scope, + map: Object.values(scope.map).map(demo => demo) +})); + +interface IRedirect { + from: string; + to: string; +} + +const getHashRedirect = (scopeId: string, demoId?: string): IRedirect => ({ + from: '#/' + scopeId + (demoId ? '#' + demoId : ''), + to: '/' + scopeId + (demoId ? '#' + demoId : '') +}); + +const redirects = Object.values(demos).reduce((acc: IRedirect[], scope) => [ + ...acc, + getHashRedirect(scope.id), + ...Object.values(scope.map).map(({ id }) => + getHashRedirect(scope.id, id) + ) +], []); + +export { IDemo, globalScope, demos, demoList, redirects }; diff --git a/demo/app/samples/adapter.component.html b/demo/app/samples/adapter.component.html index 34745350..a8b6e535 100644 --- a/demo/app/samples/adapter.component.html +++ b/demo/app/samples/adapter.component.html @@ -31,9 +31,10 @@

Angular UI Scroll Adapter Demos

+ + - - + diff --git a/demo/app/samples/adapter/adapter-relax.component.html b/demo/app/samples/adapter/adapter-relax.component.html index 59420275..9d32715c 100644 --- a/demo/app/samples/adapter/adapter-relax.component.html +++ b/demo/app/samples/adapter/adapter-relax.component.html @@ -44,7 +44,11 @@ This might be helpful if we don't want to wait until the end of current call stack.

- At last, we may use Return value API + At last, we may use + Return value API (which is a part of each Adapter method) and get rid of the second "relax". This approach is demonstrated on the last tab "Return".

diff --git a/demo/app/samples/adapter/adapter-relax.component.ts b/demo/app/samples/adapter/adapter-relax.component.ts index e06b22f2..5332bbdf 100644 --- a/demo/app/samples/adapter/adapter-relax.component.ts +++ b/demo/app/samples/adapter/adapter-relax.component.ts @@ -1,6 +1,8 @@ import { Component } from '@angular/core'; -import { DemoContext, DemoSources } from '../../shared/interfaces'; +import { demos } from '../../routes'; +import { DemoSources } from '../../shared/interfaces'; + import { Datasource } from '../../../../public_api'; @Component({ @@ -10,11 +12,11 @@ import { Datasource } from '../../../../public_api'; export class DemoAdapterRelaxComponent { demoContext = { - scope: 'adapter', - title: `Adapter relax`, - titleId: `relax`, + config: demos.adapter.map.relax, noInfo: true - } as DemoContext; + }; + + adapterReturnValueDemoConfig = demos.adapter.map.returnValue; datasource = new Datasource({ get: (index: number, count: number, success: Function) => { diff --git a/demo/app/samples/adapter/adapter-return-value.component.html b/demo/app/samples/adapter/adapter-return-value.component.html index 4a41cc3f..acc2efdd 100644 --- a/demo/app/samples/adapter/adapter-return-value.component.html +++ b/demo/app/samples/adapter/adapter-return-value.component.html @@ -1,6 +1,6 @@
- +

diff --git a/demo/app/samples/adapter/adapter-return-value.component.ts b/demo/app/samples/adapter/adapter-return-value.component.ts index c55e370b..ae7ffda9 100644 --- a/demo/app/samples/adapter/adapter-return-value.component.ts +++ b/demo/app/samples/adapter/adapter-return-value.component.ts @@ -1,18 +1,14 @@ import { Component } from '@angular/core'; -import { DemoContext } from '../../shared/interfaces'; +import { demos } from '../../routes'; @Component({ selector: 'app-demo-adapter-return-value', templateUrl: './adapter-return-value.component.html' }) export class DemoAdapterReturnValueComponent { - demoContext = { - scope: 'adapter', - title: `Return value`, - titleId: `return-value`, - noWorkView: true - } as DemoContext; + + demoConfig = demos.adapter.map.returnValue; returnValueType = ` Promise<{ success: boolean, diff --git a/demo/app/samples/adapter/append-prepend-sync.component.html b/demo/app/samples/adapter/append-prepend-sync.component.html index f42e0c43..86b85223 100644 --- a/demo/app/samples/adapter/append-prepend-sync.component.html +++ b/demo/app/samples/adapter/append-prepend-sync.component.html @@ -16,8 +16,11 @@ have eof/bof parameter which is optional and which allows to prevent rendering of new items in the Viewport when the end of the dataset (if we are speaking of append) or - beginning of the dataset (prepend case) is not reached. - See also bof/eof demo. + beginning of the dataset (prepend case) is not reached. See also + bof/eof demo.

Let's discuss {{prependCallSample}} case. @@ -43,8 +46,14 @@ the main idea is to synchronize, say, remote dataset changes with the uiScroll state visible to the end user. Another examples of such synchronization could be found in - remove and - insert demos. + remove and + insert.

diff --git a/demo/app/samples/adapter/append-prepend-sync.component.ts b/demo/app/samples/adapter/append-prepend-sync.component.ts index 0737b324..d22885aa 100644 --- a/demo/app/samples/adapter/append-prepend-sync.component.ts +++ b/demo/app/samples/adapter/append-prepend-sync.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { doLog } from '../../shared/datasource-get'; @@ -12,14 +13,14 @@ import { Datasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoAppendPrependSyncComponent { demoContext: DemoContext = { - scope: 'adapter', - title: `Append / prepend sync`, - titleId: `append-prepend-sync`, + config: demos.adapter.map.appendPrependSync, viewportId: `append-prepend-sync-viewport`, count: 0, log: '' }; + adapterScope = demos.adapter; + MIN = 100; MAX = 200; data: any[]; @@ -78,7 +79,8 @@ datasource = new Datasource({ } }); -doAppend() { +async doAppend() { + await this.datasource.adapter.relax(); const items = []; for (let i = 0; i < this.inputValue; i++) { this.MAX++; @@ -89,10 +91,11 @@ doAppend() { this.data.push(newItem); items.push(newItem); } - this.datasource.adapter.append({ items, eof: true }); + await this.datasource.adapter.append({ items, eof: true }); } -doPrepend() { +async doPrepend() { + await this.datasource.adapter.relax(); const items = []; for (let i = 0; i < this.inputValue; i++) { this.MIN--; @@ -103,7 +106,7 @@ doPrepend() { this.data.unshift(newItem); items.push(newItem); } - this.datasource.adapter.prepend({ items, bof: true}); + await this.datasource.adapter.prepend({ items, bof: true}); }` }, { active: true, @@ -141,7 +144,8 @@ doPrepend() { this.inputValue = value; } - doAppend() { + async doAppend() { + await this.datasource.adapter.relax(); const items = []; for (let i = 0; i < this.inputValue; i++) { this.MAX++; @@ -152,10 +156,11 @@ doPrepend() { this.data.push(newItem); items.push(newItem); } - this.datasource.adapter.append({ items, eof: true }); + await this.datasource.adapter.append({ items, eof: true }); } - doPrepend() { + async doPrepend() { + await this.datasource.adapter.relax(); const items = []; for (let i = 0; i < this.inputValue; i++) { this.MIN--; @@ -166,7 +171,7 @@ doPrepend() { this.data.unshift(newItem); items.push(newItem); } - this.datasource.adapter.prepend({ items, bof: true}); + await this.datasource.adapter.prepend({ items, bof: true }); } } diff --git a/demo/app/samples/adapter/append-prepend.component.ts b/demo/app/samples/adapter/append-prepend.component.ts index 7b4178d4..0683384a 100644 --- a/demo/app/samples/adapter/append-prepend.component.ts +++ b/demo/app/samples/adapter/append-prepend.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { doLog } from '../../shared/datasource-get'; @@ -12,9 +13,7 @@ import { Datasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoAppendPrependComponent { demoContext: DemoContext = { - scope: 'adapter', - title: `Append / prepend`, - titleId: `append-prepend`, + config: demos.adapter.map.appendPrepend, viewportId: `append-prepend-viewport`, count: 0, log: '' @@ -70,14 +69,16 @@ generateItems(isPrepend: boolean) { return items; } -doPrepend() { - this.datasource.adapter.prepend({ +async doPrepend() { + await this.datasource.adapter.relax(); + await this.datasource.adapter.prepend({ items: this.generateItems(true) }); } -doAppend() { - this.datasource.adapter.append({ +async doAppend() { + await this.datasource.adapter.relax(); + await this.datasource.adapter.append({ items: this.generateItems(false) }); }` @@ -128,14 +129,16 @@ doAppend() { return items; } - doPrepend() { - this.datasource.adapter.prepend({ + async doPrepend() { + await this.datasource.adapter.relax(); + await this.datasource.adapter.prepend({ items: this.generateItems(true) }); } - doAppend() { - this.datasource.adapter.append({ + async doAppend() { + await this.datasource.adapter.relax(); + await this.datasource.adapter.append({ items: this.generateItems(false) }); } diff --git a/demo/app/samples/adapter/bof-eof.component.ts b/demo/app/samples/adapter/bof-eof.component.ts index e066dc2c..86b94d67 100644 --- a/demo/app/samples/adapter/bof-eof.component.ts +++ b/demo/app/samples/adapter/bof-eof.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { merge } from 'rxjs'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { doLog } from '../../shared/datasource-get'; @@ -13,9 +14,7 @@ import { Datasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoBofEofComponent { demoContext: DemoContext = { - scope: 'adapter', - title: `Begin / end of file`, - titleId: `bof-eof`, + config: demos.adapter.map.bofEof, viewportId: `bof-eof-viewport`, count: 0, log: '' diff --git a/demo/app/samples/adapter/check-size.component.ts b/demo/app/samples/adapter/check-size.component.ts index 5416a912..8e1062b6 100644 --- a/demo/app/samples/adapter/check-size.component.ts +++ b/demo/app/samples/adapter/check-size.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; -import { Subscription } from 'rxjs'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { doLog } from '../../shared/datasource-get'; @@ -13,9 +13,7 @@ import { Datasource } from '../../../../public_api'; export class DemoCheckSizeComponent { demoContext: DemoContext = { - scope: 'adapter', - title: `Check size`, - titleId: `check-size`, + config: demos.adapter.map.check, viewportId: `check-size-viewport`, count: 0, log: '' @@ -85,7 +83,7 @@ datasource = new Datasource ({ }); findElement(index: number): HTMLElement | null { - const viewportElement = document.getElementById(this.demoContext.viewportId); + const viewportElement = document.getElementsByClassName('viewport')[0]; return viewportElement ? viewportElement.querySelector(\`[data-sid="\${index}"]\`) : null; @@ -165,7 +163,8 @@ First visible item's index: {{datasource.adapter.firstVisible.$index}} }]; findElement(index: number): HTMLElement | null { - const viewportElement = document.getElementById(this.demoContext.viewportId); + const viewportId = this.demoContext.viewportId || this.demoContext.config.id; + const viewportElement = document.getElementById(viewportId); return viewportElement ? viewportElement.querySelector(`[data-sid="${index}"]`) : null; @@ -190,7 +189,8 @@ First visible item's index: {{datasource.adapter.firstVisible.$index}} autoscroll(index: number) { const element = this.findElement(index); - const viewportElement = document.getElementById(this.demoContext.viewportId); + const viewportId = this.demoContext.viewportId || this.demoContext.config.id; + const viewportElement = document.getElementById(viewportId); if (!element || !viewportElement) { return; } diff --git a/demo/app/samples/adapter/clip.component.ts b/demo/app/samples/adapter/clip.component.ts index 471169ee..ed8af9bc 100644 --- a/demo/app/samples/adapter/clip.component.ts +++ b/demo/app/samples/adapter/clip.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; @@ -12,9 +13,7 @@ import { Datasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoClipComponent { demoContext: DemoContext = { - scope: 'adapter', - title: `Clip`, - titleId: `clip`, + config: demos.adapter.map.clip, viewportId: `clip-viewport`, count: 0, log: '' @@ -42,8 +41,9 @@ export class DemoClipComponent { } }); -doClip() { - this.datasource.adapter.clip(); +async doClip() { + await this.datasource.adapter.relax(); + await this.datasource.adapter.clip(); }` }, { active: true, @@ -75,8 +75,9 @@ doClip() { clipOptionsSample = `{ forwardOnly: true }`; - doClip() { - this.datasource.adapter.clip(); + async doClip() { + await this.datasource.adapter.relax(); + await this.datasource.adapter.clip(); } } diff --git a/demo/app/samples/adapter/first-last-visible-items.component.ts b/demo/app/samples/adapter/first-last-visible-items.component.ts index 572e4f12..b6dfca4f 100644 --- a/demo/app/samples/adapter/first-last-visible-items.component.ts +++ b/demo/app/samples/adapter/first-last-visible-items.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { combineLatest } from 'rxjs'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; @@ -13,9 +14,7 @@ import { Datasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoFirstLastVisibleItemsComponent { demoContext: DemoContext = { - scope: 'adapter', - title: `First and last visible items`, - titleId: `first-last-visible-items`, + config: demos.adapter.map.firstLastVisible, viewportId: `first-last-visible-items-viewport`, count: 0, log: '' diff --git a/demo/app/samples/adapter/insert.component.html b/demo/app/samples/adapter/insert.component.html index a34c73d4..26b7883d 100644 --- a/demo/app/samples/adapter/insert.component.html +++ b/demo/app/samples/adapter/insert.component.html @@ -1,6 +1,6 @@
- +
diff --git a/demo/app/samples/adapter/insert.component.ts b/demo/app/samples/adapter/insert.component.ts index c38d88ae..b59a2a20 100644 --- a/demo/app/samples/adapter/insert.component.ts +++ b/demo/app/samples/adapter/insert.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { demos } from '../../routes'; +import { DemoSources, DemoSourceType } from '../../shared/interfaces'; import { Datasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; @@ -10,12 +11,7 @@ import { Datasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; }) export class DemoInsertComponent { - demoContext = { - scope: 'adapter', - title: `Insert`, - titleId: `insert`, - viewportId: `insert-viewport` - } as DemoContext; + demoConfig = demos.adapter.map.insert; MIN = 1; MAX = 100; @@ -106,8 +102,9 @@ datasource = new Datasource({ ` }, { name: 'Increase', - text: ` -doInsert() { + text: ` +async doInsert() { + await this.datasource.adapter.relax(); const count = Number(this.inputCount); // first input const itemData = 'item #' + this.inputIndex; // second input const index = this.data.indexOf(itemData); @@ -125,7 +122,7 @@ doInsert() { ...items, ...this.data.slice(index) ]; - this.datasource.adapter.insert({ + await this.datasource.adapter.insert({ after: ({ data }) => data === itemData, items }); @@ -134,7 +131,8 @@ doInsert() { }, { name: 'Decrease', text: ` -doInsert() { +async doInsert() { + await this.datasource.adapter.relax(); const count = Number(this.inputCount); // first input const itemData = 'item #' + this.inputIndex; // second input const index = this.data.indexOf(itemData); @@ -152,7 +150,7 @@ doInsert() { ...items, ...this.data.slice(index) ]; - this.datasource.adapter.insert({ + await this.datasource.adapter.insert({ before: ({ data }) => data === itemData, items, decrease: true @@ -168,7 +166,8 @@ doInsert() { decrease?: boolean; }`; - doInsert() { + async doInsert() { + await this.datasource.adapter.relax(); const itemData = `item #${this.inputIndex}`; const index = this.data.indexOf(itemData); const count = Number(this.inputCount); @@ -186,13 +185,14 @@ doInsert() { ...items, ...this.data.slice(index) ]; - this.datasource.adapter.insert({ + await this.datasource.adapter.insert({ after: ({ data }) => data === itemData, items }); } - doInsert2() { + async doInsert2() { + await this.datasource2.adapter.relax(); const itemData = `item #${this.inputIndex2}`; const index = this.data.indexOf(itemData); const count = Number(this.inputCount2); @@ -210,7 +210,7 @@ doInsert() { ...items, ...this.data2.slice(index) ]; - this.datasource2.adapter.insert({ + await this.datasource2.adapter.insert({ before: ({ data }) => data === itemData, items, decrease: true diff --git a/demo/app/samples/adapter/is-loading-extended.component.ts b/demo/app/samples/adapter/is-loading-extended.component.ts index 8498de47..f615d623 100644 --- a/demo/app/samples/adapter/is-loading-extended.component.ts +++ b/demo/app/samples/adapter/is-loading-extended.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; @@ -12,9 +13,7 @@ import { Datasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoIsLoadingExtendedComponent { demoContext: DemoContext = { - scope: 'adapter', - title: `Is loading, advanced`, - titleId: `is-loading-advanced`, + config: demos.adapter.map.isLoadingAdvanced, viewportId: `is-loading-advanced-viewport`, count: 0, log: '' diff --git a/demo/app/samples/adapter/is-loading.component.ts b/demo/app/samples/adapter/is-loading.component.ts index 8407a121..4509f525 100644 --- a/demo/app/samples/adapter/is-loading.component.ts +++ b/demo/app/samples/adapter/is-loading.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; @@ -12,9 +13,7 @@ import { Datasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoIsLoadingComponent { demoContext: DemoContext = { - scope: 'adapter', - title: `Is loading?`, - titleId: `is-loading`, + config: demos.adapter.map.isLoading, viewportId: `is-loading-viewport`, count: 0, log: '' diff --git a/demo/app/samples/adapter/items-count.component.ts b/demo/app/samples/adapter/items-count.component.ts index df059e89..eae69605 100644 --- a/demo/app/samples/adapter/items-count.component.ts +++ b/demo/app/samples/adapter/items-count.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; @@ -12,9 +13,7 @@ import { Datasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoItemsCountComponent { demoContext: DemoContext = { - scope: 'adapter', - title: `Buffer items counter`, - titleId: `items-count`, + config: demos.adapter.map.itemsCount, viewportId: `items-count-viewport`, count: 0, log: '' diff --git a/demo/app/samples/adapter/reload.component.html b/demo/app/samples/adapter/reload.component.html index d3ae9c50..d2b2fefb 100644 --- a/demo/app/samples/adapter/reload.component.html +++ b/demo/app/samples/adapter/reload.component.html @@ -21,8 +21,11 @@ If index argument is not set, settings.startIndex will be used as the first index. If neither index argument not startIndex setting is present, - the default value 1 will be used. - See also startIndex setting description. + the default value 1 will be used. See also + startIndex setting description.

The Adapter.reload method is safe and needs not to be protected diff --git a/demo/app/samples/adapter/reload.component.ts b/demo/app/samples/adapter/reload.component.ts index 731aeba1..4d6314c6 100644 --- a/demo/app/samples/adapter/reload.component.ts +++ b/demo/app/samples/adapter/reload.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; @@ -12,14 +13,14 @@ import { Datasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoReloadComponent { demoContext: DemoContext = { - scope: 'adapter', - title: `Reload`, - titleId: `reload`, + config: demos.adapter.map.reload, viewportId: `reload-viewport`, count: 0, log: '' }; + startIndexDemoConfig = demos.settings.map.startIndex; + datasource = new Datasource({ get: datasourceGetCallbackInfinite(this.demoContext) }); diff --git a/demo/app/samples/adapter/remove.component.ts b/demo/app/samples/adapter/remove.component.ts index f4cb9a6e..c88c343a 100644 --- a/demo/app/samples/adapter/remove.component.ts +++ b/demo/app/samples/adapter/remove.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { doLog } from '../../shared/datasource-get'; @@ -12,9 +13,7 @@ import { Datasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoRemoveComponent { demoContext: DemoContext = { - scope: 'adapter', - title: `Remove`, - titleId: `remove`, + config: demos.adapter.map.remove, viewportId: `remove-viewport`, addClass: `remove`, count: 0, @@ -84,6 +83,7 @@ removeFromDatasource(toRemove: number, byIndex = false) { } async removeById(id: number) { + await this.datasource.adapter.relax(); this.removeFromDatasource(id); await this.datasource.adapter.remove({ predicate: ({ data }) => data.id === id @@ -91,6 +91,7 @@ async removeById(id: number) { } async removeByIndex(index: number) { + await this.datasource.adapter.relax(); this.removeFromDatasource(index, true); await this.datasource.adapter.remove({ predicate: ({ $index }) => $index === index @@ -160,6 +161,7 @@ async removeByIndex(index: number) { } async removeById(id: number) { + await this.datasource.adapter.relax(); this.removeFromDatasource(id); await this.datasource.adapter.remove({ predicate: ({ data }) => data.id === id @@ -167,6 +169,7 @@ async removeByIndex(index: number) { } async removeByIndex(index: number) { + await this.datasource.adapter.relax(); this.removeFromDatasource(index, true); await this.datasource.adapter.remove({ predicate: ({ $index }) => $index === index diff --git a/demo/app/samples/adapter/replace.component.html b/demo/app/samples/adapter/replace.component.html new file mode 100644 index 00000000..f5fb13f3 --- /dev/null +++ b/demo/app/samples/adapter/replace.component.html @@ -0,0 +1,57 @@ + + {{index}}) {{item.text}} + + + +

+ +
+
+

+ Adapter.replace method allows to perform many-to-many in-Buffer replacement. + It acts as a combination of insert and remove operations working in a single run, + which means minimal latency in comparison to applying + Adapter.insert and Adapter.remove methods one by one. + The argument of this method is an object of the following type: +

+
{{argumentsDescription}}
+

+ The predicate option is exactly the same as in + Adapter-remove and case. + The indexes options is exactly the same as in the + Adapter-insert case. + The fixRight option allows to change + the default indexing strategy. + The default fixRight value is false, + which means the indexes of the items following the replaced ones + will be changed during the replacement. + Otherwise, if fixRight is set true, + the indexes of the items preceding the replaced ones will be changed. +

+

+ Current limitations of the Adapter.replace method: + no virtual replacements are possible and only continues series are allowed. + So only a portion of items that are currently in the uiScroll Buffer + can be replaced with this method. + And this portion must consist of a continuous list of items. +

+

+ In this demo we are replacing 3 items with indexes [3, 4, 5] with 2 new items. + Switching to "fixRight" strategy is not implemented here, + in order to keep natural indexes flow as simple as possible. + Note, before running the "replace" operation over the uiScroll Buffer, + the Datasource gets the update as well. +

+
+ diff --git a/demo/app/samples/adapter/replace.component.ts b/demo/app/samples/adapter/replace.component.ts new file mode 100644 index 00000000..311070cc --- /dev/null +++ b/demo/app/samples/adapter/replace.component.ts @@ -0,0 +1,145 @@ +import { Component } from '@angular/core'; + +import { demos } from '../../routes'; +import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { doLog } from '../../shared/datasource-get'; + +import { Datasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; + +interface Item { + text: string; +} + +@Component({ + selector: 'app-demo-replace', + templateUrl: './replace.component.html' +}) +export class DemoReplaceComponent { + + demoContext: DemoContext = { + config: demos.adapter.map.replace, + viewportId: `replace-viewport`, + addClass: `replace`, + count: 0, + log: '' + }; + + adapterScope = demos.adapter; + + MAX = 100; + data: Item[]; + + constructor() { + this.data = []; + for (let i = 0; i < this.MAX; i++) { + this.data.push({ text: 'item #' + (i + 1) }); + } + } + + datasource = new Datasource({ + get: (start: number, count: number, success: Function) => { + let data: Item[] = []; + const end = Math.min(start + count - 1, this.data.length - 1); + if (start <= end) { + data = this.data.slice(start, end + 1); + } + doLog(this.demoContext, start, count, data.length); + success(data); + }, + settings: { + startIndex: 0 + } + }); + + sources: DemoSources = [{ + name: DemoSourceType.Component, + text: `MAX = 100; +data: Item[]; + +constructor() { + this.data = []; + for (let i = 0; i < this.MAX; i++) { + this.data.push({ text: 'item #' + (i + 1) }); + } +} + +datasource = new Datasource({ + get: (start: number, count: number, success: Function) => { + let data: Item[] = []; + const end = Math.min(start + count - 1, this.data.length - 1); + if (start <= end) { + data = this.data.slice(start, end + 1); + } + success(data); + }, + settings: { + startIndex: 0 + } +}); + +async doReplace() { + await this.datasource.adapter.relax(); + const toRemove = [3, 4, 5]; + const newItems: Item[] = [{ text: 'X' }, { text: 'Y' }]; + this.data = [ + ...this.data.slice(0, toRemove[0]), + ...newItems, + ...this.data.slice(toRemove[toRemove.length - 1]) + ]; + await this.datasource.adapter.replace({ + predicate: ({ $index }) => toRemove.includes($index), + items: newItems + }); +}` + }, { + active: true, + name: DemoSourceType.Template, + text: ` + +
+
+
+ {{index}} + {{item.text}} +
+
+
` + }, { + name: DemoSourceType.Styles, + text: `.viewport { + width: 150px; + height: 250px; + overflow-y: auto; +} +.item { + font-weight: bold; + height: 25px; +} +.index { + font-weight: normal; + font-size: smaller; +}` + }]; + + argumentsDescription = ` AdapterReplaceOptions { + predicate: ItemsPredicate; + items: any[]; + fixRight?: boolean; + }`; + + async doReplace() { + await this.datasource.adapter.relax(); + const toRemove = [3, 4, 5]; + const newItems: Item[] = [{ text: 'X' }, { text: 'Y' }]; + this.data = [ + ...this.data.slice(0, toRemove[0]), + ...newItems, + ...this.data.slice(toRemove[toRemove.length - 1]) + ]; + await this.datasource.adapter.replace({ + predicate: ({ $index }) => toRemove.includes($index), + items: newItems + }); + } + +} diff --git a/demo/app/samples/adapter/reset.component.ts b/demo/app/samples/adapter/reset.component.ts index c8dd2c7a..8a0c45f3 100644 --- a/demo/app/samples/adapter/reset.component.ts +++ b/demo/app/samples/adapter/reset.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; @@ -12,9 +13,7 @@ import { Datasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoResetComponent { demoContext: DemoContext = { - scope: 'adapter', - title: `Reset`, - titleId: `reset`, + config: demos.adapter.map.reset, viewportId: `reset-viewport`, count: 0, log: '' @@ -52,7 +51,7 @@ doReset() { }, { active: true, name: DemoSourceType.Template, - text: ` + text: ` - new start index - new buffer size diff --git a/demo/app/samples/common/basic.component.ts b/demo/app/samples/common/basic.component.ts index 7bad5fe7..0ba34dc6 100644 --- a/demo/app/samples/common/basic.component.ts +++ b/demo/app/samples/common/basic.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; @@ -12,9 +13,7 @@ import { IDatasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoBasicComponent { demoContext: DemoContext = { - scope: 'settings', - title: `No settings`, - titleId: `no-settings`, + config: demos.settings.map.noSettings, viewportId: `no-settings-viewport`, count: 0, log: '' diff --git a/demo/app/samples/common/buffer-size.component.ts b/demo/app/samples/common/buffer-size.component.ts index ff445a5d..b90a0455 100644 --- a/demo/app/samples/common/buffer-size.component.ts +++ b/demo/app/samples/common/buffer-size.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; @@ -12,9 +13,7 @@ import { IDatasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoBufferSizeComponent { demoContext: DemoContext = { - scope: 'settings', - title: `bufferSize setting`, - titleId: `buffer-size`, + config: demos.settings.map.bufferSize, viewportId: `buffer-size-viewport`, count: 0, log: '' diff --git a/demo/app/samples/common/different-heights.component.ts b/demo/app/samples/common/different-heights.component.ts index ed0cc7c9..25e2a725 100644 --- a/demo/app/samples/common/different-heights.component.ts +++ b/demo/app/samples/common/different-heights.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { datasourceGetCallbackLimited } from '../../shared/datasource-get'; @@ -12,9 +13,7 @@ import { IDatasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoDifferentHeightsComponent { demoContext: DemoContext = { - scope: 'settings', - title: `Different item heights`, - titleId: `different-item-heights`, + config: demos.settings.map.differentItemHeights, viewportId: `different-heights-viewport`, count: 0, log: '' diff --git a/demo/app/samples/common/horizontal.component.ts b/demo/app/samples/common/horizontal.component.ts index 6de15720..06b79b45 100644 --- a/demo/app/samples/common/horizontal.component.ts +++ b/demo/app/samples/common/horizontal.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; @@ -12,9 +13,7 @@ import { IDatasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoHorizontalComponent { demoContext: DemoContext = { - scope: 'settings', - title: `Horizontal mode`, - titleId: `horizontal-mode`, + config: demos.settings.map.horizontalMode, viewportId: `horizontal-viewport`, addClass: `horizontal`, count: 0, diff --git a/demo/app/samples/common/infinite.component.ts b/demo/app/samples/common/infinite.component.ts index b8837eb4..7e1fced8 100644 --- a/demo/app/samples/common/infinite.component.ts +++ b/demo/app/samples/common/infinite.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; @@ -12,9 +13,7 @@ import { IDatasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoInfiniteComponent { demoContext: DemoContext = { - scope: 'settings', - title: `Infinite mode`, - titleId: `infinite-mode`, + config: demos.settings.map.infiniteMode, viewportId: `infinite-viewport`, count: 0, log: '' diff --git a/demo/app/samples/common/item-size.component.ts b/demo/app/samples/common/item-size.component.ts index 07b5a146..0b0f77ea 100644 --- a/demo/app/samples/common/item-size.component.ts +++ b/demo/app/samples/common/item-size.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; @@ -12,9 +13,7 @@ import { IDatasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoItemSizeComponent { demoContext: DemoContext = { - scope: 'settings', - title: `itemSize setting`, - titleId: `item-size`, + config: demos.settings.map.itemSize, viewportId: `item-size-viewport`, count: 0, log: '' diff --git a/demo/app/samples/common/min-max-indexes.component.html b/demo/app/samples/common/min-max-indexes.component.html index 861e2450..5a86c33f 100644 --- a/demo/app/samples/common/min-max-indexes.component.html +++ b/demo/app/samples/common/min-max-indexes.component.html @@ -28,7 +28,10 @@ minIndex should be >= MIN and maxIndex should be <= MAX. An example of limited Datasource.get implementation can be found - here. + here.

The uiScroll can accept only one of min/max indexes settings. diff --git a/demo/app/samples/common/min-max-indexes.component.ts b/demo/app/samples/common/min-max-indexes.component.ts index 9456cd70..27f598d5 100644 --- a/demo/app/samples/common/min-max-indexes.component.ts +++ b/demo/app/samples/common/min-max-indexes.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; @@ -12,14 +13,14 @@ import { IDatasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoMinMaxIndexesComponent { demoContext: DemoContext = { - scope: 'settings', - title: `minIndex / maxIndex settings`, - titleId: `min-max-indexes`, + config: demos.settings.map.minMaxIndexes, viewportId: `min-max-indexes-viewport`, count: 0, log: '' }; + datasourceLimitedDemoConfig = demos.datasource.map.limited; + datasource: IDatasource = { get: datasourceGetCallbackInfinite(this.demoContext), settings: { diff --git a/demo/app/samples/common/padding.component.ts b/demo/app/samples/common/padding.component.ts index 9360c655..a93e1ea4 100644 --- a/demo/app/samples/common/padding.component.ts +++ b/demo/app/samples/common/padding.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; @@ -12,9 +13,7 @@ import { IDatasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoPaddingComponent { demoContext: DemoContext = { - scope: 'settings', - title: `padding setting`, - titleId: `padding`, + config: demos.settings.map.padding, viewportId: `padding-viewport`, count: 0, log: '' diff --git a/demo/app/samples/common/start-index.component.ts b/demo/app/samples/common/start-index.component.ts index 29bcdb9e..4e05ca0b 100644 --- a/demo/app/samples/common/start-index.component.ts +++ b/demo/app/samples/common/start-index.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; @@ -12,9 +13,7 @@ import { IDatasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoStartIndexComponent { demoContext: DemoContext = { - scope: 'settings', - title: `startIndex setting`, - titleId: `start-index`, + config: demos.settings.map.startIndex, viewportId: `start-index-viewport`, count: 0, log: '' diff --git a/demo/app/samples/common/window-viewport.component.ts b/demo/app/samples/common/window-viewport.component.ts index d7909c9b..06a5da43 100644 --- a/demo/app/samples/common/window-viewport.component.ts +++ b/demo/app/samples/common/window-viewport.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; @Component({ @@ -9,9 +10,7 @@ import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interface export class DemoWindowViewportComponent { demoContext: DemoContext = { - scope: 'settings', - title: `Entire window scrollable`, - titleId: `window-viewport`, + config: demos.settings.map.windowViewport, viewportId: `window-viewport-viewport`, noWorkView: true, count: 0, diff --git a/demo/app/samples/datasource.component.html b/demo/app/samples/datasource.component.html index ac0abd85..2d8c674f 100644 --- a/demo/app/samples/datasource.component.html +++ b/demo/app/samples/datasource.component.html @@ -25,7 +25,7 @@

Angular UI Scroll Datasource Demos

The constructor argument in the second case should be an object of IDatasource type. Instantiating via operator new is needed for the Adapter to be available on the result datasource object - (for details see Adapter demo page). + (for details see Adapter demo page). Both of IDatasource and Datasource definitions could be imported from UiScrollModule:

@@ -39,7 +39,7 @@

Angular UI Scroll Datasource Demos

With the help of settings object the scroller could be configured, - as it is  described on Settings demo page. + as it is  described on Settings demo page. Here we are going to discuss the Datasource.get method implementation.

diff --git a/demo/app/samples/datasource.component.ts b/demo/app/samples/datasource.component.ts index 7c2df500..b0491eb7 100644 --- a/demo/app/samples/datasource.component.ts +++ b/demo/app/samples/datasource.component.ts @@ -1,11 +1,15 @@ import { Component } from '@angular/core'; +import { demos } from '../routes'; + @Component({ selector: 'app-datasource', templateUrl: './datasource.component.html' }) export class DatasourceComponent { + demos = demos; + constructor() { } diff --git a/demo/app/samples/datasource/bidirectional-unlimited-datasource.component.ts b/demo/app/samples/datasource/bidirectional-unlimited-datasource.component.ts index 4ed95184..d9a484e6 100644 --- a/demo/app/samples/datasource/bidirectional-unlimited-datasource.component.ts +++ b/demo/app/samples/datasource/bidirectional-unlimited-datasource.component.ts @@ -1,7 +1,9 @@ import { Component } from '@angular/core'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { demos } from '../../routes'; +import { DemoSources, DemoSourceType } from '../../shared/interfaces'; import { doLog } from '../../shared/datasource-get'; + import { IDatasource } from '../../../../public_api'; @Component({ @@ -11,13 +13,11 @@ import { IDatasource } from '../../../../public_api'; export class DemoBidirectionalUnlimitedDatasourceComponent { demoContext = { - scope: 'datasource', - title: `Unlimited bidirectional datasource`, - titleId: `unlimited-bidirectional`, + config: demos.datasource.map.unlimitedBidirectional, logViewOnly: true, log: '', count: 0 - } as DemoContext; + }; datasource: IDatasource = { get: (index: number, count: number, success: Function) => { diff --git a/demo/app/samples/datasource/datasource-signatures.component.ts b/demo/app/samples/datasource/datasource-signatures.component.ts index 623ccae7..f812e0b6 100644 --- a/demo/app/samples/datasource/datasource-signatures.component.ts +++ b/demo/app/samples/datasource/datasource-signatures.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { demos } from '../../routes'; +import { DemoSources, DemoSourceType } from '../../shared/interfaces'; @Component({ selector: 'app-datasource-signatures', @@ -9,11 +10,9 @@ import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interface export class DemoDatasourceSignaturesComponent { demoContext = { - scope: 'datasource', - title: `Datasource get-method signatures`, - titleId: `datasource-get-signatures`, + config: demos.datasource.map.datasourceGetSignatures, noWorkView: true - } as DemoContext; + }; sources: DemoSources = [{ name: DemoSourceType.Datasource, diff --git a/demo/app/samples/datasource/inverted-datasource.component.html b/demo/app/samples/datasource/inverted-datasource.component.html index ab020a85..5d0cba0d 100644 --- a/demo/app/samples/datasource/inverted-datasource.component.html +++ b/demo/app/samples/datasource/inverted-datasource.component.html @@ -1,6 +1,6 @@
- +
diff --git a/demo/app/samples/datasource/inverted-datasource.component.ts b/demo/app/samples/datasource/inverted-datasource.component.ts index 01fce375..58eb7f6e 100644 --- a/demo/app/samples/datasource/inverted-datasource.component.ts +++ b/demo/app/samples/datasource/inverted-datasource.component.ts @@ -1,6 +1,8 @@ import { Component } from '@angular/core'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { demos } from '../../routes'; +import { DemoSources, DemoSourceType } from '../../shared/interfaces'; + import { IDatasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; @Component({ @@ -9,12 +11,7 @@ import { IDatasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; }) export class DemoInvertedDatasourceComponent { - context = { - scope: 'datasource', - title: `Inverted datasource`, - titleId: `inverted-indexes`, - noWorkView: true - } as DemoContext; + demoConfig = demos.datasource.map.invertedIndexes; MIN = 1; diff --git a/demo/app/samples/datasource/limited-datasource.component.html b/demo/app/samples/datasource/limited-datasource.component.html index fe4c2f3d..a2c9eb74 100644 --- a/demo/app/samples/datasource/limited-datasource.component.html +++ b/demo/app/samples/datasource/limited-datasource.component.html @@ -13,7 +13,10 @@

Another option of how to limit the datasource is to use - minIndex and maxIndex settings; + minIndex and maxIndex settings; this way the implementation of the Datasource.get method could remain unlimited as the uiScroll will not request for items that are out of [minIndex; maxIndex] range. diff --git a/demo/app/samples/datasource/limited-datasource.component.ts b/demo/app/samples/datasource/limited-datasource.component.ts index eb260abe..5c6b217c 100644 --- a/demo/app/samples/datasource/limited-datasource.component.ts +++ b/demo/app/samples/datasource/limited-datasource.component.ts @@ -1,9 +1,11 @@ import { Component } from '@angular/core'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; -import { IDatasource } from '../../../../public_api'; +import { demos } from '../../routes'; +import { DemoSources, DemoSourceType } from '../../shared/interfaces'; import { doLog } from '../../shared/datasource-get'; +import { IDatasource } from '../../../../public_api'; + @Component({ selector: 'app-limited-datasource', templateUrl: './limited-datasource.component.html' @@ -11,13 +13,13 @@ import { doLog } from '../../shared/datasource-get'; export class DemoLimitedDatasourceComponent { demoContext = { - scope: 'datasource', - title: `Limited datasource`, - titleId: `limited`, + config: demos.datasource.map.limited, logViewOnly: true, log: '', count: 0 - } as DemoContext; + }; + + minMaxDemoConfig = demos.settings.map.minMaxIndexes; MIN = -99; MAX = 900; diff --git a/demo/app/samples/datasource/pages-datasource.component.ts b/demo/app/samples/datasource/pages-datasource.component.ts index b2b1fda4..9757bb4e 100644 --- a/demo/app/samples/datasource/pages-datasource.component.ts +++ b/demo/app/samples/datasource/pages-datasource.component.ts @@ -1,6 +1,8 @@ import { Component } from '@angular/core'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { demos } from '../../routes'; +import { DemoSources, DemoSourceType } from '../../shared/interfaces'; + import { IDatasource } from '../../../../public_api'; @Component({ @@ -10,12 +12,10 @@ import { IDatasource } from '../../../../public_api'; export class DemoPagesDatasourceComponent { demoContext = { - scope: 'datasource', - title: `Pages datasource`, - titleId: `pages`, + config: demos.datasource.map.pages, logViewOnly: true, log: '' - } as DemoContext; + }; private getCount = 0; private pagesCount = 30; @@ -65,7 +65,7 @@ export class DemoPagesDatasourceComponent { } this.demoContext.log = `${this.getCount}.3 requesting pages: ${logPages.join(', ')}\n` + this.demoContext.log; this.demoContext.log = `${this.getCount}.4 ` + (!pagesResult.length ? 'empty result' : - `pages result [${pagesResult[0].index}..${pagesResult[pagesResult.length - 1].index}]` + `pages result [${pagesResult[0].index}..${pagesResult[pagesResult.length - 1].index}]` ) + '\n' + this.demoContext.log; // slicing pages result to satisfy start/end indexes @@ -73,7 +73,7 @@ export class DemoPagesDatasourceComponent { const end = start + endIndex - startIndex + 1; const data = pagesResult.slice(start, end); this.demoContext.log = (!data.length ? '' : - `${this.getCount}.5 sliced result [${data[0].index}..${data[data.length - 1].index}]\n` + `${this.getCount}.5 sliced result [${data[0].index}..${data[data.length - 1].index}]\n` ) + this.demoContext.log; success(data); @@ -142,9 +142,9 @@ getDataPage(page: number) { return this.data[page]; }` }, - { - name: DemoSourceType.Component + ' (async)', - text: `datasource: IDatasource = { + { + name: DemoSourceType.Component + ' (async)', + text: `datasource: IDatasource = { get: (index, count, success) => { const startIndex = Math.max(index, 0); const endIndex = index + count - 1; @@ -180,7 +180,7 @@ constructor( private remoteDataService: RemoteDataService ) { }` - }]; + }]; getDataPage(page: number) { if (page < 0 || page >= this.pagesCount) { diff --git a/demo/app/samples/datasource/positive-limited-datasource.component.html b/demo/app/samples/datasource/positive-limited-datasource.component.html index 1555cd8e..da47393a 100644 --- a/demo/app/samples/datasource/positive-limited-datasource.component.html +++ b/demo/app/samples/datasource/positive-limited-datasource.component.html @@ -10,12 +10,18 @@ This is a particular case of limited datasource discussed above with only MIN value set up. Also, positive limitation could be alternatively achieved by assigning 1 (or 0) value to - minIndex setting. + minIndex and maxIndex settings.

Note that if 0 index is the left boundary of the dataset, then it seems reasonable to assign 0 value to - startIndex setting too, + startIndex setting too, because the default startIndex value is 1.

diff --git a/demo/app/samples/datasource/positive-limited-datasource.component.ts b/demo/app/samples/datasource/positive-limited-datasource.component.ts index 592eed6f..261ac020 100644 --- a/demo/app/samples/datasource/positive-limited-datasource.component.ts +++ b/demo/app/samples/datasource/positive-limited-datasource.component.ts @@ -1,9 +1,11 @@ import { Component } from '@angular/core'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; -import { IDatasource } from '../../../../public_api'; +import { demos } from '../../routes'; +import { DemoSources, DemoSourceType } from '../../shared/interfaces'; import { doLog } from '../../shared/datasource-get'; +import { IDatasource } from '../../../../public_api'; + @Component({ selector: 'app-positive-limited-datasource', templateUrl: './positive-limited-datasource.component.html' @@ -11,13 +13,13 @@ import { doLog } from '../../shared/datasource-get'; export class DemoPositiveLimitedDatasourceComponent { demoContext = { - scope: 'datasource', - title: `Positive limited datasource`, - titleId: `positive-limited-indexes`, + config: demos.datasource.map.positiveLimitedIndexes, logViewOnly: true, log: '', count: 0 - } as DemoContext; + }; + + settingsScope = demos.settings.map; datasource: IDatasource = { get: (index: number, count: number, success: Function) => { diff --git a/demo/app/samples/datasource/remote-datasource.component.ts b/demo/app/samples/datasource/remote-datasource.component.ts index c66eaac0..6b8ea8d9 100644 --- a/demo/app/samples/datasource/remote-datasource.component.ts +++ b/demo/app/samples/datasource/remote-datasource.component.ts @@ -2,7 +2,9 @@ import { Component, Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { demos } from '../../routes'; +import { DemoSources, DemoSourceType } from '../../shared/interfaces'; + import { IDatasource } from '../../../../public_api'; @Injectable() @@ -23,13 +25,11 @@ export class RemoteDataService { export class DemoRemoteDatasourceComponent { demoContext = { - scope: 'datasource', - title: `Remote datasource`, - titleId: `remote`, + config: demos.datasource.map.remote, logViewOnly: true, log: '', count: 0 - } as DemoContext; + }; datasource: IDatasource = { get: (index: number, count: number) => { diff --git a/demo/app/samples/experimental.component.html b/demo/app/samples/experimental.component.html index 5d1e8243..69b7faf8 100644 --- a/demo/app/samples/experimental.component.html +++ b/demo/app/samples/experimental.component.html @@ -6,14 +6,26 @@

Angular UI Scroll Experimental Demos

and may have some side effects or behave imperfectly.

For example, Adapter.fix method. It has different options for different cases diff --git a/demo/app/samples/experimental.component.ts b/demo/app/samples/experimental.component.ts index 47fafab7..9c0230d7 100644 --- a/demo/app/samples/experimental.component.ts +++ b/demo/app/samples/experimental.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { demos } from '../routes'; @Component({ selector: 'app-experimental', @@ -9,6 +10,9 @@ export class ExperimentalComponent { constructor() { } + scope = demos.experimental.map; + base = '../' + demos.experimental.id; + adapterFixArgumentDescription = ` interface AdapterFixOptions { scrollPosition?: number; minIndex?: number; diff --git a/demo/app/samples/experimental/adapter-fix-position.component.ts b/demo/app/samples/experimental/adapter-fix-position.component.ts index 1f2fdd49..4e83afe8 100644 --- a/demo/app/samples/experimental/adapter-fix-position.component.ts +++ b/demo/app/samples/experimental/adapter-fix-position.component.ts @@ -1,6 +1,8 @@ import { Component } from '@angular/core'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { demos } from '../../routes'; +import { DemoSources, DemoSourceType } from '../../shared/interfaces'; + import { Datasource } from '../../../../public_api'; @Component({ @@ -10,11 +12,9 @@ import { Datasource } from '../../../../public_api'; export class DemoAdapterFixPositionComponent { demoContext = { - scope: 'experimental', - title: `Adapter fix scroll position`, - titleId: `adapter-fix-position`, + config: demos.experimental.map.adapterFixPosition, noInfo: true - } as DemoContext; + }; datasource = new Datasource({ get: (index: number, count: number, success: Function) => { diff --git a/demo/app/samples/experimental/adapter-fix-scrollToItem.component.ts b/demo/app/samples/experimental/adapter-fix-scrollToItem.component.ts index dfe08f4c..dff1b1c6 100644 --- a/demo/app/samples/experimental/adapter-fix-scrollToItem.component.ts +++ b/demo/app/samples/experimental/adapter-fix-scrollToItem.component.ts @@ -1,6 +1,8 @@ import { Component } from '@angular/core'; +import { demos } from '../../routes'; import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; + import { Datasource } from '../../../../public_api'; @Component({ @@ -10,9 +12,7 @@ import { Datasource } from '../../../../public_api'; export class DemoAdapterFixScrollToItemComponent { demoContext: DemoContext = { - scope: 'experimental', - title: `Adapter fix scrollToItem`, - titleId: `adapter-fix-scrollToItem`, + config: demos.experimental.map.adapterFixScrollToItem, noInfo: true }; @@ -47,7 +47,7 @@ export class DemoAdapterFixScrollToItemComponent {

` }, { name: DemoSourceType.Component, - text: `index = '5' + text: `index = '5' scrollToBottom = false; datasource = new Datasource({ diff --git a/demo/app/samples/experimental/adapter-fix-updater.component.html b/demo/app/samples/experimental/adapter-fix-updater.component.html index 8f6de4f6..74ca44b4 100644 --- a/demo/app/samples/experimental/adapter-fix-updater.component.html +++ b/demo/app/samples/experimental/adapter-fix-updater.component.html @@ -28,15 +28,29 @@ we need to care about datasource consistency and every update we want to be applied to the uiScroll buffer should also happen at the datasource level. The examples of Datasource-Adapter synching can be found - here, here - and here. + here, + here and + here.

The Adapter.fix({ updater }) usage can be very diverse. For example, we may not change anything in the buffer, but gather some statistic. In this demo countItems method uses updater to get a number of items in the buffer at a particular moment. The result is equal to the value of Adapter.itemsCount - read-only property (link). + read-only property + (link). Also, I would not recommend to bind countItems-like methods with the template as it is in this demo, for it might affect the performance.

diff --git a/demo/app/samples/experimental/adapter-fix-updater.component.ts b/demo/app/samples/experimental/adapter-fix-updater.component.ts index 1a020c9c..4202e0d7 100644 --- a/demo/app/samples/experimental/adapter-fix-updater.component.ts +++ b/demo/app/samples/experimental/adapter-fix-updater.component.ts @@ -1,6 +1,8 @@ import { Component } from '@angular/core'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { demos } from '../../routes'; +import { DemoSources, DemoSourceType } from '../../shared/interfaces'; + import { Datasource } from '../../../../public_api'; @Component({ @@ -10,11 +12,11 @@ import { Datasource } from '../../../../public_api'; export class DemoAdapterFixUpdaterComponent { demoContext = { - scope: 'experimental', - title: `Adapter fix updater`, - titleId: `adapter-fix-updater`, + config: demos.experimental.map.adapterFixUpdater, noInfo: true - } as DemoContext; + }; + + adapterScope = demos.adapter; datasource = new Datasource({ get: (index: number, count: number, success: Function) => { @@ -104,7 +106,7 @@ countItems() { countItems() { let count = 0; this.datasource.adapter.fix({ - updater: ({}) => count++ + updater: ({ }) => count++ }); return count; } diff --git a/demo/app/samples/experimental/inverse-setting.component.ts b/demo/app/samples/experimental/inverse-setting.component.ts index 4590891d..be4677b7 100644 --- a/demo/app/samples/experimental/inverse-setting.component.ts +++ b/demo/app/samples/experimental/inverse-setting.component.ts @@ -1,6 +1,8 @@ import { Component } from '@angular/core'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { demos } from '../../routes'; +import { DemoSources, DemoSourceType } from '../../shared/interfaces'; + import { IDatasource } from '../../../../public_api'; @Component({ @@ -10,12 +12,10 @@ import { IDatasource } from '../../../../public_api'; export class DemoInverseSettingComponent { demoContext = { - scope: 'experimental', - title: `Inverse setting`, - titleId: `inverse-setting`, + config: demos.experimental.map.inverseSetting, addClass: `inverse`, noInfo: true - } as DemoContext; + }; MIN = 1; MAX = 5; diff --git a/demo/app/samples/experimental/onBeforeClip-setting.component.ts b/demo/app/samples/experimental/onBeforeClip-setting.component.ts index d5853577..e46263a2 100644 --- a/demo/app/samples/experimental/onBeforeClip-setting.component.ts +++ b/demo/app/samples/experimental/onBeforeClip-setting.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { demos } from '../../routes'; +import { DemoSources, DemoSourceType } from '../../shared/interfaces'; import { Datasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; @@ -11,13 +12,11 @@ import { Datasource } from '../../../../public_api'; // from 'ngx-ui-scroll'; export class DemoOnBeforeClipSettingComponent { demoContext = { - scope: 'experimental', - title: `onBeforeClip setting`, - titleId: 'onBeforeClip-setting', + config: demos.experimental.map.onBeforeClipSetting, viewportId: 'onBeforeClip-setting-viewport', log: '', count: 0 - } as DemoContext; + }; datasource = new Datasource({ get: (index, count, success) => { diff --git a/demo/app/samples/experimental/viewportElement-setting.component.ts b/demo/app/samples/experimental/viewportElement-setting.component.ts index e7a11a36..c5b491d1 100644 --- a/demo/app/samples/experimental/viewportElement-setting.component.ts +++ b/demo/app/samples/experimental/viewportElement-setting.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { demos } from '../../routes'; +import { DemoSources, DemoSourceType } from '../../shared/interfaces'; @Component({ selector: 'app-viewport-element-setting', @@ -9,11 +10,9 @@ import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interface export class DemoViewportElementSettingComponent { demoContext = { - scope: 'experimental', - title: `Viewport element setting`, - titleId: `viewportElement-setting`, + config: demos.experimental.map.viewportElementSetting, noWorkView: true - } as DemoContext; + }; sources: DemoSources = [{ name: DemoSourceType.Datasource, diff --git a/demo/app/samples/home.component.html b/demo/app/samples/home.component.html index 2c01a26b..80a86b2f 100644 --- a/demo/app/samples/home.component.html +++ b/demo/app/samples/home.component.html @@ -69,10 +69,10 @@

Common demo

simplest Datasource implementation with no any settings provided and some of Adapter properties and methods usage. Other samples with all necessary details and explanations can be found at - Datasource, - Settings, - Adapter and - Experimental + {{scopes.datasource.name}}, + {{scopes.settings.name}}, + {{scopes.adapter.name}} and + {{scopes.experimental.name}} sections. Below is the list of all examples available per these sections.

@@ -81,54 +81,12 @@

Example list

- - - - diff --git a/demo/app/samples/home.component.ts b/demo/app/samples/home.component.ts index 62d09df2..6cba926e 100644 --- a/demo/app/samples/home.component.ts +++ b/demo/app/samples/home.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { DemoSources, DemoSourceType } from '../shared/interfaces'; +import { globalScope as scopes, demoList as demos } from '../routes'; import { Datasource } from '../../../public_api'; // from 'ngx-ui-scroll'; @@ -12,6 +13,8 @@ export class HomeComponent { reloadIndex = 999; delay = 25; + scopes: typeof scopes; + demos: typeof demos; datasource = new Datasource({ get: (index: number, count: number, success: Function) => { @@ -105,6 +108,8 @@ Index to reload: }]; constructor() { + this.scopes = scopes; + this.demos = demos; } doReload() { diff --git a/demo/app/shared/demo.component.html b/demo/app/shared/demo.component.html index e1f8f6c4..b2f5d720 100644 --- a/demo/app/shared/demo.component.html +++ b/demo/app/shared/demo.component.html @@ -2,7 +2,7 @@
- +
@@ -10,15 +10,15 @@
-
-
+
+
diff --git a/demo/app/shared/demo/demo-title.component.html b/demo/app/shared/demo/demo-title.component.html index 2ad56f1a..29674c3a 100644 --- a/demo/app/shared/demo/demo-title.component.html +++ b/demo/app/shared/demo/demo-title.component.html @@ -1,4 +1,7 @@ -

- {{context.title}} - # +

+ {{config.name}} + #

diff --git a/demo/app/shared/demo/demo-title.component.ts b/demo/app/shared/demo/demo-title.component.ts index 02135e89..a7bf2e4d 100644 --- a/demo/app/shared/demo/demo-title.component.ts +++ b/demo/app/shared/demo/demo-title.component.ts @@ -1,11 +1,11 @@ import { Component, Input } from '@angular/core'; -import { DemoContext } from '../interfaces'; +import { IDemo } from '../../routes'; @Component({ selector: 'app-demo-title', templateUrl: './demo-title.component.html' }) export class DemoTitleComponent { - @Input() context: DemoContext; + @Input() config: IDemo; } diff --git a/demo/app/shared/interfaces.ts b/demo/app/shared/interfaces.ts index 2faa5467..1f3256f6 100644 --- a/demo/app/shared/interfaces.ts +++ b/demo/app/shared/interfaces.ts @@ -1,3 +1,5 @@ +import { IDemo } from '../routes'; + export enum DemoSourceType { Component = 'Component', Template = 'Template', @@ -16,10 +18,8 @@ export type DemoSources = DemoSource[]; export interface DemoContext { // static data - scope?: string; - title: string; - titleId: string; - viewportId: string; + config: IDemo; + viewportId?: string; addClass?: string; noWorkView?: boolean; logViewOnly?: boolean; diff --git a/demo/app/shared/nav.component.html b/demo/app/shared/nav.component.html index 3145f887..c6777a9d 100644 --- a/demo/app/shared/nav.component.html +++ b/demo/app/shared/nav.component.html @@ -41,10 +41,10 @@
diff --git a/demo/app/shared/nav.component.ts b/demo/app/shared/nav.component.ts index eaa13f23..224dfe91 100644 --- a/demo/app/shared/nav.component.ts +++ b/demo/app/shared/nav.component.ts @@ -1,11 +1,15 @@ import { Component } from '@angular/core'; +import { demos } from '../routes'; + @Component({ selector: 'app-nav', templateUrl: './nav.component.html' }) export class NavComponent { + demos = demos; + constructor() { } diff --git a/demo/main.ts b/demo/main.ts index 91ec6da5..3c96850c 100644 --- a/demo/main.ts +++ b/demo/main.ts @@ -1,12 +1,19 @@ import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; +import { redirects } from './app/routes'; +import { AppModule } from './app/app.module'; if (environment.production) { enableProdMode(); } -platformBrowserDynamic().bootstrapModule(AppModule) - .catch(err => console.log(err)); +const redirect = redirects.find(({ from }) => from === location.hash); +if (redirect) { + location.href = redirect.to; +} else { + platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.log(err)); +} + diff --git a/demo/styles.css b/demo/styles.css index ff792431..4d9a8d92 100644 --- a/demo/styles.css +++ b/demo/styles.css @@ -260,6 +260,11 @@ input[type=checkbox], input[type=radio] { overflow: hidden; } +.demo .work .item .index { + font-weight: normal; + font-size: smaller; +} + .demo .work .viewport.remove { width: 200px; } diff --git a/package-dist.json b/package-dist.json index 13659bac..faa4bb89 100644 --- a/package-dist.json +++ b/package-dist.json @@ -1,6 +1,6 @@ { "name": "ngx-ui-scroll", - "version": "1.11.0-rc.1", + "version": "1.11.0", "description": "Infinite/virtual scroll for Angular", "main": "./bundles/ngx-ui-scroll.umd.js", "module": "./fesm5/ngx-ui-scroll.js", diff --git a/package.json b/package.json index 04d73f47..89b3f531 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "deploy-app": "npm run build-app && firebase deploy", "preinstall": "cd server && npm install", "postinstall": "npm run build-app", - "pack:install": "npm run build && npm pack ./dist && npm install ngx-ui-scroll-1.11.0-rc.1.tgz --no-save", + "pack:install": "npm run build && npm pack ./dist && npm install ngx-ui-scroll-1.11.0.tgz --no-save", "pack:start": "npm run pack:install && npm start", "build": "node build.js", "publish:lib": "npm run build && npm publish ./dist", diff --git a/src/component/classes/buffer.ts b/src/component/classes/buffer.ts index e3f47a3c..4224df1d 100644 --- a/src/component/classes/buffer.ts +++ b/src/component/classes/buffer.ts @@ -31,7 +31,7 @@ export class Buffer { this.$items = $items || new BehaviorSubject([]); this.bofSource = new Subject(); this.eofSource = new Subject(); - this.cache = new Cache(settings.itemSize, logger); + this.cache = new Cache(settings.itemSize, settings.cacheData, logger); this.startIndexUser = settings.startIndex; this.minIndexUser = settings.minIndex; this.maxIndexUser = settings.maxIndex; @@ -254,28 +254,29 @@ export class Buffer { this.cache.removeItems(toRemove, immutableTop); } - insertItems(items: Item[], from: Item, addition: number, decrement: boolean) { + insertItems(items: Item[], from: Item, addition: number, immutableTop: boolean) { const count = items.length; const index = this.items.indexOf(from) + addition; const itemsBefore = this.items.slice(0, index); const itemsAfter = this.items.slice(index); - if (decrement) { - itemsBefore.forEach((item: Item) => item.updateIndex(item.$index - count)); - } else { + if (immutableTop) { itemsAfter.forEach((item: Item) => item.updateIndex(item.$index + count)); + } else { + itemsBefore.forEach((item: Item) => item.updateIndex(item.$index - count)); } const result = [ ...itemsBefore, ...items, ...itemsAfter ]; - if (decrement) { - this.absMinIndex -= count; - } else { + if (immutableTop) { this.absMaxIndex += count; + } else { + this.absMinIndex -= count; + this.startIndex -= count; } this.items = result; - this.cache.insertItems(from.$index + addition, count, decrement); + this.cache.insertItems(from.$index + addition, count, immutableTop); } cacheItem(item: Item) { diff --git a/src/component/classes/cache.ts b/src/component/classes/cache.ts index 1489e2c9..b10006b2 100644 --- a/src/component/classes/cache.ts +++ b/src/component/classes/cache.ts @@ -4,14 +4,14 @@ import { Logger } from './logger'; export class ItemCache { $index: number; nodeId: string; - data: any; // todo: cache data only if it is permitted by settings + data: any; size: number; position: number; - constructor(item: Item) { + constructor(item: Item, saveData: boolean) { this.$index = item.$index; this.nodeId = item.nodeId; - this.data = item.data; + this.data = saveData ? item.data : null; this.size = item.size; } @@ -51,11 +51,13 @@ export class Cache { private items: Map; readonly logger: Logger; readonly itemSize: number; + readonly saveData: boolean; - constructor(itemSize: number, logger: Logger) { + constructor(itemSize: number, saveData: boolean, logger: Logger) { this.averageSizeFloat = itemSize; this.averageSize = itemSize; this.itemSize = itemSize; + this.saveData = saveData; this.items = new Map(); this.recalculateAverage = new RecalculateAverage(); this.reset(); @@ -107,7 +109,7 @@ export class Cache { itemCache.size = item.size; } } else { - itemCache = new ItemCache(item); + itemCache = new ItemCache(item, this.saveData); this.items.set(item.$index, itemCache); if (this.averageSize !== itemCache.size) { this.recalculateAverage.newItems.push({ $index: item.$index, size: itemCache.size }); @@ -155,19 +157,19 @@ export class Cache { this.maxIndex = max; } - insertItems(index: number, count: number, decrement: boolean) { + insertItems(index: number, count: number, immutableTop: boolean) { // we do not insert new items here, we just shift indexes of the existed items - // new items adding must be performed via Cache.add execution + // new items adding must be performed via Cache.add const items = new Map(); this.items.forEach((item: ItemCache) => { const { $index } = item; if ($index < index) { - if (decrement) { + if (!immutableTop) { item.changeIndex($index - count); } items.set(item.$index, item); } else { - if (!decrement) { + if (immutableTop) { item.changeIndex($index + count); } items.set(item.$index, item); diff --git a/src/component/classes/settings.ts b/src/component/classes/settings.ts index ddee15a0..85e2e72c 100644 --- a/src/component/classes/settings.ts +++ b/src/component/classes/settings.ts @@ -26,6 +26,7 @@ export class Settings implements ISettings, IDevSettings { throttle: number; // if > 0, scroll event handling is throttled (ms) initDelay: number; // if set, the Workflow initialization will be postponed (ms) initWindowDelay: number; // if set and the entire window is scrollable, the Workflow init will be postponed (ms) + cacheData: boolean; // if true, item's data will be cached along with item's size and index changeOverflow: boolean; // if true, scroll will be disabled per each item's average size change dismissOverflowAnchor: boolean; // if true, the viewport will receive "overflowAnchor: none" diff --git a/src/component/inputs/adapter.ts b/src/component/inputs/adapter.ts index 7af78043..64a6c335 100644 --- a/src/component/inputs/adapter.ts +++ b/src/component/inputs/adapter.ts @@ -121,6 +121,7 @@ const INSERT_METHOD_PARAMS: ICommonProps = { enum AdapterReplaceParams { items = 'items', predicate = 'predicate', + fixRight = 'fixRight', } const REPLACE_METHOD_PARAMS: ICommonProps = { @@ -129,7 +130,12 @@ const REPLACE_METHOD_PARAMS: ICommonProps = { mandatory: true }, [AdapterReplaceParams.predicate]: { - validators: [FUNC_WITH_X_ARGUMENTS(1)] + validators: [FUNC_WITH_X_ARGUMENTS(1)], + mandatory: true + }, + [AdapterReplaceParams.fixRight]: { + validators: [BOOLEAN], + defaultValue: false } }; diff --git a/src/component/inputs/settings.ts b/src/component/inputs/settings.ts index ab80c4b9..99064468 100644 --- a/src/component/inputs/settings.ts +++ b/src/component/inputs/settings.ts @@ -27,6 +27,7 @@ enum DevSettings { throttle = 'throttle', initDelay = 'initDelay', initWindowDelay = 'initWindowDelay', + cacheData = 'cacheData', changeOverflow = 'changeOverflow', dismissOverflowAnchor = 'dismissOverflowAnchor', } @@ -58,7 +59,7 @@ export const SETTINGS: ICommonProps = { defaultValue: Infinity }, [Settings.itemSize]: { - validators: [INTEGER, MORE_OR_EQUAL(MIN[Settings.itemSize], true)], + validators: [INTEGER, MORE_OR_EQUAL(MIN[Settings.itemSize], true)], defaultValue: NaN }, [Settings.bufferSize]: { @@ -124,6 +125,10 @@ export const DEV_SETTINGS: ICommonProps = { validators: [INTEGER, MORE_OR_EQUAL(MIN[DevSettings.initWindowDelay], true)], defaultValue: 40 }, + [DevSettings.cacheData]: { + validators: [BOOLEAN], + defaultValue: false + }, [DevSettings.changeOverflow]: { validators: [BOOLEAN], defaultValue: false diff --git a/src/component/interfaces/adapter.ts b/src/component/interfaces/adapter.ts index b67321f0..0edfdb52 100644 --- a/src/component/interfaces/adapter.ts +++ b/src/component/interfaces/adapter.ts @@ -60,6 +60,7 @@ export interface AdapterInsertOptions { export interface AdapterReplaceOptions { items: any[]; predicate: ItemsPredicate; + fixRight?: boolean; } export interface AdapterFixOptions { diff --git a/src/component/interfaces/settings.ts b/src/component/interfaces/settings.ts index 18cb6cab..0b7cf7f7 100644 --- a/src/component/interfaces/settings.ts +++ b/src/component/interfaces/settings.ts @@ -24,6 +24,7 @@ export interface DevSettings { throttle?: number; initDelay?: number; initWindowDelay?: number; + cacheData?: boolean; changeOverflow?: boolean; dismissOverflowAnchor?: boolean; } diff --git a/src/component/processes/adapter/insert.ts b/src/component/processes/adapter/insert.ts index 6cd5b806..c824958a 100644 --- a/src/component/processes/adapter/insert.ts +++ b/src/component/processes/adapter/insert.ts @@ -42,7 +42,7 @@ export default class Insert extends getBaseAdapterProcess(AdapterProcess.insert) const itemsToInsert = items.map((item: any, i: number) => new Item(from.$index + i + addition - (decrement ? count : 0), item, routines) ); - buffer.insertItems(itemsToInsert, from, addition, decrement); + buffer.insertItems(itemsToInsert, from, addition, !decrement); scroller.logger.log(() => { const newBufferLimit = decrement ? buffer.absMinIndex : buffer.absMaxIndex; const isChange = bufferLimit !== newBufferLimit; diff --git a/src/component/processes/adapter/replace.ts b/src/component/processes/adapter/replace.ts index 0a4751df..f32f09ae 100644 --- a/src/component/processes/adapter/replace.ts +++ b/src/component/processes/adapter/replace.ts @@ -1,8 +1,10 @@ import { getBaseAdapterProcess } from './_base'; import { Scroller } from '../../scroller'; -import { AdapterProcess, ProcessStatus, AdapterReplaceOptions, AdapterInsertOptions } from '../../interfaces/index'; import Remove from './remove'; import Insert from './insert'; +import { + AdapterProcess, ProcessStatus, AdapterReplaceOptions, AdapterInsertOptions, AdapterRemoveOptions +} from '../../interfaces/index'; export default class Replace extends getBaseAdapterProcess(AdapterProcess.replace) { @@ -12,8 +14,7 @@ export default class Replace extends getBaseAdapterProcess(AdapterProcess.replac return; } - const shouldRemove = Remove.doRemove(scroller, params, true); - if (!shouldRemove) { + if (!Replace.doRemove(scroller, params)) { scroller.logger.log(() => 'no items to replace (not found)'); return scroller.workflow.call({ process: Replace.process, @@ -21,13 +22,7 @@ export default class Replace extends getBaseAdapterProcess(AdapterProcess.replac }); } - const insertOptions: AdapterInsertOptions = { - items: params.items, - before: params.predicate, - decrease: false - }; - const shouldInsert = Insert.doInsert(scroller, insertOptions); - if (!shouldInsert) { + if (!Replace.doInsert(scroller, params)) { return scroller.workflow.call({ process: Replace.process, status: ProcessStatus.done @@ -40,4 +35,21 @@ export default class Replace extends getBaseAdapterProcess(AdapterProcess.replac }); } + static doRemove(scroller: Scroller, params: AdapterReplaceOptions) { + const removeOptions: AdapterRemoveOptions = { + predicate: params.predicate, + increase: params.fixRight + }; + return Remove.doRemove(scroller, removeOptions, true); + } + + static doInsert(scroller: Scroller, params: AdapterReplaceOptions) { + const insertOptions: AdapterInsertOptions = { + items: params.items, + after: params.predicate, + decrease: params.fixRight + }; + return Insert.doInsert(scroller, insertOptions); + } + } diff --git a/src/ui-scroll.version.ts b/src/ui-scroll.version.ts index db960611..2eef61bf 100644 --- a/src/ui-scroll.version.ts +++ b/src/ui-scroll.version.ts @@ -1 +1 @@ -export default '1.11.0-rc.1'; +export default '1.11.0'; diff --git a/tests/adapter.append-prepend.spec.ts b/tests/adapter.append-prepend.spec.ts index c7a5f13d..037510b8 100644 --- a/tests/adapter.append-prepend.spec.ts +++ b/tests/adapter.append-prepend.spec.ts @@ -1,5 +1,5 @@ import { Direction } from '../src/component/interfaces'; -import { makeTest, TestBedConfig, OperationConfig } from './scaffolding/runner'; +import { makeTest, OperationConfig } from './scaffolding/runner'; import { Misc } from './miscellaneous/misc'; import { Item } from './miscellaneous/items'; @@ -80,7 +80,7 @@ describe('Adapter Append-Prepend Spec', () => { [Operation.append]: [...Array(total).keys()].map((i: number) => ({ id: max - total + i + 1, text: 'item #' + (max - total + i + 1) - })), + })), [Operation.prepend]: [...Array(total).keys()].map((i: number) => ({ id: min + i, text: 'item #' + (min + i) diff --git a/tests/adapter.check.spec.ts b/tests/adapter.check.spec.ts index 0e47035d..d09c0004 100644 --- a/tests/adapter.check.spec.ts +++ b/tests/adapter.check.spec.ts @@ -1,6 +1,5 @@ import { makeTest, TestBedConfig } from './scaffolding/runner'; import { Misc } from './miscellaneous/misc'; -import { IndexedItem } from './miscellaneous/items'; const MIN_INDEX = -99; const MAX_INDEX = 100; @@ -98,12 +97,11 @@ const updateDOMElement = (misc: Misc, index: number, size: number) => { }; const updateDOM = (misc: Misc, { min, max, size, initialSize }: any) => { - const { datasource } = misc.fixture.componentInstance; for (let i = min; i <= max; i++) { updateDOMElement(misc, i, size); // persist new sizes on the datasource level - (datasource as any).setProcessGet((result: IndexedItem[]) => - result.forEach(({ data }) => data.size = data.id >= min && data.id <= max ? size : initialSize) + misc.setItemProcessor(({ data }) => + data.size = data.id >= min && data.id <= max ? size : initialSize ); } }; @@ -129,9 +127,7 @@ const shouldCheck = (config: TestBedConfig) => (misc: Misc) => (done: Function) const { min, max, size } = config.custom; const changedCount = (max - min + 1); let firstVisibleIndex = NaN; - (misc.datasource as any).setProcessGet((result: IndexedItem[]) => - result.forEach(({ data }) => data.size = initialSize) - ); + misc.setItemProcessor(({ data }) => data.size = initialSize); spyOn(misc.workflow, 'finalize').and.callFake(() => { const cycle = state.cycle.count; const { firstVisible } = adapter; // need to have a pre-call diff --git a/tests/adapter.insert.spec.ts b/tests/adapter.insert.spec.ts index 92e4422b..60a432b6 100644 --- a/tests/adapter.insert.spec.ts +++ b/tests/adapter.insert.spec.ts @@ -143,7 +143,7 @@ describe('Adapter Insert Spec', () => { return; } // insert items to the original datasource - (misc.datasource as any).setProcessGet(( + misc.setDatasourceProcessor(( result: IndexedItem[], _index: number, _count: number, _min: number, _max: number ) => insertItems(result, _index, _count, _min, _max, index + (before ? 0 : 1), amount, decrease) diff --git a/tests/adapter.reload.spec.ts b/tests/adapter.reload.spec.ts index c01970d0..6bcc50da 100644 --- a/tests/adapter.reload.spec.ts +++ b/tests/adapter.reload.spec.ts @@ -146,9 +146,8 @@ const doReload = ({ custom }: TestBedConfig, { adapter }: Misc) => { }; const doReloadOnFirstDatasourceGetCall = (config: TestBedConfig, misc: Misc) => { - const { datasource } = misc.fixture.componentInstance; let reloaded = false; - (datasource as any).setProcessGet(() => { + misc.setDatasourceProcessor(() => { if (!reloaded) { reloaded = true; doReload(config, misc); diff --git a/tests/adapter.remove.spec.ts b/tests/adapter.remove.spec.ts index 38639953..e802833d 100644 --- a/tests/adapter.remove.spec.ts +++ b/tests/adapter.remove.spec.ts @@ -121,7 +121,7 @@ const doRemove = async (config: TestBedConfig, misc: Misc, byId = false) => { const indexList = remove || [...removeBwd, ...removeFwd]; const indexListInterrupted = config.custom.interrupted; // remove item from the original datasource - (misc.datasource as any).setProcessGet((result: IndexedItem[]) => + misc.setDatasourceProcessor((result: IndexedItem[]) => [removeBwd, removeFwd, remove].forEach(list => list && removeItems(result, list, -99, 100, increase) ) diff --git a/tests/adapter.replace.spec.ts b/tests/adapter.replace.spec.ts index 70d0f17b..e912ba7a 100644 --- a/tests/adapter.replace.spec.ts +++ b/tests/adapter.replace.spec.ts @@ -11,45 +11,135 @@ const baseSettings = { itemSize: 20 }; +interface ICustom { + token: 'first' | 'last' | 'middle'; + indexesToReplace: number[]; // indexes to remove + amount: number; // how many items to insert + fixRight?: boolean; +} + const configList: TestBedConfig[] = [{ datasourceSettings: { ...baseSettings }, custom: { - indexToReplace: baseSettings.minIndex + 1, - token: 'middle' - } + indexesToReplace: [baseSettings.minIndex + 1], + token: 'middle', + amount: 1 + } as ICustom }, { datasourceSettings: { ...baseSettings }, custom: { - indexToReplace: baseSettings.minIndex, - token: 'first' - } + indexesToReplace: [baseSettings.minIndex], + token: 'first', + amount: 1 + } as ICustom }, { datasourceSettings: { ...baseSettings, startIndex: baseSettings.maxIndex }, custom: { - indexToReplace: baseSettings.maxIndex, - token: 'last' - } -}].map(config => ({ ...config, datasourceClass: getDatasourceReplacementsClass(config.datasourceSettings) })); + indexesToReplace: [baseSettings.maxIndex], + token: 'last', + amount: 1 + } as ICustom +}].map(config => ({ + ...config, + datasourceClass: getDatasourceReplacementsClass(config.datasourceSettings) +})); + +const manyToOneConfigList: TestBedConfig[] = [{ + datasourceSettings: { ...baseSettings }, + custom: { + indexesToReplace: [ + baseSettings.minIndex + 1, + baseSettings.minIndex + 2, + baseSettings.minIndex + 3 + ], + token: 'middle', + amount: 1 + } as ICustom +}, { + datasourceSettings: { ...baseSettings }, + custom: { + indexesToReplace: [ + baseSettings.minIndex, + baseSettings.minIndex + 1, + baseSettings.minIndex + 2 + ], + token: 'first', + amount: 1 + } as ICustom +}, { + datasourceSettings: { ...baseSettings, startIndex: baseSettings.maxIndex }, + custom: { + indexesToReplace: [ + baseSettings.maxIndex - 2, + baseSettings.maxIndex - 1, + baseSettings.maxIndex + ], + token: 'last', + amount: 1 + } as ICustom +}].map(config => ({ + ...config, + datasourceClass: getDatasourceReplacementsClass(config.datasourceSettings) +})); + +const manyToOneIncreaseConfigList = manyToOneConfigList.map(config => ({ + ...config, + custom: { + ...config.custom, + fixRight: true + } as ICustom +})); + +const manyToManyConfigList = [ + ...manyToOneConfigList.map(config => ({ + ...config, custom: { ...config.custom, amount: 2 } as ICustom + })), + ...manyToOneConfigList.map(config => ({ + ...config, custom: { ...config.custom, amount: 3 } as ICustom + })), + ...manyToOneConfigList.map(config => ({ + ...config, custom: { ...config.custom, amount: 4 } as ICustom + })), +]; + +const manyToManyIncreaseConfigList = manyToManyConfigList + .filter((i, j) => [0, 5, 8].includes(j)) + .map(config => ({ + ...config, custom: { ...config.custom, fixRight: true } as ICustom + })); const shouldReplace = (config: TestBedConfig) => (misc: Misc) => async (done: Function) => { await misc.relaxNext(); const { adapter } = misc; - const { custom: { indexToReplace: index, token } } = config; + const { indexesToReplace: indexes, amount, token, fixRight } = config.custom; const { datasourceSettings: { minIndex, itemSize } } = config; - const maxScrollPosition = misc.getMaxScrollPosition(); - const position = token === 'last' ? maxScrollPosition : (index - 1 + minIndex - 1) * itemSize; - const newItem = generateItem(index); - newItem.text += '*'; + const diff = amount - indexes.length; // inserted - removed + const viewportSize = misc.getScrollableSize(); + const sizeToChange = diff * misc.getItemSize(); + const maxScrollPosition = misc.getMaxScrollPosition() + sizeToChange; + const newIndexFirst = indexes[0] - (fixRight ? diff : 0); + const newIndexLast = newIndexFirst + amount - 1; + const newAbsMinIndex = fixRight ? minIndex - diff : minIndex; + const position = token === 'last' ? maxScrollPosition : (newIndexFirst - newAbsMinIndex) * itemSize; + const items = Array.from({ length: amount }).map((j, i) => generateItem(newIndexFirst + i, false, '*')); - // replace at the Datasource level - (misc.datasource as any).replaceOne(index, newItem); + // replace at the Datasource level (component) + if (indexes.length === 1 && amount === 1) { + (misc.datasource as any).replaceOneToOne(indexes[0], items[0]); + } else if (indexes.length > 1 && amount === 1) { + (misc.datasource as any).replaceManyToOne(indexes, items[0], fixRight); + } else if (indexes.length > 1 && amount > 1) { + (misc.datasource as any).replaceManyToMany(indexes, items, fixRight); + } - // replace at the Viewport level (Adapter) + // replace at the Viewport level (scroller) await adapter.replace({ - predicate: ({ $index }) => [index].includes($index), - items: [newItem] + predicate: ({ $index }) => indexes.includes($index), + items, + fixRight }); + // refresh the view via scroll to edges await misc.scrollMinMax(); // scroll to replaced item @@ -58,21 +148,75 @@ const shouldReplace = (config: TestBedConfig) => (misc: Misc) => async (done: Fu await misc.relaxNext(); } + // check edge replaced items if (token === 'last') { - expect(adapter.lastVisible.$index).toEqual(index); + expect(adapter.lastVisible.$index).toEqual(newIndexLast); } else { - expect(adapter.firstVisible.$index).toEqual(index); + expect(adapter.firstVisible.$index).toEqual(newIndexFirst); } - expect(misc.getElementText(index)).toEqual(index + ': ' + newItem.text); + expect(misc.getElementText(newIndexFirst)).toEqual(newIndexFirst + ': ' + items[0].text); + expect(misc.getElementText(newIndexLast)).toEqual(newIndexLast + ': ' + items[items.length - 1].text); + + // check the item next to the last replaced one + if (token === 'last') { + expect(misc.checkElementContent(newIndexFirst - 1, newIndexFirst - (fixRight ? 1 - diff : 1))).toEqual(true); + } else { + expect(misc.checkElementContent(newIndexLast + 1, newIndexLast + (fixRight ? 1 : 1 - diff))).toEqual(true); + } + + expect(misc.getScrollableSize()).toBe(viewportSize + sizeToChange); done(); }; describe('Adapter Replace Spec', () => { - describe('single replacement', () => + const getTitle = ({ custom: { token, indexesToReplace: { length }, amount } }: TestBedConfig) => + `should replace ${token} ${length === 1 ? 'one' : length} to ${amount === 1 ? 'one' : amount}`; + + describe('one-to-ne replacement', () => configList.forEach(config => makeTest({ - title: `should work (${config.custom.token})`, + title: getTitle(config), + config, + it: shouldReplace(config) + }) + ) + ); + + describe('many-to-one replacement', () => + manyToOneConfigList.forEach(config => + makeTest({ + title: getTitle(config), + config, + it: shouldReplace(config) + }) + ) + ); + + describe('many-to-one fixRight replacement', () => + manyToOneIncreaseConfigList.forEach(config => + makeTest({ + title: getTitle(config), + config, + it: shouldReplace(config) + }) + ) + ); + + describe('many-to-many replacement', () => + manyToManyConfigList.forEach(config => + makeTest({ + title: getTitle(config), + config, + it: shouldReplace(config) + }) + ) + ); + + describe('many-to-many fixRight replacement', () => + manyToManyIncreaseConfigList.forEach(config => + makeTest({ + title: getTitle(config), config, it: shouldReplace(config) }) diff --git a/tests/bug.spec.ts b/tests/bug.spec.ts index ef069b26..cf78213d 100644 --- a/tests/bug.spec.ts +++ b/tests/bug.spec.ts @@ -109,9 +109,10 @@ describe('Bug Spec', () => { itemSize: 20, inverse: true }, - templateSettings: { viewportHeight: 200, dynamicSize: 'size' } + templateSettings: { viewportHeight: 300, dynamicSize: 'size' } }, it: (misc: Misc) => async (done: Function) => { + misc.setItemProcessor(({ $index, data }) => $index === 4 && (data.size = 120)); await misc.relaxNext(); const { paddings } = misc.scroller.viewport; expect(paddings.backward.size).toEqual(0); @@ -152,7 +153,7 @@ describe('Bug Spec', () => { expect(misc.adapter.firstVisible.$index).toEqual(startIndex); // remove item from the original datasource - (misc.datasource as any).setProcessGet((result: IndexedItem[]) => + misc.setDatasourceProcessor((result: IndexedItem[]) => removeItems(result, Array.from({ length: MAX - MIN + 1 }).map((j, i) => MIN + i), -99, 100) ); await misc.adapter.remove({ diff --git a/tests/cache.spec.ts b/tests/cache.spec.ts index 000800ce..e55e529e 100644 --- a/tests/cache.spec.ts +++ b/tests/cache.spec.ts @@ -22,7 +22,7 @@ describe('Cache Spec', () => { }); beforeEach(() => { - cache = new Cache(NaN, loggerMock as any); + cache = new Cache(NaN, true, loggerMock as any); items.forEach(item => cache.add(item)); }); diff --git a/tests/dynamic-height-scroll.spec.ts b/tests/dynamic-height-scroll.spec.ts index 88d2d2a0..07d89c7b 100644 --- a/tests/dynamic-height-scroll.spec.ts +++ b/tests/dynamic-height-scroll.spec.ts @@ -4,7 +4,7 @@ import { Stat } from './miscellaneous/stat'; import { appendItems, generateItems, IndexedItem } from './miscellaneous/items'; const configFetch: TestBedConfig = { - datasourceName: 'limited-1-20-dynamic-size-special', + datasourceName: 'limited-1-20-dynamic-size-processor', datasourceSettings: { startIndex: 1, padding: 0.5, bufferSize: 5, minIndex: 1, maxIndex: 20, itemSize: 20, adapter: true }, @@ -25,6 +25,7 @@ const configAppend: TestBedConfig = { const testConfigFetch = (config: TestBedConfig, misc: Misc, done: Function) => { const { scroller, shared } = misc; + misc.setItemProcessor(({ $index, data }) => $index === 1 && (data.size = 200)); let initialFetchCount = 0; spyOn(misc.workflow, 'finalize').and.callFake(() => { const cycle = scroller.state.cycle.count; @@ -48,7 +49,7 @@ const testConfigAppend = async (config: TestBedConfig, misc: Misc, done: Functio const { MAX, AMOUNT } = config.custom; await misc.relaxNext(); // append items to the original datasource - (misc.datasource as any).setProcessGet(( + misc.setDatasourceProcessor(( result: IndexedItem[], _index: number, _count: number, _min: number, _max: number ) => appendItems(result, _index, _count, _min, _max, AMOUNT, true) diff --git a/tests/dynamic-size.spec.ts b/tests/dynamic-size.spec.ts index 990af171..b412275d 100644 --- a/tests/dynamic-size.spec.ts +++ b/tests/dynamic-size.spec.ts @@ -8,7 +8,6 @@ import { getDynamicSizeData, getDynamicSumSize } from './miscellaneous/dynamicSize'; -import { IndexedItem } from './miscellaneous/items'; const configList: TestBedConfig[] = [{ datasourceName: 'limited--50-99-dynamic-size', @@ -219,14 +218,16 @@ describe('Zero Size Spec', () => { makeTest({ config: { ...config, - datasourceName: 'limited-1-100-zero-size-started-from-6' + datasourceName: 'limited-1-100-processor' }, title: 'should stop the Workflow after the second loop', - it: (misc: Misc) => (done: Function) => + it: (misc: Misc) => (done: Function) => { + misc.setItemProcessor(({ $index, data }) => data.size = $index >= 6 ? 0 : 20); spyOn(misc.workflow, 'finalize').and.callFake(() => { expect(misc.innerLoopCount).toEqual(2); done(); - }) + }); + } }) ); @@ -240,12 +241,10 @@ describe('Zero Size Spec', () => { title: 'should continue the Workflow after re-size and check', it: (misc: Misc) => (done: Function) => spyOn(misc.workflow, 'finalize').and.callFake(() => { - const { scroller: { viewport }, adapter, datasource } = misc; + const { scroller: { viewport }, adapter } = misc; if (misc.workflow.cyclesDone === 1) { expect(viewport.getScrollableSize()).toEqual(viewport.paddings.forward.size); - (datasource as any).setProcessGet((result: IndexedItem[]) => - result.forEach(({ data }) => data.size = 20) - ); + misc.setItemProcessor(({ data }) => data.size = 20); adapter.fix({ updater: ({ element, data }) => { data.size = 20; diff --git a/tests/initial-load.spec.ts b/tests/initial-load.spec.ts index b127407a..7a5087a0 100644 --- a/tests/initial-load.spec.ts +++ b/tests/initial-load.spec.ts @@ -1,6 +1,6 @@ import { makeTest, TestBedConfig } from './scaffolding/runner'; import { Misc } from './miscellaneous/misc'; -import { ItemsCounter, ItemsDirCounter, testItemsCounter } from './miscellaneous/itemsCounter'; +import { ItemsCounter, testItemsCounter } from './miscellaneous/itemsCounter'; const fixedItemSizeConfigList: TestBedConfig[] = [{ datasourceSettings: { startIndex: 1, padding: 2, itemSize: 15 }, diff --git a/tests/miscellaneous/dynamicSize.ts b/tests/miscellaneous/dynamicSize.ts index 944441d7..9f8d5d8a 100644 --- a/tests/miscellaneous/dynamicSize.ts +++ b/tests/miscellaneous/dynamicSize.ts @@ -2,6 +2,8 @@ const INITIAL_ITEM_SIZE = 20; const MIN_ITEM_SIZE = 1; const MAX_ITEM_SIZE = 100; +export type DynamicSizeArg = boolean | number; + export interface DynamicSizeData { size: number; average: number; diff --git a/tests/miscellaneous/items.ts b/tests/miscellaneous/items.ts index 815e6e84..8a237c6f 100644 --- a/tests/miscellaneous/items.ts +++ b/tests/miscellaneous/items.ts @@ -1,4 +1,4 @@ -import { getDynamicSizeByIndex } from './dynamicSize'; +import { DynamicSizeArg, getDynamicSizeByIndex } from './dynamicSize'; import { getMin, getMax } from './common'; export interface Item { @@ -12,9 +12,7 @@ export interface IndexedItem { data: Item; } -type DynamicSize = boolean | number; - -const generateItemWithId = (id: number, index: number, dynamicSize?: DynamicSize, suffix = ''): Item => ({ +const generateItemWithId = (id: number, index: number, dynamicSize?: DynamicSizeArg, suffix = ''): Item => ({ id, text: 'item #' + index + suffix, ...( @@ -28,7 +26,7 @@ const generateItemWithId = (id: number, index: number, dynamicSize?: DynamicSize ) }); -export const generateItem = (index: number, dynamicSize: DynamicSize = false, suffix = ''): Item => +export const generateItem = (index: number, dynamicSize: DynamicSizeArg = false, suffix = ''): Item => generateItemWithId(index, index, dynamicSize, suffix); export const generateItems = (length: number, lastIndex: number): Item[] => @@ -62,7 +60,7 @@ export const insertItems = ( index: number, count: number, decrease: boolean, - dynamicSize?: DynamicSize + dynamicSize?: DynamicSizeArg ) => { let i = 1; const items: IndexedItem[] = []; diff --git a/tests/miscellaneous/misc.ts b/tests/miscellaneous/misc.ts index a1f48e90..2c3ae602 100644 --- a/tests/miscellaneous/misc.ts +++ b/tests/miscellaneous/misc.ts @@ -5,7 +5,7 @@ import { debounceTime, filter, take } from 'rxjs/operators'; import { TestComponentInterface } from '../scaffolding/testComponent'; import { TestBedConfig } from '../scaffolding/runner'; -import { generateItem } from './items'; +import { generateItem, IndexedItem } from './items'; import { Direction, DatasourceGet, IAdapter } from '../../src/component/interfaces'; import { UiScrollComponent } from '../../src/ui-scroll.component'; @@ -200,4 +200,17 @@ export class Misc { setTimeout(() => resolve(), ms) ); } + + setDatasourceProcessor(processor: Function) { + const setProcessor = (this.datasource as any).setProcessGet; + if (typeof processor === 'function') { + setProcessor.call(this.datasource, processor); + } + } + + setItemProcessor(itemUpdater: (item: IndexedItem) => any) { + this.setDatasourceProcessor((result: IndexedItem[]) => + result.forEach(itemUpdater) + ); + } } diff --git a/tests/scaffolding/datasources/class.ts b/tests/scaffolding/datasources/class.ts index c85bb87b..6d31c625 100644 --- a/tests/scaffolding/datasources/class.ts +++ b/tests/scaffolding/datasources/class.ts @@ -68,10 +68,40 @@ export const getDatasourceReplacementsClass = (settings: Settings) => }; } - replaceOne(idToReplace: number, item: Item) { + replaceOneToOne(idToReplace: number, item: Item) { const itemToReplace = this.data.find(({ id }) => id === idToReplace); if (itemToReplace) { Object.assign(itemToReplace, item); } } + + replaceManyToOne(idsToReplace: number[], item: Item, increase: boolean) { + this.replaceManyToMany(idsToReplace, [item], increase); + } + + replaceManyToMany(idsToReplace: number[], items: Item[], increase: boolean) { + idsToReplace.sort((a, b) => a - b); + const minRem = idsToReplace[0]; + const maxRem = idsToReplace.slice(1).reduce((acc, id) => + id === acc + 1 ? id : acc, minRem // only continuous series allowed + ); + const itemsToRemove = maxRem - minRem + 1; + const diff = itemsToRemove - items.length; + + let inserted = false; + this.data = this.data.reduce((acc, item: Item) => { + if ((!increase && item.id < minRem) || (increase && item.id > maxRem)) { + // below (or above if increase): persist + acc.push(item); + } else if ((!increase && item.id > maxRem) || (increase && item.id < minRem)) { + // above (or below if increase): shift + acc.push({ ...item, id: item.id + (!increase ? -1 : 1) * diff }); + } else if (!inserted) { + // in the middle: replace + acc.push(...items); + inserted = true; + } + return acc; + }, [] as Item[]); + } }; diff --git a/tests/scaffolding/datasources/get.ts b/tests/scaffolding/datasources/get.ts index f733d89f..3a4179ea 100644 --- a/tests/scaffolding/datasources/get.ts +++ b/tests/scaffolding/datasources/get.ts @@ -1,6 +1,7 @@ import { Observable, Observer } from 'rxjs'; import { generateItem, IndexedItem, Item } from '../../miscellaneous/items'; +import { DynamicSizeArg } from '../../miscellaneous/dynamicSize'; const datasourceGetInfinite = (index: number, count: number, suffix?: string) => { const data = []; @@ -11,7 +12,7 @@ const datasourceGetInfinite = (index: number, count: number, suffix?: string) => }; export const getLimitedData = ( - index: number, count: number, min: number, max: number, dynamicSize: boolean | number, inverse: boolean, processor?: any + index: number, count: number, min: number, max: number, dynamicSize: DynamicSizeArg, inverse: boolean, processor?: any ): Item[] => { const result: IndexedItem[] = []; const start = inverse ? -index - count : index; @@ -64,7 +65,7 @@ export const infiniteDatasourceGet = (type?: DatasourceType, delay?: number, suf }; export const limitedDatasourceGet = ( - min: number, max: number, dynamicSize: boolean, type: DatasourceType, delay: number, process?: boolean + min: number, max: number, dynamicSize: DynamicSizeArg, type: DatasourceType, delay: number, process?: boolean ) => (index: number, count: number, success?: (data: any[]) => any, reject?: Function, processor?: () => any) => { switch (type) { @@ -84,15 +85,3 @@ export const limitedDatasourceGet = ( )); } }; - -export const limitedDatasourceSpecialGet = ( - min: number, max: number, getSizeByIndex?: Function | number -) => ( - index: number, count: number, success: Function, reject?: Function, processor?: Function -) => { - const dynamicSize = typeof getSizeByIndex === 'number' ? getSizeByIndex as number : false; - if (typeof getSizeByIndex === 'function') { - processor = (items: IndexedItem[]) => items.forEach(({ $index, data }) => data.size = getSizeByIndex($index)); - } - success(getLimitedData(index, count, min, max, dynamicSize, false, processor)); - }; diff --git a/tests/scaffolding/datasources/store.ts b/tests/scaffolding/datasources/store.ts index e90d2546..4f298ce0 100644 --- a/tests/scaffolding/datasources/store.ts +++ b/tests/scaffolding/datasources/store.ts @@ -1,5 +1,5 @@ import { IDatasource } from '../../../src/component/interfaces'; -import { DatasourceType, infiniteDatasourceGet, limitedDatasourceGet, limitedDatasourceSpecialGet } from './get'; +import { DatasourceType, infiniteDatasourceGet, limitedDatasourceGet } from './get'; interface IDatasourceStore { [key: string]: IDatasource; @@ -81,20 +81,20 @@ export const datasourceStore: IDatasourceStore = { get: limitedDatasourceGet(-99, 100, true, DatasourceType.Callback, 0) }, - 'limited-1-20-dynamic-size-special': { - get: limitedDatasourceSpecialGet(1, 20, (i: number) => i === 1 ? 200 : 20) + 'limited-1-20-dynamic-size-processor': { + get: limitedDatasourceGet(1, 20, true, DatasourceType.Callback, 0, true) }, 'limited-1-10-with-big-item-4': { - get: limitedDatasourceSpecialGet(1, 10, (i: number) => i === 4 ? 93 : 20) + get: limitedDatasourceGet(1, 10, false, DatasourceType.Callback, 0, true) }, 'limited-1-100-zero-size': { - get: limitedDatasourceSpecialGet(1, 100, 0) + get: limitedDatasourceGet(1, 100, 0, DatasourceType.Callback, 0, true) }, - 'limited-1-100-zero-size-started-from-6': { - get: limitedDatasourceSpecialGet(1, 100, (i: number) => i >= 6 ? 0 : 20) + 'limited-1-100-processor': { + get: limitedDatasourceGet(1, 100, false, DatasourceType.Callback, 0, true) }, 'limited--99-100-processor': { diff --git a/tests/scroll-delay.spec.ts b/tests/scroll-delay.spec.ts index bf11c422..6f6020c7 100644 --- a/tests/scroll-delay.spec.ts +++ b/tests/scroll-delay.spec.ts @@ -1,6 +1,4 @@ -import { Direction } from '../src/component/interfaces'; import { makeTest, TestBedConfig } from './scaffolding/runner'; -import { debounce } from './miscellaneous/debounce'; import { Misc } from './miscellaneous/misc'; const configThrottle: TestBedConfig = { diff --git a/tests/validation.spec.ts b/tests/validation.spec.ts index 42a78638..4c3bfd02 100644 --- a/tests/validation.spec.ts +++ b/tests/validation.spec.ts @@ -1,5 +1,5 @@ import { VALIDATORS, validateOne, validate } from '../src/component/inputs'; -import { ValidatorType, IValidator } from 'src/component/interfaces'; +import { IValidator } from 'src/component/interfaces'; const { INTEGER, @@ -317,10 +317,10 @@ describe('Validation', () => { true, '', () => null, - function () {}, + function () { }, [], null, - class {}, + class { }, new Map(), new Set(), Symbol(), diff --git a/tests/viewport.spec.ts b/tests/viewport.spec.ts index cae268ca..dfeed37d 100644 --- a/tests/viewport.spec.ts +++ b/tests/viewport.spec.ts @@ -1,4 +1,3 @@ -import { Direction } from '../src/component/interfaces'; import { makeTest, TestBedConfig } from './scaffolding/runner'; import { Misc } from './miscellaneous/misc';