Skip to content

Table layout #146

New issue

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

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

Already on GitHub? Sign in to your account

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions demo/app/samples/test.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,25 @@
<br><br>
</div>

<div class="viewport" style="height: 600px" id="my-viewport">
<div
*uiScroll="let item of datasource"
(click)="doToggleItem(item)"
[style.height]="item.height + 'px'"
>
<app-samples-test-inner>
{{item.text}}
<span [style.color]="item.color">
{{item.isSelected ? '********' : ''}}
</span>
</app-samples-test-inner>
</div>
<div class="viewport tableFixHead" style="height: 600px;" id="my-viewport">
<table>
<thead><th>data</th></thead>
<tbody>
<tr class="temp"
*uiScroll="let item of datasource; table:true"
(click)="doToggleItem(item)"
>
<td>
<div [style.height]="item.height + 'px'">
<app-samples-test-inner>
{{item.text}}
<span [style.color]="item.color">
{{item.isSelected ? '********' : ''}}
</span>
</app-samples-test-inner>
</div>
</td>
</tr>
</tbody>
</table>
</div>
17 changes: 17 additions & 0 deletions demo/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,20 @@ li.L1,li.L3,li.L5,li.L7,li.L9 { }
pre .typ, code .typ { font-weight: bold }
pre .tag, code .tag { font-weight: bold; color: #204a87; }
}

table tr.temp {
color: red;
position: fixed;
left: -99999px;
}

table, caption, tbody, tfoot, thead, tr, th, td {
padding: 0;
}

.tableFixHead { overflow-y: auto; height: 100px; }
.tableFixHead thead th { position: sticky; top: 0; }

/* Just common table stuff. Really. */
table { border-collapse: collapse; }
th { background:#eee; }
10 changes: 8 additions & 2 deletions src/component/classes/viewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,15 @@ export class Viewport {
this.scrollEventElement = <Document>(this.element.ownerDocument);
this.scrollable = <HTMLElement>this.scrollEventElement.scrollingElement;
} else {
this.host = <HTMLElement>this.element.parentElement;
this.host = <HTMLElement>this.element.parentElement; // .parentElement.parentElement;
if (this.host) {
this.host = <HTMLElement>this.host.parentElement;
}
if (this.host) {
this.host = <HTMLElement>this.host.parentElement;
}
this.scrollEventElement = this.host;
this.scrollable = <HTMLElement>this.element.parentElement;
this.scrollable = this.host;
}

this.paddings = new Paddings(this.element, this.routines, settings);
Expand Down
8 changes: 7 additions & 1 deletion src/component/processes/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ export default class Render {
scroller.logger.stat('before new items render');
scroller.innerLoopSubscriptions.push(
scroller.bindData().subscribe(() => {
const elts = scroller.viewport.element.querySelectorAll(`tr:not([data-sid])`);
if (elts && elts.length && elts.length === scroller.state.fetch.items.length) {
elts.forEach((elt, index) => {
(<any>elt).dataset['sid'] = scroller.state.fetch.items[index].nodeId;
});
if (Render.processElements(scroller)) {
scroller.workflow.call({
process: Process.render,
status: ProcessStatus.next,
payload: { noClip: scroller.state.clip.noClip }
});
} else {
} } else {
scroller.workflow.call({
process: Process.render,
status: ProcessStatus.error,
Expand Down Expand Up @@ -55,6 +60,7 @@ export default class Render {
item.element = <HTMLElement>element;
item.element.style.left = '';
item.element.style.position = '';
item.element.classList.remove('temp');
item.invisible = false;
item.setSize();
buffer.cache.add(item);
Expand Down
79 changes: 58 additions & 21 deletions src/ui-scroll.component.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,76 @@
import {
Component, OnInit, OnDestroy,
TemplateRef, ElementRef,
ChangeDetectionStrategy, ChangeDetectorRef
ChangeDetectionStrategy, ChangeDetectorRef,
ViewChild
} from '@angular/core';

import { Workflow } from './component/workflow';
import { Datasource as IDatasource } from './component/interfaces/index';
import { Datasource } from './component/classes/datasource';
import { Item } from './component/classes/item';

const tableTemplate = `
<ng-container *ngIf="isTable">
<tr data-sid="backward">
<td><div data-padding-backward></div></td>
</tr>
<ng-template
*ngFor="let item of items"
[ngTemplateOutlet]="template"
[ngTemplateOutletContext]="{
$implicit: item.data,
index: item.$index,
odd: item.$index % 2,
even: !(item.$index % 2)
}"></ng-template>
<tr data-sid="forward">
<td><div data-padding-forward></div></td>
</tr>
</ng-container>
`;

const commonTemplate = `
<ng-container *ngIf="!isTable">
<div data-padding-backward></div>
<div
*ngFor="let item of items"
[attr.data-sid]="item.nodeId"
[style.position]="item.invisible ? 'fixed' : null"
[style.left]="item.invisible ? '-99999px' : null">
<ng-template
[ngTemplateOutlet]="template"
[ngTemplateOutletContext]="{
$implicit: item.data,
index: item.$index,
odd: item.$index % 2,
even: !(item.$index % 2)
}"></ng-template>
</div>
<div data-padding-forward></div>
</ng-container>
`;

/* tslint:disable:component-selector */
@Component({
selector: '[ui-scroll]',
selector: 'tbody [ui-scroll]',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div data-padding-backward></div>
<div
*ngFor="let item of items"
[attr.data-sid]="item.nodeId"
[style.position]="item.invisible ? 'fixed' : null"
[style.left]="item.invisible ? '-99999px' : null">
<ng-template
[ngTemplateOutlet]="template"
[ngTemplateOutletContext]="{
$implicit: item.data,
index: item.$index,
odd: item.$index % 2,
even: !(item.$index % 2)
}"></ng-template>
</div>
<div data-padding-forward></div>`
template: `<ng-template #uiScrollTemplateRef>
${commonTemplate}
${tableTemplate}
</ng-template>`
})
export class UiScrollComponent implements OnInit, OnDestroy {

@ViewChild('uiScrollTemplateRef', { static: true })
public uiScrollTemplateRef: TemplateRef<any>;

// come from the directive
public version: string;
public template: TemplateRef<any>;
public datasource: IDatasource | Datasource;
public isTable: boolean;
public parentElement: HTMLElement;

// use in the template
public items: Item[] = [];
Expand All @@ -46,11 +80,14 @@ export class UiScrollComponent implements OnInit, OnDestroy {

constructor(
public changeDetector: ChangeDetectorRef,
public elementRef: ElementRef) { }
public elementRef: ElementRef
) {
setTimeout(() => this.ngOnInit()); // 😢
}
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@markdBC To say I don't like this would be a considerable understatement... ngOnInit must be invoked implicitly, and I have no idea why it became broken. It has something to do with new approach of creating the component on the directive level. The demo we developed on stackblitz has the same issue.

It would be very nice if you could take a look and help with it. For testing in ngx-ui-scroll environment you may use http://localhost:4200/#/test (after npm start), which runs demo/app/samples/test.component.html template. There are multiple issues behind this one, so to see the result is still impossible, but the initialization could be handeled in a proper way

Copy link

@markdBC markdBC Feb 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dhilt The problem is that ngOnInit is called after the first time ngOnChanges is called (https://angular.io/guide/lifecycle-hooks#lifecycle-sequence).

However, while ViewContainerRef.createComponent causes an invocation of ngOnChanges, the same is not true for ComponentRef.create.

You can find the definition of ViewContainerRef.createComponent, at packages/core/src/view/refs.ts:180 of the Angular source code:

  createComponent<C>(
      componentFactory: ComponentFactory<C>, index?: number, injector?: Injector,
      projectableNodes?: any[][], ngModuleRef?: NgModuleRef<any>): ComponentRef<C> {
    const contextInjector = injector || this.parentInjector;
    if (!ngModuleRef && !(componentFactory instanceof ComponentFactoryBoundToModule)) {
      ngModuleRef = contextInjector.get(NgModuleRef);
    }
    const componentRef =
        componentFactory.create(contextInjector, projectableNodes, undefined, ngModuleRef);
    this.insert(componentRef.hostView, index);
    return componentRef;
  }

ViewContainerRef.createComponent calls the ComponentRef.create method, and then calls ViewContainerRef.insert, which among other things causes change detection to occur inside the component.

A solution is to explicitly invoke change detection for the component, via ComponentRef.changeDetectorRef.detectChanges(). This is probably only slightly better than explicitly invoking ngOnInit, but at least we're not calling a lifecycle hook directly.

I've made a PR with this change at #147 and have a StackBlitz demo at https://stackblitz.com/edit/table-implicit-lifecycle-hook-invocation.


ngOnInit() {
this.workflow = new Workflow(
this.elementRef.nativeElement,
this.parentElement, // this.elementRef.nativeElement,
this.datasource,
this.version,
(items: Item[]) => {
Expand Down
12 changes: 9 additions & 3 deletions src/ui-scroll.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Datasource } from './component/interfaces/datasource';
export class UiScrollDirective implements OnInit {
private version: string;
private datasource: Datasource;
private isTable: boolean;

constructor(
private templateRef: TemplateRef<any>,
Expand All @@ -20,13 +21,18 @@ export class UiScrollDirective implements OnInit {
this.datasource = datasource;
}

@Input() set uiScrollTable(value: any) {
this.isTable = !!value;
}

ngOnInit() {
const compFactory = this.resolver.resolveComponentFactory(UiScrollComponent);
const componentRef = this.viewContainer.createComponent(
compFactory, undefined, this.viewContainer.injector
);
const componentRef = compFactory.create(this.viewContainer.injector);
componentRef.instance.datasource = this.datasource;
componentRef.instance.template = this.templateRef;
componentRef.instance.isTable = this.isTable;
componentRef.instance.version = version;
componentRef.instance.parentElement = this.templateRef.elementRef.nativeElement.parentElement;
this.viewContainer.createEmbeddedView(componentRef.instance.uiScrollTemplateRef);
}
}