diff --git a/.eslintignore b/.eslintignore index ce402928..980512ac 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,5 @@ node_modules dist dist-app +package.json +package-lock.json \ No newline at end of file diff --git a/.eslintrc b/.eslintrc index 8d1fd58a..eaba20b9 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,9 +1,7 @@ { "root": true, "parser": "@typescript-eslint/parser", - "plugins": [ - "@typescript-eslint" - ], + "plugins": ["@typescript-eslint"], "extends": [ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", @@ -11,10 +9,13 @@ ], "rules": { "max-len": ["error", { "code": 120 }], - "quotes": ["error", "single"], + "quotes": ["error", "single", { "avoidEscape": true }], "semi": ["error", "always"], "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }] + "@typescript-eslint/no-unused-vars": [ + "error", + { "argsIgnorePattern": "^_" } + ] }, "overrides": [ { diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 00000000..d24fdfc6 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npx lint-staged diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 00000000..67afd1ca --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,4 @@ +{ + "*.ts": "npm run lint", + "*.{ts,js,html,scss,md,json}": "prettier --write" +} diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..f8637288 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "tabWidth": 2, + "singleQuote": true, + "arrowParens": "avoid", + "trailingComma": "none" +} \ No newline at end of file diff --git a/demo/app/app-routing.module.ts b/demo/app/app-routing.module.ts index 2f48cbb0..5509ac06 100644 --- a/demo/app/app-routing.module.ts +++ b/demo/app/app-routing.module.ts @@ -24,5 +24,4 @@ const routes: Routes = [ imports: [RouterModule.forRoot(routes, { useHash: true })], exports: [RouterModule] }) -export class AppRoutingModule { -} +export class AppRoutingModule {} diff --git a/demo/app/app.component.html b/demo/app/app.component.html index 49599729..a3ee781d 100644 --- a/demo/app/app.component.html +++ b/demo/app/app.component.html @@ -2,13 +2,10 @@
- -
- diff --git a/demo/app/app.component.ts b/demo/app/app.component.ts index d8bda1eb..540e880d 100644 --- a/demo/app/app.component.ts +++ b/demo/app/app.component.ts @@ -1,5 +1,10 @@ import { Component, AfterViewInit, OnDestroy } from '@angular/core'; -import { ActivatedRoute, NavigationStart, Router, Event } from '@angular/router'; +import { + ActivatedRoute, + NavigationStart, + Router, + Event +} from '@angular/router'; import { Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; @@ -8,29 +13,25 @@ import { filter } from 'rxjs/operators'; templateUrl: './app.component.html' }) export class AppComponent implements AfterViewInit, OnDestroy { - hasLayout = true; private subscriptions: Subscription[] = []; - constructor( - private route: ActivatedRoute, - private router: Router - ) { + constructor(private route: ActivatedRoute, private router: Router) { this.subscriptions.push( - router.events.pipe( - filter((event: Event) => event instanceof NavigationStart) - ).subscribe((event: Event) => { - const url = (event as NavigationStart).url; - this.hasLayout = !(url === '/window' || url === '/test'); - if (url === '/window') { - document.body.classList.add('entire-window'); - } else { - document.body.classList.remove('entire-window'); - } - if (!url.includes('#')) { - window.scrollTo(0, 0); - } - }) + router.events + .pipe(filter((event: Event) => event instanceof NavigationStart)) + .subscribe((event: Event) => { + const url = (event as NavigationStart).url; + this.hasLayout = !(url === '/window' || url === '/test'); + if (url === '/window') { + document.body.classList.add('entire-window'); + } else { + document.body.classList.remove('entire-window'); + } + if (!url.includes('#')) { + window.scrollTo(0, 0); + } + }) ); if ('scrollRestoration' in history) { history.scrollRestoration = 'manual'; @@ -39,7 +40,7 @@ export class AppComponent implements AfterViewInit, OnDestroy { ngAfterViewInit() { this.subscriptions.push( - this.route.fragment.subscribe((hash) => { + this.route.fragment.subscribe(hash => { if (hash) { setTimeout(() => { const cmp = document.getElementById(hash); @@ -55,7 +56,8 @@ export class AppComponent implements AfterViewInit, OnDestroy { } ngOnDestroy() { - this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe()); + this.subscriptions.forEach((subscription: Subscription) => + subscription.unsubscribe() + ); } - } diff --git a/demo/app/app.module.ts b/demo/app/app.module.ts index c7213aff..0724ba78 100644 --- a/demo/app/app.module.ts +++ b/demo/app/app.module.ts @@ -43,7 +43,7 @@ import { AppRoutingModule } from './app-routing.module'; ...demos.common, ...demos.datasource, ...demos.adapter, - ...demos.experimental, + ...demos.experimental ], imports: [ BrowserModule, @@ -53,10 +53,7 @@ import { AppRoutingModule } from './app-routing.module'; UiScrollModule, AppRoutingModule ], - providers: [ - RemoteDataService - ], + providers: [RemoteDataService], bootstrap: [AppComponent] }) -export class AppModule { -} +export class AppModule {} diff --git a/demo/app/demos.ts b/demo/app/demos.ts index 45eb0f72..404cb1b4 100644 --- a/demo/app/demos.ts +++ b/demo/app/demos.ts @@ -10,9 +10,8 @@ import { DemoDifferentHeightsComponent } from './samples/common/different-height import { DemoWindowViewportComponent } from './samples/common/window-viewport.component'; import { DemoDatasourceSignaturesComponent } from './samples/datasource/datasource-signatures.component'; -import { - DemoBidirectionalUnlimitedDatasourceComponent -} from './samples/datasource/bidirectional-unlimited-datasource.component'; +// eslint-disable-next-line max-len +import { DemoBidirectionalUnlimitedDatasourceComponent } from './samples/datasource/bidirectional-unlimited-datasource.component'; import { DemoLimitedDatasourceComponent } from './samples/datasource/limited-datasource.component'; import { DemoPositiveLimitedDatasourceComponent } from './samples/datasource/positive-limited-datasource.component'; import { DemoRemoteDatasourceComponent } from './samples/datasource/remote-datasource.component'; @@ -57,7 +56,7 @@ const common = [ DemoInfiniteComponent, DemoHorizontalComponent, DemoDifferentHeightsComponent, - DemoWindowViewportComponent, + DemoWindowViewportComponent ]; const datasource = [ @@ -67,7 +66,7 @@ const datasource = [ DemoPositiveLimitedDatasourceComponent, DemoRemoteDatasourceComponent, DemoInvertedDatasourceComponent, - DemoPagesDatasourceComponent, + DemoPagesDatasourceComponent ]; const adapter = [ @@ -90,7 +89,7 @@ const adapter = [ DemoRemoveComponent, DemoReplaceComponent, DemoClipComponent, - DemoUpdateComponent, + DemoUpdateComponent ]; const experimental = [ @@ -99,7 +98,7 @@ const experimental = [ DemoAdapterFixPositionComponent, DemoAdapterFixUpdaterComponent, DemoAdapterFixScrollToItemComponent, - DemoOnBeforeClipSettingComponent, + DemoOnBeforeClipSettingComponent ]; export default { diff --git a/demo/app/routes.ts b/demo/app/routes.ts index 92d16516..a451b9a0 100644 --- a/demo/app/routes.ts +++ b/demo/app/routes.ts @@ -31,7 +31,7 @@ const globalScope = { experimental: { id: 'experimental', name: 'Experimental' - }, + } }; const datasourceScope = { @@ -69,7 +69,7 @@ const datasourceScope = { id: 'pages', name: 'Pages', scope: globalScope.datasource.id - }, + } }; const settingsScope = { @@ -122,7 +122,7 @@ const settingsScope = { id: 'window-viewport', name: 'Entire window scrollable', scope: globalScope.settings.id - }, + } }; const adapterPropsScope = { @@ -165,7 +165,7 @@ const adapterPropsScope = { id: 'first-last-visible-items', name: 'First and last visible items', scope: globalScope.adapterProps.id - }, + } }; const adapterMethodsScope = { @@ -228,7 +228,7 @@ const adapterMethodsScope = { id: 'update', name: 'Update', scope: globalScope.adapterMethods.id - }, + } }; const experimentalScope = { @@ -261,7 +261,7 @@ const experimentalScope = { id: 'onBeforeClip-setting', name: 'onBeforeClip setting', scope: globalScope.experimental.id - }, + } }; const demos = { @@ -275,7 +275,7 @@ const demos = { }, adapter: { ...globalScope.adapter, - map: [], + map: [] }, adapterProps: { ...globalScope.adapterProps, diff --git a/demo/app/samples/adapter.component.html b/demo/app/samples/adapter.component.html index afa7908c..22fae334 100644 --- a/demo/app/samples/adapter.component.html +++ b/demo/app/samples/adapter.component.html @@ -1,28 +1,28 @@

Angular UI Scroll Adapter Demos

- Adapter is a special object to assess and manipulate the Scroller. - The API it provides is  discussed on this page. - In order to get an access to the Adapter API, - the Datasource has to be instantiated via operator new: + Adapter is a special object to assess and manipulate + the Scroller. The API it provides is  discussed on this page. + In order to get an access to the Adapter API, the + Datasource has to be instantiated via operator new:

-
{{datasourceSample}}
+
{{ datasourceSample }}

For better typings it is recommended to provide also data item type argument:

-
{{datasourceTypedSample}}
+
{{ datasourceTypedSample }}

- The constructor argument is an object of IDatasource type which - must include get method property and - may include settings object property. - The Scroller's augments the result Datasource object - with the Adapter object property during instantiating. - So, after the Datasource object had been instantiated, we may access - this.datasource.adapter property object with its API methods and properties. + The constructor argument is an object of IDatasource + type which must include get method property and may include + settings object property. The Scroller's augments the result + Datasource object with the Adapter object property during + instantiating. So, after the Datasource object had been instantiated, + we may access this.datasource.adapter property object with its + API methods and properties.

diff --git a/demo/app/samples/adapter.component.ts b/demo/app/samples/adapter.component.ts index d4f07de9..4c4d7238 100644 --- a/demo/app/samples/adapter.component.ts +++ b/demo/app/samples/adapter.component.ts @@ -5,14 +5,12 @@ import { Component } from '@angular/core'; templateUrl: './adapter.component.html' }) export class AdapterComponent { - - constructor() { - } + constructor() {} datasourceSample = ` import { Datasource } from 'ngx-ui-scroll'; datasource = new Datasource({ get, settings });`; - datasourceTypedSample = 'datasource = new Datasource({ get, settings });'; - + datasourceTypedSample = + 'datasource = new Datasource({ get, settings });'; } diff --git a/demo/app/samples/adapter/adapter-relax.component.html b/demo/app/samples/adapter/adapter-relax.component.html index 086c7e14..67d9d70b 100644 --- a/demo/app/samples/adapter/adapter-relax.component.html +++ b/demo/app/samples/adapter/adapter-relax.component.html @@ -1,27 +1,24 @@ -

- The Adapter.relax is a kind of empty Adapter method - that does only one thing: it resolves when the Scroller is relaxed. - If we need to run any logic that should not interfere with the Scroller internal processes, - it should be protected by the "relax" promise: + The Adapter.relax is a kind of empty Adapter method that + does only one thing: it resolves when the Scroller is relaxed. If we need + to run any logic that should not interfere with the Scroller internal + processes, it should be protected by the "relax" promise:

-
{{relaxSample}}
+
{{ relaxSample }}

- In this demo we simulate items replacement - by performing a sequence of 2 Adapter methods: remove and insert. - A few existed items (5, 6, 7) are being replaced with a new one (5*). - Removing is an asynchronous operation, and we can't perform - "insert" before "remove" is done (it will produce an error). - Thus, both operations must be run in sequence. - Also, we should be wary of any async processes - before running the Adapter methods chain. - So the algorithm for this demo can be written as follows: + In this demo we simulate items replacement by performing a sequence of 2 + Adapter methods: remove and insert. A few existed items (5, 6, 7) + are being replaced with a new one (5*). Removing is an asynchronous + operation, and we can't perform "insert" before "remove" is done (it will + produce an error). Thus, both operations must be run in sequence. Also, we + should be wary of any async processes before running the Adapter methods + chain. So the algorithm for this demo can be written as follows:

  • wait for Scroller stops
  • @@ -30,37 +27,41 @@
  • run insert

- (Please note, a replacement can be done via + (Please note, a replacement can be done via Adapter.replace API in a single run; - the insert-remove approach is less performant than the Adapter.replace) + fragment="{{ adapterMethodsScope.map.replace.id }}" + >Adapter.replace API + in a single run; the insert-remove approach is less performant + than the Adapter.replace)

- The purpose of the Adapter.relax method is to make sure that the uiScroll relaxes - and there are no pending internal tasks running on the Scroller's end. - The first tab "Relax" demonstrates how the desired sequence can be implemented with - the Adapter.relax method. - This method returns a promise which becomes resolved when the Scroller stops. + The purpose of the Adapter.relax method is to make sure that the + uiScroll relaxes and there are no pending internal tasks running + on the Scroller's end. The first tab "Relax" demonstrates how the desired + sequence can be implemented with the Adapter.relax method. This + method returns a promise which becomes resolved when the Scroller stops. Basically, this may be treated as a shortcut for the following approach, which is presented on the "Is loading" tab:

-
{{isLoadingSample}}
+
{{ isLoadingSample }}

- All the tabs are equivalent. The third tab "Callback" uses a callback signature of - the Adapter.relax method. The method accepts callback as an optional argument, - and this allows to run necessary logic right before resolving the "relax" promise. - This might be helpful if we don't want to wait until the end of the current call stack. + All the tabs are equivalent. The third tab "Callback" uses a callback + signature of the Adapter.relax method. The method accepts + callback as an optional argument, and this allows to run necessary logic + right before resolving the "relax" promise. This might be helpful if we + don't want to wait until the end of the current call stack.

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". + fragment="{{ adapterMethodsScope.map.returnValue.id }}" + >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 c16b1cce..c23d1000 100644 --- a/demo/app/samples/adapter/adapter-relax.component.ts +++ b/demo/app/samples/adapter/adapter-relax.component.ts @@ -10,7 +10,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './adapter-relax.component.html' }) export class DemoAdapterRelaxComponent { - demoContext = { config: demos.adapterMethods.map.relax, noInfo: true @@ -28,10 +27,11 @@ export class DemoAdapterRelaxComponent { } }); - sources: DemoSources = [{ - active: true, - name: 'Relax', - text: `async doReplace() { + sources: DemoSources = [ + { + active: true, + name: 'Relax', + text: `async doReplace() { const { adapter } = this.datasource; await adapter.relax(); adapter.remove({ @@ -44,9 +44,10 @@ export class DemoAdapterRelaxComponent { }); } ` - }, { - name: 'Is loading', - text: `relax(cb: Function) { + }, + { + name: 'Is loading', + text: `relax(cb: Function) { const { adapter } = this.datasource; if (!adapter.isLoading) { cb(); @@ -72,9 +73,10 @@ doReplace() { ); } ` - }, { - name: 'Callback', - text: `doReplace() { + }, + { + name: 'Callback', + text: `doReplace() { const { adapter } = this.datasource; adapter.relax(() => adapter.remove({ @@ -89,9 +91,10 @@ doReplace() { ); } ` - }, { - name: 'Return', - text: `async doReplace() { + }, + { + name: 'Return', + text: `async doReplace() { const { adapter } = this.datasource; await adapter.relax(); await adapter.remove({ @@ -103,7 +106,8 @@ doReplace() { }); } ` - }]; + } + ]; isLoadingSample = ` if (!adapter.isLoading) { @@ -131,5 +135,4 @@ doReplace() { after: ({ $index }) => $index === 4 }); } - } diff --git a/demo/app/samples/adapter/adapter-return-value.component.html b/demo/app/samples/adapter/adapter-return-value.component.html index 21e5151a..2b073a6e 100644 --- a/demo/app/samples/adapter/adapter-return-value.component.html +++ b/demo/app/samples/adapter/adapter-return-value.component.html @@ -1,45 +1,45 @@
-

- Along with Adapter properties there are several Adapter methods. - Each Adapter method runs the Scroller's Workflow - that handles multiple internal processes, some of which may be asynchronous. - In order to deal with such asynchronicity, - the return value of any Adapter method is a Promise - that is resolved when all internal processes - triggered by the Adapter are terminated. + Along with Adapter properties there are several + Adapter methods. Each Adapter method runs the Scroller's + Workflow that handles multiple internal processes, some of which may be + asynchronous. In order to deal with such asynchronicity, the return value + of any Adapter method is a Promise that is resolved when + all internal processes triggered by the Adapter are terminated. Return value has following type

-
{{returnValueType}}
+
{{ returnValueType }}

- - success: specifies whether the Adapter method completed successfully or not; - for example, if some method is invoked with wrong arguments, no error will be thrown, - but we'll get success: false in the result; -
- - details: contains error message in case success is false; -
- - immediate: specifies whether the Adapter method completed immediately or not; - for example, if Adapter.remove won't remove any items, then it will be - terminated immediately and we'll get immediate: true in the result. + - success: specifies whether the Adapter method + completed successfully or not; for example, if some method is invoked with + wrong arguments, no error will be thrown, but we'll get + success: false in the result; +
+ - details: contains error message in case success is + false; +
+ - immediate: specifies whether the Adapter method + completed immediately or not; for example, if + Adapter.remove won't remove any items, then it will be terminated + immediately and we'll get immediate: true in the result.

-
{{returnValueSample}}
+
{{ returnValueSample }}

- Note, immediate: false does not necessarily mean that the Adapter method - worked asynchronously. For example, Adapter.clip does its work in a single call stack - regardless of whether there are items to be clipped or not. But in any case, - if there is any logic that needs to be run after the Adapter method completes its job, + Note, immediate: false does not necessarily mean that the + Adapter method worked asynchronously. For example, + Adapter.clip does its work in a single call stack regardless of + whether there are items to be clipped or not. But in any case, if there is + any logic that needs to be run after the Adapter method completes its job, it is highly recommended to implement an explicit sequence like this:

-
{{explicitSequenceSample}}
- +
{{ explicitSequenceSample }}
-
diff --git a/demo/app/samples/adapter/adapter-return-value.component.ts b/demo/app/samples/adapter/adapter-return-value.component.ts index a7e3bf08..d0fe5dc8 100644 --- a/demo/app/samples/adapter/adapter-return-value.component.ts +++ b/demo/app/samples/adapter/adapter-return-value.component.ts @@ -7,7 +7,6 @@ import { demos } from '../../routes'; templateUrl: './adapter-return-value.component.html' }) export class DemoAdapterReturnValueComponent { - demoConfig = demos.adapterMethods.map.returnValue; returnValueType = ` Promise<{ diff --git a/demo/app/samples/adapter/append-prepend-sync.component.html b/demo/app/samples/adapter/append-prepend-sync.component.html index 162e12a5..995775f3 100644 --- a/demo/app/samples/adapter/append-prepend-sync.component.html +++ b/demo/app/samples/adapter/append-prepend-sync.component.html @@ -1,5 +1,5 @@ - {{index}}) {{item.text}} + {{ index }}) {{ item.text }}
  - -  items {{increasePrepend ? 'increasingly' : 'decreasingly'}}   -
+ +  items + {{ increasePrepend ? 'increasingly' : 'decreasingly' }}   +
  - -  items {{decreaseAppend ? 'decreasingly' : 'increasingly'}}   + +  items + {{ decreaseAppend ? 'decreasingly' : 'increasingly' }}  

- Along with items parameter both append and prepend methods - have eof/bof parameter - which is optional and which prevents rendering of new items - when the end of the dataset (if we are speaking of append) or - beginning of the dataset (prepend case) is not reached. See also + Along with items parameter both append and prepend methods have + eof/bof parameter which is optional and which prevents + rendering of new items 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. + fragment="{{ adapterPropsScope.map.bofEof.id }}" + >bof/eof demo.

- For example, if we call {{prependCallSample}} when the beginning of the dataset is reached - and no more items can be fetched in the backward direction, then the new items will be injected and rendered immediately. - But if we are not in BOF, new items will not appear, they will be virtualized: - viewport backward padding element size will be increased in accordance with - the number of new items multiplied by the default item size. - The same works for {{appendCallSample}} call + For example, if we call {{ prependCallSample }} when the + beginning of the dataset is reached and no more items can be fetched in + the backward direction, then the new items will be injected and rendered + immediately. But if we are not in BOF, new items will not appear, they + will be virtualized: viewport backward padding element size will be + increased in accordance with the number of new items multiplied by the + default item size. The same works for {{ appendCallSample }} call adjusted for forward direction and forward padding element.

- Indexes increase by default when Adapter.append and - decrease by default when Adapter.prepend. - The indexing strategy can be changed by decrease/increase params. - They are boolean and set to false by default. - For example, if we call {{prependIncreaseCallSample}}, - the topmost index remains unchanged, prepended items start with the topmost index, - the rest indexes are incremented. + Indexes increase by default when Adapter.append and decrease by + default when Adapter.prepend. The indexing strategy can be + changed by decrease/increase params. They are boolean + and set to false by default. For example, if we call + {{ prependIncreaseCallSample }}, the topmost index remains unchanged, prepended items start with the + topmost index, the rest indexes are incremented.

- The most important idea behind the Adapter update API is that - the changes made via Adapter must be synched with the Datasource. - This demo implements one of the possible ways of synching - the Datasource with the Adapter.append/prepend updates. - The external dataset is changed per each doAppend / doPrepend call - destructively by unshift and push Array's methods. - There are also some additional variables that provide index offsets and - that are used in the Datasource.get and doPrepend/doAppend methods - to guarantee a stable data flow from the App component to the Scroller. - Another examples of such synchronization could be found in + The most important idea behind the Adapter update API is that the changes + made via Adapter must be synched with the Datasource. This demo implements + one of the possible ways of synching the Datasource with the + Adapter.append/prepend updates. The external dataset is changed per each + doAppend / doPrepend call destructively by + unshift and push Array's methods. There are also some + additional variables that provide index offsets and that are used in the + Datasource.get and doPrepend/doAppend methods to guarantee a + stable data flow from the App component to the Scroller. Another examples + of such synchronization could be found in remove and + fragment="{{ adapterMethodsScope.map.remove.id }}" + >remove + and insert demos. + fragment="{{ adapterMethodsScope.map.insert.id }}" + >insert + demos.

diff --git a/demo/app/samples/adapter/append-prepend-sync.component.ts b/demo/app/samples/adapter/append-prepend-sync.component.ts index e24dc7af..d60ad424 100644 --- a/demo/app/samples/adapter/append-prepend-sync.component.ts +++ b/demo/app/samples/adapter/append-prepend-sync.component.ts @@ -1,7 +1,12 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType, MyItem } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType, + MyItem +} from '../../shared/interfaces'; import { doLog } from '../../shared/datasource-get'; import { Datasource } from 'ngx-ui-scroll'; @@ -11,7 +16,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './append-prepend-sync.component.html' }) export class DemoAppendPrependSyncComponent { - demoContext: DemoContext = { config: demos.adapterMethods.map.appendPrependSync, viewportId: 'append-prepend-sync-viewport', @@ -60,9 +64,10 @@ export class DemoAppendPrependSyncComponent { } }); - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `inputPrepend = 4; + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `inputPrepend = 4; inputAppend = 4; increasePrepend = false; decreaseAppend = false; @@ -139,10 +144,11 @@ async doAppend() { items, eof: true, decrease: this.decreaseAppend }); }` - }, { - active: true, - name: DemoSourceType.Template, - text: ` + }, + { + active: true, + name: DemoSourceType.Template, + text: ` items {{increasePrepend ? 'increasingly' : 'decreasingly'}} @@ -159,9 +165,10 @@ async doAppend() { ` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -170,7 +177,8 @@ async doAppend() { font-weight: bold; height: 25px; }` - }]; + } + ]; prependCallSample = 'Adapter.prepend({ items, bof: true })'; appendCallSample = 'Adapter.append({ items, eof: true })'; @@ -196,12 +204,17 @@ async doAppend() { this.MAX++; } this.absMinIndex--; - const newItem: MyItem = { id: 'x', text: 'item #' + this.absMinIndex + '*' }; + const newItem: MyItem = { + id: 'x', + text: 'item #' + this.absMinIndex + '*' + }; this.data.unshift(newItem); items.push(newItem); } this.datasource.adapter.prepend({ - items, bof: true, increase: this.increasePrepend + items, + bof: true, + increase: this.increasePrepend }); } @@ -215,13 +228,17 @@ async doAppend() { this.MIN--; } this.absMaxIndex++; - const newItem: MyItem = { id: 'x', text: 'item #' + this.absMaxIndex + '*' }; + const newItem: MyItem = { + id: 'x', + text: 'item #' + this.absMaxIndex + '*' + }; this.data.push(newItem); items.push(newItem); } this.datasource.adapter.append({ - items, eof: true, decrease: this.decreaseAppend + items, + eof: true, + decrease: this.decreaseAppend }); } - } diff --git a/demo/app/samples/adapter/append-prepend.component.html b/demo/app/samples/adapter/append-prepend.component.html index 4c1b6886..a4822cfe 100644 --- a/demo/app/samples/adapter/append-prepend.component.html +++ b/demo/app/samples/adapter/append-prepend.component.html @@ -1,59 +1,69 @@ - +
/ - +

- Adding items at the end and at the beginning of the Scroller's Buffer is possible with + Adding items at the end and at the beginning of the Scroller's Buffer is + possible with Adapter.append and Adapter.prepend methods respectively. They have the following object-arguments:

-
{{appendArgumentsDescription}}
-
{{prependArgumentsDescription}}
+
+
{{ appendArgumentsDescription }}
+
+
+
{{ prependArgumentsDescription }}
+

The items parameter is an array of items we want to add, eof/bof params provide virtualization, - decrease/increase define index strategy. - Here we deal with only items parameter, - other params are considered in the decrease/increase define index strategy. Here we deal + with only items parameter, other params are considered in the + next demo. - Both append and prepend methods act in the same way, + fragment="{{ adapterMethodsScope.map.appendPrependSync.id }}" + >next demo. Both append and prepend methods act in the same way, so let's discuss prepend.

- In this demo we have 20 items on start, 5 of them (95-99) are invisible on the backward direction. - By pushing "Prepend" button we want to add 4 (which is the input value) new items - to the top of the list. After they are prepended, they become visible when scrolling up. - The Scroller accurately injects prepended items into - its Buffer consisting of 95-114 rows initially, - so 1-4 new items temporary take place of items with indexes 91-94. + In this demo we have 20 items on start, 5 of them (95-99) are invisible on + the backward direction. By pushing "Prepend" button we want to add 4 + (which is the input value) new items to the top of the list. After they + are prepended, they become visible when scrolling up. The Scroller + accurately injects prepended items into its Buffer consisting of 95-114 + rows initially, so 1-4 new items temporary take place of items with + indexes 91-94.

- But if we scroll away and make '90s items invisible and removed from the viewport, - and then scroll back, we will realise that nothing changes: + But if we scroll away and make '90s items invisible and removed from the + viewport, and then scroll back, we will realise that nothing changes: "new" items are gone and the old 91-94 items returned to initial position. - The point is that the changes we provide via Adapter over the internal Scroller's Buffer - don't affect the external Datasource we implemented on the Component level. - This is the end app developer responsibility to take care of the data consistency - during manual updates such as append, prepend, insert, remove, etc: - the Datasource.get must provide correct data, while the Scroller maintains indexes. + The point is that the changes we provide via Adapter over the + internal Scroller's Buffer don't affect the external + Datasource we implemented on the Component level. This is the end + app developer responsibility to take care of the data consistency during + manual updates such as append, prepend, insert, remove, etc: the + Datasource.get must provide correct data, while the Scroller + maintains indexes. The next sample - provides one of the approach of consistent Datasource implementation - with Adapter.append and Adapter.prepend methods usage. + fragment="{{ adapterMethodsScope.map.appendPrependSync.id }}" + >The next sample + provides one of the approach of consistent + Datasource implementation with Adapter.append and + Adapter.prepend methods usage.

diff --git a/demo/app/samples/adapter/append-prepend.component.ts b/demo/app/samples/adapter/append-prepend.component.ts index 37c65407..5da7f4de 100644 --- a/demo/app/samples/adapter/append-prepend.component.ts +++ b/demo/app/samples/adapter/append-prepend.component.ts @@ -1,7 +1,12 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType, MyItem } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType, + MyItem +} from '../../shared/interfaces'; import { doLog } from '../../shared/datasource-get'; import { Datasource } from 'ngx-ui-scroll'; @@ -11,7 +16,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './append-prepend.component.html' }) export class DemoAppendPrependComponent { - demoContext: DemoContext = { config: demos.adapterMethods.map.appendPrepend, viewportId: 'append-prepend-viewport', @@ -40,9 +44,10 @@ export class DemoAppendPrependComponent { } }); - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `data: MyItem[]; + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `data: MyItem[]; inputValue = 4; newIndex = 0; @@ -85,10 +90,11 @@ async doAppend() { items: this.generateItems(false) }); }` - }, { - active: true, - name: DemoSourceType.Template, - text: ` / + }, + { + active: true, + name: DemoSourceType.Template, + text: ` / @@ -97,9 +103,10 @@ async doAppend() {
{{item.text}}
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -108,7 +115,8 @@ async doAppend() { font-weight: bold; height: 25px; }` - }]; + } + ]; prependCallSample = 'Adapter.prepend({ items })'; appendCallSample = 'Adapter.append({ items })'; @@ -155,5 +163,4 @@ async doAppend() { items: this.generateItems(false) }); } - } diff --git a/demo/app/samples/adapter/bof-eof.component.html b/demo/app/samples/adapter/bof-eof.component.html index 06a62132..57e546b4 100644 --- a/demo/app/samples/adapter/bof-eof.component.html +++ b/demo/app/samples/adapter/bof-eof.component.html @@ -1,36 +1,33 @@ - +
- Begin of file is {{datasource.adapter.bof ? '' : 'not'}} reached -
- End of file is {{datasource.adapter.eof ? '' : 'not'}} reached -
- BOF / EOF changes counter: {{edgeCounter}} + Begin of file is {{ datasource.adapter.bof ? '' : 'not' }} reached +
+ End of file is {{ datasource.adapter.eof ? '' : 'not' }} reached +
+ BOF / EOF changes counter: {{ edgeCounter }}

- Adapter.bof and Adapter.eof are read-only boolean properties - that indicate whether the dataset beginning (bof, begin of file) or - the dataset ending (eof, end of file) is reached or not. + Adapter.bof and Adapter.eof are read-only boolean + properties that indicate whether the dataset beginning (bof, begin of + file) or the dataset ending (eof, end of file) is reached or not.

Along with scalar properties there are also two Observable ones: - Adapter.bof$ and Adapter.eof$ which are Subjects and produce - new values when scalar properties change. + Adapter.bof$ and Adapter.eof$ which are Subjects and + produce new values when scalar properties change.

- In this demo we have limited datasource consisting of 100 items starting from index 1. - So initially we are at the left border of the dataset, and that's why we see - "Begin of file is reached" message. - After some scrolling down bof is turned to false, - the first item is destroyed and can be retrieved again. + In this demo we have limited datasource consisting of 100 items starting + from index 1. So initially we are at the left border of the dataset, and + that's why we see "Begin of file is reached" message. After some scrolling + down bof is turned to false, the first item is destroyed + and can be retrieved again.

- The edgeCounter is bof/eof changes counter. It is defined via merge(bof$, eof$) - subscription which is triggered each time bof$/eof$ emits a new value. + The edgeCounter is bof/eof changes counter. It is defined via + merge(bof$, eof$) subscription which is triggered each time + bof$/eof$ emits a new value.

diff --git a/demo/app/samples/adapter/bof-eof.component.ts b/demo/app/samples/adapter/bof-eof.component.ts index d25f264e..f4eee0ce 100644 --- a/demo/app/samples/adapter/bof-eof.component.ts +++ b/demo/app/samples/adapter/bof-eof.component.ts @@ -2,7 +2,12 @@ import { Component } from '@angular/core'; import { merge } from 'rxjs'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType, MyItem } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType, + MyItem +} from '../../shared/interfaces'; import { doLog } from '../../shared/datasource-get'; import { Datasource } from 'ngx-ui-scroll'; @@ -12,7 +17,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './bof-eof.component.html' }) export class DemoBofEofComponent { - demoContext: DemoContext = { config: demos.adapterProps.map.bofEof, viewportId: 'bof-eof-viewport', @@ -22,7 +26,8 @@ export class DemoBofEofComponent { datasource = new Datasource({ get: (index, count, success) => { - const MIN = 1, MAX = 100; + const MIN = 1, + MAX = 100; const data = []; const start = Math.max(MIN, index); const end = Math.min(index + count - 1, MAX); @@ -43,9 +48,10 @@ export class DemoBofEofComponent { merge(bof$, eof$).subscribe(() => this.edgeCounter++); } - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `datasource = new Datasource({ + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `datasource = new Datasource({ get: (index, count, success) => { const MIN = 1, MAX = 100; const data = []; @@ -67,10 +73,11 @@ constructor() { merge(bof$, eof$).subscribe(() => this.edgeCounter++); } ` - }, { - active: true, - name: DemoSourceType.Template, - text: `Begin of file is {{datasource.adapter.bof ? '' : 'not'}} reached + }, + { + active: true, + name: DemoSourceType.Template, + text: `Begin of file is {{datasource.adapter.bof ? '' : 'not'}} reached
End of file is {{datasource.adapter.eof ? '' : 'not'}} reached
@@ -81,9 +88,10 @@ BOF / EOF changes counter: {{edgeCounter}}
{{item.text}}
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -92,5 +100,6 @@ BOF / EOF changes counter: {{edgeCounter}} font-weight: bold; height: 25px; }` - }]; + } + ]; } diff --git a/demo/app/samples/adapter/buffer-info.component.html b/demo/app/samples/adapter/buffer-info.component.html index f3bd2c4f..c00a5a83 100644 --- a/demo/app/samples/adapter/buffer-info.component.html +++ b/demo/app/samples/adapter/buffer-info.component.html @@ -1,25 +1,22 @@ - +
- firstIndex: {{datasource.adapter.bufferInfo.firstIndex}} / - lastIndex: {{datasource.adapter.bufferInfo.lastIndex}}
- minIndex: {{datasource.adapter.bufferInfo.minIndex}} / - maxIndex: {{datasource.adapter.bufferInfo.maxIndex}}
+ firstIndex: {{ datasource.adapter.bufferInfo.firstIndex }} / lastIndex: + {{ datasource.adapter.bufferInfo.lastIndex }}
+ minIndex: {{ datasource.adapter.bufferInfo.minIndex }} / maxIndex: + {{ datasource.adapter.bufferInfo.maxIndex }}

- Adapter.bufferInfo is a read-only property which exposes - an object of the following type: + Adapter.bufferInfo is a read-only property which exposes an + object of the following type:

-
{{bufferInfoType}}
+
{{ bufferInfoType }}

firstIndex & lastIndex are the first and the last indexes in the current Buffer, - minIndex & maxIndex are min and max indexes that were present in the Buffer, - absMinIndex & absMaxIndex are min and max indexes that can be present in the Buffer. + minIndex & maxIndex are min and max indexes that were + present in the Buffer, absMinIndex & absMaxIndex are min + and max indexes that can be present in the Buffer.

diff --git a/demo/app/samples/adapter/buffer-info.component.ts b/demo/app/samples/adapter/buffer-info.component.ts index ddc984ed..78ff4973 100644 --- a/demo/app/samples/adapter/buffer-info.component.ts +++ b/demo/app/samples/adapter/buffer-info.component.ts @@ -1,7 +1,11 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; import { Datasource } from 'ngx-ui-scroll'; @@ -11,7 +15,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './buffer-info.component.html' }) export class DemoBufferInfoComponent { - demoContext: DemoContext = { config: demos.adapterProps.map.bufferInfo, viewportId: 'buffer-info-viewport', @@ -23,9 +26,10 @@ export class DemoBufferInfoComponent { get: datasourceGetCallbackInfinite(this.demoContext) }); - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `datasource = new Datasource ({ + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `datasource = new Datasource ({ get: (index, count, success) => { const data = []; for (let i = index; i <= index + count - 1; i++) { @@ -34,10 +38,11 @@ export class DemoBufferInfoComponent { success(data); } });` - }, { - active: true, - name: DemoSourceType.Template, - text: `firstIndex: {{datasource.adapter.bufferInfo.firstIndex}} / + }, + { + active: true, + name: DemoSourceType.Template, + text: `firstIndex: {{datasource.adapter.bufferInfo.firstIndex}} / lastIndex: {{datasource.adapter.bufferInfo.lastIndex}}
minIndex: {{datasource.adapter.bufferInfo.minIndex}} / maxIndex: {{datasource.adapter.bufferInfo.maxIndex}}
@@ -47,9 +52,10 @@ maxIndex: {{datasource.adapter.bufferInfo.maxIndex}}
{{item.text}}
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -58,7 +64,8 @@ maxIndex: {{datasource.adapter.bufferInfo.maxIndex}}
font-weight: bold; height: 25px; }` - }]; + } + ]; bufferInfoType = `interface IBufferInfo { firstIndex: number; @@ -68,5 +75,4 @@ maxIndex: {{datasource.adapter.bufferInfo.maxIndex}}
absMinIndex: number; absMaxIndex: number; }`; - } diff --git a/demo/app/samples/adapter/check-size.component.html b/demo/app/samples/adapter/check-size.component.html index ef7cec69..f1200355 100644 --- a/demo/app/samples/adapter/check-size.component.html +++ b/demo/app/samples/adapter/check-size.component.html @@ -1,24 +1,19 @@ - +

- Autoscroll: + Autoscroll:
- First visible item's index: {{datasource.adapter.firstVisible.$index}} + First visible item's index: {{ datasource.adapter.firstVisible.$index }}

- The Adapter.check method could be useful when rows heights can change after rendering. - By invoking the Adapter.check method we ask the Scroller to check - if any of items that are in the current Buffer (and present in DOM) changed their size. -

-

- In this demo, the doCheck method is implemented to + The Adapter.check method could be useful when rows heights can + change after rendering. By invoking the Adapter.check method we + ask the Scroller to check if any of items that are in the current Buffer + (and present in DOM) changed their size.

+

In this demo, the doCheck method is implemented to

  • change the actual size of 10 items (from 15 to 25) in DOM;
  • run Adapter.check();
  • diff --git a/demo/app/samples/adapter/check-size.component.ts b/demo/app/samples/adapter/check-size.component.ts index 51f30d6a..5cf34492 100644 --- a/demo/app/samples/adapter/check-size.component.ts +++ b/demo/app/samples/adapter/check-size.component.ts @@ -1,7 +1,12 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType, MyItem } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType, + MyItem +} from '../../shared/interfaces'; import { doLog } from '../../shared/datasource-get'; import { Datasource } from 'ngx-ui-scroll'; @@ -11,7 +16,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './check-size.component.html' }) export class DemoCheckSizeComponent { - demoContext: DemoContext = { config: demos.adapterMethods.map.check, viewportId: 'check-size-viewport', @@ -50,9 +54,10 @@ export class DemoCheckSizeComponent { } }); - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `MIN = 1; + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `MIN = 1; MAX = 200; startIndex = 20; sizeValue = 15; @@ -134,10 +139,11 @@ async doCheck() { this.autoscroll(firstVisibleIndex); } }` - }, { - active: true, - name: DemoSourceType.Template, - text: ` + }, + { + active: true, + name: DemoSourceType.Template, + text: `
    Autoscroll:
    @@ -148,9 +154,10 @@ First visible item's index: {{datasource.adapter.firstVisible.$index}}
    {{item.text}}
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -160,10 +167,12 @@ First visible item's index: {{datasource.adapter.firstVisible.$index}} height: 25px; overflow: hidden; }` - }]; + } + ]; findElement(index: number): HTMLElement | null { - const viewportId = this.demoContext.viewportId || this.demoContext.config.id; + const viewportId = + this.demoContext.viewportId || this.demoContext.config.id; const viewportElement = document.getElementById(viewportId); return viewportElement ? viewportElement.querySelector(`[data-sid="${index}"]`) @@ -189,7 +198,8 @@ First visible item's index: {{datasource.adapter.firstVisible.$index}} autoscroll(index: number) { const element = this.findElement(index); - const viewportId = this.demoContext.viewportId || this.demoContext.config.id; + const viewportId = + this.demoContext.viewportId || this.demoContext.config.id; const viewportElement = document.getElementById(viewportId); if (!element || !viewportElement) { return; @@ -216,5 +226,4 @@ First visible item's index: {{datasource.adapter.firstVisible.$index}} this.autoscroll(firstVisibleIndex); } } - } diff --git a/demo/app/samples/adapter/clip.component.html b/demo/app/samples/adapter/clip.component.html index e3fe4eeb..8abe0c77 100644 --- a/demo/app/samples/adapter/clip.component.html +++ b/demo/app/samples/adapter/clip.component.html @@ -5,48 +5,50 @@

- The Adapter.clip method allows to remove out-of-viewport items on demand. - Out-of-viewport means out of visible part of the viewport + paddings, see + The Adapter.clip method allows to remove out-of-viewport items on + demand. Out-of-viewport means out of visible part of the viewport + + paddings, see padding setting demo. - Not to be confused with Adapter.remove, - as the Adapter.clip method has no impact on the Datasource. - It just cleans up the DOM. + fragment="{{ settingsScope.map.padding.id }}" + >padding setting + demo. Not to be confused with Adapter.remove, as the + Adapter.clip method has no impact on the Datasource. It + just cleans up the DOM.

- Commonly, the Scroller runs a clipping procedure - each time new items are fetched after scrolling - and it clips old items from a side of the viewport - that is opposite to a side where new items appear. - This is one of the core parts of the virtualization concept. - By invoking the Adapter.clip method - we are telling the Scroller to run this procedure immediately - and remove items that are out-of-viewport in both directions or in specific direction. - Here is an argument object that lets to specify the clipping direction: + Commonly, the Scroller runs a clipping procedure each time new items are + fetched after scrolling and it clips old items from a side of the viewport + that is opposite to a side where new items appear. This is one of the core + parts of the virtualization concept. By invoking the + Adapter.clip method we are telling the Scroller to run this + procedure immediately and remove items that are out-of-viewport in both + directions or in specific direction. Here is an argument object that lets + to specify the clipping direction:

-
{{clipOptionsDescription}}
+
{{ clipOptionsDescription }}

- This could be useful when we enlarge the list of items manually, - via Adapter.append or Adapter.prepend methods. - For example, we appended 100 new items and started scrolling down, in forward direction. - We might want the items that exit the viewport in backward direction to be clipped. - Such a clipping will occur automatically only when we reach the bottom line of the buffer - and new items are fetched and rendered in forward direction. - If we don't want to wait, we just call Adapter.clip({{clipOptionsSample}}). + This could be useful when we enlarge the list of items manually, via + Adapter.append or Adapter.prepend methods. For example, + we appended 100 new items and started scrolling down, in forward + direction. We might want the items that exit the viewport in backward + direction to be clipped. Such a clipping will occur automatically only + when we reach the bottom line of the buffer and new items are fetched and + rendered in forward direction. If we don't want to wait, we just call + Adapter.clip({{ clipOptionsSample }}).

- This demo implements some artificial but quite illustrative case. - Here we disabled virtualization by turning on the + This demo implements some artificial but quite illustrative case. Here we + disabled virtualization by turning on the infinite setting. - Clipping will never happen automatically. - We see how the DOM elements counter value is getting bigger and bigger as we scroll on and on. - By pressing the "Clip" button, we let only 20-21 items to survive. - This way a kind of manual virtualization could be implemented. + fragment="{{ settingsScope.map.infiniteMode.id }}" + >infinite setting. Clipping will never happen automatically. We see how the DOM elements + counter value is getting bigger and bigger as we scroll on and on. By + pressing the "Clip" button, we let only 20-21 items to survive. This way a + kind of manual virtualization could be implemented.

diff --git a/demo/app/samples/adapter/clip.component.ts b/demo/app/samples/adapter/clip.component.ts index 5d6728d7..749b8557 100644 --- a/demo/app/samples/adapter/clip.component.ts +++ b/demo/app/samples/adapter/clip.component.ts @@ -1,7 +1,11 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; import { Datasource } from 'ngx-ui-scroll'; @@ -11,7 +15,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './clip.component.html' }) export class DemoClipComponent { - demoContext: DemoContext = { config: demos.adapterMethods.map.clip, viewportId: 'clip-viewport', @@ -28,9 +31,10 @@ export class DemoClipComponent { } }); - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `datasource = new Datasource ({ + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `datasource = new Datasource ({ get: (index, count, success) => { const data = []; for (let i = index; i <= index + count - 1; i++) { @@ -47,19 +51,21 @@ async doClip() { await this.datasource.adapter.relax(); await this.datasource.adapter.clip(); }` - }, { - active: true, - name: DemoSourceType.Template, - text: ` + }, + { + active: true, + name: DemoSourceType.Template, + text: `
{{item.text}}
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -68,7 +74,8 @@ async doClip() { font-weight: bold; height: 25px; }` - }]; + } + ]; clipOptionsDescription = ` AdapterClipOptions { forwardOnly?: boolean; @@ -81,5 +88,4 @@ async doClip() { await this.datasource.adapter.relax(); await this.datasource.adapter.clip(); } - } diff --git a/demo/app/samples/adapter/first-last-visible-items.component.html b/demo/app/samples/adapter/first-last-visible-items.component.html index 42cc5d6a..249aa5fb 100644 --- a/demo/app/samples/adapter/first-last-visible-items.component.html +++ b/demo/app/samples/adapter/first-last-visible-items.component.html @@ -1,38 +1,37 @@ - +
- First visible item's index: {{datasource.adapter.firstVisible.$index}} -
- Last visible item's index: {{(datasource.adapter.lastVisible$ | async)?.$index}} -
- Visible items counter: {{visibleCount}} + First visible item's index: {{ datasource.adapter.firstVisible.$index }} +
+ Last visible item's index: + {{ (datasource.adapter.lastVisible$ | async)?.$index }} +
+ Visible items counter: {{ visibleCount }}

- The Adapter has two read-only properties - which allow to determine the first and the last visible items: - firstVisible and lastVisible. - These are the objects of special ItemAdapter type + The Adapter has two read-only properties which allow to determine + the first and the last visible items: firstVisible and + lastVisible. These are the objects of special + ItemAdapter type

-
{{itemAdapterDescription}}
+
{{ itemAdapterDescription }}

$index corresponds to Datasource item index value; - data is what we were pushing to the Scroller - on Datasource.get (item's content); - element is the DOM element that is relevant to our item. + data is what we were pushing to the Scroller on + Datasource.get (item's content); element is the DOM + element that is relevant to our item.

- In addition to scalar firstVisible and lastVisible properties - there are also Observable versions: firstVisible$ and lastVisible$. - In this demo we calculate a number of visible items with the help of combineLatest RxJs method. + In addition to scalar firstVisible and + lastVisible properties there are also Observable versions: + firstVisible$ and lastVisible$. In this demo we + calculate a number of visible items with the help of + combineLatest RxJs method.

Recalculation of the first and the last items occurs per each non-empty - "inner loop" session, when a bunch of new items are fetched via Datasource.get, - rendered and unnecessary items are clipped out. + "inner loop" session, when a bunch of new items are fetched via + Datasource.get, rendered and unnecessary items are clipped out.

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 5d7a5fff..ac4cf78d 100644 --- a/demo/app/samples/adapter/first-last-visible-items.component.ts +++ b/demo/app/samples/adapter/first-last-visible-items.component.ts @@ -2,7 +2,11 @@ import { Component } from '@angular/core'; import { combineLatest } from 'rxjs'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; import { Datasource } from 'ngx-ui-scroll'; @@ -12,7 +16,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './first-last-visible-items.component.html' }) export class DemoFirstLastVisibleItemsComponent { - demoContext: DemoContext = { config: demos.adapterProps.map.firstLastVisible, viewportId: 'first-last-visible-items-viewport', @@ -28,19 +31,19 @@ export class DemoFirstLastVisibleItemsComponent { visibleCount = 0; constructor() { - setTimeout(() => this.init = true); + setTimeout(() => (this.init = true)); const { firstVisible$, lastVisible$ } = this.datasource.adapter; - combineLatest([firstVisible$, lastVisible$]) - .subscribe(result => { - const first = Number(result[0].$index); - const last = Number(result[1].$index); - this.visibleCount = !isNaN(first) && !isNaN(last) ? last - first + 1 : 0; - }); + combineLatest([firstVisible$, lastVisible$]).subscribe(result => { + const first = Number(result[0].$index); + const last = Number(result[1].$index); + this.visibleCount = !isNaN(first) && !isNaN(last) ? last - first + 1 : 0; + }); } - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `datasource = new Datasource ({ + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `datasource = new Datasource ({ get: (index, count, success) => { const data = []; for (let i = index; i <= index + count - 1; i++) { @@ -60,10 +63,11 @@ constructor() { isNaN(first) || isNaN(last) ? 0 : last - first + 1; }); }` - }, { - active: true, - name: DemoSourceType.Template, - text: `First visible item's index: + }, + { + active: true, + name: DemoSourceType.Template, + text: `First visible item's index: {{datasource.adapter.firstVisible.$index}}
Last visible item's index: @@ -76,9 +80,10 @@ Visible items counter: {{visibleCount}}
{{item.text}}
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -87,12 +92,12 @@ Visible items counter: {{visibleCount}} font-weight: bold; height: 25px; }` - }]; + } + ]; itemAdapterDescription = ` ItemAdapter { $index: number; data: any; element?: HTMLElement; }`; - } diff --git a/demo/app/samples/adapter/init.component.html b/demo/app/samples/adapter/init.component.html index ab8f416d..62d513a4 100644 --- a/demo/app/samples/adapter/init.component.html +++ b/demo/app/samples/adapter/init.component.html @@ -1,32 +1,26 @@ - -
- ngx-ui-scroll version is {{version}} -
-
-

- Adapter.init$ is a Subject-boolean property that fires once - after the Adapter is initialized and - the Scroller is ready to run. - There is also Adapter.init read-only boolean property. - An important point is that there is a time gap between - the Datasource/Adapter instantiation and the Scroller/Adapter initialization. - In other words, we may instantiate the Datasource long before - the Scroller's viewport is rendered and it starts working. -

-

- This demo displays ngx-ui-scroll version taken from the Adapter. - The Adapter.init$ subscription is used to set version property - exactly when the Adapter initializes and receives all data. -

-

- The second tab (OnPush) implements OnPush strategy - with an additional changeDetector call, whish is required to update the view, - because initialization is an asynchronous process. - If we remove changeDetector call, we see 3 dots, the initial value. -

-
-
+ +
ngx-ui-scroll version is {{ version }}
+
+

+ Adapter.init$ is a Subject-boolean property that fires once after + the Adapter is initialized and the Scroller is ready to run. + There is also Adapter.init read-only boolean property. An + important point is that there is a time gap between the Datasource/Adapter + instantiation and the Scroller/Adapter initialization. In other words, we + may instantiate the Datasource long before the Scroller's viewport is + rendered and it starts working. +

+

+ This demo displays ngx-ui-scroll version taken from the + Adapter. The Adapter.init$ subscription is used to set + version property exactly when the Adapter initializes + and receives all data. +

+

+ The second tab (OnPush) implements OnPush strategy with an + additional changeDetector call, whish is required to update the + view, because initialization is an asynchronous process. If we remove + changeDetector call, we see 3 dots, the initial value. +

+
+
diff --git a/demo/app/samples/adapter/init.component.ts b/demo/app/samples/adapter/init.component.ts index ca9356ca..c4a4072d 100644 --- a/demo/app/samples/adapter/init.component.ts +++ b/demo/app/samples/adapter/init.component.ts @@ -1,104 +1,115 @@ -import { Component, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; -import { take } from 'rxjs/operators'; - -import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; -import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; - -import { Datasource } from 'ngx-ui-scroll'; - -@Component({ - selector: 'app-demo-init', - changeDetection: ChangeDetectionStrategy.OnPush, - templateUrl: './init.component.html' -}) -export class DemoInitComponent { - - demoContext: DemoContext = { - config: demos.adapterProps.map.init, - viewportId: 'init-viewport', - noInfo: true, - count: 0, - log: '' - }; - - version = '...'; - - datasource = new Datasource({ - get: datasourceGetCallbackInfinite(this.demoContext) - }); - - sources: DemoSources = [{ - active: true, - name: DemoSourceType.Component, - text: `version = '...'; - -datasource = new Datasource ({ - get: (index, length, success) => - success(Array.from({ length }).map((i, j) => - ({ id: index + j, text: 'item #' + (index + j) }) - )) -}); - -constructor() { - const { adapter } = this.datasource; - adapter.init$.pipe(take(1)).subscribe(() => - this.version = adapter.packageInfo.consumer.version - ); -}` - }, { - name: DemoSourceType.Component + ' (OnPush)', - text: `@Component({ - changeDetection: ChangeDetectionStrategy.OnPush, - ... -}) - -... - -version = '...'; - -datasource = new Datasource ({ - get: (index, length, success) => - success(Array.from({ length }).map((i, j) => - ({ id: index + j, text: 'item #' + (index + j) }) - )) -}); - -constructor(public changeDetector: ChangeDetectorRef) { - const { adapter } = this.datasource; - adapter.init$.pipe(take(1)).subscribe(() => { - this.version = adapter.packageInfo.consumer.version; - this.changeDetector.detectChanges(); - }); -}` - }, { - name: DemoSourceType.Template, - text: `ngx-ui-scroll version is {{version}} - -
-
-
{{item.text}}
-
-
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { - width: 150px; - height: 250px; - overflow-y: auto; -} -.item { - font-weight: bold; - height: 25px; -}` - }]; - - constructor(public changeDetector: ChangeDetectorRef) { - const { adapter } = this.datasource; - adapter.init$.pipe(take(1)).subscribe(() => { - this.version = adapter.packageInfo.consumer.version; - this.changeDetector.detectChanges(); - }); - } - -} +import { + Component, + ChangeDetectionStrategy, + ChangeDetectorRef +} from '@angular/core'; +import { take } from 'rxjs/operators'; + +import { demos } from '../../routes'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; +import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; + +import { Datasource } from 'ngx-ui-scroll'; + +@Component({ + selector: 'app-demo-init', + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './init.component.html' +}) +export class DemoInitComponent { + demoContext: DemoContext = { + config: demos.adapterProps.map.init, + viewportId: 'init-viewport', + noInfo: true, + count: 0, + log: '' + }; + + version = '...'; + + datasource = new Datasource({ + get: datasourceGetCallbackInfinite(this.demoContext) + }); + + sources: DemoSources = [ + { + active: true, + name: DemoSourceType.Component, + text: `version = '...'; + +datasource = new Datasource ({ + get: (index, length, success) => + success(Array.from({ length }).map((i, j) => + ({ id: index + j, text: 'item #' + (index + j) }) + )) +}); + +constructor() { + const { adapter } = this.datasource; + adapter.init$.pipe(take(1)).subscribe(() => + this.version = adapter.packageInfo.consumer.version + ); +}` + }, + { + name: DemoSourceType.Component + ' (OnPush)', + text: `@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + ... +}) + +... + +version = '...'; + +datasource = new Datasource ({ + get: (index, length, success) => + success(Array.from({ length }).map((i, j) => + ({ id: index + j, text: 'item #' + (index + j) }) + )) +}); + +constructor(public changeDetector: ChangeDetectorRef) { + const { adapter } = this.datasource; + adapter.init$.pipe(take(1)).subscribe(() => { + this.version = adapter.packageInfo.consumer.version; + this.changeDetector.detectChanges(); + }); +}` + }, + { + name: DemoSourceType.Template, + text: `ngx-ui-scroll version is {{version}} + +
+
+
{{item.text}}
+
+
` + }, + { + name: DemoSourceType.Styles, + text: `.viewport { + width: 150px; + height: 250px; + overflow-y: auto; +} +.item { + font-weight: bold; + height: 25px; +}` + } + ]; + + constructor(public changeDetector: ChangeDetectorRef) { + const { adapter } = this.datasource; + adapter.init$.pipe(take(1)).subscribe(() => { + this.version = adapter.packageInfo.consumer.version; + this.changeDetector.detectChanges(); + }); + } +} diff --git a/demo/app/samples/adapter/insert.component.html b/demo/app/samples/adapter/insert.component.html index 59acca6a..05d74f5a 100644 --- a/demo/app/samples/adapter/insert.component.html +++ b/demo/app/samples/adapter/insert.component.html @@ -1,21 +1,20 @@
-
- items -
- after item #
+ items +
+ after item #
incrementally
- {{index}}) - {{item}} + {{ index }}) + {{ item }}
@@ -23,17 +22,17 @@
- items -
- before item # -
+ items +
+ before item # +
decrementally
- {{index}}) - {{item}} + {{ index }}) + {{ item }}
@@ -45,48 +44,51 @@

- The Adapter.insert method is dedicated for adding items in the runtime without scrolling. - It allows to insert new items before or after specified one. - The argument of this method is an object of the following type: + The Adapter.insert method is dedicated for adding items in the + runtime without scrolling. It allows to insert new items before or after + specified one. The argument of this method is an object of the following + type:

-
{{argumentsDescription}}
+
{{ argumentsDescription }}

The items option is an array we want to be added to the dataset. - The before, after, beforeIndex and afterIndex options - define the position of the insertion; only one of these options is allowed. - Predicate function (before/after) is applied to each item in the current buffer, - and the first true result of running the predicate determines - the item before/after which the new items should appear. + The before, after, beforeIndex and + afterIndex options define the position of the insertion; only one + of these options is allowed. Predicate function + (before/after) is applied to each item in the current + buffer, and the first true result of running the predicate + determines the item before/after which the new items should + appear.

- Using the beforeIndex/afterIndex option allows to insert items by index. - Also, these options provide virtual insertion, if selected index is out the buffer but - still belongs to the known datasource boundaries. + Using the beforeIndex/afterIndex option allows to insert + items by index. Also, these options provide virtual insertion, if + selected index is out the buffer but still belongs to the known datasource + boundaries.

- The last option is decrease. - The indexes of the items following after the inserted ones are increased by default. - This behavior can be changed, by setting decrease to true, - we are telling the Scroller that the indexes of the items that are before the inserted ones should decrease. + The last option is decrease. The indexes of the items following + after the inserted ones are increased by default. This behavior can be + changed, by setting decrease to true, we are telling the + Scroller that the indexes of the items that are before the inserted ones + should decrease.

- This demo shows how the Adapter.insert method can be used in the real life. - "Datasource" tab contains common datasource logic. "Increase" and "Decrease" tabs - present inserting logic for both cases we have in this demo. - "Increase" uses index API (in-buffer + virtual insertions), - "Decrease" uses predicate API (in-buffer insertions only). - Note that each item is just a string, and the Scroller deals - with natural indexes, which forces us to subtract MIN value from - the index coming into the Datasource.get body. + This demo shows how the Adapter.insert method can be used in the + real life. "Datasource" tab contains common datasource logic. "Increase" + and "Decrease" tabs present inserting logic for both cases we have in this + demo. "Increase" uses index API (in-buffer + virtual insertions), + "Decrease" uses predicate API (in-buffer insertions only). Note that each + item is just a string, and the Scroller deals with natural indexes, which + forces us to subtract MIN value from the index coming + into the Datasource.get body.

- Also, this is very important to maintain consistency - between the external Datasource and the internal Scroller's buffer. - Both doInsert methods do it. They - a) generate new items in a for-loop, - b) insert them into the datasource by updating this.data, - c) insert them into the internal Buffer via Adapter.insert. + Also, this is very important to maintain consistency between the external + Datasource and the internal Scroller's buffer. Both + doInsert methods do it. They a) generate new items in a for-loop, + b) insert them into the datasource by updating this.data, c) + insert them into the internal Buffer via Adapter.insert.

- diff --git a/demo/app/samples/adapter/insert.component.ts b/demo/app/samples/adapter/insert.component.ts index 07f3a2c2..291bdc1c 100644 --- a/demo/app/samples/adapter/insert.component.ts +++ b/demo/app/samples/adapter/insert.component.ts @@ -10,7 +10,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './insert.component.html' }) export class DemoInsertComponent { - demoConfig = demos.adapterMethods.map.insert; MIN = 1; @@ -62,10 +61,11 @@ export class DemoInsertComponent { inputCount2 = '2'; inputIndex2 = '3'; - sources: DemoSources = [{ - active: true, - name: 'Datasource', - text: `MIN = 1; + sources: DemoSources = [ + { + active: true, + name: 'Datasource', + text: `MIN = 1; MAX = 100; data: string[] = []; @@ -91,9 +91,10 @@ datasource = new Datasource({ } }); ` - }, { - name: 'Increase', - text: ` + }, + { + name: 'Increase', + text: ` async doInsert() { await this.datasource.adapter.relax(); const count = Number(this.inputCount); // first input @@ -119,9 +120,10 @@ async doInsert() { }); } ` - }, { - name: 'Decrease', - text: ` + }, + { + name: 'Decrease', + text: ` async doInsert() { await this.datasource.adapter.relax(); const count = Number(this.inputCount); // first input @@ -148,7 +150,8 @@ async doInsert() { }); } ` - }]; + } + ]; argumentsDescription = ` AdapterInsertOptions { items: any[]; diff --git a/demo/app/samples/adapter/is-loading-extended.component.html b/demo/app/samples/adapter/is-loading-extended.component.html index 10e03cdf..30c15159 100644 --- a/demo/app/samples/adapter/is-loading-extended.component.html +++ b/demo/app/samples/adapter/is-loading-extended.component.html @@ -1,57 +1,51 @@ - +
- The Scroller is {{datasource.adapter.isLoading ? 'loading': 'relaxing'}}, - counter {{loadingCounter}} -
- Inner loop is {{datasource.adapter.loopPending ? 'pending': 'stopped'}}, - counter: {{innerLoopCounter}} + The Scroller is {{ datasource.adapter.isLoading ? 'loading' : 'relaxing' }}, + counter {{ loadingCounter }} +
+ Inner loop is {{ datasource.adapter.loopPending ? 'pending' : 'stopped' }}, + counter: {{ innerLoopCounter }}

The Scroller has internal process layer that might be called "inner loop", - which is responsible for single fetch-clip-render chain. - Inner loop can be empty and non-empty. - Empty inner loop does nothing but check if the Scroller could relax. - Non-empty inner loop corresponds to 1 Datasource.get call (which is "fetch" subprocess) - and following viewport updates: clipping old items, inserting new ones and render. - Adapter.loopPending read-only scalar property - and Adapter.loopPending$ observable property - let us to know if the "inner loop" is pending or not. + which is responsible for single fetch-clip-render chain. Inner loop can be + empty and non-empty. Empty inner loop does nothing but check if the + Scroller could relax. Non-empty inner loop corresponds to 1 + Datasource.get call (which is "fetch" subprocess) and following + viewport updates: clipping old items, inserting new ones and render. + Adapter.loopPending read-only scalar property and + Adapter.loopPending$ observable property let us to know if the + "inner loop" is pending or not.

- The isLoading property, which we discussed earlier, - majorizes loopPending property, and this demo shows how they correlate. - Here we count the Scroller "loading" sessions and - the Scroller "inner loops" within the - Adapter.isLoading$ and Adapter.loopPending$ subscriptions respectively. - After the Scroller is initialized and started relaxing, - we see that there are 4 inner loops done, while isLoading counter is 1. - Practically it means that we have 3 Datasource.get calls and 1 more empty loop - to ensure that the Scroller could relax. - And these 4 inner loops are being packed into 1 "isLoading" session. -

-

- So, there might be distinguished 3 levels of pending: + The isLoading property, which we discussed earlier, majorizes + loopPending property, and this demo shows how they correlate. + Here we count the Scroller "loading" sessions and the Scroller "inner + loops" within the Adapter.isLoading$ and + Adapter.loopPending$ subscriptions respectively. After the + Scroller is initialized and started relaxing, we see that there are 4 + inner loops done, while isLoading counter is 1. Practically it + means that we have 3 Datasource.get calls and 1 more empty loop + to ensure that the Scroller could relax. And these 4 inner loops are being + packed into 1 "isLoading" session.

+

So, there might be distinguished 3 levels of pending:

  • - Datasource.get pending, which is fully manual, because - the implementation of the Datasource is in our hands; + Datasource.get pending, which is fully manual, because the + implementation of the Datasource is in our hands;
  • - inner loop pending, which is loopPending and which includes - some internal Scroller's subprocesses (fetch, clip, render) + inner loop pending, which is loopPending and which + includes some internal Scroller's subprocesses (fetch, clip, render) that can take some time in addition to Datasource.get call (which is a part of "fetch" subprocess);
  • Workflow pending, which is isLoading - and which consists of 1 and more inner loops - making up one continuous "loading" session. + and which consists of 1 and more inner loops making up one continuous + "loading" session.
diff --git a/demo/app/samples/adapter/is-loading-extended.component.ts b/demo/app/samples/adapter/is-loading-extended.component.ts index 021968d8..cc5b8b26 100644 --- a/demo/app/samples/adapter/is-loading-extended.component.ts +++ b/demo/app/samples/adapter/is-loading-extended.component.ts @@ -1,7 +1,11 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; import { Datasource } from 'ngx-ui-scroll'; @@ -11,7 +15,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './is-loading-extended.component.html' }) export class DemoIsLoadingExtendedComponent { - demoContext: DemoContext = { config: demos.adapterProps.map.isLoadingAdvanced, viewportId: 'is-loading-advanced-viewport', @@ -23,9 +26,10 @@ export class DemoIsLoadingExtendedComponent { get: datasourceGetCallbackInfinite(this.demoContext, 125) }); - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `datasource = new Datasource ({ + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `datasource = new Datasource ({ get: (index, count, success) => { const data = []; for (let i = index; i <= index + count - 1; i++) { @@ -48,10 +52,11 @@ constructor() { this.innerLoopCounter += !result ? 1 : 0 ); }` - }, { - active: true, - name: DemoSourceType.Template, - text: `The uiScroll is + }, + { + active: true, + name: DemoSourceType.Template, + text: `The uiScroll is {{datasource.adapter.isLoading ? 'loading': 'relaxing'}}, counter {{loadingCounter}} @@ -66,9 +71,10 @@ counter: {{innerLoopCounter}}
{{item.text}}
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 175px; overflow-y: auto; @@ -77,19 +83,18 @@ counter: {{innerLoopCounter}} font-weight: bold; height: 25px; }` - }]; + } + ]; loadingCounter = 0; innerLoopCounter = 0; constructor() { - this.datasource.adapter.isLoading$ - .subscribe(result => - this.loadingCounter += !result ? 1 : 0 - ); - this.datasource.adapter.loopPending$ - .subscribe(result => - this.innerLoopCounter += !result ? 1 : 0 - ); + this.datasource.adapter.isLoading$.subscribe( + result => (this.loadingCounter += !result ? 1 : 0) + ); + this.datasource.adapter.loopPending$.subscribe( + result => (this.innerLoopCounter += !result ? 1 : 0) + ); } } diff --git a/demo/app/samples/adapter/is-loading.component.html b/demo/app/samples/adapter/is-loading.component.html index 80f82a71..855e8b34 100644 --- a/demo/app/samples/adapter/is-loading.component.html +++ b/demo/app/samples/adapter/is-loading.component.html @@ -1,28 +1,27 @@ - +
- The uiScroll is {{datasource.adapter.isLoading ? 'loading': 'relaxing'}}. -
- The value of isLoading counter has been changed for {{isLoadingCounter}} times. + The uiScroll is {{ datasource.adapter.isLoading ? 'loading' : 'relaxing' }}. +
+ The value of isLoading counter has been changed for + {{ isLoadingCounter }} times.

- Adapter.isLoading is a read-only boolean property - indicating whether there are any pending processes running by the Scroller. - So when datasource.adapter.isLoading is true, it means that - the Scroller is working right now and the viewport might be updated soon. - 225ms delay was added to the Datasource.get implementation in this sample. + Adapter.isLoading is a read-only boolean property indicating + whether there are any pending processes running by the Scroller. So when + datasource.adapter.isLoading is true, it means that the + Scroller is working right now and the viewport might be updated soon. + 225ms delay was added to the Datasource.get implementation in + this sample.

In addition to read-only scalar isLoading property there is also isLoading$ property which is Observable. A Subject, to be exact. - In this demo we have a subscription and isLoadingCounter - which is incremented each time the isLoading value becomes false. - We see that the initial value of this counter on the UI (before any scrolling) is 1. - So the Scroller does initialization and first 3 fetches in a single session. + In this demo we have a subscription and isLoadingCounter which is + incremented each time the isLoading value becomes false. + We see that the initial value of this counter on the UI (before any + scrolling) is 1. So the Scroller does initialization and first 3 fetches + in a single session.

diff --git a/demo/app/samples/adapter/is-loading.component.ts b/demo/app/samples/adapter/is-loading.component.ts index 42425803..96d2a61c 100644 --- a/demo/app/samples/adapter/is-loading.component.ts +++ b/demo/app/samples/adapter/is-loading.component.ts @@ -1,7 +1,11 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; import { Datasource } from 'ngx-ui-scroll'; @@ -11,7 +15,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './is-loading.component.html' }) export class DemoIsLoadingComponent { - demoContext: DemoContext = { config: demos.adapterProps.map.isLoading, viewportId: 'is-loading-viewport', @@ -23,9 +26,10 @@ export class DemoIsLoadingComponent { get: datasourceGetCallbackInfinite(this.demoContext, 225) }); - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `datasource = new Datasource ({ + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `datasource = new Datasource ({ get: (index, count, success) => { const data = []; for (let i = index; i <= index + count - 1; i++) { @@ -44,10 +48,11 @@ constructor() { ); } ` - }, { - active: true, - name: DemoSourceType.Template, - text: `The uiScroll is + }, + { + active: true, + name: DemoSourceType.Template, + text: `The uiScroll is {{datasource.adapter.isLoading ? 'loading': 'relaxing'}}.
@@ -60,9 +65,10 @@ for {{isLoadingCounter}} times.
{{item.text}}
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -71,12 +77,14 @@ for {{isLoadingCounter}} times. font-weight: bold; height: 25px; }` - }]; + } + ]; isLoadingCounter = 0; constructor() { - this.datasource.adapter.isLoading$ - .subscribe(isLoading => this.isLoadingCounter += !isLoading ? 1 : 0); + this.datasource.adapter.isLoading$.subscribe( + isLoading => (this.isLoadingCounter += !isLoading ? 1 : 0) + ); } } diff --git a/demo/app/samples/adapter/items-count.component.html b/demo/app/samples/adapter/items-count.component.html index 23374e96..1089fe43 100644 --- a/demo/app/samples/adapter/items-count.component.html +++ b/demo/app/samples/adapter/items-count.component.html @@ -1,16 +1,11 @@ - -
- The Buffer has {{datasource.adapter.itemsCount}} items. -
+ +
The Buffer has {{ datasource.adapter.itemsCount }} items.

- We may use Adapter.itemsCount read-only property to see - how many items exist in the Scroller's Buffer at the moment. - The itemsCount value is the same as the value of DOM elements counter. + We may use Adapter.itemsCount read-only property to see how many + items exist in the Scroller's Buffer at the moment. The + itemsCount value is the same as the value of DOM elements + counter.

diff --git a/demo/app/samples/adapter/items-count.component.ts b/demo/app/samples/adapter/items-count.component.ts index 9f90ded2..5b134660 100644 --- a/demo/app/samples/adapter/items-count.component.ts +++ b/demo/app/samples/adapter/items-count.component.ts @@ -1,7 +1,11 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; import { Datasource } from 'ngx-ui-scroll'; @@ -11,7 +15,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './items-count.component.html' }) export class DemoItemsCountComponent { - demoContext: DemoContext = { config: demos.adapterProps.map.itemsCount, viewportId: 'items-count-viewport', @@ -23,9 +26,10 @@ export class DemoItemsCountComponent { get: datasourceGetCallbackInfinite(this.demoContext) }); - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `datasource = new Datasource ({ + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `datasource = new Datasource ({ get: (index, count, success) => { const data = []; for (let i = index; i <= index + count - 1; i++) { @@ -34,10 +38,11 @@ export class DemoItemsCountComponent { success(data); } });` - }, { - active: true, - name: DemoSourceType.Template, - text: `The uiScroll buffer has + }, + { + active: true, + name: DemoSourceType.Template, + text: `The uiScroll buffer has {{datasource.adapter.itemsCount}} items.
@@ -45,9 +50,10 @@ export class DemoItemsCountComponent {
{{item.text}}
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -56,5 +62,6 @@ export class DemoItemsCountComponent { font-weight: bold; height: 25px; }` - }]; + } + ]; } diff --git a/demo/app/samples/adapter/package-info.component.html b/demo/app/samples/adapter/package-info.component.html index 26cf054c..3c60ed9a 100644 --- a/demo/app/samples/adapter/package-info.component.html +++ b/demo/app/samples/adapter/package-info.component.html @@ -1,22 +1,20 @@ - -
- Consumer: {{datasource.adapter.packageInfo.consumer.name}} - v{{datasource.adapter.packageInfo.consumer.version}} -
- Core: {{datasource.adapter.packageInfo.core.name}} - v{{datasource.adapter.packageInfo.core.version}} -
-
-

- Adapter.packageInfo represents a read-only object - which contains information about versions of - ngx-ui-scroll and its external core. Prior to ngx-ui-scroll v2, - this core was a physical part of the ngx-ui-scroll codebase. - Now it is a separate npm package "vscroll" that "ngx-ui-scroll" depends on. -

-
-
+ +
+ Consumer: {{ datasource.adapter.packageInfo.consumer.name }} v{{ + datasource.adapter.packageInfo.consumer.version + }} +
+ Core: {{ datasource.adapter.packageInfo.core.name }} v{{ + datasource.adapter.packageInfo.core.version + }} +
+
+

+ Adapter.packageInfo represents a read-only object which contains + information about versions of ngx-ui-scroll and its external core. Prior + to ngx-ui-scroll v2, this core was a physical part of the ngx-ui-scroll + codebase. Now it is a separate npm package "vscroll" that "ngx-ui-scroll" + depends on. +

+
+
diff --git a/demo/app/samples/adapter/package-info.component.ts b/demo/app/samples/adapter/package-info.component.ts index 3f587721..f11f5d78 100644 --- a/demo/app/samples/adapter/package-info.component.ts +++ b/demo/app/samples/adapter/package-info.component.ts @@ -1,64 +1,70 @@ -import { Component } from '@angular/core'; - -import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; -import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; - -import { Datasource } from 'ngx-ui-scroll'; - -@Component({ - selector: 'app-demo-package-info', - templateUrl: './package-info.component.html' -}) -export class DemoPackageInfoComponent { - - demoContext: DemoContext = { - config: demos.adapterProps.map.packageInfo, - viewportId: 'package-info-viewport', - count: 0, - log: '' - }; - - datasource = new Datasource({ - get: datasourceGetCallbackInfinite(this.demoContext) - }); - - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `datasource = new Datasource ({ - get: (index, count, success) => { - const data = []; - for (let i = index; i <= index + count - 1; i++) { - data.push({ id: i, text: 'item #' + i }); - } - success(data); - } -});` - }, { - active: true, - name: DemoSourceType.Template, - text: `Consumer: {{datasource.adapter.packageInfo.consumer.name}} -v{{datasource.adapter.packageInfo.consumer.version}} -
-Core: {{datasource.adapter.packageInfo.core.name}} -v{{datasource.adapter.packageInfo.core.version}} - -
-
-
{{item.text}}
-
-
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { - width: 150px; - height: 250px; - overflow-y: auto; -} -.item { - font-weight: bold; - height: 25px; -}` - }]; - -} +import { Component } from '@angular/core'; + +import { demos } from '../../routes'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; +import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; + +import { Datasource } from 'ngx-ui-scroll'; + +@Component({ + selector: 'app-demo-package-info', + templateUrl: './package-info.component.html' +}) +export class DemoPackageInfoComponent { + demoContext: DemoContext = { + config: demos.adapterProps.map.packageInfo, + viewportId: 'package-info-viewport', + count: 0, + log: '' + }; + + datasource = new Datasource({ + get: datasourceGetCallbackInfinite(this.demoContext) + }); + + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `datasource = new Datasource ({ + get: (index, count, success) => { + const data = []; + for (let i = index; i <= index + count - 1; i++) { + data.push({ id: i, text: 'item #' + i }); + } + success(data); + } +});` + }, + { + active: true, + name: DemoSourceType.Template, + text: `Consumer: {{datasource.adapter.packageInfo.consumer.name}} +v{{datasource.adapter.packageInfo.consumer.version}} +
+Core: {{datasource.adapter.packageInfo.core.name}} +v{{datasource.adapter.packageInfo.core.version}} + +
+
+
{{item.text}}
+
+
` + }, + { + name: DemoSourceType.Styles, + text: `.viewport { + width: 150px; + height: 250px; + overflow-y: auto; +} +.item { + font-weight: bold; + height: 25px; +}` + } + ]; +} diff --git a/demo/app/samples/adapter/reload.component.html b/demo/app/samples/adapter/reload.component.html index 45438367..713c3324 100644 --- a/demo/app/samples/adapter/reload.component.html +++ b/demo/app/samples/adapter/reload.component.html @@ -1,38 +1,40 @@ - +
  by index   - +

- The Adapter.reload method allows to reload the Scroller. - This includes resetting the items Buffer and the Viewport params. - The reload method has 1 optional argument: index. It specifies - the index of the item to be first in the visible part of the viewport after reload. + The Adapter.reload method allows to reload the Scroller. This + includes resetting the items Buffer and the + Viewport params. The reload method has 1 optional + argument: index. It specifies the index of the item to be first + in the visible part of the viewport after reload.

- 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 + 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 demo. + fragment="{{ startIndexDemoConfig.id }}" + >startIndex setting + demo.

The Adapter.reload method is safe and needs not to be protected - with the Adapter.relax - (though it could be protected if that's the UX requirement). - This method interrupts the Scroller's flow and synchronously terminates - any pending processes on the Scroller's end. - The same is true also for the Adapter.reset method. + with the Adapter.relax (though it could be protected if that's + the UX requirement). This method interrupts the Scroller's flow and + synchronously terminates any pending processes on the Scroller's end. The + same is true also for the Adapter.reset method.

diff --git a/demo/app/samples/adapter/reload.component.ts b/demo/app/samples/adapter/reload.component.ts index ad6e0f0b..f410a2cf 100644 --- a/demo/app/samples/adapter/reload.component.ts +++ b/demo/app/samples/adapter/reload.component.ts @@ -1,7 +1,11 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; import { Datasource } from 'ngx-ui-scroll'; @@ -11,7 +15,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './reload.component.html' }) export class DemoReloadComponent { - demoContext: DemoContext = { config: demos.adapterMethods.map.reload, viewportId: 'reload-viewport', @@ -25,9 +28,10 @@ export class DemoReloadComponent { get: datasourceGetCallbackInfinite(this.demoContext) }); - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `datasource = new Datasource ({ + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `datasource = new Datasource ({ get: (index, count, success) => { const data = []; for (let i = index; i <= index + count - 1; i++) { @@ -42,10 +46,11 @@ reloadIndex: number = 99; doReload() { this.datasource.adapter.reload(this.reloadIndex); }` - }, { - active: true, - name: DemoSourceType.Template, - text: ` + }, + { + active: true, + name: DemoSourceType.Template, + text: ` by index
@@ -53,9 +58,10 @@ by index
{{item.text}}
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -64,7 +70,8 @@ by index font-weight: bold; height: 25px; }` - }]; + } + ]; reloadIndex = 99; @@ -82,5 +89,4 @@ by index this.demoContext.log = ''; this.datasource.adapter.reload(this.reloadIndex); } - } diff --git a/demo/app/samples/adapter/remove.component.html b/demo/app/samples/adapter/remove.component.html index 7aefccda..87a21006 100644 --- a/demo/app/samples/adapter/remove.component.html +++ b/demo/app/samples/adapter/remove.component.html @@ -1,5 +1,5 @@ - {{index}}) {{item.text}} + {{ index }}) {{ item.text }} [remove] @@ -10,59 +10,65 @@ [itemTemplate]="itemTemplate" >
- +

- The Adapter.remove method allows to remove items from current buffer or/and virtually. - The argument of this method is an object of the following type: + The Adapter.remove method allows to remove items from current + buffer or/and virtually. The argument of this method is an object of the + following type:

-
{{argumentsDescription}}
+
{{ argumentsDescription }}

- The predicate option is a function applying to each item in the buffer. - If the return value is true, the item will be removed. - The argument of the predicate is of ItemAdapter type, and - in this demo we have the following version of predicate for removing by id: + The predicate option is a function applying to each item in the + buffer. If the return value is true, the item will be removed. The + argument of the predicate is of ItemAdapter type, and in this + demo we have the following version of predicate for removing by + id:

-
{{predicateDescription}}
+
{{ predicateDescription }}

- Indexes are adjusted each time a delete is performed via Adapter. - For example, it's impossible to remove item with id = 5 twice, - because there is only one item with id = 5. - But we may remove item with index = 5 as many times - as many indexes we have after this one. + Indexes are adjusted each time a delete is performed via Adapter. For + example, it's impossible to remove item with id = 5 twice, because there + is only one item with id = 5. But we may remove item with index = 5 as + many times as many indexes we have after this one.

- Instead of predicate running over buffered items, - it is possible to remove items by indexes. - This method works with both buffered and virtual items. - For example, we have [1..10] buffered items (they are rendered) - and [11..100] virtual items (emulated via forward padding element - which size corresponds to 90 virtual items), and we want to remove the last item via - Adapter.remove({ indexes: [100] }), the result will be as follows: + Instead of predicate running over buffered items, it is possible + to remove items by indexes. This method works with both buffered + and virtual items. For example, we have [1..10] buffered items (they are + rendered) and [11..100] virtual items (emulated via forward padding + element which size corresponds to 90 virtual items), and we want to remove + the last item via Adapter.remove({ indexes: [100] }), + the result will be as follows:

  • buffer is not affected
  • the visible part of the viewport remains the same
  • - size of the forward padding is reduced by size of the 100th item, - so the scrollable area becomes slightly smaller + size of the forward padding is reduced by size of the 100th item, so the + scrollable area becomes slightly smaller

- By default, indexes are decreased, that is, - the indexes following the deleted one(s) are decremented. - The increase option allows to change the default indexes adjustment strategy. - By setting increase to true, we tell the Scroller - that we want to increase indexes of the items before the removed one(s). + By default, indexes are decreased, that is, the indexes following the + deleted one(s) are decremented. The increase option allows to + change the default indexes adjustment strategy. By setting + increase to true, we tell the Scroller that we want to + increase indexes of the items before the removed one(s).

- The very important point is that we need to synchronize the Datasource with the changes - we are making over the Scroller's buffer via Adapter.remove. - Generally, this is the App component responsibility, - and in this demo it is done by the removeFromDatasource method. - It removes 1 item from the initial dataset, and decrements the value of the right border. + The very important point is that we need to synchronize the Datasource + with the changes we are making over the Scroller's buffer via + Adapter.remove. Generally, this is the App component + responsibility, and in this demo it is done by the + removeFromDatasource method. It removes 1 item from the initial + dataset, and decrements the value of the right border.

diff --git a/demo/app/samples/adapter/remove.component.ts b/demo/app/samples/adapter/remove.component.ts index eee5be65..38f67644 100644 --- a/demo/app/samples/adapter/remove.component.ts +++ b/demo/app/samples/adapter/remove.component.ts @@ -1,7 +1,12 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType, MyItem } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType, + MyItem +} from '../../shared/interfaces'; import { doLog } from '../../shared/datasource-get'; import { Datasource } from 'ngx-ui-scroll'; @@ -11,7 +16,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './remove.component.html' }) export class DemoRemoveComponent { - demoContext: DemoContext = { config: demos.adapterMethods.map.remove, viewportId: 'remove-viewport', @@ -46,9 +50,10 @@ export class DemoRemoveComponent { } }); - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `MIN = -50; + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `MIN = -50; MAX = 50; data: MyItem[]; inputValue = 5; @@ -97,10 +102,11 @@ async removeByIndex(index: number) { indexes: [index] }); }` - }, { - active: true, - name: DemoSourceType.Template, - text: ` + }, + { + active: true, + name: DemoSourceType.Template, + text: ` @@ -115,9 +121,10 @@ async removeByIndex(index: number) { ` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -134,14 +141,16 @@ async removeByIndex(index: number) { .remove:hover { color: rgb(158, 0, 0); }` - }]; + } + ]; argumentsDescription = ` AdapterRemoveOptions { predicate?: ItemsPredicate; indexes?: number[]; increase?: boolean; }`; - predicateDescription = ' adapter.remove({ predicate: ({ data }) => data.id === id });'; + predicateDescription = + ' adapter.remove({ predicate: ({ data }) => data.id === id });'; onInputChanged(target: HTMLInputElement) { const value = parseInt(target.value.trim(), 10); @@ -174,5 +183,4 @@ async removeByIndex(index: number) { indexes: [index] }); } - } diff --git a/demo/app/samples/adapter/replace.component.html b/demo/app/samples/adapter/replace.component.html index baede368..a4f2d9e9 100644 --- a/demo/app/samples/adapter/replace.component.html +++ b/demo/app/samples/adapter/replace.component.html @@ -1,5 +1,5 @@ - {{index}}) {{item.text}} + {{ index }}) {{ item.text }}

- The 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: + The 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}}
+
{{ argumentsDescription }}

The predicate option is exactly the same as in Adapter-remove and case. - The indexes options is exactly the same as in the + fragment="{{ adapterMethodsScope.map.remove.id }}" + >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. + fragment="{{ adapterMethodsScope.map.insert.id }}" + >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 Scroller Buffer - can be replaced with this method and this portion must consist of a continuous list of items. + 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 Scroller 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 Scroller's Buffer, - the Datasource gets the update as well. + 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 Scroller's 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 index 47575c02..a20bdc99 100644 --- a/demo/app/samples/adapter/replace.component.ts +++ b/demo/app/samples/adapter/replace.component.ts @@ -1,7 +1,11 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; import { doLog } from '../../shared/datasource-get'; import { Datasource } from 'ngx-ui-scroll'; @@ -15,7 +19,6 @@ interface MyItem { templateUrl: './replace.component.html' }) export class DemoReplaceComponent { - demoContext: DemoContext = { config: demos.adapterMethods.map.replace, viewportId: 'replace-viewport', @@ -52,9 +55,10 @@ export class DemoReplaceComponent { } }); - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `MAX = 100; + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `MAX = 100; data: Item[]; constructor() { @@ -92,10 +96,11 @@ async doReplace() { items: newItems }); }` - }, { - active: true, - name: DemoSourceType.Template, - text: ` + }, + { + active: true, + name: DemoSourceType.Template, + text: `
@@ -105,9 +110,10 @@ async doReplace() {
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -120,7 +126,8 @@ async doReplace() { font-weight: normal; font-size: smaller; }` - }]; + } + ]; argumentsDescription = ` AdapterReplaceOptions { predicate: ItemsPredicate; @@ -142,5 +149,4 @@ async doReplace() { items: newItems }); } - } diff --git a/demo/app/samples/adapter/reset.component.html b/demo/app/samples/adapter/reset.component.html index 49e70c60..aa755f1a 100644 --- a/demo/app/samples/adapter/reset.component.html +++ b/demo/app/samples/adapter/reset.component.html @@ -1,43 +1,40 @@ - +
-
- - new start index
- - new buffer size +
+ - new start index
+ - new buffer size

- The Adapter.reset method is designed to reset the internal state of the Scroller. - It differs from the Adapter.reload because it re-instantiates all internal entities, - which is equivalent to destroying the Scroller and re-creating it again via ngIf. + The Adapter.reset method is designed to reset the internal state + of the Scroller. It differs from the Adapter.reload because it + re-instantiates all internal entities, which is equivalent to destroying + the Scroller and re-creating it again via ngIf.

- The method has 1 optional argument: datasource object. By passing it, - we tell the Scroller that we want to use a new datasource. - If the argument is not passed, the old Datasource will be used. + The method has 1 optional argument: datasource object. By passing + it, we tell the Scroller that we want to use a new datasource. If the + argument is not passed, the old Datasource will be used.

- All fields of the datasource argument are optional. - Missing parts of a new Datasource will be taken from the original one. - For instance, in this demo only settings field is passed: - Adapter.reset({ settings }). - This sets new startIndex and bufferSize settings - while Datasource.get remains pristine. - It affects the value of the DOM elements counter. + All fields of the datasource argument are optional. Missing parts + of a new Datasource will be taken from the original one. For instance, in + this demo only settings field is passed: + Adapter.reset({ settings }). This sets new + startIndex and bufferSize settings while + Datasource.get remains pristine. It affects the value of the DOM + elements counter.

- Important note! Do not re-assign the Datasource at the App component level, - this.datasource must keep the same reference before and after reset. - So this.datasource = ... expressions are prohibited after reset, - for it will make the Adapter subscriptions broken. - The Scroller maintains Datasource params and - provides the Adapter consistency across the reset. - Even in case of passing a new instance of Datasource: + Important note! Do not re-assign the Datasource at the App + component level, this.datasource must keep the same reference + before and after reset. So + this.datasource = ... expressions are prohibited after + reset, for it will make the Adapter subscriptions broken. The Scroller + maintains Datasource params and provides the Adapter consistency across + the reset. Even in case of passing a new instance of Datasource:

-
{{resetWithNewInstanceSample}}
+
{{ resetWithNewInstanceSample }}
diff --git a/demo/app/samples/adapter/reset.component.ts b/demo/app/samples/adapter/reset.component.ts index 1f647029..4fd5c51e 100644 --- a/demo/app/samples/adapter/reset.component.ts +++ b/demo/app/samples/adapter/reset.component.ts @@ -1,7 +1,11 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; import { Datasource } from 'ngx-ui-scroll'; @@ -11,7 +15,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './reset.component.html' }) export class DemoResetComponent { - demoContext: DemoContext = { config: demos.adapterMethods.map.reset, viewportId: 'reset-viewport', @@ -26,9 +29,10 @@ export class DemoResetComponent { startIndex = 100; bufferSize = 20; - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `datasource = new Datasource ({ + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `datasource = new Datasource ({ get: (index, count, success) => { const data = []; for (let i = index; i <= index + count - 1; i++) { @@ -48,10 +52,11 @@ doReset() { }; this.datasource.adapter.reset({ settings }); }` - }, { - active: true, - name: DemoSourceType.Template, - text: ` + }, + { + active: true, + name: DemoSourceType.Template, + text: ` - new start index - new buffer size @@ -60,9 +65,11 @@ doReset() {
{{item.text}}
` - }]; + } + ]; - resetWithNewInstanceSample = ' this.datasource.adapter.reset(new Datasource(...));'; + resetWithNewInstanceSample = + ' this.datasource.adapter.reset(new Datasource(...));'; doReset() { this.demoContext.count = 0; @@ -73,5 +80,4 @@ doReset() { }; this.datasource.adapter.reset({ settings }); } - } diff --git a/demo/app/samples/adapter/update.component.html b/demo/app/samples/adapter/update.component.html index 4a5663c6..91ae0bd4 100644 --- a/demo/app/samples/adapter/update.component.html +++ b/demo/app/samples/adapter/update.component.html @@ -1,5 +1,5 @@ - {{index}}) {{item.text}} + {{ index }}) {{ item.text }}
-
- fix right +
+ fix right

The Adapter.update method provides access to Scroller's Buffer - and allows to perform insert/replace/remove operations on the fly. - The Adapter.update method has 1 required argument which is a predicate - that is applied to each item in the current Scroller's Buffer. - The argument of the predicate gives natural datasource $index - of the item, its data payload and its HTML element. - The return value of predicate determines what should happen with each item in the Buffer: + and allows to perform insert/replace/remove operations on the fly. The + Adapter.update method has 1 required argument which is a + predicate that is applied to each item in the current Scroller's Buffer. + The argument of the predicate gives natural datasource $index of + the item, its data payload and its HTML element. The + return value of predicate determines what should happen with each + item in the Buffer:

  • returning of a falsy value or an empty array means removal of an item;
  • - returning of a truthy value or an array with only current item in it means leaving an item as is; -
  • -
  • - returning of a non-empty array means replacement/insertion. + returning of a truthy value or an array with only current item in it + means leaving an item as is;
  • +
  • returning of a non-empty array means replacement/insertion.

- In the demo the predicate is implemented to remove item 3, - replace item 5 with new A and B items, - insert new C and D items after item 7, while other items should remain pristine. - This particular predicate should provide + In the demo the predicate is implemented to remove item 3, replace item 5 + with new A and B items, insert new C and D items after item 7, while other + items should remain pristine. This particular predicate should provide [1..10] to [1, 2, 4, A, B, 6, 7, C, D, 8] contents transition.

- On index shifting. - The second option of the Adapter.update is fixRight. - This is a non-required boolean setting that defines the indexing strategy. - If fixRight is false (which is default), - indexes to the right of the updated ones will be affected. - Since the first updated item has index 3, - the indexes of the first two items should not change after update when fixRight = false. - But the index of the 8 item should grow up to 10: - 2 removals (3, 5) and 4 insertions (A, B, C, D) results in +2 shift. - If fixRight is true, indexes to the left of the updated ones will be affected. - And we'll see -2 shift on the first two items when fixRight = true. + On index shifting. The second option of the Adapter.update is + fixRight. This is a non-required boolean setting that defines the + indexing strategy. If fixRight is false (which is default), + indexes to the right of the updated ones will be affected. Since the first + updated item has index 3, the indexes of the first two items should not + change after update when fixRight = false. But the index of the + 8 item should grow up to 10: 2 removals (3, 5) and 4 insertions (A, B, C, + D) results in +2 shift. If fixRight is true, indexes to the + left of the updated ones will be affected. And we'll see -2 shift on + the first two items when fixRight = true.

- The very important thing is to synchronize each update we perform via Adapter API - over the Scroller's Buffer with the Datasource outside the Scroller. - This demo does not have such synchronization due to high complexity of the operations. - But there are some examples in other demos: + The very important thing is to synchronize each update we perform via + Adapter API over the Scroller's Buffer with the Datasource outside the + Scroller. This demo does not have such synchronization due to high + complexity of the operations. But there are some examples in other demos: append, + fragment="{{ adapterMethodsScope.map.appendPrependSync.id }}" + >append, remove and + fragment="{{ adapterMethodsScope.map.remove.id }}" + >remove + and insert. + fragment="{{ adapterMethodsScope.map.insert.id }}" + >insert.

Since v2, the Adapter.insert and Adapter.replace methods - use the Adapter.update method under hood. + use the Adapter.update method under hood.

diff --git a/demo/app/samples/adapter/update.component.ts b/demo/app/samples/adapter/update.component.ts index 9378424a..01dd7b10 100644 --- a/demo/app/samples/adapter/update.component.ts +++ b/demo/app/samples/adapter/update.component.ts @@ -1,7 +1,11 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; import { doLog } from '../../shared/datasource-get'; import { Datasource } from 'ngx-ui-scroll'; @@ -15,7 +19,6 @@ interface MyItem { templateUrl: './update.component.html' }) export class DemoUpdateComponent { - demoContext: DemoContext = { config: demos.adapterMethods.map.update, viewportId: 'update-viewport', @@ -55,9 +58,10 @@ export class DemoUpdateComponent { } }); - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `MAX = 100; + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `MAX = 100; START = 1; fixRight = false; data: MyItem[]; @@ -106,10 +110,11 @@ async doUpdate() { fixRight: this.fixRight }); }` - }, { - active: true, - name: DemoSourceType.Template, - text: ` + }, + { + active: true, + name: DemoSourceType.Template, + text: ` fix right
@@ -120,9 +125,10 @@ async doUpdate() {
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -135,7 +141,8 @@ async doUpdate() { font-weight: normal; font-size: smaller; }` - }]; + } + ]; argumentsDescription = ` AdapterReplaceOptions { predicate: ItemsPredicate; @@ -165,5 +172,4 @@ async doUpdate() { fixRight: this.fixRight }); } - } diff --git a/demo/app/samples/common/basic.component.html b/demo/app/samples/common/basic.component.html index 8331ae00..fb5a0c5e 100644 --- a/demo/app/samples/common/basic.component.html +++ b/demo/app/samples/common/basic.component.html @@ -1,40 +1,37 @@ - +

- Speaking of what impact could Settings have on the Scroller behavior, - we need to get an idea of its behavior before any settings applied. - This demo is the very basic example with no Settings provided. + Speaking of what impact could Settings have on the Scroller + behavior, we need to get an idea of its behavior before any settings + applied. This demo is the very basic example with no + Settings provided.

- Let's first investigate the demo's log. - The visible part of the viewport contains 10 items in accordance with CSS: - 10 items of 25px each are equal to 250px of the viewport height. - Datasource.get log shows that there were 3 requests - of 5, 10 and 5 items on the initial load (before any scrolling). - So we have 20 DOM elements initially: - 15 downward items with positive indexes and 5 upward negative items. - 20 items result in 500px and this is the initial viewport scrollable size. + Let's first investigate the demo's log. The visible part of the viewport + contains 10 items in accordance with CSS: 10 items of 25px each are equal + to 250px of the viewport height. + Datasource.get log shows that there were 3 requests of 5, 10 and + 5 items on the initial load (before any scrolling). So we have 20 DOM + elements initially: 15 downward items with positive indexes and 5 upward + negative items. 20 items result in 500px and this is the initial viewport + scrollable size.

- The viewport scrollable size value can increase only; - as we scroll more and more, items flow through the Datasource.get. - In this demo the Datasource is unlimited, - it produces items with indexes from -Infinity to +Infinity. - The number of DOM elements in the viewport is not constant, - but it fluctuates around the initial value during scrolling (20-31). - The core concept of the virtualization is to keep as small number of real items as needed - while the scrollable size of the viewport can be very big. + The viewport scrollable size value can increase only; as we scroll more + and more, items flow through the Datasource.get. In this demo the + Datasource is unlimited, it produces items with indexes from + -Infinity to +Infinity. The number of DOM elements in the viewport is not + constant, but it fluctuates around the initial value during scrolling + (20-31). The core concept of the virtualization is to keep as small number + of real items as needed while the scrollable size of the viewport can be + very big.

- Why do we have 3 requests initially, - why the first one is about only 5 items, - why at least 10 items are invisible? - This is how the *uiScroll works and further reading should shed light on the details. - But we need to know that the default behavior could be changed via Settings object. + Why do we have 3 requests initially, why the first one is about only 5 + items, why at least 10 items are invisible? This is how the + *uiScroll works and further reading should shed light on the + details. But we need to know that the default behavior could be changed + via Settings object.

diff --git a/demo/app/samples/common/basic.component.ts b/demo/app/samples/common/basic.component.ts index cea7ee5f..21f98cf9 100644 --- a/demo/app/samples/common/basic.component.ts +++ b/demo/app/samples/common/basic.component.ts @@ -1,7 +1,11 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; import { IDatasource } from 'ngx-ui-scroll'; @@ -11,7 +15,6 @@ import { IDatasource } from 'ngx-ui-scroll'; templateUrl: './basic.component.html' }) export class DemoBasicComponent { - demoContext: DemoContext = { config: demos.settings.map.noSettings, viewportId: 'no-settings-viewport', @@ -23,9 +26,10 @@ export class DemoBasicComponent { get: datasourceGetCallbackInfinite(this.demoContext) }; - sources: DemoSources = [{ - name: DemoSourceType.Datasource, - text: `datasource: IDatasource = { + sources: DemoSources = [ + { + name: DemoSourceType.Datasource, + text: `datasource: IDatasource = { get: (index, count, success) => { const data = []; for (let i = index; i <= index + count - 1; i++) { @@ -34,16 +38,18 @@ export class DemoBasicComponent { success(data); } }` - }, { - name: DemoSourceType.Template, - text: `
+ }, + { + name: DemoSourceType.Template, + text: `
{{item.text}}
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -52,6 +58,6 @@ export class DemoBasicComponent { font-weight: bold; height: 25px; }` - }]; - + } + ]; } diff --git a/demo/app/samples/common/buffer-size.component.html b/demo/app/samples/common/buffer-size.component.html index 1cc6150e..033273a8 100644 --- a/demo/app/samples/common/buffer-size.component.html +++ b/demo/app/samples/common/buffer-size.component.html @@ -1,31 +1,27 @@ - +

- The bufferSize property defines minimal number of items - that the Scroller may retrieve via Datasource.get method in a single request. - In this demo we see that the initial load consists of 2 requests of 15 items (from 1 to 15 and from -14 to 0). - Initially the Scroller starts fill the viewport downward from zero scroll position. - If the first fetch is enough, the second one will be in the backward direction. - The size of these fetches is determined by the bufferSize setting. - The next fetches (during scrolling) could ask for more than bufferSize items, - but never less than bufferSize. + The bufferSize property defines minimal number of items that the + Scroller may retrieve via Datasource.get method in a single + request. In this demo we see that the initial load consists of 2 requests + of 15 items (from 1 to 15 and from -14 to 0). Initially the Scroller + starts fill the viewport downward from zero scroll position. If the first + fetch is enough, the second one will be in the backward direction. The + size of these fetches is determined by the bufferSize setting. + The next fetches (during scrolling) could ask for more than + bufferSize items, but never less than bufferSize.

- The bigger the bufferSize value is, the less requests generally are needed - to fill out the viewport. - For example, scrolling from 1 to 100 item when the bufferSize is 1, - we may see about 40 Datasource.get calls (depends on the environment). - But in this demo (where the bufferSize is 15) we may - reach the 100th item in about 10 Datasource.get calls. + The bigger the bufferSize value is, the less requests generally + are needed to fill out the viewport. For example, scrolling from 1 to 100 + item when the bufferSize is 1, we may see about 40 + Datasource.get calls (depends on the environment). But in this + demo (where the bufferSize is 15) we may reach the 100th item in + about 10 Datasource.get calls.

- The default value of bufferSize property is 5. - It has to be a positive integer. - The minimum value is 1. + The default value of bufferSize property is 5. It has to be a + positive integer. The minimum value is 1.

diff --git a/demo/app/samples/common/buffer-size.component.ts b/demo/app/samples/common/buffer-size.component.ts index 778290ce..00e4a3c5 100644 --- a/demo/app/samples/common/buffer-size.component.ts +++ b/demo/app/samples/common/buffer-size.component.ts @@ -1,7 +1,11 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; import { IDatasource } from 'ngx-ui-scroll'; @@ -11,7 +15,6 @@ import { IDatasource } from 'ngx-ui-scroll'; templateUrl: './buffer-size.component.html' }) export class DemoBufferSizeComponent { - demoContext: DemoContext = { config: demos.settings.map.bufferSize, viewportId: 'buffer-size-viewport', @@ -26,9 +29,10 @@ export class DemoBufferSizeComponent { } }; - sources: DemoSources = [{ - name: DemoSourceType.Datasource, - text: `datasource: IDatasource = { + sources: DemoSources = [ + { + name: DemoSourceType.Datasource, + text: `datasource: IDatasource = { get: (index, count, success) => { const data = []; for (let i = index; i <= index + count - 1; i++) { @@ -40,16 +44,18 @@ export class DemoBufferSizeComponent { bufferSize: 15 } }` - }, { - name: DemoSourceType.Template, - text: `
+ }, + { + name: DemoSourceType.Template, + text: `
{{item.text}}
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -58,6 +64,6 @@ export class DemoBufferSizeComponent { font-weight: bold; height: 25px; }` - }]; - + } + ]; } diff --git a/demo/app/samples/common/different-heights.component.html b/demo/app/samples/common/different-heights.component.html index 1fa5d350..c5e4d153 100644 --- a/demo/app/samples/common/different-heights.component.html +++ b/demo/app/samples/common/different-heights.component.html @@ -1,5 +1,4 @@
-
@@ -8,7 +7,7 @@
- {{item.text}} + {{ item.text }}
@@ -18,19 +17,21 @@
- {{item.text}} + {{ item.text }}
- average log   - frequent log + + average log   + + frequent log
-
{{averageLog}}
-
{{frequentLog}}
+
{{ averageLog }}
+
{{ frequentLog }}
@@ -40,57 +41,66 @@

- Scroller works fine with non-constant item heights. - The sizeStrategy setting defines the default item size. - Scroller uses this default value when it needs to fetch new items and - some or all of these new items had never been fetched before, - so their sizes are unknown. (See + Scroller works fine with non-constant item heights. The + sizeStrategy setting defines the default item size. Scroller uses + this default value when it needs to fetch new items and some or all of + these new items had never been fetched before, so their sizes are unknown. + (See padding setting to get more details on fetching.) - The following values are available + fragment="{{ settingsScope.padding.id }}" + >padding setting + to get more details on fetching.) The following values are available

  • - SizeStrategy.Average ("average"), the average size is calculated for each new item render and is considered the default + SizeStrategy.Average ("average"), the average size is + calculated for each new item render and is considered the default
  • - SizeStrategy.Frequent ("frequent"), the most frequent size across all renders is considered the default + SizeStrategy.Frequent ("frequent"), the most frequent size + across all renders is considered the default
  • - SizeStrategy.Constant ("constant"), the default item size value is taken from the + SizeStrategy.Constant ("constant"), the default item size value + is taken from the itemSize setting and never changes. - If the itemSize setting is not defined, the size of the first rendered item will be used as the default. + fragment="{{ settingsScope.itemSize.id }}" + >itemSize setting + and never changes. If the itemSize setting is not defined, the + size of the first rendered item will be used as the default.

- In this sample we have two Scrollers demonstrating "average" and "frequent" strategies. - The log section helps to understand behavior and catch the difference between strategies. - The "average" datasource provides incrementally growing items, - and we see how the default size increases per each scroll down. - Having 100 items with height of (20 + i)px, we'll get 70px as the average value in the end: + In this sample we have two Scrollers demonstrating "average" and + "frequent" strategies. The log section helps to understand behavior and + catch the difference between strategies. The "average" datasource provides + incrementally growing items, and we see how the default size + increases per each scroll down. Having 100 items with height of (20 + + i)px, we'll get 70px as the average value in the end:

-      {{averageSample}}
+      {{ averageSample }}
     

- The total size in the log section is an expectation of scrollable area for 100 items, - it consists of two parts: 1) cumulative height of the items that have been rendered at least once, - and 2) anticipated height of unknown items based on the "default" size. - This second part increases per each default size increase in the "average" sample. + The total size in the log section is an expectation of scrollable + area for 100 items, it consists of two parts: 1) cumulative height of the + items that have been rendered at least once, and 2) anticipated height of + unknown items based on the "default" size. This second part increases per + each default size increase in the "average" sample.

- The "frequent" datasource provides the size of each 10th item = 40px, the rest = 20px. - In this case it seems reasonable to use SizeStrategy.Frequent strategy, - though there is no strict conditions defining which strategy should be used in the end App. - If accuracy is not a concern, the "constant" strategy - can be enabled by using the SizeStrategy.Constant value - as it results in less calculations on the Scroller's end. - However, it is recommended to make additional research on the impact on performance for each specific case. + The "frequent" datasource provides the size of each 10th item = 40px, the + rest = 20px. In this case it seems reasonable to use + SizeStrategy.Frequent strategy, though there is no strict + conditions defining which strategy should be used in the end App. If + accuracy is not a concern, the "constant" strategy can be enabled by using + the SizeStrategy.Constant value as it results in less + calculations on the Scroller's end. However, it is recommended to make + additional research on the impact on performance for each specific case.

diff --git a/demo/app/samples/common/different-heights.component.ts b/demo/app/samples/common/different-heights.component.ts index 1cb70b51..41265080 100644 --- a/demo/app/samples/common/different-heights.component.ts +++ b/demo/app/samples/common/different-heights.component.ts @@ -17,7 +17,6 @@ interface MyItem { templateUrl: './different-heights.component.html' }) export class DemoDifferentHeightsComponent { - settingsScope = demos.settings.map; demoConfig = demos.settings.map.differentItemHeights; viewportId = 'different-heights-viewport'; @@ -34,33 +33,38 @@ export class DemoDifferentHeightsComponent { merge( this.datasourceAverage.adapter.init$, this.datasourceFrequent.adapter.init$ - ).pipe(take(1)).subscribe(() => this.setupLog()); + ) + .pipe(take(1)) + .subscribe(() => this.setupLog()); } setupLog() { const adapter1 = this.datasourceAverage.adapter; const adapter2 = this.datasourceFrequent.adapter; const wrapperElement = document.getElementById(this.viewportId as string); - const viewports = (wrapperElement as HTMLElement).getElementsByClassName('viewport'); + const viewports = (wrapperElement as HTMLElement).getElementsByClassName( + 'viewport' + ); const vp1 = viewports[0] as HTMLElement; const vp2 = viewports[1] as HTMLElement; adapter1.loopPending$.subscribe(pending => { if (!pending) { this.averageLog = - `default: ${adapter1.bufferInfo.defaultSize}px, total: ${vp1.scrollHeight}px\n` + this.averageLog; + `default: ${adapter1.bufferInfo.defaultSize}px, total: ${vp1.scrollHeight}px\n` + + this.averageLog; } }); adapter2.loopPending$.subscribe(pending => { if (!pending) { this.frequentLog = - `default: ${adapter2.bufferInfo.defaultSize}px, total: ${vp2.scrollHeight}px\n` + this.frequentLog; + `default: ${adapter2.bufferInfo.defaultSize}px, total: ${vp2.scrollHeight}px\n` + + this.frequentLog; } }); } datasourceAverage = new Datasource({ - get: (index, count, success) => - success(this.getData(index, count, false)), + get: (index, count, success) => success(this.getData(index, count, false)), settings: { startIndex: this.MIN, minIndex: this.MIN, @@ -70,8 +74,7 @@ export class DemoDifferentHeightsComponent { }); datasourceFrequent = new Datasource({ - get: (index, count, success) => - success(this.getData(index, count, true)), + get: (index, count, success) => success(this.getData(index, count, true)), settings: { startIndex: this.MIN, minIndex: this.MIN, @@ -80,9 +83,10 @@ export class DemoDifferentHeightsComponent { } }); - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `MIN = 0; + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `MIN = 0; MAX = 99; SIZE = 20; @@ -120,10 +124,11 @@ getData(index: number, count: number, isFrequent: boolean): MyItem[] { } return data; }` - }, { - name: DemoSourceType.Template, - active: true, - text: `average + }, + { + name: DemoSourceType.Template, + active: true, + text: `average
@@ -140,9 +145,10 @@ getData(index: number, count: number, isFrequent: boolean): MyItem[] {
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -150,9 +156,11 @@ getData(index: number, count: number, isFrequent: boolean): MyItem[] { .item { font-weight: bold; }` - }]; + } + ]; - averageSample = 'Array.from({length: 100}).reduce((a, i, j) => a + j + 20 , 0) / 100; // 69.5px'; + averageSample = + 'Array.from({length: 100}).reduce((a, i, j) => a + j + 20 , 0) / 100; // 69.5px'; getData(index: number, count: number, isFrequent: boolean): MyItem[] { const data = []; @@ -166,5 +174,4 @@ getData(index: number, count: number, isFrequent: boolean): MyItem[] { } return data; } - } diff --git a/demo/app/samples/common/horizontal.component.html b/demo/app/samples/common/horizontal.component.html index b9106fb0..4c4a8d0b 100644 --- a/demo/app/samples/common/horizontal.component.html +++ b/demo/app/samples/common/horizontal.component.html @@ -1,13 +1,11 @@ - +

- Horizontal scrolling could be enabled via horizontal setting. The styles also need to be fixed in - accordance with horizontal scrolling. Currently we have some limitations at the template layer but - display: inline-block; rule applied to all nested containers makes the horizontal mode work. + Horizontal scrolling could be enabled via horizontal setting. The + styles also need to be fixed in accordance with horizontal scrolling. + Currently we have some limitations at the template layer but + display: inline-block; rule applied to all nested containers + makes the horizontal mode work.

diff --git a/demo/app/samples/common/horizontal.component.ts b/demo/app/samples/common/horizontal.component.ts index d57fe96f..dff3f57b 100644 --- a/demo/app/samples/common/horizontal.component.ts +++ b/demo/app/samples/common/horizontal.component.ts @@ -1,7 +1,11 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; import { IDatasource } from 'ngx-ui-scroll'; @@ -11,7 +15,6 @@ import { IDatasource } from 'ngx-ui-scroll'; templateUrl: './horizontal.component.html' }) export class DemoHorizontalComponent { - demoContext: DemoContext = { config: demos.settings.map.horizontalMode, viewportId: 'horizontal-viewport', @@ -27,9 +30,10 @@ export class DemoHorizontalComponent { } }; - sources: DemoSources = [{ - name: DemoSourceType.Datasource, - text: `datasource: IDatasource = { + sources: DemoSources = [ + { + name: DemoSourceType.Datasource, + text: `datasource: IDatasource = { get: (index, count, success) => { const data = []; for (let i = index; i <= index + count - 1; i++) { @@ -41,16 +45,18 @@ export class DemoHorizontalComponent { horizontal: true } }` - }, { - name: DemoSourceType.Template, - text: `
+ }, + { + name: DemoSourceType.Template, + text: `
{{item.text}}
` - }, { - name: DemoSourceType.Styles, - text: `.viewport.horizontal { + }, + { + name: DemoSourceType.Styles, + text: `.viewport.horizontal { width: 250px; height: 100px; overflow-x: scroll; @@ -66,6 +72,6 @@ export class DemoHorizontalComponent { padding: 0 5px; font-weight: bolder; }` - }]; - + } + ]; } diff --git a/demo/app/samples/common/infinite.component.html b/demo/app/samples/common/infinite.component.html index e53eb77f..7512bdfb 100644 --- a/demo/app/samples/common/infinite.component.html +++ b/demo/app/samples/common/infinite.component.html @@ -1,16 +1,12 @@ - +

The Scroller might work in "infinite" mode, when items are never removed. - This mode can be turned on via infinite property of the settings object. - Setting infinite to true, we ask the Scroller - not to remove items that are getting out of the visible part of the viewport. - That's why the DOM elements counter value increases per each edge-scroll event - and never decreased. + This mode can be turned on via infinite property of the + settings object. Setting infinite to true, we ask the + Scroller not to remove items that are getting out of the visible part of + the viewport. That's why the DOM elements counter value increases per each + edge-scroll event and never decreased.

diff --git a/demo/app/samples/common/infinite.component.ts b/demo/app/samples/common/infinite.component.ts index 3196e264..1eca5bb3 100644 --- a/demo/app/samples/common/infinite.component.ts +++ b/demo/app/samples/common/infinite.component.ts @@ -1,7 +1,11 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; import { IDatasource } from 'ngx-ui-scroll'; @@ -11,7 +15,6 @@ import { IDatasource } from 'ngx-ui-scroll'; templateUrl: './infinite.component.html' }) export class DemoInfiniteComponent { - demoContext: DemoContext = { config: demos.settings.map.infiniteMode, viewportId: 'infinite-viewport', @@ -26,9 +29,10 @@ export class DemoInfiniteComponent { } }; - sources: DemoSources = [{ - name: DemoSourceType.Datasource, - text: `datasource: IDatasource = { + sources: DemoSources = [ + { + name: DemoSourceType.Datasource, + text: `datasource: IDatasource = { get: (index, count, success) => { const data = []; for (let i = index; i <= index + count - 1; i++) { @@ -40,16 +44,18 @@ export class DemoInfiniteComponent { infinite: true } }` - }, { - name: DemoSourceType.Template, - text: `
+ }, + { + name: DemoSourceType.Template, + text: `
{{item.text}}
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 175px; overflow-y: auto; @@ -58,6 +64,6 @@ export class DemoInfiniteComponent { font-weight: bold; height: 25px; }` - }]; - + } + ]; } diff --git a/demo/app/samples/common/item-size.component.html b/demo/app/samples/common/item-size.component.html index 3f629fde..6407467b 100644 --- a/demo/app/samples/common/item-size.component.html +++ b/demo/app/samples/common/item-size.component.html @@ -1,40 +1,38 @@ - +

- Another setting which has an impact on the viewport filling procedure is itemSize. - After the first fetch is done, the Scroller gets knowledge on average item's size - and can make further requests precisely. - The itemSize property tells the Scroller that - the initial average item's size is determined and could be used right on the first cycle. - That's why we have only 2 initial Datasource.get calls in this demo: + Another setting which has an impact on the viewport filling procedure is + itemSize. After the first fetch is done, the Scroller gets + knowledge on average item's size and can make further requests precisely. + The itemSize property tells the Scroller that the initial average + item's size is determined and could be used right on the first cycle. + That's why we have only 2 initial Datasource.get calls in this + demo:

  1. 250px + 0.5 * 250px = 375px — downward (positive) size
  2. -
  3. ceil(375px / 25px) = 15 — items number to fill the area of 375px
  4. +
  5. + ceil(375px / 25px) = 15 — items number to fill the area of 375px +
  6. 0.5 * 250px = 125px — upward (negative) size
  7. -
  8. ceil(125px / 25px) = 5 — items number to fill the area of 125px
  9. +
  10. + ceil(125px / 25px) = 5 — items number to fill the area of 125px +

- itemSize don't fix item's size on the template layer, - it is just an expectation of average item's size. - If we set it to, say, 10, then the first Datasource.get call will result in - ceil(375 / 10) = 38 items which will occupy 38 * 25 = 950px downward area. - The second fetch will be the same as above, because the Scroller - will recalculate average item's size after the first fetch is done. - Setting itemSize to, say, 9999 will give us good old 3 fetches: - 5 (not 1, because 1 < bufferSize that is 5 by default) and 10 items on forward direction - and 5 items on backward direction. + itemSize don't fix item's size on the template layer, it is just + an expectation of average item's size. If we set it to, say, 10, then the + first Datasource.get call will result in ceil(375 / 10) = 38 + items which will occupy 38 * 25 = 950px downward area. The second fetch + will be the same as above, because the Scroller will recalculate average + item's size after the first fetch is done. Setting itemSize to, + say, 9999 will give us good old 3 fetches: 5 (not 1, because 1 < + bufferSize that is 5 by default) and 10 items on forward direction and 5 + items on backward direction.

- itemSize has no default value. - If set, it has to be a positive integer. - The minimum value is 1. + itemSize has no default value. If set, it has to be a positive + integer. The minimum value is 1.

- - diff --git a/demo/app/samples/common/item-size.component.ts b/demo/app/samples/common/item-size.component.ts index 20b18d76..77adb6c9 100644 --- a/demo/app/samples/common/item-size.component.ts +++ b/demo/app/samples/common/item-size.component.ts @@ -1,7 +1,11 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; import { IDatasource } from 'ngx-ui-scroll'; @@ -11,7 +15,6 @@ import { IDatasource } from 'ngx-ui-scroll'; templateUrl: './item-size.component.html' }) export class DemoItemSizeComponent { - demoContext: DemoContext = { config: demos.settings.map.itemSize, viewportId: 'item-size-viewport', @@ -26,9 +29,10 @@ export class DemoItemSizeComponent { } }; - sources: DemoSources = [{ - name: DemoSourceType.Datasource, - text: `datasource: IDatasource = { + sources: DemoSources = [ + { + name: DemoSourceType.Datasource, + text: `datasource: IDatasource = { get: (index, count, success) => { const data = []; for (let i = index; i <= index + count - 1; i++) { @@ -40,16 +44,18 @@ export class DemoItemSizeComponent { itemSize: 25 } }` - }, { - name: DemoSourceType.Template, - text: `
+ }, + { + name: DemoSourceType.Template, + text: `
{{item.text}}
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -58,6 +64,6 @@ export class DemoItemSizeComponent { font-weight: bold; height: 25px; }` - }]; - + } + ]; } diff --git a/demo/app/samples/common/min-max-indexes.component.html b/demo/app/samples/common/min-max-indexes.component.html index 931f6762..7e3bdcc3 100644 --- a/demo/app/samples/common/min-max-indexes.component.html +++ b/demo/app/samples/common/min-max-indexes.component.html @@ -1,51 +1,49 @@ - +

- If the boundaries of the dataset are known, - we may virtualize all the data items right on the Scroller initialization, in other words - the Scroller will maintain forward and backward padding elements of the viewport - in assumption that the dataset consists of (maxIndex - minIndex) items. - So it will be possible to jump to any position immediately. + If the boundaries of the dataset are known, we may virtualize all the data + items right on the Scroller initialization, in other words the Scroller + will maintain forward and backward padding elements of the viewport in + assumption that the dataset consists of (maxIndex - + minIndex) items. So it will be possible to jump to any position + immediately.

- In this demo we tell the Scroller to work with indexes in-between [1..1000] range only. - 1000 items of 25px amount 25000px, - this is the end viewport scrollable size which will not be changed. + In this demo we tell the Scroller to work with indexes in-between + [1..1000] range only. 1000 items of 25px amount 25000px, this is the end + viewport scrollable size which will not be changed.

- An important note is that minIndex and maxIndex - should stay within the real datasource boundaries. - All the demos we discussed before have unlimited datasources, - so minIndex and maxIndex could accept any values. - But in case the datasource is limited, say, by [MIN..MAX] range, then - minIndex should be >= MIN and - maxIndex should be <= MAX. - An example of limited Datasource.get implementation can be found + An important note is that minIndex and maxIndex should + stay within the real datasource boundaries. All the demos we discussed + before have unlimited datasources, so minIndex and + maxIndex could accept any values. But in case the datasource is + limited, say, by [MIN..MAX] range, then minIndex should be >= + MIN and maxIndex should be <= MAX. An example + of limited Datasource.get implementation can be found here. + fragment="{{ datasourceLimitedDemoConfig.id }}" + >here.

- The Scroller can accept only one of min/max indexes settings. - For example, if we know that our datasource have no negative values, - then we may define minIndex as 1 (or 0) and not define maxIndex. - It also seems right idea to set startIndex value to 0 in case - minIndex value had been set to 0. - An example of positive-limited Datasource.get implementation can be found + The Scroller can accept only one of min/max indexes settings. For example, + if we know that our datasource have no negative values, then we may define + minIndex as 1 (or 0) and not define maxIndex. It also + seems right idea to set startIndex value to 0 in case + minIndex value had been set to 0. An example of positive-limited + Datasource.get implementation can be found here. + fragment="{{ datasourcePositiveLimitedDemoConfig.id }}" + >here.

Both of minIndex and maxIndex values should be integer. - The default minIndex value is -Infinity. - The default maxIndex value is +Infinity. + The default minIndex value is -Infinity. The default + maxIndex value is +Infinity.

diff --git a/demo/app/samples/common/min-max-indexes.component.ts b/demo/app/samples/common/min-max-indexes.component.ts index e0fef33e..c60a5af8 100644 --- a/demo/app/samples/common/min-max-indexes.component.ts +++ b/demo/app/samples/common/min-max-indexes.component.ts @@ -1,7 +1,11 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; import { IDatasource } from 'ngx-ui-scroll'; @@ -11,7 +15,6 @@ import { IDatasource } from 'ngx-ui-scroll'; templateUrl: './min-max-indexes.component.html' }) export class DemoMinMaxIndexesComponent { - demoContext: DemoContext = { config: demos.settings.map.minMaxIndexes, viewportId: 'min-max-indexes-viewport', @@ -20,7 +23,8 @@ export class DemoMinMaxIndexesComponent { }; datasourceLimitedDemoConfig = demos.datasource.map.limited; - datasourcePositiveLimitedDemoConfig = demos.datasource.map.positiveLimitedIndexes; + datasourcePositiveLimitedDemoConfig = + demos.datasource.map.positiveLimitedIndexes; datasource: IDatasource = { get: datasourceGetCallbackInfinite(this.demoContext), @@ -30,9 +34,10 @@ export class DemoMinMaxIndexesComponent { } }; - sources: DemoSources = [{ - name: DemoSourceType.Datasource, - text: `datasource: IDatasource = { + sources: DemoSources = [ + { + name: DemoSourceType.Datasource, + text: `datasource: IDatasource = { get: (index, count, success) => { const data = []; for (let i = index; i <= index + count - 1; i++) { @@ -45,16 +50,18 @@ export class DemoMinMaxIndexesComponent { maxIndex: 1000 } }` - }, { - name: DemoSourceType.Template, - text: `
+ }, + { + name: DemoSourceType.Template, + text: `
{{item.text}}
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -63,6 +70,6 @@ export class DemoMinMaxIndexesComponent { font-weight: bold; height: 25px; }` - }]; - + } + ]; } diff --git a/demo/app/samples/common/padding.component.html b/demo/app/samples/common/padding.component.html index 9e1aaf98..a8bcd731 100644 --- a/demo/app/samples/common/padding.component.html +++ b/demo/app/samples/common/padding.component.html @@ -1,36 +1,38 @@ - +

- The padding setting defines an extra space out of the visible part of the viewport - that should be filled with items. - This provides outlets that determine when the items are to be created/destroyed. - See also the Viewport doc for details. + The padding setting defines an extra space out of the visible + part of the viewport that should be filled with items. This provides + outlets that determine when the items are to be created/destroyed. See + also the + Viewport doc + for details.

- The value is relative to the visible size of the viewport. - So 1.46 means that the Scroller would request new items - until the areas of 146% of the viewport size in both forward and backward directions - are filled with rendered items: 100% + 146% downward and 146% upward. - In this demo we have 3 requests on the initial load, let's see what happened: + The value is relative to the visible size of the viewport. So + 1.46 means that the Scroller would request new items until the + areas of 146% of the viewport size in both forward and backward directions + are filled with rendered items: 100% + 146% downward and 146% upward. In + this demo we have 3 requests on the initial load, let's see what happened:

    -
  1. 250px + 1.46 * 250px = 615px - — total size of area that needs to be filled in downward (positive) direction, - where 250px is the size of the visible viewport (per CSS) +
  2. + 250px + 1.46 * 250px = 615px — total size of area that needs to be + filled in downward (positive) direction, where 250px is the size of the + visible viewport (per CSS)
  3. -
  4. 7 items * 25px = 175px - — the result size of the first downward fetch, - where bufferSize is 7 and 25px is the item's size (per CSS) +
  5. + 7 items * 25px = 175px — the result size of the first downward + fetch, where bufferSize is 7 and 25px is the item's size (per + CSS)
  6. -
  7. 615px - 175px = 440px - — downward space which has to be filled during the second downward fetch +
  8. + 615px - 175px = 440px — downward space which has to be filled + during the second downward fetch
  9. -
  10. ceil(440px / 25px) = 18 - — the number of items that should fill 440px area during the second downward fetch +
  11. + ceil(440px / 25px) = 18 — the number of items that should fill + 440px area during the second downward fetch

@@ -38,21 +40,23 @@ And this is an explanation of the last (initial backward) fetch:

    -
  1. 1.46 * 250px = 360px - — upward space that has to be filled in backward (negative) direction +
  2. + 1.46 * 250px = 360px — upward space that has to be filled in + backward (negative) direction
  3. -
  4. ceil(360px / 25px) = 15 - — the number of items that should fill 360px during backward fetch +
  5. + ceil(360px / 25px) = 15 — the number of items that should fill + 360px during backward fetch

- This way we got 40 DOM elements (7 + 18 + 15 = 40). - The bigger padding value is, the more DOM elements should be present in the viewport. + This way we got 40 DOM elements (7 + 18 + 15 = 40). The bigger + padding value is, the more DOM elements should be present in the + viewport.

- The default value of padding property is 0.5. - It is a floating point number. - The minimum value is 0.01. + The default value of padding property is 0.5. It is a floating + point number. The minimum value is 0.01.

diff --git a/demo/app/samples/common/padding.component.ts b/demo/app/samples/common/padding.component.ts index 6f8157a9..f502ec33 100644 --- a/demo/app/samples/common/padding.component.ts +++ b/demo/app/samples/common/padding.component.ts @@ -1,7 +1,11 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; import { IDatasource } from 'ngx-ui-scroll'; @@ -11,7 +15,6 @@ import { IDatasource } from 'ngx-ui-scroll'; templateUrl: './padding.component.html' }) export class DemoPaddingComponent { - demoContext: DemoContext = { config: demos.settings.map.padding, viewportId: 'padding-viewport', @@ -27,9 +30,10 @@ export class DemoPaddingComponent { } }; - sources: DemoSources = [{ - name: DemoSourceType.Datasource, - text: `datasource: IDatasource = { + sources: DemoSources = [ + { + name: DemoSourceType.Datasource, + text: `datasource: IDatasource = { get: (index, count, success) => { const data = []; for (let i = index; i <= index + count - 1; i++) { @@ -42,16 +46,18 @@ export class DemoPaddingComponent { padding: 1.46 } }` - }, { - name: DemoSourceType.Template, - text: `
+ }, + { + name: DemoSourceType.Template, + text: `
{{item.text}}
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -60,6 +66,6 @@ export class DemoPaddingComponent { font-weight: bold; height: 25px; }` - }]; - + } + ]; } diff --git a/demo/app/samples/common/start-index.component.html b/demo/app/samples/common/start-index.component.html index e07af27f..4c7945ac 100644 --- a/demo/app/samples/common/start-index.component.html +++ b/demo/app/samples/common/start-index.component.html @@ -1,20 +1,15 @@ - -
-

- What if we want to start the Scroller from some specific position? - The startIndex setting determines what index - the Scroller will use to start the load process - when Datasource.get method is called for the first time. -

-

- The default startIndex value is 1. - It has to be an integer with no range limitations. - But real datasource boundaries (in case the Datasource is limited) - should be taken into the account. -

-
-
+ +
+

+ What if we want to start the Scroller from some specific position? The + startIndex setting determines what index the Scroller will use to + start the load process when Datasource.get method is called for + the first time. +

+

+ The default startIndex value is 1. It has to be an integer with + no range limitations. But real datasource boundaries (in case the + Datasource is limited) should be taken into the account. +

+
+
diff --git a/demo/app/samples/common/start-index.component.ts b/demo/app/samples/common/start-index.component.ts index add48311..a0b37403 100644 --- a/demo/app/samples/common/start-index.component.ts +++ b/demo/app/samples/common/start-index.component.ts @@ -1,63 +1,69 @@ -import { Component } from '@angular/core'; - -import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; -import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; - -import { IDatasource } from 'ngx-ui-scroll'; - -@Component({ - selector: 'app-demo-start-index', - templateUrl: './start-index.component.html' -}) -export class DemoStartIndexComponent { - - demoContext: DemoContext = { - config: demos.settings.map.startIndex, - viewportId: 'start-index-viewport', - count: 0, - log: '' - }; - - datasource: IDatasource = { - get: datasourceGetCallbackInfinite(this.demoContext), - settings: { - startIndex: 137 - } - }; - - sources: DemoSources = [{ - name: DemoSourceType.Datasource, - text: `datasource: IDatasource = { - get: (index, count, success) => { - const data = []; - for (let i = index; i <= index + count - 1; i++) { - data.push({ id: i, text: 'item #' + i }); - } - success(data); - }, - settings: { - startIndex: 137 - } -}` - }, { - name: DemoSourceType.Template, - text: `
-
-
{{item.text}}
-
-
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { - width: 150px; - height: 250px; - overflow-y: auto; -} -.item { - font-weight: bold; - height: 25px; -}` - }]; - -} +import { Component } from '@angular/core'; + +import { demos } from '../../routes'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; +import { datasourceGetCallbackInfinite } from '../../shared/datasource-get'; + +import { IDatasource } from 'ngx-ui-scroll'; + +@Component({ + selector: 'app-demo-start-index', + templateUrl: './start-index.component.html' +}) +export class DemoStartIndexComponent { + demoContext: DemoContext = { + config: demos.settings.map.startIndex, + viewportId: 'start-index-viewport', + count: 0, + log: '' + }; + + datasource: IDatasource = { + get: datasourceGetCallbackInfinite(this.demoContext), + settings: { + startIndex: 137 + } + }; + + sources: DemoSources = [ + { + name: DemoSourceType.Datasource, + text: `datasource: IDatasource = { + get: (index, count, success) => { + const data = []; + for (let i = index; i <= index + count - 1; i++) { + data.push({ id: i, text: 'item #' + i }); + } + success(data); + }, + settings: { + startIndex: 137 + } +}` + }, + { + name: DemoSourceType.Template, + text: `
+
+
{{item.text}}
+
+
` + }, + { + name: DemoSourceType.Styles, + text: `.viewport { + width: 150px; + height: 250px; + overflow-y: auto; +} +.item { + font-weight: bold; + height: 25px; +}` + } + ]; +} diff --git a/demo/app/samples/common/window-viewport.component.html b/demo/app/samples/common/window-viewport.component.html index 0f7cf1f3..986534d8 100644 --- a/demo/app/samples/common/window-viewport.component.html +++ b/demo/app/samples/common/window-viewport.component.html @@ -1,18 +1,18 @@ - -
-

- Follow this link to open the Entire Window scrollable demo. -

-
- -
-

- The entire window might work as the viewport if windowViewport setting is set to true. - Previous versions of the library (prior to 1.6.4) required the "overflow-anchor" css property - to be disabled at the top level of the DOM tree (body or html tag). Today it is not necessary. -

-
-
+ +
+

+ Follow this link to open the Entire Window + scrollable demo. +

+
+ +
+

+ The entire window might work as the viewport if + windowViewport setting is set to true. Previous versions of the + library (prior to 1.6.4) required the "overflow-anchor" css property to be + disabled at the top level of the DOM tree (body or html tag). Today it is + not necessary. +

+
+
diff --git a/demo/app/samples/common/window-viewport.component.ts b/demo/app/samples/common/window-viewport.component.ts index 7bb75cb4..aac62d72 100644 --- a/demo/app/samples/common/window-viewport.component.ts +++ b/demo/app/samples/common/window-viewport.component.ts @@ -1,47 +1,53 @@ -import { Component } from '@angular/core'; - -import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType } from '../../shared/interfaces'; - -@Component({ - selector: 'app-demo-window-viewport', - templateUrl: './window-viewport.component.html' -}) -export class DemoWindowViewportComponent { - - demoContext: DemoContext = { - config: demos.settings.map.windowViewport, - viewportId: 'window-viewport-viewport', - noWorkView: true, - count: 0, - log: '' - }; - - sources: DemoSources = [{ - name: DemoSourceType.Datasource, - text: `datasource: IDatasource = { - get: (index, count, success) => { - const data = []; - for (let i = index; i <= index + count - 1; i++) { - data.push({ id: i, text: 'item #' + i }); - } - success(data); - }, - settings: { - windowViewport: true - } -}` - }, { - name: DemoSourceType.Template, - text: `
-
{{item.text}}
-
` - }, { - name: DemoSourceType.Styles, - text: `.item { - font-weight: bold; - height: 25px; -}` - }]; - -} +import { Component } from '@angular/core'; + +import { demos } from '../../routes'; +import { + DemoContext, + DemoSources, + DemoSourceType +} from '../../shared/interfaces'; + +@Component({ + selector: 'app-demo-window-viewport', + templateUrl: './window-viewport.component.html' +}) +export class DemoWindowViewportComponent { + demoContext: DemoContext = { + config: demos.settings.map.windowViewport, + viewportId: 'window-viewport-viewport', + noWorkView: true, + count: 0, + log: '' + }; + + sources: DemoSources = [ + { + name: DemoSourceType.Datasource, + text: `datasource: IDatasource = { + get: (index, count, success) => { + const data = []; + for (let i = index; i <= index + count - 1; i++) { + data.push({ id: i, text: 'item #' + i }); + } + success(data); + }, + settings: { + windowViewport: true + } +}` + }, + { + name: DemoSourceType.Template, + text: `
+
{{item.text}}
+
` + }, + { + name: DemoSourceType.Styles, + text: `.item { + font-weight: bold; + height: 25px; +}` + } + ]; +} diff --git a/demo/app/samples/datasource.component.html b/demo/app/samples/datasource.component.html index 18f45bb2..3444bf26 100644 --- a/demo/app/samples/datasource.component.html +++ b/demo/app/samples/datasource.component.html @@ -2,45 +2,47 @@

Angular UI Scroll Datasource Demos

The idea of the *uiScroll directive usage is very similar - to the use of *ngFor directive - in the simplest case, but as it follows from the documentation, - a special Datasource object needs to be passed - instead of Angular Iterable. - Datasource is an entry point to the uiScroll, - that provides data flow from the host App to the Scroller. - There are two common ways of how the Datasource could be defined: + to the use of *ngFor directive in the simplest + case, but as it follows from the documentation, a special + Datasource object needs to be passed instead of Angular + Iterable. Datasource is an entry point to the + uiScroll, that provides data flow from the host App to the Scroller. + There are two common ways of how the Datasource could + be defined:

  • as an object literal of IDatasource type -
    {{datasourceSampleLiteral}}
    +
    {{ datasourceSampleLiteral }}
  • as an instance of Datasource class -
    {{datasourceSampleClass}}
    +
    {{ datasourceSampleClass }}

- 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 - (see Adapter demo page for details). - Both of IDatasource and Datasource definitions - could be imported from UiScrollModule: -

-
{{importSample}}
-

- The IDatasource object we need to define in both cases + 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 (see + Adapter demo page for details). + Both of IDatasource and Datasource definitions could + be imported from UiScrollModule:

+
{{ importSample }}
+

The IDatasource object we need to define in both cases

  • must contain get method property and
  • may contain settings object property.

- With the help of settings object the Scroller could be configured, - as it is  described on Settings demo page. - On this page we are going to discuss the Datasource.get method implementation. + With the help of settings object the Scroller could + be configured, as it is  described on Settings demo page. On this page 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 79f77d02..47bd9cf8 100644 --- a/demo/app/samples/datasource.component.ts +++ b/demo/app/samples/datasource.component.ts @@ -7,14 +7,11 @@ import { demos } from '../routes'; templateUrl: './datasource.component.html' }) export class DatasourceComponent { - demos = demos; - constructor() { - } + constructor() {} - importSample = 'import { Datasource, IDatasource } from \'ngx-ui-scroll\';'; + importSample = "import { Datasource, IDatasource } from 'ngx-ui-scroll';"; datasourceSampleLiteral = 'datasource: IDatasource = { get, settings };'; datasourceSampleClass = 'datasource = new Datasource({ get, settings });'; - } diff --git a/demo/app/samples/datasource/bidirectional-unlimited-datasource.component.html b/demo/app/samples/datasource/bidirectional-unlimited-datasource.component.html index ed0cb779..51fb85e4 100644 --- a/demo/app/samples/datasource/bidirectional-unlimited-datasource.component.html +++ b/demo/app/samples/datasource/bidirectional-unlimited-datasource.component.html @@ -1,19 +1,15 @@ - +

- The datasource implemented here has no boundaries. - The result data array will consist of count items - started exactly from index position regardless of - count and index values. - They could be any finite integers. + The datasource implemented here has no boundaries. The result + data array will consist of count items started exactly + from index position regardless of count and + index values. They could be any finite integers.

- This sample as well as following ones does include Datasource.get method log - telling us how many items are fetched by the Scroller via Datasource in a single run. + This sample as well as following ones does include + Datasource.get method log telling us how many items are fetched + by the Scroller via Datasource in a single run.

diff --git a/demo/app/samples/datasource/bidirectional-unlimited-datasource.component.ts b/demo/app/samples/datasource/bidirectional-unlimited-datasource.component.ts index d98f8e89..ab4db1ab 100644 --- a/demo/app/samples/datasource/bidirectional-unlimited-datasource.component.ts +++ b/demo/app/samples/datasource/bidirectional-unlimited-datasource.component.ts @@ -11,7 +11,6 @@ import { IDatasource } from 'ngx-ui-scroll'; templateUrl: './bidirectional-unlimited-datasource.component.html' }) export class DemoBidirectionalUnlimitedDatasourceComponent { - demoContext = { config: demos.datasource.map.unlimitedBidirectional, logViewOnly: true, @@ -30,9 +29,10 @@ export class DemoBidirectionalUnlimitedDatasourceComponent { } }; - sources: DemoSources = [{ - name: DemoSourceType.Datasource, - text: `datasource: IDatasource = { + sources: DemoSources = [ + { + name: DemoSourceType.Datasource, + text: `datasource: IDatasource = { get: (index, count, success) => { const data = []; for (let i = index; i <= index + count - 1; i++) { @@ -41,6 +41,6 @@ export class DemoBidirectionalUnlimitedDatasourceComponent { success(data); } };` - }]; - + } + ]; } diff --git a/demo/app/samples/datasource/datasource-signatures.component.html b/demo/app/samples/datasource/datasource-signatures.component.html index 5c3dc631..47602dc3 100644 --- a/demo/app/samples/datasource/datasource-signatures.component.html +++ b/demo/app/samples/datasource/datasource-signatures.component.html @@ -1,32 +1,30 @@ - +

All the datasources listed above are valid. The first version (datasourceCallback) introduces simplest callback-based signature - when we need to pass a third argument which is a callback function - that should be called explicitly with the items array argument. - We assume that this.getData(index, count) returns - appropriate part of the dataset we want to virtualize, which is - an array of count items started from index position. - datasourceCallback2 demonstrates an asynchronous version of callback-based Datasource. + when we need to pass a third argument which is a callback function that + should be called explicitly with the items array argument. We assume that + this.getData(index, count) returns appropriate part of the + dataset we want to virtualize, which is an array of count items + started from index position. + datasourceCallback2 demonstrates an asynchronous version of + callback-based Datasource.

- datasourcePromise and datasourcePromise2 introduce promise-based - signature when an object of Promise type needs to be returned. - Resolving this promise is our responsibility, and we assume that - this.getDataPromise method resolves necessary part of the dataset - based on index and count parameters. + datasourcePromise and datasourcePromise2 introduce + promise-based signature when an object of Promise type needs to + be returned. Resolving this promise is our responsibility, and we assume + that this.getDataPromise method resolves necessary part of the + dataset based on index and count parameters.

- datasourceObservable and datasourceObservable2 introduce observable-based - signature when an object of Observable type needs to be returned. - Similar to Promise case we need to be sure that the next method - is called in appropriate time, and we assume that - this.getDataObservable method delivers necessary part of the dataset - based on index and count parameters. + datasourceObservable and datasourceObservable2 introduce + observable-based signature when an object of Observable type + needs to be returned. Similar to Promise case we need to be sure that the + next method is called in appropriate time, and we assume that + this.getDataObservable method delivers necessary part of the + dataset based on index and count parameters.

diff --git a/demo/app/samples/datasource/datasource-signatures.component.ts b/demo/app/samples/datasource/datasource-signatures.component.ts index af71a2e3..a2037645 100644 --- a/demo/app/samples/datasource/datasource-signatures.component.ts +++ b/demo/app/samples/datasource/datasource-signatures.component.ts @@ -8,15 +8,15 @@ import { DemoSources, DemoSourceType } from '../../shared/interfaces'; templateUrl: './datasource-signatures.component.html' }) export class DemoDatasourceSignaturesComponent { - demoContext = { config: demos.datasource.map.datasourceGetSignatures, noWorkView: true }; - sources: DemoSources = [{ - name: DemoSourceType.Datasource, - text: `datasourceCallback: IDatasource = { + sources: DemoSources = [ + { + name: DemoSourceType.Datasource, + text: `datasourceCallback: IDatasource = { get: (index, count, success) => success(this.getData(index, count)) }; @@ -47,6 +47,6 @@ datasourceObservable2: IDatasource = { get: (index, count) => this.getDataObservable(index, count) };` - }]; - + } + ]; } diff --git a/demo/app/samples/datasource/inverted-datasource.component.html b/demo/app/samples/datasource/inverted-datasource.component.html index 833e1b0d..acc03bff 100644 --- a/demo/app/samples/datasource/inverted-datasource.component.html +++ b/demo/app/samples/datasource/inverted-datasource.component.html @@ -1,5 +1,4 @@
-
@@ -7,7 +6,7 @@
common ↓
-
{{item.text}}
+
{{ item.text }}
@@ -15,36 +14,34 @@
inverted ↑
-
{{item.text}}
+
{{ item.text }}
-
{{sources[0].text}}
+
{{ sources[0].text }}

- Datasource.get method implementation could be quite complex and flexible. - This piece demonstrates how the dataset we want to virtualize could be - processed to provide "inverted" viewport logic when - new positive items are retrieved by scrolling up. + Datasource.get method implementation could be quite complex and + flexible. This piece demonstrates how the dataset we want to virtualize + could be processed to provide "inverted" viewport logic when new positive + items are retrieved by scrolling up.

- getData method implements positive limited datasource - as it was done in the previous sample with one change: left dataset boundary - is parametrized by MIN property. - So datasourceCommon.get just passes getData result - to its success callback. + getData method implements positive limited datasource as it was + done in the previous sample with one change: left dataset boundary is + parametrized by MIN property. So datasourceCommon.get just + passes getData result to its success callback.

- But datasourceInverted.get does the simple math: - it inverts and shifts the index and reverses the result array. - So the Scroller gets some specifically processed portion of our dataset - that reflects our needs. By settings startIndex value to -10 in this case - we are telling that we want 10 items to be fetched and rendered - in forward direction initially. And it does not depend on MIN, - it does depend on the viewport and single item size: 10 items are visible. + But datasourceInverted.get does the simple math: it inverts and + shifts the index and reverses the result array. So the Scroller + gets some specifically processed portion of our dataset that reflects our + needs. By settings startIndex value to -10 in this case we are + telling that we want 10 items to be fetched and rendered in forward + direction initially. And it does not depend on MIN, it does depend + on the viewport and single item size: 10 items are visible.

- diff --git a/demo/app/samples/datasource/inverted-datasource.component.ts b/demo/app/samples/datasource/inverted-datasource.component.ts index 2b716408..54c3ed5e 100644 --- a/demo/app/samples/datasource/inverted-datasource.component.ts +++ b/demo/app/samples/datasource/inverted-datasource.component.ts @@ -10,14 +10,12 @@ import { IDatasource } from 'ngx-ui-scroll'; templateUrl: './inverted-datasource.component.html' }) export class DemoInvertedDatasourceComponent { - demoConfig = demos.datasource.map.invertedIndexes; MIN = 1; datasourceCommon: IDatasource = { - get: (index, count, success) => - success(this.getData(index, count)) + get: (index, count, success) => success(this.getData(index, count)) }; datasourceInverted: IDatasource = { @@ -31,9 +29,10 @@ export class DemoInvertedDatasourceComponent { } }; - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `MIN = 1; + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `MIN = 1; datasourceCommon: IDatasource = { get: (index, count, success) => @@ -62,7 +61,8 @@ getData(index: number, count: number) { } return data; }` - }]; + } + ]; getData(index: number, count: number) { const data = []; @@ -75,5 +75,4 @@ getData(index: number, count: number) { } return data; } - } diff --git a/demo/app/samples/datasource/limited-datasource.component.html b/demo/app/samples/datasource/limited-datasource.component.html index 450032b6..f01c4425 100644 --- a/demo/app/samples/datasource/limited-datasource.component.html +++ b/demo/app/samples/datasource/limited-datasource.component.html @@ -1,36 +1,31 @@ - +

- The datasource could be limited. - The main point of limited datasource implementation is - to cut the result in accordance with dataset boundaries and - return an empty array if we are out of range. -

+ The datasource could be limited. The main point of limited datasource + implementation is to cut the result in accordance with dataset boundaries + and return an empty array if we are out of range. +

- In this sample we have only 200 items in the dataset: - from -99 to 100 including 0. If at some point the Scroller will request - this datasource for, say, 10 items started from -105 index, - the result will consist of only 4 items: -99, -98, -97, -96. - If a request falls out of [-99..100] range, an empty array will be returned. + In this sample we have only 200 items in the dataset: from -99 to 100 + including 0. If at some point the Scroller will request this datasource + for, say, 10 items started from -105 index, the result will consist of + only 4 items: -99, -98, -97, -96. If a request falls out of [-99..100] + range, an empty array will be returned.

- The second tab (Fixed dataset) implements a limited datasource - over a fixed dataset, which is initialized in the Component's constructor. - This way data could be retrieved from a remote source. + The second tab (Fixed dataset) implements a limited datasource over a + fixed dataset, which is initialized in the Component's constructor. This + way data could be retrieved from a remote source.

Another option of how to limit the datasource is to use minIndex/maxIndex settings; - this way the implementation of the Datasource.get method could remain unlimited - as the Scroller will not request for items that are out of - [minIndex..maxIndex] range. + fragment="{{ minMaxDemoConfig.id }}" + >minIndex/maxIndex settings; this way the implementation of the Datasource.get method could + remain unlimited as the Scroller 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 650867cf..7aa3bee2 100644 --- a/demo/app/samples/datasource/limited-datasource.component.ts +++ b/demo/app/samples/datasource/limited-datasource.component.ts @@ -11,7 +11,6 @@ import { IDatasource } from 'ngx-ui-scroll'; templateUrl: './limited-datasource.component.html' }) export class DemoLimitedDatasourceComponent { - demoContext = { config: demos.datasource.map.limited, logViewOnly: true, @@ -39,9 +38,10 @@ export class DemoLimitedDatasourceComponent { } }; - sources: DemoSources = [{ - name: 'Runtime generation', - text: `MIN = -99; + sources: DemoSources = [ + { + name: 'Runtime generation', + text: `MIN = -99; MAX = 100; datasource: IDatasource = { @@ -57,9 +57,10 @@ datasource: IDatasource = { success(data); } };` - }, { - name: 'Fixed dataset', - text: `MIN = -99; + }, + { + name: 'Fixed dataset', + text: `MIN = -99; MAX = 100; constructor() { @@ -82,6 +83,6 @@ datasource: IDatasource = { success(data); } };` - }]; - + } + ]; } diff --git a/demo/app/samples/datasource/pages-datasource.component.html b/demo/app/samples/datasource/pages-datasource.component.html index e47ac795..cf923268 100644 --- a/demo/app/samples/datasource/pages-datasource.component.html +++ b/demo/app/samples/datasource/pages-datasource.component.html @@ -1,30 +1,26 @@ - +

- This sample demonstrates how the Scroller can be integrated - with the datasource which has "pages" API only. The Scroller - deals with "offset-limit" (that is "index-count") API by default, but - necessary transition layer could be implemented via datasource.get. + This sample demonstrates how the Scroller can be integrated with the + datasource which has "pages" API only. The Scroller deals with + "offset-limit" (that is "index-count") API by default, but necessary + transition layer could be implemented via datasource.get.

The constructor initializes data two-dimensional array, getDataPage method simulates "pages" API. We are assuming that - getDataPage is the only entry point to our data. Then - each time the datasource.get method is called, we are - converting "index-count" API params to appropriate pages numbers, - retrieving these pages, joining pages results and slicing it to - resolve the exactly part of the dataset the Scroller needs for. + getDataPage is the only entry point to our data. Then each time + the datasource.get method is called, we are converting + "index-count" API params to appropriate pages numbers, retrieving these + pages, joining pages results and slicing it to resolve the exactly part of + the dataset the Scroller needs for.

getDataPage could be async, let's imagine that there is - remoteDataService which has - getDataPageAsync method which returns Promise. - Using promise-based signature in this case, - x.3, x.4 and x.5 code sections might be rewritten as it is shown in "Component (async)" tab. + remoteDataService which has getDataPageAsync method + which returns Promise. Using promise-based signature in this + case, x.3, x.4 and x.5 code sections might be rewritten as it is shown in + "Component (async)" tab.

diff --git a/demo/app/samples/datasource/pages-datasource.component.ts b/demo/app/samples/datasource/pages-datasource.component.ts index 13f228a3..7a64ac4d 100644 --- a/demo/app/samples/datasource/pages-datasource.component.ts +++ b/demo/app/samples/datasource/pages-datasource.component.ts @@ -15,7 +15,6 @@ interface MyItem { templateUrl: './pages-datasource.component.html' }) export class DemoPagesDatasourceComponent { - demoContext = { config: demos.datasource.map.pages, logViewOnly: true, @@ -45,13 +44,16 @@ export class DemoPagesDatasourceComponent { get: (index, count, success) => { this.getCount++; this.demoContext.log = '\n' + this.demoContext.log; - this.demoContext.log = `${this.getCount}.1 index = ${index}, count = ${count}\n` + this.demoContext.log; + this.demoContext.log = + `${this.getCount}.1 index = ${index}, count = ${count}\n` + + this.demoContext.log; // getting start/end item indexes with no negative values const startIndex = Math.max(index, 0); const endIndex = index + count - 1; if (startIndex > endIndex) { - this.demoContext.log = `${this.getCount}.2 empty result\n` + this.demoContext.log; + this.demoContext.log = + `${this.getCount}.2 empty result\n` + this.demoContext.log; success([]); return; } @@ -69,18 +71,29 @@ ${this.demoContext.log}`; logPages.push(i); pagesResult = [...pagesResult, ...this.getDataPage(i)]; } - 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}]` - ) + '\n' + this.demoContext.log; + 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 + }]`) + + '\n' + + this.demoContext.log; // slicing pages result to satisfy start/end indexes const start = startIndex - startPage * this.pageSize; 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.demoContext.log; + this.demoContext.log = + (!data.length + ? '' + : `${this.getCount}.5 sliced result [${data[0].index}..${ + data[data.length - 1].index + }]\n`) + this.demoContext.log; success(data); }, @@ -89,9 +102,10 @@ ${this.demoContext.log}`; } }; - sources: DemoSources = [{ - name: DemoSourceType.Component, - text: `datasource: IDatasource = { + sources: DemoSources = [ + { + name: DemoSourceType.Component, + text: `datasource: IDatasource = { get: (index, count, success) => { // items to request (x.2) const startIndex = Math.max(index, 0); @@ -147,10 +161,10 @@ 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; @@ -186,7 +200,8 @@ constructor( private remoteDataService: RemoteDataService ) { }` - }]; + } + ]; getDataPage(page: number) { if (page < 0 || page >= this.pagesCount) { @@ -194,5 +209,4 @@ constructor( } return this.data[page]; } - } diff --git a/demo/app/samples/datasource/positive-limited-datasource.component.html b/demo/app/samples/datasource/positive-limited-datasource.component.html index 7e32bc25..2e00db44 100644 --- a/demo/app/samples/datasource/positive-limited-datasource.component.html +++ b/demo/app/samples/datasource/positive-limited-datasource.component.html @@ -1,28 +1,27 @@ - +

- If we are not going to deal with negative indexes, - we can fix the datasource left boundary at 1 (or 0 depends on your needs). - This is a particular case of a limited datasource implementation discussed above - with only MIN value set up. Also, positive limitation - could be alternatively achieved by assigning + If we are not going to deal with negative indexes, we can fix the + datasource left boundary at 1 (or 0 depends on your needs). This is a + particular case of a limited datasource implementation discussed above + with only MIN value set up. Also, positive limitation could be + alternatively achieved by assigning minIndex setting to 1 (or 0). + fragment="{{ settingsScope.minMaxIndexes.id }}" + >minIndex setting + to 1 (or 0).

- Note that if 0 index is the left boundary of the dataset, - then it seems reasonable to assign 0 value to + Note that if 0 index is the left boundary of the dataset, then it seems + reasonable to assign 0 value to startIndex setting too, - because the default startIndex value is 1. + fragment="{{ settingsScope.startIndex.id }}" + >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 0a54fb0e..2448c290 100644 --- a/demo/app/samples/datasource/positive-limited-datasource.component.ts +++ b/demo/app/samples/datasource/positive-limited-datasource.component.ts @@ -11,7 +11,6 @@ import { IDatasource } from 'ngx-ui-scroll'; templateUrl: './positive-limited-datasource.component.html' }) export class DemoPositiveLimitedDatasourceComponent { - demoContext = { config: demos.datasource.map.positiveLimitedIndexes, logViewOnly: true, @@ -36,9 +35,10 @@ export class DemoPositiveLimitedDatasourceComponent { } }; - sources: DemoSources = [{ - name: DemoSourceType.Datasource, - text: `datasource: IDatasource = { + sources: DemoSources = [ + { + name: DemoSourceType.Datasource, + text: `datasource: IDatasource = { get: (index, count, success) => { const data = []; const start = Math.max(1, index); // or 0 @@ -51,6 +51,6 @@ export class DemoPositiveLimitedDatasourceComponent { success(data); } };` - }]; - + } + ]; } diff --git a/demo/app/samples/datasource/remote-datasource.component.html b/demo/app/samples/datasource/remote-datasource.component.html index 40e103c5..ba5d2fd2 100644 --- a/demo/app/samples/datasource/remote-datasource.component.html +++ b/demo/app/samples/datasource/remote-datasource.component.html @@ -1,19 +1,14 @@ - +

- In case the data is accessible via some remote API, - the Datasource.get could be implemented using Observable signature. - In this demo we have a nodejs application with - /api/data GET request handler ("Server" tab), - which emulates a remote limited Datasource implementation. - The Component just provides /api/data GET method call - inside Datasource.get body. - And again, the only requirement is that the API handler on server - must return an array of index-count items or empty array if we are out of range. + In case the data is accessible via some remote API, the + Datasource.get could be implemented using Observable signature. + In this demo we have a nodejs application with /api/data GET + request handler ("Server" tab), which emulates a remote limited Datasource + implementation. The Component just provides /api/data GET method + call inside Datasource.get body. And again, the only requirement + is that the API handler on server must return an array of index-count + items or empty array if we are out of range.

diff --git a/demo/app/samples/datasource/remote-datasource.component.ts b/demo/app/samples/datasource/remote-datasource.component.ts index 5d30427f..262def37 100644 --- a/demo/app/samples/datasource/remote-datasource.component.ts +++ b/demo/app/samples/datasource/remote-datasource.component.ts @@ -9,9 +9,7 @@ import { IDatasource } from 'ngx-ui-scroll'; @Injectable() export class RemoteDataService { - - constructor(private http: HttpClient) { - } + constructor(private http: HttpClient) {} getData(index: number, count: number): Observable { return this.http.get(`/api/data?index=${index}&count=${count}`); @@ -23,7 +21,6 @@ export class RemoteDataService { templateUrl: './remote-datasource.component.html' }) export class DemoRemoteDatasourceComponent { - demoContext = { config: demos.datasource.map.remote, logViewOnly: true, @@ -33,15 +30,17 @@ export class DemoRemoteDatasourceComponent { datasource: IDatasource = { get: (index: number, count: number) => { - this.demoContext.log = `${++this.demoContext.count}) get items [${index}..${index + count - 1}] + this.demoContext.log = `${++this.demoContext + .count}) get items [${index}..${index + count - 1}] ${this.demoContext.log}`; return this.remoteDataService.getData(index, count); } }; - sources: DemoSources = [{ - name: 'Service/Component', - text: `@Injectable() + sources: DemoSources = [ + { + name: 'Service/Component', + text: `@Injectable() export class RemoteDataService { constructor(private http: HttpClient) { @@ -65,9 +64,10 @@ export class DemoRemoteDatasourceComponent { constructor(private remoteDataService: RemoteDataService) { } }` - }, { - name: DemoSourceType.Server, - text: `const MIN = -99; + }, + { + name: DemoSourceType.Server, + text: `const MIN = -99; const MAX = 900; app.get('/api/data', (req, res) => { @@ -87,9 +87,8 @@ app.get('/api/data', (req, res) => { } res.send(result); });` - }]; - - constructor(private remoteDataService: RemoteDataService) { - } + } + ]; + constructor(private remoteDataService: RemoteDataService) {} } diff --git a/demo/app/samples/experimental.component.html b/demo/app/samples/experimental.component.html index 69b7faf8..d314ee99 100644 --- a/demo/app/samples/experimental.component.html +++ b/demo/app/samples/experimental.component.html @@ -1,37 +1,46 @@

Angular UI Scroll Experimental Demos

- There are some undocumented settings and Adapter methods that provide additional flexibility. - We can consider them as experimental features. Most of these features are not tested well - and may have some side effects or behave imperfectly. + There are some undocumented settings and Adapter methods that provide + additional flexibility. We can consider them as experimental features. Most of + these features are not tested well and may have some side effects or behave + imperfectly.

- For example, Adapter.fix method. It has different options for different cases - which are discussed below. These options are passed as an object of the following type: + For example, Adapter.fix method. It has different options for + different cases which are discussed below. These options are passed as an + object of the following type:

-
{{adapterFixArgumentDescription}}
+
{{ adapterFixArgumentDescription }}
diff --git a/demo/app/samples/experimental.component.ts b/demo/app/samples/experimental.component.ts index 030a2408..c8328f90 100644 --- a/demo/app/samples/experimental.component.ts +++ b/demo/app/samples/experimental.component.ts @@ -6,9 +6,7 @@ import { demos } from '../routes'; templateUrl: './experimental.component.html' }) export class ExperimentalComponent { - - constructor() { - } + constructor() {} scope = demos.experimental.map; base = '../' + demos.experimental.id; @@ -21,5 +19,4 @@ export class ExperimentalComponent { scrollToItem?: (item: ItemAdapter) => boolean; scrollToItemOpt?: boolean | ScrollIntoViewOptions; }`; - } diff --git a/demo/app/samples/experimental/adapter-fix-position.component.html b/demo/app/samples/experimental/adapter-fix-position.component.html index 9db05922..235ba2e2 100644 --- a/demo/app/samples/experimental/adapter-fix-position.component.html +++ b/demo/app/samples/experimental/adapter-fix-position.component.html @@ -1,5 +1,4 @@ -
Available since v1.2.4, undocumented
@@ -8,22 +7,22 @@

- The Adapter.fix method has a number of settings, - one of them is scrollPosition. - It allows to change the position of the viewport's scrollbar programmatically. - It is used in the lib tests. + The Adapter.fix method has a number of settings, one of them is + scrollPosition. It allows to change the position of the + viewport's scrollbar programmatically. It is used in the lib tests.

The value of scrollPosition should be an integer or Infinity. - Negative value would be treated as zero. - Despite the browsers have no agreement on how to deal with - setting scrollTop/scrollLeft properties - into +/-Infinity, the Scroller treats them as follows: + Negative value would be treated as zero. Despite the browsers have no + agreement on how to deal with setting scrollTop/scrollLeft + properties into +/-Infinity, the Scroller treats them as follows:

  • - scroll to the beginning of the viewport if -Infinity, - (the value will be set to 0) + scroll to the beginning of the viewport if -Infinity, (the + value will be set to 0)
  • scroll to the end of the viewport if Infinity diff --git a/demo/app/samples/experimental/adapter-fix-position.component.ts b/demo/app/samples/experimental/adapter-fix-position.component.ts index fb273cc1..fdd27e12 100644 --- a/demo/app/samples/experimental/adapter-fix-position.component.ts +++ b/demo/app/samples/experimental/adapter-fix-position.component.ts @@ -10,7 +10,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './adapter-fix-position.component.html' }) export class DemoAdapterFixPositionComponent { - demoContext = { config: demos.experimental.map.adapterFixPosition, noInfo: true @@ -26,9 +25,10 @@ export class DemoAdapterFixPositionComponent { } }); - sources: DemoSources = [{ - name: DemoSourceType.Template, - text: ` + sources: DemoSources = [ + { + name: DemoSourceType.Template, + text: `
    @@ -36,9 +36,10 @@ export class DemoAdapterFixPositionComponent {
    {{item.text}}
` - }, { - name: DemoSourceType.Component, - text: `datasource = new Datasource({ + }, + { + name: DemoSourceType.Component, + text: `datasource = new Datasource({ get: (index, count, success) => { const data = []; for (let i = index; i < index + count; i++) { @@ -56,7 +57,8 @@ export class DemoAdapterFixPositionComponent { this.datasource.adapter.fix({ scrollPosition: +Infinity }); } ` - }]; + } + ]; adapterFixPosition = 'Adapter.fix({ scrollPosition: 0 })'; @@ -67,5 +69,4 @@ export class DemoAdapterFixPositionComponent { scrollBottom() { this.datasource.adapter.fix({ scrollPosition: +Infinity }); } - } diff --git a/demo/app/samples/experimental/adapter-fix-scrollToItem.component.html b/demo/app/samples/experimental/adapter-fix-scrollToItem.component.html index 2e24fa5a..74f3ca34 100644 --- a/demo/app/samples/experimental/adapter-fix-scrollToItem.component.html +++ b/demo/app/samples/experimental/adapter-fix-scrollToItem.component.html @@ -1,27 +1,31 @@ -
Available since v1.6.2, undocumented
item # - -
- - scroll to bottom + +
+ - scroll to bottom

- What if we want to scroll to a specific item in the viewport? - This can be done via scrollToItem option of the Adapter.fix method. - This option specifies an item predicate function, and the first item in the Buffer, - that satisfies this predicate, will be the item to which the viewport will scroll. + What if we want to scroll to a specific item in the viewport? This can be + done via scrollToItem option of the Adapter.fix method. + This option specifies an item predicate function, and the first item in + the Buffer, that satisfies this predicate, will be the item to which the + viewport will scroll.

- Scrolling is performed via element.scrollIntoView() native DOM Element interface's method. - According to its specification, it accepts 1 optional argument which can be a Boolean parameter or - special Object parameter (details can be found on - MDN). - To provide this option, the Adapter.fix method handles another property of its argument object: - scrollToItemOpt. The demo shows how to use it. + Scrolling is performed via element.scrollIntoView() native DOM + Element interface's method. According to its specification, it accepts 1 + optional argument which can be a Boolean parameter or special Object + parameter (details can be found on + MDN). To provide this option, the Adapter.fix method handles + another property of its argument object: scrollToItemOpt. The + demo shows how to use it.

diff --git a/demo/app/samples/experimental/adapter-fix-scrollToItem.component.ts b/demo/app/samples/experimental/adapter-fix-scrollToItem.component.ts index 090d2e7a..ae5a2d15 100644 --- a/demo/app/samples/experimental/adapter-fix-scrollToItem.component.ts +++ b/demo/app/samples/experimental/adapter-fix-scrollToItem.component.ts @@ -1,7 +1,12 @@ import { Component } from '@angular/core'; import { demos } from '../../routes'; -import { DemoContext, DemoSources, DemoSourceType, MyItem } from '../../shared/interfaces'; +import { + DemoContext, + DemoSources, + DemoSourceType, + MyItem +} from '../../shared/interfaces'; import { Datasource } from 'ngx-ui-scroll'; @@ -10,7 +15,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './adapter-fix-scrollToItem.component.html' }) export class DemoAdapterFixScrollToItemComponent { - demoContext: DemoContext = { config: demos.experimental.map.adapterFixScrollToItem, noInfo: true @@ -34,9 +38,10 @@ export class DemoAdapterFixScrollToItemComponent { this.scrollToBottom = false; } - sources: DemoSources = [{ - name: DemoSourceType.Template, - text: ` item # + sources: DemoSources = [ + { + name: DemoSourceType.Template, + text: ` item # - scroll to bottom @@ -45,9 +50,10 @@ export class DemoAdapterFixScrollToItemComponent {
{{item.text}}
` - }, { - name: DemoSourceType.Component, - text: `index = '5' + }, + { + name: DemoSourceType.Component, + text: `index = '5' scrollToBottom = false; datasource = new Datasource({ @@ -71,7 +77,8 @@ doScrollTo() { } } ` - }]; + } + ]; adapterFixUpdater = 'Adapter.fix({ scrollToItem })'; @@ -85,5 +92,4 @@ doScrollTo() { }); } } - } diff --git a/demo/app/samples/experimental/adapter-fix-updater.component.html b/demo/app/samples/experimental/adapter-fix-updater.component.html index 92f0f0a1..28c78a6d 100644 --- a/demo/app/samples/experimental/adapter-fix-updater.component.html +++ b/demo/app/samples/experimental/adapter-fix-updater.component.html @@ -1,63 +1,65 @@ - - +
Available since v1.3.1, undocumented
item # - -
Items in buffer: {{this.countItems()}}
+ +
Items in buffer: {{ this.countItems() }}

- The Adapter.fix method could be invoked with updater option - which is a function that is applied to each element in the current Scroller's Buffer list - and allows to update buffered items on the fly. - The argument of the updater function is an object of special ItemAdapter type + The Adapter.fix method could be invoked with + updater option which is a function that is applied to each + element in the current Scroller's Buffer list and allows to update + buffered items on the fly. The argument of the updater function + is an object of special ItemAdapter type

-
{{itemAdapterDescription}}
+
{{ itemAdapterDescription }}

- The data is exactly what the Datasource sends to the Scroller - as the items contents. So we have access to the items inside the Scroller's Buffer, - and in this demo we update the text of selected item by its index. - If we destroy the updated item by scrolling out and then scroll back, - we will see that our update is gone. Speaking generally, - we need to care about datasource consistency and every update we want to - be applied to the Scroller's Buffer should also happen at the datasource level. - The examples of Datasource-Adapter synching can be found + The data is exactly what the Datasource sends to the + Scroller as the items contents. So we have access to the items inside the + Scroller's Buffer, and in this demo we update the text of + selected item by its index. If we destroy the updated item by scrolling + out and then scroll back, we will see that our update is gone. Speaking + generally, we need to care about datasource consistency and every update + we want to be applied to the Scroller's Buffer should also happen at the + datasource level. The examples of Datasource-Adapter synching can be found here, + fragment="{{ adapterMethodsScope.map.appendPrependSync.id }}" + >here, here and + fragment="{{ adapterMethodsScope.map.remove.id }}" + >here + and here. + fragment="{{ adapterMethodsScope.map.insert.id }}" + >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 - (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). - 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. + fragment="{{ adapterPropsScope.map.itemsCount.id }}" + >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.

- The Adapter.fix-updater method has the second argument update - (available since v2.0.0-rc.6) which is a function that can be invoked - in case the Buffer.items array reference needs to be updated. + The Adapter.fix-updater method has the second argument + update (available since v2.0.0-rc.6) which is a function that can + be invoked in case the Buffer.items array reference needs to be + updated.

diff --git a/demo/app/samples/experimental/adapter-fix-updater.component.ts b/demo/app/samples/experimental/adapter-fix-updater.component.ts index a528d0ae..1c69d5ae 100644 --- a/demo/app/samples/experimental/adapter-fix-updater.component.ts +++ b/demo/app/samples/experimental/adapter-fix-updater.component.ts @@ -10,7 +10,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './adapter-fix-updater.component.html' }) export class DemoAdapterFixUpdaterComponent { - demoContext = { config: demos.experimental.map.adapterFixUpdater, noInfo: true @@ -35,9 +34,10 @@ export class DemoAdapterFixUpdaterComponent { this.inputValue = '5'; } - sources: DemoSources = [{ - name: DemoSourceType.Template, - text: ` item # + sources: DemoSources = [ + { + name: DemoSourceType.Template, + text: ` item #
Items in buffer: {{this.countItems()}}
@@ -46,9 +46,10 @@ export class DemoAdapterFixUpdaterComponent {
{{item.text}}
` - }, { - name: DemoSourceType.Component, - text: `inputValue = '5' + }, + { + name: DemoSourceType.Component, + text: `inputValue = '5' datasource = new Datasource({ get: (index, count, success) => { @@ -82,7 +83,8 @@ countItems() { return count; } ` - }]; + } + ]; itemAdapterDescription = ` ItemAdapter { $index: number; @@ -113,5 +115,4 @@ countItems() { }); return count; } - } diff --git a/demo/app/samples/experimental/inverse-setting.component.html b/demo/app/samples/experimental/inverse-setting.component.html index 1d55bbde..9a27dfbe 100644 --- a/demo/app/samples/experimental/inverse-setting.component.html +++ b/demo/app/samples/experimental/inverse-setting.component.html @@ -1,21 +1,17 @@ - - +
Available since v1.3.4, undocumented

- When there is a lack of items in the viewport, the Scroller sets - the height of the forward padding element to some value enough to fill the viewport. - This is the default behavior, and this can be changed with the help of the inverse setting. + When there is a lack of items in the viewport, the Scroller sets the + height of the forward padding element to some value enough to fill the + viewport. This is the default behavior, and this can be changed with the + help of the inverse setting.

- When inverse setting is set to true, - the backward padding element will have a priority over the forward one, - and it will be stretched if there are not enough items in the viewport. + When inverse setting is set to true, the backward + padding element will have a priority over the forward one, and it will be + stretched if there are not enough items in the viewport.

diff --git a/demo/app/samples/experimental/inverse-setting.component.ts b/demo/app/samples/experimental/inverse-setting.component.ts index 2772dfbc..4656036e 100644 --- a/demo/app/samples/experimental/inverse-setting.component.ts +++ b/demo/app/samples/experimental/inverse-setting.component.ts @@ -10,7 +10,6 @@ import { IDatasource } from 'ngx-ui-scroll'; templateUrl: './inverse-setting.component.html' }) export class DemoInverseSettingComponent { - demoContext = { config: demos.experimental.map.inverseSetting, addClass: 'inverse', @@ -37,9 +36,10 @@ export class DemoInverseSettingComponent { } }; - sources: DemoSources = [{ - name: DemoSourceType.Datasource, - text: `MIN = 1; + sources: DemoSources = [ + { + name: DemoSourceType.Datasource, + text: `MIN = 1; MAX = 5; datasource: IDatasource = { @@ -58,14 +58,15 @@ datasource: IDatasource = { inverse: true } };` - }, { - name: DemoSourceType.Styles, - text: `.viewport div[data-padding-backward] { + }, + { + name: DemoSourceType.Styles, + text: `.viewport div[data-padding-backward] { background-color: #e1f0ff; } .item { background-color: #bcdeff; }` - }]; - + } + ]; } diff --git a/demo/app/samples/experimental/onBeforeClip-setting.component.html b/demo/app/samples/experimental/onBeforeClip-setting.component.html index 8aedf241..86262bdf 100644 --- a/demo/app/samples/experimental/onBeforeClip-setting.component.html +++ b/demo/app/samples/experimental/onBeforeClip-setting.component.html @@ -1,9 +1,4 @@ - - +
Available since v1.9.0, undocumented
@@ -11,16 +6,17 @@

- onBeforeClip setting allows to run some code at the moment when - 1 or more items are to clip out of the viewport. - The code will run synchronously. Clipping cannot be cancelled. - It is like to OnDestroy Angular lifecycle hook, - but it is triggered before the elements are removed from DOM. + onBeforeClip setting allows to run some code at the moment when 1 + or more items are to clip out of the viewport. The code will run + synchronously. Clipping cannot be cancelled. It is like to + OnDestroy Angular lifecycle hook, but it is triggered before the + elements are removed from DOM.

- The log in this demo shows exactly what the onBeforeClip method sends to the console. - The argument of the callback is the array of items of ItemAdapter type: + The log in this demo shows exactly what the onBeforeClip method + sends to the console. The argument of the callback is the array of items + of ItemAdapter type:

-
{{argumentDescription}}
+
{{ argumentDescription }}
diff --git a/demo/app/samples/experimental/onBeforeClip-setting.component.ts b/demo/app/samples/experimental/onBeforeClip-setting.component.ts index 4a4933ca..c7c000f7 100644 --- a/demo/app/samples/experimental/onBeforeClip-setting.component.ts +++ b/demo/app/samples/experimental/onBeforeClip-setting.component.ts @@ -10,7 +10,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './onBeforeClip-setting.component.html' }) export class DemoOnBeforeClipSettingComponent { - demoContext = { config: demos.experimental.map.onBeforeClipSetting, viewportId: 'onBeforeClip-setting-viewport', @@ -28,18 +27,20 @@ export class DemoOnBeforeClipSettingComponent { }, settings: { bufferSize: 25, - onBeforeClip: (items) => { - const log = `${++this.demoContext.count}) clipping ${items.length} items` + + onBeforeClip: items => { + const log = + `${++this.demoContext.count}) clipping ${items.length} items` + `[${items[0].$index}..${items[items.length - 1].$index}]\n`; this.demoContext.log = log + this.demoContext.log; } } }); - sources: DemoSources = [{ - name: DemoSourceType.Datasource, - active: true, - text: `datasource = new Datasource({ + sources: DemoSources = [ + { + name: DemoSourceType.Datasource, + active: true, + text: `datasource = new Datasource({ get: (index, count, success) => { const data = []; for (let i = index; i <= index + count - 1; i++) { @@ -56,16 +57,18 @@ export class DemoOnBeforeClipSettingComponent { } } })` - }, { - name: DemoSourceType.Template, - text: ` + }, + { + name: DemoSourceType.Template, + text: `
{{item.text}}
` - }]; + } + ]; argumentDescription = ` onBeforeClip: (items: { $index: number, diff --git a/demo/app/samples/experimental/viewportElement-setting.component.html b/demo/app/samples/experimental/viewportElement-setting.component.html index aa840207..5a517670 100644 --- a/demo/app/samples/experimental/viewportElement-setting.component.html +++ b/demo/app/samples/experimental/viewportElement-setting.component.html @@ -1,35 +1,33 @@ - - +
Available since v1.8.1, undocumented

- As it follows from the doc, the viewport is a scrollable area with a finite height. - By default, the nearest ancestor of the *uiScroll container is treated as the Viewport. - With the viewportElement setting we may change the default behavior - and make another html element to be the Viewport. + As it follows from the doc, the viewport is a scrollable area with a + finite height. By default, the nearest ancestor of the + *uiScroll container is treated as the Viewport. With the + viewportElement setting we may change the default behavior and + make another html element to be the Viewport.

- The value of viewportElement setting can be an html element or - a function synchronously returning an html element. - The second option is useful if the Viewport is a part of the template of the same component - that contains the Scroller, and the Datasource is initialized before - the view is ready (before ngOnInit). + The value of viewportElement setting can be an html element or a + function synchronously returning an html element. The second option is + useful if the Viewport is a part of the template of the same component + that contains the Scroller, and the Datasource is initialized + before the view is ready (before ngOnInit).

This sample shows a declarative way and uses - ViewChild decorator and #viewport syntax - to access the 3d ancestor of the Scroller base container. - This also could be achieved via native javascript methods, for example + ViewChild decorator and #viewport syntax to access the + 3d ancestor of the Scroller base container. This also could be achieved + via native javascript methods, for example

-
{{nativeSample}}
+
{{ nativeSample }}

- Note, using this feature is potentially dangerous, because any html element - can be set as the Viewport this way. For example, the engine does not check - whether this element is an ancestor of the Scroller element or not. + Note, using this feature is potentially dangerous, because any html + element can be set as the Viewport this way. For example, the engine does + not check whether this element is an ancestor of the Scroller element or + not.

diff --git a/demo/app/samples/experimental/viewportElement-setting.component.ts b/demo/app/samples/experimental/viewportElement-setting.component.ts index c5b491d1..5b62ff24 100644 --- a/demo/app/samples/experimental/viewportElement-setting.component.ts +++ b/demo/app/samples/experimental/viewportElement-setting.component.ts @@ -8,15 +8,15 @@ import { DemoSources, DemoSourceType } from '../../shared/interfaces'; templateUrl: './viewportElement-setting.component.html' }) export class DemoViewportElementSettingComponent { - demoContext = { config: demos.experimental.map.viewportElementSetting, noWorkView: true }; - sources: DemoSources = [{ - name: DemoSourceType.Datasource, - text: `@ViewChild('viewport', { static: true }) viewportRef: ElementRef; + sources: DemoSources = [ + { + name: DemoSourceType.Datasource, + text: `@ViewChild('viewport', { static: true }) viewportRef: ElementRef; datasource: IDatasource = { get: (index, count, success) => { @@ -30,10 +30,11 @@ datasource: IDatasource = { viewportElement: () => this.viewportRef.nativeElement } }` - }, { - name: DemoSourceType.Template, - active: true, - text: `
+ }, + { + name: DemoSourceType.Template, + active: true, + text: `
@@ -42,9 +43,10 @@ datasource: IDatasource = {
` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -53,7 +55,8 @@ datasource: IDatasource = { font-weight: bold; height: 25px; }` - }]; + } + ]; nativeSample = `settings: { viewportElement: () => diff --git a/demo/app/samples/home.component.html b/demo/app/samples/home.component.html index 38c326d9..b2763ca3 100644 --- a/demo/app/samples/home.component.html +++ b/demo/app/samples/home.component.html @@ -1,10 +1,11 @@

Angular UI Scroll Demo App

- ngx-ui-scroll is the Angular library for large datasets virtualization. - More information regarding motivation, infrastructure, features, developing - can be found on GitHub. - This site is dedicated to practical examples of ngx-ui-scroll usage. + ngx-ui-scroll is the Angular library for large datasets + virtualization. More information regarding motivation, infrastructure, + features, developing can be found on + GitHub. This site is + dedicated to practical examples of ngx-ui-scroll usage.

@@ -14,82 +15,85 @@

Common demo

- {{item.text}} + {{ item.text }}
-
- ngx-ui-scroll v{{datasource.adapter.packageInfo.consumer.version}} -
- ({{datasource.adapter.packageInfo.core.name}} v{{datasource.adapter.packageInfo.core.version}}, - angular v{{angularVersion}}) -
- First visible: #{{datasource.adapter.firstVisible.$index}} -
- Last visible: #{{(datasource.adapter.lastVisible$ | async)?.$index}} -
- Items in DOM: {{datasource.adapter.itemsCount}} -
- Is loading: {{datasource.adapter.isLoading}} -
+
+ ngx-ui-scroll v{{ datasource.adapter.packageInfo.consumer.version }} +
+ ({{ datasource.adapter.packageInfo.core.name }} v{{ + datasource.adapter.packageInfo.core.version + }}, angular v{{ angularVersion }}) +
+ First visible: #{{ datasource.adapter.firstVisible.$index }} +
+ Last visible: #{{ (datasource.adapter.lastVisible$ | async)?.$index }} +
+ Items in DOM: {{ datasource.adapter.itemsCount }} +
+ Is loading: {{ datasource.adapter.isLoading }} +
-
+
Datasource delay (ms):  - +
Index to reload:  -   +  
-
- -
{{sources[0].text}}
+ +
{{ sources[0].text }}
- -
{{sources[1].text}}
+ +
{{ sources[1].text }}
- -
{{sources[2].text}}
+ +
{{ sources[2].text }}
- -
{{sources[3].text}}
+ +
{{ sources[3].text }}

- This particular demo includes simplest virtual list template (tab 1), - simplest Datasource implementation with no any settings provided (tab 2), - some of the Adapter read-only properties output (tab 3), - simplest styling (tab 4). - Many other samples with all necessary details and explanations can be found at - {{scopes.datasource.name}}, - {{scopes.settings.name}}, - {{scopes.adapter.name}} and - {{scopes.experimental.name}} + This particular demo includes simplest virtual list template (tab 1), simplest + Datasource implementation with no any settings provided (tab 2), some of the + Adapter read-only properties output (tab 3), simplest styling (tab 4). Many + other samples with all necessary details and explanations can be found at + {{ scopes.datasource.name }}, {{ scopes.settings.name }}, {{ scopes.adapter.name }} and + {{ + scopes.experimental.name + }} sections. Below is the list of all examples available in these sections.

-

- Example list -

+

Example list

-
{{scope.name}}
+
{{ scope.name }}
- - {{demo.name}} -
+ + {{ demo.name }}
diff --git a/demo/app/samples/home.component.ts b/demo/app/samples/home.component.ts index 9517265f..3c3c07f5 100644 --- a/demo/app/samples/home.component.ts +++ b/demo/app/samples/home.component.ts @@ -10,7 +10,6 @@ import { Datasource } from 'ngx-ui-scroll'; templateUrl: './home.component.html' }) export class HomeComponent { - angularVersion = VERSION.full; reloadIndex = 999; delay = 0; @@ -41,18 +40,20 @@ export class HomeComponent { } }); - sources: DemoSources = [{ - name: DemoSourceType.Template, - text: `
+ sources: DemoSources = [ + { + name: DemoSourceType.Template, + text: `
{{item.text}}
` - }, { - name: DemoSourceType.Component, - text: `delay = 25; + }, + { + name: DemoSourceType.Component, + text: `delay = 25; reloadIndex = 999; datasource = new Datasource ({ @@ -72,9 +73,10 @@ datasource = new Datasource ({ doReload() { this.datasource.adapter.reload(this.reloadIndex); }` - }, { - name: 'Adapter', - text: `Version: + }, + { + name: 'Adapter', + text: `Version: {{datasource.adapter.packageInfo.consumer.version}} Core: @@ -99,9 +101,10 @@ Datasource delay (ms): Index to reload: ` - }, { - name: DemoSourceType.Styles, - text: `.viewport { + }, + { + name: DemoSourceType.Styles, + text: `.viewport { width: 150px; height: 250px; overflow-y: auto; @@ -113,7 +116,8 @@ Index to reload: .item.even { background-color: #f2f2f2; }` - }]; + } + ]; constructor() { this.scopes = scopes; @@ -124,5 +128,4 @@ Index to reload: doReload() { this.datasource.adapter.reload(this.reloadIndex); } - } diff --git a/demo/app/samples/settings.component.html b/demo/app/samples/settings.component.html index 8efd10f8..020d7224 100644 --- a/demo/app/samples/settings.component.html +++ b/demo/app/samples/settings.component.html @@ -1,19 +1,23 @@

Angular UI Scroll Settings Demos

- Settings object is a part of Datasource implementation. - It allows to configure the Scroller during its initialization. - Each sample on this page deals with a single setting and includes: + Settings object is a part of Datasource + implementation. It allows to configure the Scroller during its + initialization. Each sample on this page deals with a single setting and + includes:

  • working viewport with scrollable area;
  • -
  • log section where we may see - current size of the viewport, - how many real items (DOM elements) the viewport has in the moment - and how many items have been retrieved via Datasource.get; +
  • + log section where we may see current size of the viewport, how + many real items (DOM elements) the viewport has in the moment and + how many items have been retrieved via Datasource.get; +
  • +
  • + code listing which contains Datasource typescript code, Template html code + and CSS;
  • -
  • code listing which contains Datasource typescript code, Template html code and CSS;
  • description and explanations.
diff --git a/demo/app/samples/settings.component.ts b/demo/app/samples/settings.component.ts index ad9993d5..14cec122 100644 --- a/demo/app/samples/settings.component.ts +++ b/demo/app/samples/settings.component.ts @@ -5,8 +5,5 @@ import { Component } from '@angular/core'; templateUrl: './settings.component.html' }) export class SettingsComponent { - - constructor() { - } - + constructor() {} } diff --git a/demo/app/samples/test.component.html b/demo/app/samples/test.component.html index 29dc3bd2..87f77870 100644 --- a/demo/app/samples/test.component.html +++ b/demo/app/samples/test.component.html @@ -1,61 +1,70 @@ -
- - - datasource delay -
- - - reload index -
- -
-
- - - -
- - - -
- - -
- ngx-ui-scroll version: {{datasource.adapter.packageInfo.consumer.version}} -
- isLoading: - {{datasource.adapter.isLoading}} - ({{(datasource.adapter.isLoading$ | async)}}) - -
- first visible: - {{datasource.adapter.firstVisible.$index}} - ({{(datasource.adapter.firstVisible$ | async)?.$index}}) - -
- last visible: - {{datasource.adapter.lastVisible.$index}} - ({{(datasource.adapter.lastVisible$ | async)?.$index}}) - -
- total: - {{datasource.adapter.itemsCount}}, - visible: - {{getVisibleItemsCount()}} - -
-

-
-
-
-
- - {{item.text}} - - {{item.isSelected ? '********' : ''}} - - -
-
-
\ No newline at end of file +
+ - + datasource delay +
+ - + reload index +
+ +
+
+ + + +
+ + + +
+ + +
+ ngx-ui-scroll version: + {{ datasource.adapter.packageInfo.consumer.version }} +
+ isLoading: + {{ datasource.adapter.isLoading }} + ({{ datasource.adapter.isLoading$ | async }}) + +
+ first visible: + {{ datasource.adapter.firstVisible.$index }} + ({{ (datasource.adapter.firstVisible$ | async)?.$index }}) + +
+ last visible: + {{ datasource.adapter.lastVisible.$index }} + ({{ (datasource.adapter.lastVisible$ | async)?.$index }}) + +
+ total: {{ datasource.adapter.itemsCount }}, visible: + {{ getVisibleItemsCount() }} + +
+

+
+
+
+
+ + {{ item.text }} + + {{ item.isSelected ? '********' : '' }} + + +
+
+
diff --git a/demo/app/samples/test.component.ts b/demo/app/samples/test.component.ts index bcac58d1..159360fb 100644 --- a/demo/app/samples/test.component.ts +++ b/demo/app/samples/test.component.ts @@ -1,225 +1,230 @@ -import { Component, ViewChild, ElementRef } from '@angular/core'; -import { Observable, Observer } from 'rxjs'; - -import { Datasource } from 'ngx-ui-scroll'; - -const MAX = 50; -let MIN = -199; -const MIN_ROW_HEIGHT = 5; - -interface MyItem { - id: number; - text: string; - size: number; - isSelected: boolean; - data?: string; - color?: string; -} - -@Component({ - selector: 'app-samples-test-inner', - template: '' -}) -export class TestInnerComponent { - constructor() { - } -} - -@Component({ - selector: 'app-samples-test', - templateUrl: './test.component.html' -}) -export class TestComponent { - - @ViewChild('viewport', { static: true }) viewportRef!: ElementRef; - - reloadIndex = 1; - sizeIndex = 1; - sizeValue = 10; - datasourceDelay = 0; - data!: MyItem[]; - - datasource = new Datasource({ - get: (index: number, count: number) => - this.fetchData(index, count) - , - settings: { - padding: 0.1, - bufferSize: 10, - // minIndex: MIN, - // maxIndex: MAX, - itemSize: 100, - startIndex: 1, - // onBeforeClip: (item) => item, - viewportElement: () => this.viewportRef.nativeElement - // viewportElement: () => document.getElementById('my-viewport') - }, - devSettings: { - debug: true, - immediateLog: false, - logTime: false, - logProcessRun: true, - throttle: 40, - } - }); - - constructor() { - this.generateData(); - // this.autoscroll(); - } - - generateData() { - this.data = []; - for (let i = 0; i <= MAX - MIN; i++) { - const item: MyItem = { - id: i + MIN, - text: 'item #' + (i + MIN), - isSelected: i % 15 === 0, - size: Math.max(MIN_ROW_HEIGHT, 20 + i + MIN) // 100 - }; - if (item.isSelected) { - item.data = Array.from({ length: Math.random() * (10 - 3) + 3 }, () => '*').join(''); - item.color = i % 30 === 0 ? 'red' : 'black'; - } - this.data.push(item); - } - } - - fetchData(index: number, count: number): Observable { - const data: MyItem[] = []; - const start = Math.max(MIN, index); - const end = Math.min(MAX, index + count - 1); - if (start <= end) { - for (let i = start; i <= end; i++) { - data.push(this.data[i - MIN]); - // if (i > 0) { - // this.data[i - MIN].size = 25; - // } - } - if (start === MIN) { - // this.datasource.adapter.setMinIndex(MIN); - } - } - return new Observable((observer: Observer) => { - if (!this.datasourceDelay) { - observer.next(data); - } else { - setTimeout(() => observer.next(data), this.datasourceDelay); - } - }); - } - - getVisibleItemsCount(): number { - const adapter = this.datasource.adapter; - let last = adapter.lastVisible.$index; - last = Number.isInteger(last) ? last : NaN; - let first = adapter.firstVisible.$index; - first = Number.isInteger(first) ? first : NaN; - return (Number.isNaN(last) || Number.isNaN(first)) ? 0 : last - first + 1; - } - - getViewportElement(): Element { - return document.getElementsByClassName('viewport')[0]; - } - - doScroll(limit: number, delay: number, delta: number) { - const viewportElement = this.getViewportElement(); - setTimeout(() => { - viewportElement.scrollTop -= delta; - if (--limit > 0) { - this.doScroll(limit, delay, delta); - } - }, delay); - } - - show = true; - doHideShow() { - this.show = !this.show; - } - - doReload() { - this.datasource.adapter.reload(this.reloadIndex); - } - - doPrepend() { - MIN--; - const item: MyItem = { - id: MIN, - text: 'item #' + MIN, - isSelected: false, - size: 25 - }; - this.data.unshift(item); - this.datasource.adapter.prepend(item, true); - } - - doScrollHome() { - this.getViewportElement().scrollTop = 0; - } - - doScrollEnd() { - this.getViewportElement().scrollTop = 999999; - } - - doScrollSome() { - const current = this.getViewportElement().scrollTop; - // this.doScroll(400, 25, 1); - this.getViewportElement().scrollTop = current + 300; - // this.datasource.adapter.setScrollPosition(current + 300); - } - - doChangeSize() { - const viewportElement = document.getElementById('my-viewport'); - const index = Number(this.sizeIndex); - if (!isNaN(index) && viewportElement) { - for (let i = index; i < index + 5; i++) { - const element = viewportElement.querySelector(`[data-sid="${i}"]`); - if (element) { - (element as HTMLElement).style.height = this.sizeValue + 'px'; - const item = this.data.find(_item => _item.id === i); - if (item) { - item.size = this.sizeValue; - } - } - } - this.datasource.adapter.check(); - } - } - - doLog() { - this.datasource.adapter.showLog(); - } - - doToggleItem(item: MyItem) { - this.datasource.adapter.fix({ - updater: ({ data }) => { - if (item.id === data.id) { - data.isSelected = !data.isSelected; - } - } - }); - // item.isSelected = !item.isSelected; - } - - autoscroll() { - const { adapter } = this.datasource; - const isLoadingSubscription = adapter.isLoading$.subscribe(isLoading => { - const viewportElement = document.getElementById('my-viewport'); - const lastVisible = adapter.lastVisible.element; - if (!isLoading && viewportElement && lastVisible && lastVisible.getBoundingClientRect) { - const lastElementBottom = lastVisible.getBoundingClientRect().bottom; - const viewportBottom = viewportElement.getBoundingClientRect().bottom; - const toScroll = viewportBottom - lastElementBottom; - const diff = viewportElement.scrollTop - toScroll; - if (viewportElement.scrollTop === diff) { - isLoadingSubscription.unsubscribe(); - } else { - adapter.fix({ scrollPosition: diff }); - if (viewportElement.scrollTop === diff) { - isLoadingSubscription.unsubscribe(); - } - } - } - }); - } -} +import { Component, ViewChild, ElementRef } from '@angular/core'; +import { Observable, Observer } from 'rxjs'; + +import { Datasource } from 'ngx-ui-scroll'; + +const MAX = 50; +let MIN = -199; +const MIN_ROW_HEIGHT = 5; + +interface MyItem { + id: number; + text: string; + size: number; + isSelected: boolean; + data?: string; + color?: string; +} + +@Component({ + selector: 'app-samples-test-inner', + template: '' +}) +export class TestInnerComponent { + constructor() {} +} + +@Component({ + selector: 'app-samples-test', + templateUrl: './test.component.html' +}) +export class TestComponent { + @ViewChild('viewport', { static: true }) + viewportRef!: ElementRef; + + reloadIndex = 1; + sizeIndex = 1; + sizeValue = 10; + datasourceDelay = 0; + data!: MyItem[]; + + datasource = new Datasource({ + get: (index: number, count: number) => this.fetchData(index, count), + settings: { + padding: 0.1, + bufferSize: 10, + // minIndex: MIN, + // maxIndex: MAX, + itemSize: 100, + startIndex: 1, + // onBeforeClip: (item) => item, + viewportElement: () => this.viewportRef.nativeElement + // viewportElement: () => document.getElementById('my-viewport') + }, + devSettings: { + debug: true, + immediateLog: false, + logTime: false, + logProcessRun: true, + throttle: 40 + } + }); + + constructor() { + this.generateData(); + // this.autoscroll(); + } + + generateData() { + this.data = []; + for (let i = 0; i <= MAX - MIN; i++) { + const item: MyItem = { + id: i + MIN, + text: 'item #' + (i + MIN), + isSelected: i % 15 === 0, + size: Math.max(MIN_ROW_HEIGHT, 20 + i + MIN) // 100 + }; + if (item.isSelected) { + item.data = Array.from( + { length: Math.random() * (10 - 3) + 3 }, + () => '*' + ).join(''); + item.color = i % 30 === 0 ? 'red' : 'black'; + } + this.data.push(item); + } + } + + fetchData(index: number, count: number): Observable { + const data: MyItem[] = []; + const start = Math.max(MIN, index); + const end = Math.min(MAX, index + count - 1); + if (start <= end) { + for (let i = start; i <= end; i++) { + data.push(this.data[i - MIN]); + // if (i > 0) { + // this.data[i - MIN].size = 25; + // } + } + if (start === MIN) { + // this.datasource.adapter.setMinIndex(MIN); + } + } + return new Observable((observer: Observer) => { + if (!this.datasourceDelay) { + observer.next(data); + } else { + setTimeout(() => observer.next(data), this.datasourceDelay); + } + }); + } + + getVisibleItemsCount(): number { + const adapter = this.datasource.adapter; + let last = adapter.lastVisible.$index; + last = Number.isInteger(last) ? last : NaN; + let first = adapter.firstVisible.$index; + first = Number.isInteger(first) ? first : NaN; + return Number.isNaN(last) || Number.isNaN(first) ? 0 : last - first + 1; + } + + getViewportElement(): Element { + return document.getElementsByClassName('viewport')[0]; + } + + doScroll(limit: number, delay: number, delta: number) { + const viewportElement = this.getViewportElement(); + setTimeout(() => { + viewportElement.scrollTop -= delta; + if (--limit > 0) { + this.doScroll(limit, delay, delta); + } + }, delay); + } + + show = true; + doHideShow() { + this.show = !this.show; + } + + doReload() { + this.datasource.adapter.reload(this.reloadIndex); + } + + doPrepend() { + MIN--; + const item: MyItem = { + id: MIN, + text: 'item #' + MIN, + isSelected: false, + size: 25 + }; + this.data.unshift(item); + this.datasource.adapter.prepend(item, true); + } + + doScrollHome() { + this.getViewportElement().scrollTop = 0; + } + + doScrollEnd() { + this.getViewportElement().scrollTop = 999999; + } + + doScrollSome() { + const current = this.getViewportElement().scrollTop; + // this.doScroll(400, 25, 1); + this.getViewportElement().scrollTop = current + 300; + // this.datasource.adapter.setScrollPosition(current + 300); + } + + doChangeSize() { + const viewportElement = document.getElementById('my-viewport'); + const index = Number(this.sizeIndex); + if (!isNaN(index) && viewportElement) { + for (let i = index; i < index + 5; i++) { + const element = viewportElement.querySelector(`[data-sid="${i}"]`); + if (element) { + (element as HTMLElement).style.height = this.sizeValue + 'px'; + const item = this.data.find(_item => _item.id === i); + if (item) { + item.size = this.sizeValue; + } + } + } + this.datasource.adapter.check(); + } + } + + doLog() { + this.datasource.adapter.showLog(); + } + + doToggleItem(item: MyItem) { + this.datasource.adapter.fix({ + updater: ({ data }) => { + if (item.id === data.id) { + data.isSelected = !data.isSelected; + } + } + }); + // item.isSelected = !item.isSelected; + } + + autoscroll() { + const { adapter } = this.datasource; + const isLoadingSubscription = adapter.isLoading$.subscribe(isLoading => { + const viewportElement = document.getElementById('my-viewport'); + const lastVisible = adapter.lastVisible.element; + if ( + !isLoading && + viewportElement && + lastVisible && + lastVisible.getBoundingClientRect + ) { + const lastElementBottom = lastVisible.getBoundingClientRect().bottom; + const viewportBottom = viewportElement.getBoundingClientRect().bottom; + const toScroll = viewportBottom - lastElementBottom; + const diff = viewportElement.scrollTop - toScroll; + if (viewportElement.scrollTop === diff) { + isLoadingSubscription.unsubscribe(); + } else { + adapter.fix({ scrollPosition: diff }); + if (viewportElement.scrollTop === diff) { + isLoadingSubscription.unsubscribe(); + } + } + } + }); + } +} diff --git a/demo/app/samples/window.component.html b/demo/app/samples/window.component.html index e825abc9..0ab98b73 100644 --- a/demo/app/samples/window.component.html +++ b/demo/app/samples/window.component.html @@ -4,14 +4,14 @@

Entire window scrolling demo

- Top visible: {{(datasource.adapter.firstVisible$ | async)?.$index}} + Top visible: {{ (datasource.adapter.firstVisible$ | async)?.$index }}
-
- {{item.text}} +
+ {{ item.text }} - {{item.data}} + {{ item.data }}
diff --git a/demo/app/samples/window.component.ts b/demo/app/samples/window.component.ts index c5cbaaf5..8dc46815 100644 --- a/demo/app/samples/window.component.ts +++ b/demo/app/samples/window.component.ts @@ -18,12 +18,11 @@ interface MyItem { templateUrl: './window.component.html' }) export class WindowComponent { - init: boolean; constructor() { this.init = false; - setTimeout(() => this.init = true); + setTimeout(() => (this.init = true)); } datasource = new Datasource({ @@ -35,12 +34,15 @@ export class WindowComponent { for (let i = start; i <= end; i++) { const item = { data: '', - text: 'item #' + (i), + text: 'item #' + i, color: 'black', size: 20 // Math.max(MIN_ROW_HEIGHT, 20 + i + MIN) }; if (i % 15 === 0) { - item.data = Array.from({ length: Math.random() * (10 - 3) + 3 }, () => '*').join(''); + item.data = Array.from( + { length: Math.random() * (10 - 3) + 3 }, + () => '*' + ).join(''); item.color = i % 30 === 0 ? 'red' : 'black'; } data.push(item); @@ -62,5 +64,4 @@ export class WindowComponent { windowViewport: true } }); - } diff --git a/demo/app/shared/datasource-get.ts b/demo/app/shared/datasource-get.ts index e41dfdd0..cd17b9c8 100644 --- a/demo/app/shared/datasource-get.ts +++ b/demo/app/shared/datasource-get.ts @@ -1,65 +1,100 @@ -import { Observable, Observer } from 'rxjs'; - -import { IDatasource } from '../../../scroller/src/ui-scroll.datasource'; -import { DemoContext, MyItem } from './interfaces'; - -const delayedCall = (result: MyItem[], callback: (items: MyItem[]) => void, delay?: number) => { - if (isNaN(Number(delay))) { - callback(result); - } else { - setTimeout(() => callback(result), Number(delay)); - } -}; - -export const doLog = (demoContext: DemoContext, index: number, count: number, resolved: number): void => { - demoContext.count = demoContext.count || 0; - demoContext.log = - `${++demoContext.count}) got ${resolved} items [${index}..${index + count - 1}]\n` - + demoContext.log; -}; - -export const datasourceGetInfinite = - (demoContext: DemoContext, index: number, count: number): MyItem[] => { - const data: MyItem[] = []; - for (let i = index; i <= index + count - 1; i++) { - data.push({ id: i, text: 'item #' + i }); - } - doLog(demoContext, index, count, data.length); - return data; - }; - -export const datasourceGetLimited = - (demoContext: DemoContext, min: number, max: number, index: number, count: number): MyItem[] => { - min = isNaN(Number(min)) ? -Infinity : Number(min); - max = isNaN(Number(max)) ? Infinity : Number(max); - const data: MyItem[] = []; - const start = Math.max(min, index); - const end = Math.min(index + count - 1, max); - if (start <= end) { - for (let i = start; i <= end; i++) { - data.push({ id: i, text: 'item #' + i, size: 20 + i }); - } - } - doLog(demoContext, index, count, data.length); - return data; - }; - -export const datasourceGetObservableInfinite = (demoContext: DemoContext) => - (index: number, count: number): Observable => new Observable((observer: Observer) => - observer.next(datasourceGetInfinite(demoContext, index, count)) - ); - -export const datasourceGetPromiseInfinite = (demoContext: DemoContext) => - (index: number, count: number): Promise => new Promise(success => - success(datasourceGetInfinite(demoContext, index, count)) - ); - -export const datasourceGetCallbackInfinite = ( - demoContext: DemoContext, delay?: number -): IDatasource['get'] => (index, count, success) => - delayedCall(datasourceGetInfinite(demoContext, index, count), success, delay); - -export const datasourceGetCallbackLimited = ( - demoContext: DemoContext, min: number, max: number, delay?: number -): IDatasource['get'] => (index, count, success) => - delayedCall(datasourceGetLimited(demoContext, min, max, index, count), success, delay); +import { Observable, Observer } from 'rxjs'; + +import { IDatasource } from '../../../scroller/src/ui-scroll.datasource'; +import { DemoContext, MyItem } from './interfaces'; + +const delayedCall = ( + result: MyItem[], + callback: (items: MyItem[]) => void, + delay?: number +) => { + if (isNaN(Number(delay))) { + callback(result); + } else { + setTimeout(() => callback(result), Number(delay)); + } +}; + +export const doLog = ( + demoContext: DemoContext, + index: number, + count: number, + resolved: number +): void => { + demoContext.count = demoContext.count || 0; + demoContext.log = + `${++demoContext.count}) got ${resolved} items [${index}..${ + index + count - 1 + }]\n` + demoContext.log; +}; + +export const datasourceGetInfinite = ( + demoContext: DemoContext, + index: number, + count: number +): MyItem[] => { + const data: MyItem[] = []; + for (let i = index; i <= index + count - 1; i++) { + data.push({ id: i, text: 'item #' + i }); + } + doLog(demoContext, index, count, data.length); + return data; +}; + +export const datasourceGetLimited = ( + demoContext: DemoContext, + min: number, + max: number, + index: number, + count: number +): MyItem[] => { + min = isNaN(Number(min)) ? -Infinity : Number(min); + max = isNaN(Number(max)) ? Infinity : Number(max); + const data: MyItem[] = []; + const start = Math.max(min, index); + const end = Math.min(index + count - 1, max); + if (start <= end) { + for (let i = start; i <= end; i++) { + data.push({ id: i, text: 'item #' + i, size: 20 + i }); + } + } + doLog(demoContext, index, count, data.length); + return data; +}; + +export const datasourceGetObservableInfinite = + (demoContext: DemoContext) => + (index: number, count: number): Observable => + new Observable((observer: Observer) => + observer.next(datasourceGetInfinite(demoContext, index, count)) + ); + +export const datasourceGetPromiseInfinite = + (demoContext: DemoContext) => + (index: number, count: number): Promise => + new Promise(success => + success(datasourceGetInfinite(demoContext, index, count)) + ); + +export const datasourceGetCallbackInfinite = + (demoContext: DemoContext, delay?: number): IDatasource['get'] => + (index, count, success) => + delayedCall( + datasourceGetInfinite(demoContext, index, count), + success, + delay + ); + +export const datasourceGetCallbackLimited = + ( + demoContext: DemoContext, + min: number, + max: number, + delay?: number + ): IDatasource['get'] => + (index, count, success) => + delayedCall( + datasourceGetLimited(demoContext, min, max, index, count), + success, + delay + ); diff --git a/demo/app/shared/demo.component.html b/demo/app/shared/demo.component.html index 3324d341..2c63ecb5 100644 --- a/demo/app/shared/demo.component.html +++ b/demo/app/shared/demo.component.html @@ -1,65 +1,67 @@ -{{item.text}} - -
- - - -
- -
- -
-
-
-
-
- -
-
-
-
-
- - - -
- Viewport scrollable size: - {{viewport(context.viewportId!)}} -
-
- DOM elements count: - {{elements(context.viewportId!)}} -
-
- Datasource.get log -
-
{{context.log}}
-
-
-
- -
- Datasource.get log -
-
{{context.log}}
-
-
-
-
-
-
- - - -
- -
- -
+{{ item.text }} + +
+ + +
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+ + + +
+ Viewport scrollable size: + {{ viewport(context.viewportId!) }} +
+
+ DOM elements count: + {{ elements(context.viewportId!) }} +
+
+ Datasource.get log +
+
{{ context.log }}
+
+
+
+ +
+ Datasource.get log +
+
{{ context.log }}
+
+
+
+
+
+
+ + + +
+ +
+
diff --git a/demo/app/shared/demo.component.ts b/demo/app/shared/demo.component.ts index 8386e425..62e376f4 100644 --- a/demo/app/shared/demo.component.ts +++ b/demo/app/shared/demo.component.ts @@ -1,47 +1,47 @@ -import { Component, Input, OnInit, TemplateRef } from '@angular/core'; - -import { DemoContext, DemoSources } from './interfaces'; - -@Component({ - selector: 'app-demo', - templateUrl: './demo.component.html' -}) -export class DemoComponent implements OnInit { - - init = false; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - @Input() datasource: any; - @Input() context!: DemoContext; - @Input() sources!: DemoSources; - @Input() itemTemplate!: TemplateRef; - - viewport(token: string): string { - const element = document.getElementById(token); - if (!element) { - return ''; - } - const sizeToken = this.datasource.settings && this.datasource.settings.horizontal - ? 'scrollWidth' : 'scrollHeight'; - return element[sizeToken].toString(); - } - - elements(token: string): string { - const element = document.getElementById(token); - if (!element) { - return ''; - } - const count = element.children[0].childElementCount || 0; - return (count - 2).toString(10); - } - - ngOnInit() { - setTimeout(() => { - if (this.sources.every(s => !s.active)) { - this.sources[0].active = true; - } - this.init = true; - }); - } - -} +import { Component, Input, OnInit, TemplateRef } from '@angular/core'; + +import { DemoContext, DemoSources } from './interfaces'; + +@Component({ + selector: 'app-demo', + templateUrl: './demo.component.html' +}) +export class DemoComponent implements OnInit { + init = false; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + @Input() datasource: any; + @Input() context!: DemoContext; + @Input() sources!: DemoSources; + @Input() itemTemplate!: TemplateRef; + + viewport(token: string): string { + const element = document.getElementById(token); + if (!element) { + return ''; + } + const sizeToken = + this.datasource.settings && this.datasource.settings.horizontal + ? 'scrollWidth' + : 'scrollHeight'; + return element[sizeToken].toString(); + } + + elements(token: string): string { + const element = document.getElementById(token); + if (!element) { + return ''; + } + const count = element.children[0].childElementCount || 0; + return (count - 2).toString(10); + } + + ngOnInit() { + setTimeout(() => { + if (this.sources.every(s => !s.active)) { + this.sources[0].active = true; + } + this.init = true; + }); + } +} diff --git a/demo/app/shared/demo/demo-sources.component.html b/demo/app/shared/demo/demo-sources.component.html index 7f6475ed..5be1c235 100644 --- a/demo/app/shared/demo/demo-sources.component.html +++ b/demo/app/shared/demo/demo-sources.component.html @@ -1,9 +1,14 @@
- -
{{sources[index].text}}
+ +
{{ sources[index].text }}
-
{{sources[0].text}}
+
{{
+    sources[0].text
+  }}
diff --git a/demo/app/shared/demo/demo-title.component.html b/demo/app/shared/demo/demo-title.component.html index 29674c3a..3d97c570 100644 --- a/demo/app/shared/demo/demo-title.component.html +++ b/demo/app/shared/demo/demo-title.component.html @@ -1,7 +1,4 @@ -

- {{config.name}} - # +

+ {{ config.name }} + #

diff --git a/demo/app/shared/interfaces.ts b/demo/app/shared/interfaces.ts index 26b086fa..c32ba138 100644 --- a/demo/app/shared/interfaces.ts +++ b/demo/app/shared/interfaces.ts @@ -1,37 +1,37 @@ -import { IDemo } from '../routes'; - -export enum DemoSourceType { - Component = 'Component', - Template = 'Template', - Styles = 'CSS', - Datasource = 'Datasource', - Server = 'Server' -} - -interface DemoSource { - name: DemoSourceType | string; - text: string; - active?: boolean; -} - -export type DemoSources = DemoSource[]; - -export interface DemoContext { - // static data - config: IDemo; - viewportId?: string; - addClass?: string; - noWorkView?: boolean; - logViewOnly?: boolean; - noInfo?: boolean; - - // dynamic data - count?: number; - log?: string; -} - -export interface MyItem { - id: number | string; - text: string; - size?: number; -} +import { IDemo } from '../routes'; + +export enum DemoSourceType { + Component = 'Component', + Template = 'Template', + Styles = 'CSS', + Datasource = 'Datasource', + Server = 'Server' +} + +interface DemoSource { + name: DemoSourceType | string; + text: string; + active?: boolean; +} + +export type DemoSources = DemoSource[]; + +export interface DemoContext { + // static data + config: IDemo; + viewportId?: string; + addClass?: string; + noWorkView?: boolean; + logViewOnly?: boolean; + noInfo?: boolean; + + // dynamic data + count?: number; + log?: string; +} + +export interface MyItem { + id: number | string; + text: string; + size?: number; +} diff --git a/demo/app/shared/nav.component.html b/demo/app/shared/nav.component.html index fb106af7..9a6edbad 100644 --- a/demo/app/shared/nav.component.html +++ b/demo/app/shared/nav.component.html @@ -1,55 +1,107 @@ -