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