import {Component, EventEmitter, ContentChild} from '@angular/core'; import {AfterContentInit, OnChanges} from '@angular/core'; import {TemplateRef, SimpleChange} from '@angular/core'; import {CORE_DIRECTIVES} from '@angular/common'; import {BdItem} from './item'; import {BdItemTemplate} from './item-template'; export {BdItemTemplate}; const CONTAINER_HEIGHT = 300; const ITEM_HEIGHT = 50; const CONTAINER_CAPACITY = CONTAINER_HEIGHT / ITEM_HEIGHT; @Component({ selector: 'bd-v-repeat', inputs: ['items', 'outerTemplate:template', 'selectedItem', 'scrollToSelection'], outputs: ['itemClicked'], directives: [CORE_DIRECTIVES, BdItem], styles: [require('./vrepeat.scss')], template: `
` }) export class BdVRepeat implements AfterContentInit, OnChanges { @ContentChild(BdItemTemplate) itemTemplate: BdItemTemplate; public itemClicked: EventEmitter; public scrollerHeight: number; public marginTop: number; public scrollTop: number; public scrollToSelection: boolean; public items: Array; public visibleItems: Array; public selectedItem: any; public template: TemplateRef; public outerTemplate: TemplateRef; private firstItem: number; private lastItem: number; constructor() { this.firstItem = 0; this.lastItem = 0; this.marginTop = 0; this.scrollTop = 0; this.scrollerHeight = CONTAINER_HEIGHT; this.itemClicked = new EventEmitter(false); } onClick(item) { this.itemClicked.emit(item); } onScroll(scrollTop: number) { this.scrollTop = scrollTop; let shouldHave = Math.ceil((this.scrollTop + CONTAINER_HEIGHT) / ITEM_HEIGHT + 5); this.lastItem = shouldHave >= this.items.length ? this.items.length - 1 : shouldHave; let shouldBeFirst = this.lastItem - (CONTAINER_CAPACITY + 10); this.selectVisibleItems(shouldBeFirst, shouldHave); } ngAfterContentInit() { if (this.outerTemplate) { this.template = this.outerTemplate; this.initItems(); } else if (this.itemTemplate) { this.template = this.itemTemplate.getTemplate(); this.initItems(); } else { throw Error('BdItemTemplate directive inside BdVRepeat or template properties is required'); } } ngOnChanges(changes: { [key: string]: SimpleChange }) { if (changes['items'] && changes['items'].currentValue) { this.initItems(); } if (this.scrollToSelection && changes['selectedItem'] && changes['selectedItem'].currentValue) { this.scrollToSelectedItem(); } } private scrollToSelectedItem() { let itemIndex = this.items.indexOf(this.selectedItem); let firstVisibleItem = Math.floor(this.scrollTop / ITEM_HEIGHT); let lastVisibleItem = firstVisibleItem + CONTAINER_CAPACITY; let isItemVisible = itemIndex >= firstVisibleItem && itemIndex < lastVisibleItem; if (isItemVisible) { return; } let haveToScrollBy = 0; if (itemIndex < firstVisibleItem) { haveToScrollBy = itemIndex - firstVisibleItem; } else { haveToScrollBy = itemIndex - lastVisibleItem + 1; } this.scrollTop = this.scrollTop + haveToScrollBy * ITEM_HEIGHT; } private selectVisibleItems(start: number, end: number) { this.lastItem = end >= this.items.length ? this.items.length - 1 : end; this.firstItem = start <= 0 ? 0 : start; this.marginTop = this.firstItem * ITEM_HEIGHT; this.visibleItems = this.items.slice(this.firstItem, this.lastItem + 1); } private initItems() { if (!this.items) { return; } this.scrollerHeight = this.items.length * ITEM_HEIGHT; this.firstItem = 0; this.lastItem = (CONTAINER_CAPACITY + 5) > this.items.length ? this.items.length - 1 : CONTAINER_CAPACITY + 5; this.visibleItems = this.items.slice(this.firstItem, this.lastItem + 1); } }