|
| 1 | +/* |
| 2 | +Based on https://github.com/NativeScript/nativescript-angular/blob/3.1.0/nativescript-angular/directives/list-view-comp.ts |
| 3 | +Original License |
| 4 | + Copyright (c) 2015-2016 Telerik AD |
| 5 | +
|
| 6 | + Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 | + you may not use this file except in compliance with the License. |
| 8 | + You may obtain a copy of the License at |
| 9 | +
|
| 10 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | +
|
| 12 | + Unless required by applicable law or agreed to in writing, software |
| 13 | + distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | + See the License for the specific language governing permissions and |
| 16 | + limitations under the License. |
| 17 | +END - original License |
| 18 | + */ |
| 19 | + |
| 20 | +import { |
| 21 | + AfterContentInit, |
| 22 | + AfterViewInit, |
| 23 | + ChangeDetectionStrategy, |
| 24 | + ChangeDetectorRef, |
| 25 | + Component, |
| 26 | + ContentChild, |
| 27 | + DoCheck, |
| 28 | + ElementRef, |
| 29 | + EmbeddedViewRef, |
| 30 | + EventEmitter, |
| 31 | + Inject, |
| 32 | + Input, |
| 33 | + IterableDiffer, |
| 34 | + IterableDiffers, |
| 35 | + OnDestroy, |
| 36 | + Output, |
| 37 | + TemplateRef, |
| 38 | + ViewChild, |
| 39 | + ViewContainerRef, |
| 40 | +} from "@angular/core"; |
| 41 | +import { ObservableArray } from "tns-core-modules/data/observable-array"; |
| 42 | +import { profile } from "tns-core-modules/profiling"; |
| 43 | +import { messageType, write } from "tns-core-modules/trace"; |
| 44 | +import { |
| 45 | + KeyedTemplate, |
| 46 | + View, |
| 47 | +} from "tns-core-modules/ui/core/view"; |
| 48 | +import { LayoutBase } from "tns-core-modules/ui/layouts/layout-base"; |
| 49 | +import { |
| 50 | + GridItemEventData, |
| 51 | + GridView, |
| 52 | +} from "../grid-view"; |
| 53 | + |
| 54 | +import { isListLikeIterable } from "nativescript-angular/collection-facade"; |
| 55 | +import { |
| 56 | + getSingleViewRecursive, |
| 57 | + isKnownView, |
| 58 | + registerElement, |
| 59 | +} from "nativescript-angular/element-registry"; |
| 60 | + |
| 61 | +export const gridViewTraceCategory = "ns-grid-view"; |
| 62 | + |
| 63 | +export function gridViewLog(message: string): void { |
| 64 | + write(message, gridViewTraceCategory); |
| 65 | +} |
| 66 | + |
| 67 | +export function listViewError(message: string): void { |
| 68 | + write(message, gridViewTraceCategory, messageType.error); |
| 69 | +} |
| 70 | + |
| 71 | +const NG_VIEW = "_ngViewRef"; |
| 72 | + |
| 73 | +export class GridItemContext { |
| 74 | + constructor( |
| 75 | + public $implicit?: any, |
| 76 | + public item?: any, |
| 77 | + public index?: number, |
| 78 | + public even?: boolean, |
| 79 | + public odd?: boolean |
| 80 | + ) { |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +export interface SetupGridViewArgs { |
| 85 | + view: EmbeddedViewRef<any>; |
| 86 | + data: any; |
| 87 | + index: number; |
| 88 | + context: GridItemContext; |
| 89 | +} |
| 90 | + |
| 91 | +@Component({ |
| 92 | + selector: "GridView", |
| 93 | + template: ` |
| 94 | + <DetachedContainer> |
| 95 | + <Placeholder #loader></Placeholder> |
| 96 | + </DetachedContainer>`, |
| 97 | + changeDetection: ChangeDetectionStrategy.OnPush |
| 98 | +}) |
| 99 | +export class GridViewComponent implements DoCheck, OnDestroy, AfterContentInit, AfterViewInit { |
| 100 | + public get nativeElement(): GridView { |
| 101 | + return this.gridView; |
| 102 | + } |
| 103 | + |
| 104 | + @ViewChild("loader", { read: ViewContainerRef }) public loader: ViewContainerRef; |
| 105 | + |
| 106 | + @Output() public setupGridView = new EventEmitter<SetupGridViewArgs>(); |
| 107 | + |
| 108 | + @ContentChild(TemplateRef) public itemTemplateQuery: TemplateRef<GridItemContext>; |
| 109 | + |
| 110 | + @Input() |
| 111 | + public get items() { |
| 112 | + return this._items; |
| 113 | + } |
| 114 | + |
| 115 | + public set items(value: any) { |
| 116 | + this._items = value; |
| 117 | + let needDiffer = true; |
| 118 | + if (value instanceof ObservableArray) { |
| 119 | + needDiffer = false; |
| 120 | + } |
| 121 | + if (needDiffer && !this._differ && isListLikeIterable(value)) { |
| 122 | + this._differ = this._iterableDiffers.find(this._items) |
| 123 | + .create(this._cdr, (_index, item) => item); |
| 124 | + } |
| 125 | + |
| 126 | + this.gridView.items = this._items; |
| 127 | + } |
| 128 | + |
| 129 | + private gridView: GridView; |
| 130 | + private _items: any; |
| 131 | + private _differ: IterableDiffer<KeyedTemplate>; |
| 132 | + private itemTemplate: TemplateRef<GridItemContext>; |
| 133 | + |
| 134 | + constructor(@Inject(ElementRef) _elementRef: ElementRef, |
| 135 | + @Inject(IterableDiffers) private _iterableDiffers: IterableDiffers, |
| 136 | + @Inject(ChangeDetectorRef) private _cdr: ChangeDetectorRef) { |
| 137 | + this.gridView = _elementRef.nativeElement; |
| 138 | + |
| 139 | + this.gridView.on(GridView.itemLoadingEvent, this.onItemLoading, this); |
| 140 | + } |
| 141 | + |
| 142 | + public ngAfterContentInit() { |
| 143 | + gridViewLog("GridView.ngAfterContentInit()"); |
| 144 | + this.setItemTemplates(); |
| 145 | + } |
| 146 | + |
| 147 | + public ngAfterViewInit() { |
| 148 | + gridViewLog("GridView.ngAfterViewInit()"); |
| 149 | + } |
| 150 | + |
| 151 | + public ngOnDestroy() { |
| 152 | + this.gridView.off(GridView.itemLoadingEvent, this.onItemLoading, this); |
| 153 | + } |
| 154 | + |
| 155 | + public ngDoCheck() { |
| 156 | + gridViewLog("ngDoCheck() - execute differ? " + this._differ); |
| 157 | + if (this._differ) { |
| 158 | + gridViewLog("ngDoCheck() - execute differ"); |
| 159 | + const changes = this._differ.diff(this._items); |
| 160 | + if (changes) { |
| 161 | + gridViewLog("ngDoCheck() - refresh"); |
| 162 | + this.refresh(); |
| 163 | + } |
| 164 | + } |
| 165 | + } |
| 166 | + |
| 167 | + @profile |
| 168 | + public onItemLoading(args: GridItemEventData) { |
| 169 | + if (!args.view && !this.itemTemplate) { |
| 170 | + return; |
| 171 | + } |
| 172 | + |
| 173 | + const index = args.index; |
| 174 | + const items = args.object.items as any; |
| 175 | + const currentItem = typeof items.getItem === "function" ? items.getItem(index) : items[index]; |
| 176 | + let viewRef: EmbeddedViewRef<GridItemContext>; |
| 177 | + |
| 178 | + if (args.view) { |
| 179 | + gridViewLog("onItemLoading: " + index + " - Reusing existing view"); |
| 180 | + viewRef = args.view[NG_VIEW]; |
| 181 | + // Getting angular view from original element (in cases when ProxyViewContainer |
| 182 | + // is used NativeScript internally wraps it in a StackLayout) |
| 183 | + if (!viewRef && args.view instanceof LayoutBase && args.view.getChildrenCount() > 0) { |
| 184 | + viewRef = args.view.getChildAt(0)[NG_VIEW]; |
| 185 | + } |
| 186 | + |
| 187 | + if (!viewRef) { |
| 188 | + listViewError("ViewReference not found for item " + index + ". View recycling is not working"); |
| 189 | + } |
| 190 | + } |
| 191 | + |
| 192 | + if (!viewRef) { |
| 193 | + gridViewLog("onItemLoading: " + index + " - Creating view from template"); |
| 194 | + viewRef = this.loader.createEmbeddedView(this.itemTemplate, new GridItemContext(), 0); |
| 195 | + args.view = getGridItemRoot(viewRef); |
| 196 | + args.view[NG_VIEW] = viewRef; |
| 197 | + } |
| 198 | + |
| 199 | + this.setupViewRef(viewRef, currentItem, index); |
| 200 | + |
| 201 | + this.detectChangesOnChild(viewRef, index); |
| 202 | + } |
| 203 | + |
| 204 | + public setupViewRef(view: EmbeddedViewRef<GridItemContext>, data: any, index: number): void { |
| 205 | + const context = view.context; |
| 206 | + context.$implicit = data; |
| 207 | + context.item = data; |
| 208 | + context.index = index; |
| 209 | + context.even = (index % 2 === 0); |
| 210 | + context.odd = !context.even; |
| 211 | + |
| 212 | + this.setupGridView.next({ |
| 213 | + context, |
| 214 | + data, |
| 215 | + index, |
| 216 | + view, |
| 217 | + }); |
| 218 | + } |
| 219 | + |
| 220 | + private setItemTemplates() { |
| 221 | + // The itemTemplateQuery may be changed after list items are added that contain <template> inside, |
| 222 | + // so cache and use only the original template to avoid errors. |
| 223 | + this.itemTemplate = this.itemTemplateQuery; |
| 224 | + |
| 225 | + this.gridView.itemTemplate = () => { |
| 226 | + const viewRef = this.loader.createEmbeddedView(this.itemTemplate, new GridItemContext(), 0); |
| 227 | + const resultView = getGridItemRoot(viewRef); |
| 228 | + resultView[NG_VIEW] = viewRef; |
| 229 | + |
| 230 | + return resultView; |
| 231 | + }; |
| 232 | + } |
| 233 | + |
| 234 | + @profile |
| 235 | + private detectChangesOnChild(viewRef: EmbeddedViewRef<GridItemContext>, index: number) { |
| 236 | + gridViewLog("Manually detect changes in child: " + index); |
| 237 | + viewRef.markForCheck(); |
| 238 | + viewRef.detectChanges(); |
| 239 | + } |
| 240 | + |
| 241 | + private refresh() { |
| 242 | + if (this.gridView) { |
| 243 | + this.gridView.refresh(); |
| 244 | + } |
| 245 | + } |
| 246 | +} |
| 247 | + |
| 248 | +export interface ComponentView { |
| 249 | + rootNodes: any[]; |
| 250 | + destroy(): void; |
| 251 | +} |
| 252 | + |
| 253 | +export type RootLocator = (nodes: any[], nestLevel: number) => View; |
| 254 | + |
| 255 | +export function getGridItemRoot(viewRef: ComponentView, rootLocator: RootLocator = getSingleViewRecursive): View { |
| 256 | + const rootView = rootLocator(viewRef.rootNodes, 0); |
| 257 | + return rootView; |
| 258 | +} |
| 259 | + |
| 260 | +if (!isKnownView("GridView")) { |
| 261 | + registerElement("GridView", () => GridView); |
| 262 | +} |
0 commit comments