1 | import debug = require("debug");
|
2 | import { Component, hydrate, replace } from "neweb-components";
|
3 | import {
|
4 | IPage, IPageFrame,
|
5 | IRemoteFrameControllerDataParams, IRemoteFrameControllerDispatchParams,
|
6 | } from "neweb-core";
|
7 | import { Onemitter } from "onemitter";
|
8 | import { BehaviorSubject } from "rxjs/BehaviorSubject";
|
9 | import { Subject } from "rxjs/Subject";
|
10 | import { IViewParams } from "./IViewParams";
|
11 | export interface IClientPageRendererConfig {
|
12 | rootHtmlElement: HTMLElement;
|
13 | app: {
|
14 | getFrameViewClass: (pageFrame: IPageFrame) => any;
|
15 | };
|
16 | }
|
17 | class ClientPageRenderer {
|
18 | protected navigate: (url: string) => void;
|
19 | protected dispatch: (params: IRemoteFrameControllerDispatchParams) => Promise<void>;
|
20 | protected seansStatusEmitter: Onemitter<string>;
|
21 | protected networkStatusEmitter: Onemitter<string>;
|
22 | protected historyContext: any;
|
23 | protected views: { [index: string]: new (config: IViewParams<any, any, any>) => Component<any> } = {};
|
24 | protected frames: {
|
25 | [index: string]: {
|
26 | component: Component<any>;
|
27 | data: { [index: string]: Subject<any> };
|
28 | children: { [index: string]: BehaviorSubject<Component<any>> };
|
29 | params: BehaviorSubject<any>;
|
30 | pageFrame: IPageFrame;
|
31 | };
|
32 | } = {};
|
33 | protected currentPage: IPage;
|
34 | constructor(protected config: IClientPageRendererConfig) { }
|
35 | public setMethods(params: {
|
36 | navigate: (url: string) => void;
|
37 | dispatch: (params: IRemoteFrameControllerDispatchParams) => Promise<void>;
|
38 | seansStatusEmitter: Onemitter<any>;
|
39 | networkStatusEmitter: Onemitter<any>;
|
40 | historyContext: any;
|
41 | }) {
|
42 | this.navigate = params.navigate;
|
43 | this.dispatch = params.dispatch;
|
44 | this.seansStatusEmitter = params.seansStatusEmitter;
|
45 | this.networkStatusEmitter = params.networkStatusEmitter;
|
46 | this.historyContext = params.historyContext;
|
47 | }
|
48 | public async loadPage(page: IPage) {
|
49 | await this.loadViews(page);
|
50 |
|
51 | page.frames.map((pageFrame) => {
|
52 | this.frames[pageFrame.frameId] = this.createFrame(pageFrame);
|
53 | });
|
54 | this.renderFrame(page.rootFrame, page);
|
55 | this.currentPage = page;
|
56 | }
|
57 | public async newPage(page: IPage) {
|
58 | debug("neweb:renderer")("new page", page);
|
59 | await this.loadViews(page);
|
60 | const frameIds: string[] = [];
|
61 | page.frames.map(async (frame) => {
|
62 | if (!this.frames[frame.frameId]) {
|
63 | this.frames[frame.frameId] = this.createFrame(frame);
|
64 | frameIds.push(frame.frameId);
|
65 | } else {
|
66 | const xFrame = this.frames[frame.frameId];
|
67 | if (JSON.stringify(xFrame.params.getValue()) !== frame.params) {
|
68 | xFrame.params.next(frame.params);
|
69 | }
|
70 | }
|
71 | });
|
72 |
|
73 | this.renderFrame(page.rootFrame, page);
|
74 |
|
75 | if (this.currentPage.rootFrame !== page.rootFrame) {
|
76 | replace(this.frames[page.rootFrame].component, this.config.rootHtmlElement);
|
77 | }
|
78 | this.currentPage = page;
|
79 | }
|
80 | public async initialize() {
|
81 | hydrate(this.frames[this.currentPage.rootFrame].component, this.config.rootHtmlElement);
|
82 | }
|
83 | public emitFrameControllerData(params: IRemoteFrameControllerDataParams) {
|
84 | const frame = this.frames[params.frameId];
|
85 | if (frame) {
|
86 | frame.data[params.fieldName].next(params.value);
|
87 | }
|
88 | }
|
89 | protected async loadViews(page: IPage) {
|
90 | await Promise.all(page.frames.map(async (pageFrame) => {
|
91 | this.views[pageFrame.frameName] = await this.config.app.getFrameViewClass(pageFrame);
|
92 | }));
|
93 | }
|
94 | protected renderFrame(frameId: string, page: IPage) {
|
95 | const frame = this.frames[frameId];
|
96 | const pageFrame = page.frames.filter((f) => f.frameId === frameId)[0];
|
97 | Object.keys(pageFrame.frames).map((placeName) => {
|
98 | const childFrameId = pageFrame.frames[placeName];
|
99 | this.renderFrame(childFrameId, page);
|
100 | const childFrame = this.frames[childFrameId];
|
101 | if (frame.pageFrame.frames[placeName] !== pageFrame.frames[placeName]
|
102 | || !frame.children[placeName].getValue()) {
|
103 | frame.children[placeName].next(childFrame.component);
|
104 | }
|
105 | });
|
106 | }
|
107 | protected createFrame(pageFrame: IPageFrame) {
|
108 | const ViewClass = this.views[pageFrame.frameName];
|
109 | const data: { [index: string]: Subject<any> } = {};
|
110 | Object.keys(pageFrame.data).map((dataName) => {
|
111 | data[dataName] = new BehaviorSubject(pageFrame.data[dataName]);
|
112 | });
|
113 | const children: { [index: string]: BehaviorSubject<Component<any>> } = {};
|
114 | Object.keys(pageFrame.frames).map((childName) => {
|
115 | children[childName] = new BehaviorSubject(undefined as any);
|
116 | });
|
117 | const params = new BehaviorSubject(pageFrame.params);
|
118 | const component = new ViewClass({
|
119 | data,
|
120 | children,
|
121 | params,
|
122 | navigate: this.navigate,
|
123 | dispatch: (actionName: string, ...args: any[]) => this.dispatch({
|
124 | frameId: pageFrame.frameId,
|
125 | actionName,
|
126 | args,
|
127 | }),
|
128 | });
|
129 | return {
|
130 | pageFrame,
|
131 | component,
|
132 | data,
|
133 | children,
|
134 | params,
|
135 | };
|
136 | }
|
137 | }
|
138 | export default ClientPageRenderer;
|