Permalink
Browse files

feat(template-strategy): adjust tbody template strategy

  • Loading branch information...
bigopon committed Jan 13, 2019
1 parent 8271abe commit c1fe513cc62dbd1261d37e8f3b4a16d281c44324
Showing with 171 additions and 37 deletions.
  1. +8 −3 karma.conf.js
  2. +12 −1 src/interfaces.ts
  3. +95 −17 src/template-strategy.ts
  4. +10 −2 src/virtual-repeat.ts
  5. +4 −1 test/utilities.ts
  6. +42 −12 test/virtual-repeat-integration.table.spec.ts
  7. +0 −1 tsconfig.json
@@ -7,9 +7,9 @@ module.exports = function(config) {

basePath: '',
frameworks: ["jasmine"],
files: ["test/*.spec.ts"],
files: ["test/**/*.spec.ts"],
preprocessors: {
"test/*.spec.ts": ["webpack", "sourcemap"]
"test/**/*.spec.ts": ["webpack", "sourcemap"]
},
webpack: {
mode: "development",
@@ -27,7 +27,12 @@ module.exports = function(config) {
{
test: /\.ts$/,
loader: "ts-loader",
exclude: /node_modules/
exclude: /node_modules/,
options: {
compilerOptions: {
sourceMap: true
}
}
},
{
test: /\.html$/i,
@@ -1,7 +1,6 @@
import { Repeat, RepeatStrategy } from 'aurelia-templating-resources';
import { ViewSlot, View, ViewFactory, BoundViewFactory, Controller } from 'aurelia-templating';
import { Scope, Binding } from 'aurelia-binding';
import { ITemplateStrategy } from './template-strategy';
import { TaskQueue } from 'aurelia-task-queue';

/**@internal */
@@ -120,3 +119,15 @@ export interface IVirtualRepeat extends Repeat {
/**@internal*/ __queuedSplices: any[];
/**@internal*/ __array: any[];
}

export interface ITemplateStrategy {
getScrollContainer(element: Element): HTMLElement;
moveViewFirst(view: View, topBuffer: Element): void;
moveViewLast(view: View, bottomBuffer: Element): void;
createTopBufferElement(element: Element): HTMLElement;
createBottomBufferElement(element: Element): HTMLElement;
removeBufferElements(element: Element, topBuffer: Element, bottomBuffer: Element): void;
getFirstElement(topBuffer: Element): Element;
getLastElement(bottomBuffer: Element): Element;
getTopBufferDistance(topBuffer: Element): number;
}
@@ -3,18 +3,7 @@ import { DOM } from 'aurelia-pal';
import { View } from 'aurelia-templating';
import { DomHelper } from './dom-helper';
import { insertBeforeNode } from './utilities';

export interface ITemplateStrategy {
getScrollContainer(element: Element): HTMLElement;
moveViewFirst(view: View, topBuffer: Element): void;
moveViewLast(view: View, bottomBuffer: Element): void;
createTopBufferElement(element: Element): HTMLElement;
createBottomBufferElement(element: Element): HTMLElement;
removeBufferElements(element: Element, topBuffer: Element, bottomBuffer: Element): void;
getFirstElement(topBuffer: Element): Element;
getLastElement(bottomBuffer: Element): Element;
getTopBufferDistance(topBuffer: Element): number;
}
import { ITemplateStrategy } from './interfaces';

export class TemplateStrategyLocator {

@@ -30,14 +19,96 @@ export class TemplateStrategyLocator {
* Selects the template strategy based on element hosting `virtual-repeat` custom attribute
*/
getStrategy(element: Element): ITemplateStrategy {
if (element.parentNode && (element.parentNode as Element).tagName === 'TBODY') {
return this.container.get(TableStrategy);
const parent = element.parentElement;
if (parent === null) {
return this.container.get(DefaultTemplateStrategy);
}
const parentTagName = parent.tagName;
// placed on tr, as it is automatically wrapped in a TBODY
// if not wrapped, then it is already inside a thead or tfoot
if (parentTagName === 'TBODY' || parentTagName === 'THEAD' || parentTagName === 'TFOOT') {
return this.container.get(TableRowStrategy);
}
// place on a tbody/thead/tfoot
if (parentTagName === 'TABLE') {
return this.container.get(TableBodyStrategy);
}
// if (element.parentNode && (element.parentNode as Element).tagName === 'TBODY') {
// return this.container.get(TableStrategy);
// }
return this.container.get(DefaultTemplateStrategy);
}
}

export class TableStrategy implements ITemplateStrategy {
export class TableBodyStrategy implements ITemplateStrategy {


getScrollContainer(element: Element): HTMLElement {
return this.getTable(element).parentNode as HTMLElement;
}

moveViewFirst(view: View, topBuffer: Element): void {
insertBeforeNode(view, DOM.nextElementSibling(topBuffer));
}

moveViewLast(view: View, bottomBuffer: Element): void {
const previousSibling = bottomBuffer.previousSibling;
const referenceNode = previousSibling.nodeType === 8 && (previousSibling as Comment).data === 'anchor' ? previousSibling : bottomBuffer;
insertBeforeNode(view, referenceNode as Element);
}

createTopBufferElement(element: Element): HTMLElement {
// append tbody with empty row before the element
return element.parentNode.insertBefore(DOM.createElement('tr'), element);
}

createBottomBufferElement(element: Element): HTMLElement {
return element.parentNode.insertBefore(DOM.createElement('tr'), element.nextSibling);
}

removeBufferElements(element: Element, topBuffer: Element, bottomBuffer: Element): void {
DOM.removeNode(topBuffer);
DOM.removeNode(bottomBuffer);
}

getFirstElement(topBuffer: Element): Element {
return topBuffer.nextElementSibling;
}

getLastElement(bottomBuffer: Element): Element {
return bottomBuffer.previousElementSibling;
}

getTopBufferDistance(topBuffer: Element): number {
return 0;
}

private getFirstTbody(tableElement: HTMLTableElement): HTMLTableSectionElement {
let child = tableElement.firstElementChild;
while (child !== null && child.tagName !== 'TBODY') {
child = child.nextElementSibling;
}
return child.tagName === 'TBODY' ? child as HTMLTableSectionElement : null;
}

private _getLastTbody(tableElement: HTMLTableElement): HTMLTableSectionElement {
let child = tableElement.lastElementChild;
while (child !== null && child.tagName !== 'TBODY') {
child = child.previousElementSibling;
}
return child.tagName === 'TBODY' ? child as HTMLTableSectionElement : null;
}

/**
* `element` is actually a comment, acting as anchor for `virtual-repeat` attribute
* `element` will be placed next to a tbody
*/
private getTable(element: Element): HTMLTableElement {
return element.parentNode as HTMLTableElement;
}
}

export class TableRowStrategy implements ITemplateStrategy {

static inject = [DomHelper];

@@ -80,7 +151,7 @@ export class TableStrategy implements ITemplateStrategy {
createTopBufferElement(element: Element): HTMLElement {
const elementName = /^[UO]L$/.test((element.parentNode as Element).tagName) ? 'li' : 'div';
const buffer = DOM.createElement(elementName);
const tableElement = element.parentNode.parentNode;
const tableElement = this.getTable(element);
tableElement.parentNode.insertBefore(buffer, tableElement);
buffer.innerHTML = ' ';
return buffer;
@@ -89,7 +160,7 @@ export class TableStrategy implements ITemplateStrategy {
createBottomBufferElement(element: Element): HTMLElement {
const elementName = /^[UO]L$/.test((element.parentNode as Element).tagName) ? 'li' : 'div';
const buffer = DOM.createElement(elementName);
const tableElement = element.parentNode.parentNode;
const tableElement = this.getTable(element);
tableElement.parentNode.insertBefore(buffer, tableElement.nextSibling);
return buffer;
}
@@ -131,6 +202,13 @@ export class TableStrategy implements ITemplateStrategy {
}
return child.tagName === 'TBODY' ? child as HTMLTableSectionElement : null;
}

/**
* `element` is actually a comment, acting as anchor for `virtual-repeat` attribute
*/
private getTable(element: Element): HTMLTableElement {
return element.parentNode.parentNode as HTMLTableElement;
}
}

export class DefaultTemplateStrategy implements ITemplateStrategy {
@@ -23,8 +23,12 @@ import {
} from './utilities';
import { DomHelper } from './dom-helper';
import { VirtualRepeatStrategyLocator } from './virtual-repeat-strategy-locator';
import { TemplateStrategyLocator, ITemplateStrategy } from './template-strategy';
import { IVirtualRepeat, IVirtualRepeatStrategy } from './interfaces';
import { TemplateStrategyLocator } from './template-strategy';
import {
IVirtualRepeat,
IVirtualRepeatStrategy,
ITemplateStrategy
} from './interfaces';

export class VirtualRepeat extends AbstractRepeater implements IVirtualRepeat {

@@ -499,6 +503,10 @@ export class VirtualRepeat extends AbstractRepeater implements IVirtualRepeat {
}

_adjustBufferHeights(): void {
// let templateStrategy = this.templateStrategy;
// let { topBuffer, _topBufferHeight, bottomBuffer, _bottomBufferHeight } = this;
// templateStrategy.adjustBufferHeight(topBuffer, _topBufferHeight);
// templateStrategy.adjustBufferHeight(bottomBuffer, _bottomBufferHeight);
this.topBuffer.style.height = `${this._topBufferHeight}px`;
this.bottomBuffer.style.height = `${this._bottomBufferHeight}px`;
}
@@ -28,7 +28,10 @@ export function validateState(virtualRepeat: VirtualRepeat, viewModel: any, item
let topBufferHeight = virtualRepeat.topBuffer.getBoundingClientRect().height;
let bottomBufferHeight = virtualRepeat.bottomBuffer.getBoundingClientRect().height;
let renderedItemsHeight = views.length * itemHeight;
expect(topBufferHeight + renderedItemsHeight + bottomBufferHeight).toBe(expectedHeight);
expect(topBufferHeight + renderedItemsHeight + bottomBufferHeight).toBe(
expectedHeight,
`Top buffer (${topBufferHeight}) + items height (${renderedItemsHeight}) + bottom buffer (${bottomBufferHeight}) should have been correct`
);

if (viewModel.items.length > views.length) {
expect(topBufferHeight + bottomBufferHeight).toBeGreaterThan(0);
@@ -11,7 +11,7 @@ fdescribe('VirtualRepeat Integration', () => {
const itemHeight = 100;
const nq = createAssertionQueue();

describe('iterating table', () => {
describe('<tr virtual-repeat.for>', () => {
let component: ComponentTester;
let virtualRepeat;
let viewModel;
@@ -52,7 +52,7 @@ fdescribe('VirtualRepeat Integration', () => {
});
});

fdescribe('<tbody virtual-repeat.for>', () => {
describe('<tbody virtual-repeat.for>', () => {
let component: ComponentTester;
let virtualRepeat: VirtualRepeat;
let viewModel;
@@ -85,17 +85,47 @@ fdescribe('VirtualRepeat Integration', () => {
}
});

it('works', async (done) => {
it('creates right structure', async (done) => {
try {
component.inView('<table><tbody virtual-repeat.for="item of items"><tr><td>\${item}</td></tr></tbody>');
await component.create().then(() => {
virtualRepeat = component.sut;
viewModel = component.viewModel;
});
const element = virtualRepeat['element'];
const { topBuffer, bottomBuffer } = virtualRepeat;
expect(topBuffer.nextElementSibling.tagName).toBe('TBODY');
expect(topBuffer.tagName).toBe('TR');
expect(topBuffer.childNodes.length).toBe(0);
expect(bottomBuffer.previousSibling.nodeType).toBe(Node.COMMENT_NODE);
expect(bottomBuffer.previousElementSibling.tagName).toBe('TBODY');
expect(bottomBuffer.tagName).toBe('TR');
expect(bottomBuffer.childNodes.length).toBe(0);
done();
} catch (ex) {
done.fail(ex);
}
});

component.inView('<table><tbody virtual-repeat.for="item of items"><tr><td>\${item}</td></tr></tbody>');
await component.create().then(() => {
virtualRepeat = component.sut;
viewModel = component.viewModel;
});
const element = virtualRepeat['element'];
expect(virtualRepeat.topBuffer.nextElementSibling.tagName).toBe('TABLE');
expect(virtualRepeat.bottomBuffer.previousElementSibling.tagName).toBe('TABLE');
done();
it('works', async (done) => {
try {
component.inView(
// there is a small border spacing between tbodies, rows that will add up
// need to add border spacing 0 for testing purposes
`<table style="border-spacing: 0">
<tbody virtual-repeat.for="item of items">
<tr style="height: ${itemHeight}px;"><td>\${item}</td></tr>
</tbody>
</table>`);
await component.create().then(() => {
virtualRepeat = component.sut;
viewModel = component.viewModel;
});
nq(() => validateState(virtualRepeat, viewModel, itemHeight));
nq(() => done());
} catch (ex) {
done.fail(ex);
}
});
});

@@ -15,7 +15,6 @@
"dist",
"build",
"doc",
"test",
"config.js",
"gulpfile.js",
"karma.conf.js"

0 comments on commit c1fe513

Please sign in to comment.