##// END OF EJS Templates
Fixed scheduleRender context restoration
cin -
r122:fb2ea4d6aaba v1.6.3 default
parent child
Show More

The requested changes are too big and content was truncated. Show full diff

1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,43 +1,43
1 {
1 {
2 "name": "@implab/djx",
2 "name": "@implab/djx",
3 "version": "0.0.1-dev",
3 "version": "0.0.1-dev",
4 "description": "Supports using dojo version 1 with typescript and .tsx files",
4 "description": "Supports using dojo version 1 with typescript and .tsx files",
5 "keywords": [
5 "keywords": [
6 "dojo",
6 "dojo",
7 "tsx",
7 "tsx",
8 "typescript",
8 "typescript",
9 "widgets"
9 "widgets"
10 ],
10 ],
11 "author": "Implab team",
11 "author": "Implab team",
12 "license": "BSD-2-Clause",
12 "license": "BSD-2-Clause",
13 "repository": "https://code.implab.org/implab/implabjs-djx",
13 "repository": "https://code.implab.org/implab/implabjs-djx",
14 "publishConfig": {
14 "publishConfig": {
15 "access": "public"
15 "access": "public"
16 },
16 },
17 "peerDependencies": {
17 "peerDependencies": {
18 "@implab/core-amd": "^1.4.6",
18 "@implab/core-amd": "^1.4.6",
19 "dojo": "^1.10.0"
19 "dojo": "^1.10.0"
20 },
20 },
21 "devDependencies": {
21 "devDependencies": {
22 "@implab/core-amd": "^1.4.6",
22 "@implab/core-amd": "^1.4.6",
23 "@types/chai": "4.1.3",
23 "@types/chai": "4.1.3",
24 "@types/requirejs": "2.1.31",
24 "@types/requirejs": "2.1.31",
25 "@types/yaml": "1.2.0",
25 "@types/yaml": "1.2.0",
26 "@types/tap": "15.0.7",
26 "@types/tap": "15.0.7",
27 "rxjs": "7.5.6",
27 "rxjs": "7.5.6",
28 "dojo": "1.16.0",
28 "dojo": "1.16.0",
29 "@implab/dojo-typings": "1.0.3",
29 "@implab/dojo-typings": "1.0.3",
30 "@typescript-eslint/eslint-plugin": "^5.23.0",
30 "@typescript-eslint/eslint-plugin": "^5.23.0",
31 "@typescript-eslint/parser": "^5.23.0",
31 "@typescript-eslint/parser": "^5.23.0",
32 "eslint": "^8.23.0",
32 "eslint": "^8.28.0",
33 "eslint-config-standard": "^17.0.0",
33 "eslint-config-standard": "^17.0.0",
34 "eslint-plugin-import": "^2.26.0",
34 "eslint-plugin-import": "^2.26.0",
35 "eslint-plugin-n": "^15.2.0",
35 "eslint-plugin-n": "^15.2.0",
36 "eslint-plugin-promise": "^6.0.0",
36 "eslint-plugin-promise": "^6.0.0",
37 "eslint-plugin-react": "^7.29.4",
37 "eslint-plugin-react": "^7.29.4",
38 "requirejs": "2.3.6",
38 "requirejs": "2.3.6",
39 "typescript": "4.8.3",
39 "typescript": "4.8.3",
40 "yaml": "~1.7.2",
40 "yaml": "~1.7.2",
41 "tap": "16.3.0"
41 "tap": "16.3.0"
42 }
42 }
43 }
43 }
@@ -1,56 +1,57
1 import { id as mid} from "module";
1 import { id as mid} from "module";
2 import { PromiseOrValue } from "@implab/core-amd/interfaces";
2 import { PromiseOrValue } from "@implab/core-amd/interfaces";
3 import { NlsBundle } from "./NlsBundle";
3 import { NlsBundle } from "./NlsBundle";
4 import { isPromise } from "@implab/core-amd/safe";
4 import { isPromise } from "@implab/core-amd/safe";
5 import { locale as sysLocale } from "dojo/_base/kernel";
5 import { locale as sysLocale } from "dojo/_base/kernel";
6 import { TraceSource } from "@implab/core-amd/log/TraceSource";
6 import { TraceSource } from "@implab/core-amd/log/TraceSource";
7
7
8 const trace = TraceSource.get(mid);
8 const trace = TraceSource.get(mid);
9
9
10 trace.debug("Current sysLocale: {0}", sysLocale);
10 trace.debug("Current sysLocale: {0}", sysLocale);
11
11
12 export interface OnLoad {
12 export interface OnLoad {
13 (result?: unknown): void;
13 (result?: unknown): void;
14 error(err: unknown): void;
14 error(err: unknown): void;
15 }
15 }
16
16
17 export const bundle = <T extends object>(nls: T, locales?: Record<string, () => PromiseOrValue<object>>) : {
17 export const bundle = <T extends object>(nls: T, locales?: Record<string, () => PromiseOrValue<object>>) : {
18 i18n: (locale?: string) => T;
18 i18n: (this: void, locale?: string) => T;
19 define: (pack: Partial<T>) => object;
19 define: (this: void, pack: Partial<T>) => object;
20 load: (id: string, require: Require, cb: OnLoad, config: {isBuild?: boolean}) => void;
20 load: (this: void, id: string, require: Require, cb: OnLoad, config: {isBuild?: boolean}) => void;
21 } => {
21 } => {
22 const nlsBundle = new NlsBundle(nls, locales);
22 const nlsBundle = new NlsBundle(nls, locales);
23
23
24 const fn = (_locale?: string) => {
24 const fn = (_locale?: string) => {
25 const locale = _locale || sysLocale;
25 const locale = _locale || sysLocale;
26 const result = nlsBundle.getLocale(locale);
26 const result = nlsBundle.getLocale(locale);
27
27
28 if (isPromise(result))
28 if (isPromise(result))
29 throw new Error(`The bundle '${locale}' isn't loaded`);
29 throw new Error(`The bundle '${locale}' isn't loaded`);
30 else
30 else
31 return result;
31 return result;
32 };
32 };
33
33
34 fn.i18n = fn;
34 fn.i18n = fn;
35 fn.nls = fn;
35 fn.define = (pack: Partial<T>): object => pack;
36 fn.define = (pack: Partial<T>): object => pack;
36 fn.load = async (id: string, require: Require, cb: OnLoad, config: {isBuild?: boolean}) => {
37 fn.load = async (id: string, require: Require, cb: OnLoad, config: {isBuild?: boolean}) => {
37 const locale = id || sysLocale;
38 const locale = id || sysLocale;
38 if (config && config.isBuild) {
39 if (config && config.isBuild) {
39 cb();
40 cb();
40 } else {
41 } else {
41 try {
42 try {
42 await nlsBundle.getLocale(locale);
43 await nlsBundle.getLocale(locale);
43 cb();
44 cb();
44 } catch (e) {
45 } catch (e) {
45 if(cb.error) {
46 if(cb.error) {
46 cb.error(e);
47 cb.error(e);
47 } else {
48 } else {
48 // in case the loader doesn't support error reporting
49 // in case the loader doesn't support error reporting
49 trace.error("Error loading {0}: {1}", locale, e);
50 trace.error("Error loading {0}: {1}", locale, e);
50 }
51 }
51 }
52 }
52 }
53 }
53 };
54 };
54
55
55 return fn;
56 return fn;
56 };
57 };
@@ -1,197 +1,198
1 import { TraceSource } from "@implab/core-amd/log/TraceSource";
1 import { TraceSource } from "@implab/core-amd/log/TraceSource";
2 import { isPromise } from "@implab/core-amd/safe";
2 import { isPromise } from "@implab/core-amd/safe";
3 import { id as mid } from "module";
3 import { id as mid } from "module";
4 import { IScope, Scope } from "./Scope";
4 import { IScope, Scope } from "./Scope";
5 import { isNode, isRendition, isWidget } from "./traits";
5 import { isNode, isRendition, isWidget } from "./traits";
6
6
7 const trace = TraceSource.get(mid);
7 const trace = TraceSource.get(mid);
8
8
9 interface Context {
9 interface Context {
10 readonly scope: IScope;
10 readonly scope: IScope;
11
11
12 readonly hooks?: (() => void)[];
12 readonly hooks?: (() => void)[];
13 }
13 }
14
14
15 let _context: Context = {
15 let _context: Context = {
16 scope: Scope.dummy
16 scope: Scope.dummy
17 };
17 };
18
18
19 let _renderCount = 0;
19 let _renderCount = 0;
20 let _renderId = 1;
20 let _renderId = 1;
21 let _renderedHooks: (() => void)[] = [];
21 let _renderedHooks: (() => void)[] = [];
22
22
23
23
24 const guard = (cb: () => unknown) => {
24 const guard = (cb: () => unknown) => {
25 try {
25 try {
26 const result = cb();
26 const result = cb();
27 if (isPromise(result)) {
27 if (isPromise(result)) {
28 const warn = (ret: unknown) => trace.error("The callback {0} competed asynchronously. result = {1}", cb, ret);
28 const warn = (ret: unknown) => trace.error("The callback {0} competed asynchronously. result = {1}", cb, ret);
29 result.then(warn, warn);
29 result.then(warn, warn);
30 }
30 }
31 } catch (e) {
31 } catch (e) {
32 trace.error(e);
32 trace.error(e);
33 }
33 }
34 };
34 };
35
35
36 /**
36 /**
37 *
37 *
38 * @param scope
38 * @param scope
39 * @returns
39 * @returns
40 */
40 */
41 export const beginRender = (scope = getScope()) => {
41 export const beginRender = (scope = getScope()) => {
42 const prev = _context;
42 const prev = _context;
43 _renderCount++;
43 _renderCount++;
44 const renderId = _renderId++;
44 const renderId = _renderId++;
45 trace.debug("beginRender [{0}], pending = {1}", renderId, _renderCount);
45 trace.debug("beginRender [{0}], pending = {1}", renderId, _renderCount);
46 if (_renderCount === 1)
46 if (_renderCount === 1)
47 onRendering();
47 onRendering();
48
48
49 _context = {
49 _context = {
50 scope,
50 scope,
51 hooks: []
51 hooks: []
52 };
52 };
53 return endRender(prev, _context, renderId);
53 return endRender(prev, _context, renderId);
54 };
54 };
55
55
56 /**
56 /**
57 * Method for a deferred rendering. Returns a promise with `beginRender()` function.
57 * Method for a deferred rendering. Returns a promise with `beginRender()` function.
58 * Call to `scheduleRender` will save the current context, and will increment pending
58 * Call to `scheduleRender` will save the current context, and will increment pending
59 * operations counter.
59 * operations counter.
60 *
60 *
61 * @example
61 * @example
62 *
62 *
63 * const begin = await scheduleRender();
63 * const begin = await scheduleRender();
64 * const end = begin();
64 * const end = begin();
65 * try {
65 * try {
66 * // do some DOM manipulations
66 * // do some DOM manipulations
67 * } finally {
67 * } finally {
68 * end();
68 * end();
69 * }
69 * }
70 *
70 *
71 * @param scope
71 * @param scope
72 * @returns
72 * @returns
73 */
73 */
74 export const scheduleRender = async (scope = getScope()) => {
74 export const scheduleRender = async (scope = getScope()) => {
75 const prev = _context;
76 _renderCount++;
75 _renderCount++;
77 const renderId = _renderId ++;
76 const renderId = _renderId ++;
78 trace.debug("scheduleRender [{0}], pending = {1}", renderId, _renderCount);
77 trace.debug("scheduleRender [{0}], pending = {1}", renderId, _renderCount);
79 if (_renderCount === 1)
78 if (_renderCount === 1)
80 onRendering();
79 onRendering();
81
80
82 await Promise.resolve();
81 await Promise.resolve();
83
82
84 return () => {
83 return () => {
85 trace.debug("beginRender [{0}], pending = {1}", renderId, _renderCount);
84 trace.debug("beginRender [{0}], pending = {1}", renderId, _renderCount);
85 const prev = _context;
86
86 _context = {
87 _context = {
87 scope,
88 scope,
88 hooks: []
89 hooks: []
89 };
90 };
90 return endRender(prev, _context, renderId);
91 return endRender(prev, _context, renderId);
91 };
92 };
92 };
93 };
93
94
94 /**
95 /**
95 * Completes render operation
96 * Completes render operation
96 */
97 */
97 const endRender = (prev: Context, current: Context, renderId: number) => () => {
98 const endRender = (prev: Context, current: Context, renderId: number) => () => {
98 if (_context !== current)
99 if (_context !== current)
99 trace.error("endRender mismatched beginRender call");
100 trace.error("endRender mismatched beginRender call");
100
101
101 const { hooks } = _context;
102 const { hooks } = _context;
102 if (hooks)
103 if (hooks)
103 hooks.forEach(guard);
104 hooks.forEach(guard);
104
105
105 _renderCount--;
106 _renderCount--;
106 _context = prev;
107 _context = prev;
107
108
108 trace.debug("endRender [{0}], pending = {1}", renderId, _renderCount);
109 trace.debug("endRender [{0}], pending = {1}", renderId, _renderCount);
109 if (_renderCount === 0)
110 if (_renderCount === 0)
110 onRendered();
111 onRendered();
111 };
112 };
112
113
113 // called when the first beginRender is called for this iteration
114 // called when the first beginRender is called for this iteration
114 const onRendering = () => {
115 const onRendering = () => {
115 setTimeout(() => {
116 setTimeout(() => {
116 if (_renderCount !== 0)
117 if (_renderCount !== 0)
117 trace.error("Rendering tasks aren't finished, currently running = {0}", _renderCount);
118 trace.error("Rendering tasks aren't finished, currently running = {0}", _renderCount);
118 });
119 });
119 };
120 };
120
121
121 // called when all render operations are complete
122 // called when all render operations are complete
122 const onRendered = () => {
123 const onRendered = () => {
123 _renderedHooks.forEach(guard);
124 _renderedHooks.forEach(guard);
124 _renderedHooks = [];
125 _renderedHooks = [];
125 };
126 };
126
127
127 export const whenRendered = () => new Promise<void>((resolve) => {
128 export const whenRendered = () => new Promise<void>((resolve) => {
128 if (_renderCount)
129 if (_renderCount)
129 _renderedHooks.push(resolve);
130 _renderedHooks.push(resolve);
130 else
131 else
131 resolve();
132 resolve();
132 });
133 });
133
134
134 export const renderHook = (hook: () => void) => {
135 export const renderHook = (hook: () => void) => {
135 const { hooks } = _context;
136 const { hooks } = _context;
136 if (hooks)
137 if (hooks)
137 hooks.push(hook);
138 hooks.push(hook);
138 else
139 else
139 guard(hook);
140 guard(hook);
140 };
141 };
141
142
142 export const refHook = <T>(value: T, ref: JSX.Ref<T>) => {
143 export const refHook = <T>(value: T, ref: JSX.Ref<T>) => {
143 const { hooks, scope } = _context;
144 const { hooks, scope } = _context;
144 if (hooks)
145 if (hooks)
145 hooks.push(() => ref(value));
146 hooks.push(() => ref(value));
146 else
147 else
147 guard(() => ref(value));
148 guard(() => ref(value));
148
149
149 scope.own(() => ref(undefined));
150 scope.own(() => ref(undefined));
150 };
151 };
151
152
152 /** Returns the current scope */
153 /** Returns the current scope */
153 export const getScope = () => _context.scope;
154 export const getScope = () => _context.scope;
154
155
155 /** Schedules the rendition to be rendered to the DOM Node
156 /** Schedules the rendition to be rendered to the DOM Node
156 * @param rendition The rendition to be rendered
157 * @param rendition The rendition to be rendered
157 * @param scope The scope
158 * @param scope The scope
158 */
159 */
159 export const render = (rendition: unknown, scope = Scope.dummy) => {
160 export const render = (rendition: unknown, scope = Scope.dummy) => {
160 const complete = beginRender(scope);
161 const complete = beginRender(scope);
161 try {
162 try {
162 return getItemDom(rendition);
163 return getItemDom(rendition);
163 } finally {
164 } finally {
164 complete();
165 complete();
165 }
166 }
166 };
167 };
167
168
168 const emptyFragment = document.createDocumentFragment();
169 const emptyFragment = document.createDocumentFragment();
169
170
170 /** Renders DOM element for different types of the argument. */
171 /** Renders DOM element for different types of the argument. */
171 export const getItemDom = (v: unknown) => {
172 export const getItemDom = (v: unknown) => {
172 if (typeof v === "string" || typeof v === "number" || v instanceof RegExp || v instanceof Date) {
173 if (typeof v === "string" || typeof v === "number" || v instanceof RegExp || v instanceof Date) {
173 // primitive types converted to the text nodes
174 // primitive types converted to the text nodes
174 return document.createTextNode(v.toString());
175 return document.createTextNode(v.toString());
175 } else if (isNode(v)) {
176 } else if (isNode(v)) {
176 // nodes are kept as is
177 // nodes are kept as is
177 return v;
178 return v;
178 } else if (isRendition(v)) {
179 } else if (isRendition(v)) {
179 // renditions are instantiated
180 // renditions are instantiated
180 return v.getDomNode();
181 return v.getDomNode();
181 } else if (isWidget(v)) {
182 } else if (isWidget(v)) {
182 // widgets are converted to it's markup
183 // widgets are converted to it's markup
183 return v.domNode;
184 return v.domNode;
184 } else if (typeof v === "boolean" || v === null || v === undefined) {
185 } else if (typeof v === "boolean" || v === null || v === undefined) {
185 // null | undefined | boolean are removed
186 // null | undefined | boolean are removed
186 return emptyFragment;
187 return emptyFragment;
187 } else if (v instanceof Array) {
188 } else if (v instanceof Array) {
188 // arrays will be translated to document fragments
189 // arrays will be translated to document fragments
189 const fragment = document.createDocumentFragment();
190 const fragment = document.createDocumentFragment();
190 v.map(item => getItemDom(item))
191 v.map(item => getItemDom(item))
191 .forEach(node => fragment.appendChild(node));
192 .forEach(node => fragment.appendChild(node));
192 return fragment;
193 return fragment;
193 } else {
194 } else {
194 // bug: explicit error otherwise
195 // bug: explicit error otherwise
195 throw new Error(`Invalid parameter: ${String(v)}`);
196 throw new Error(`Invalid parameter: ${String(v)}`);
196 }
197 }
197 };
198 };
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now