##// END OF EJS Templates
Merge with v1.3
cin -
r104:15c829aa08a2 merge v1.3.0 default
parent child
Show More

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

@@ -0,0 +1,147
1 /**
2 * The interface for the consumer of an observable sequence
3 */
4 export interface Observer<T> {
5 /**
6 * Called for the next element in the sequence
7 */
8 next: (value: T) => void;
9
10 /**
11 * Called once when the error occurs in the sequence.
12 */
13 error: (e: unknown) => void;
14
15 /**
16 * Called once at the end of the sequence.
17 */
18 complete: () => void;
19 }
20
21 /**
22 * The group of functions to feed an observable. This methods are provided to
23 * the producer to generate a stream of events.
24 */
25 export type Sink<T> = {
26 [k in keyof Observer<T>]: (this: void, ...args: Parameters<Observer<T>[k]>) => void;
27 };
28
29 export type Producer<T> = (sink: Sink<T>) => (void | (() => void));
30
31 export interface Unsubscribable {
32 unsubscribe(): void;
33 }
34
35 export const isUnsubsribable = (v: unknown): v is Unsubscribable =>
36 v !== null && v !== undefined && typeof (v as Unsubscribable).unsubscribe === "function";
37
38 export const isSubsribable = (v: unknown): v is Subscribable<unknown> =>
39 v !== null && v !== undefined && typeof (v as Subscribable<unknown>).subscribe === "function";
40
41 export interface Subscribable<T> {
42 subscribe(consumer: Partial<Observer<T>>): Unsubscribable;
43 }
44
45 /** The observable source of items. */
46 export interface Observable<T> extends Subscribable<T> {
47 /** Transforms elements of the sequence with the specified mapper
48 *
49 * @param mapper The mapper used to transform the values
50 */
51 map<T2>(mapper: (value: T) => T2): Observable<T2>;
52
53 /** Filters elements of the sequence. The resulting sequence will
54 * contain only elements which match the specified predicate.
55 *
56 * @param predicate The filter predicate.
57 */
58 filter(predicate: (value: T) => boolean): Observable<T>;
59
60 /** Applies accumulator to each value in the sequence and
61 * emits the accumulated value for each source element
62 *
63 * @param accumulator
64 * @param initial
65 */
66 scan<A>(accumulator: (acc: A, value: T) => A, initial: A): Observable<A>;
67 }
68
69 const noop = () => { };
70
71 const sink = <T>(consumer: Partial<Observer<T>>) => {
72 const { next, error, complete } = consumer;
73 return {
74 next: next ? next.bind(consumer) : noop,
75 error: error ? error.bind(consumer) : noop,
76 complete: complete ? complete.bind(consumer) : noop
77 }
78 };
79
80 const fuse = <T>({ next, error, complete }: Sink<T>) => {
81 let done = false;
82 return {
83 next: (value: T) => { !done && next(value) },
84 error: (e: unknown) => { !done && (done = true, error(e)) },
85 complete: () => { !done && (done = true, complete()) }
86 }
87 }
88
89 const _observe = <T>(producer: Producer<T>): Observable<T> => ({
90 subscribe: (consumer: Partial<Observer<T>>) => ({
91 unsubscribe: producer(sink(consumer)) ?? noop
92 }),
93 map: (mapper) => _observe(({ next, error, complete }) =>
94 producer({
95 next: next !== noop ? (v: T) => next(mapper(v)) : noop,
96 error,
97 complete
98 })
99 ),
100 filter: (predicate) => _observe(({ next, error, complete }) =>
101 producer({
102 next: next !== noop ?
103 (v: T) => predicate(v) ? next(v) : void(0) : noop,
104 error,
105 complete
106 })
107 ),
108 scan: (accumulator, initial) => _observe(({ next, error, complete }) => {
109 let _acc = initial;
110 return producer({
111 next: next !== noop ?
112 (v: T) => next(_acc = accumulator(_acc, v)) : noop,
113 error,
114 complete
115 });
116 })
117 });
118
119 export const observe = <T>(producer: Producer<T>): Observable<T> => ({
120 subscribe: (consumer: Partial<Observer<T>>) => ({
121 unsubscribe: producer(fuse(sink(consumer))) ?? noop
122 }),
123 map: (mapper) => _observe(({ next, error, complete }) =>
124 producer(fuse({
125 next: next !== noop ?
126 (v: T) => next(mapper(v)) : noop,
127 error,
128 complete
129 }))
130 ),
131 filter: (predicate) => _observe(({ next, error, complete }) =>
132 producer(fuse({
133 next: next !== noop ?
134 (v: T) => predicate(v) ? next(v) : void (0) : noop,
135 error,
136 complete
137 }))
138 ),
139 scan: (accumulator, initial?) => observe(({ next, error, complete }) => {
140 let _acc = initial;
141 return producer(fuse({
142 next: next !== noop ? (v: T) => next(_acc = accumulator(_acc, v)) : noop,
143 error,
144 complete
145 }));
146 })
147 });
@@ -0,0 +1,13
1 import * as t from "tap";
2 import { Baz } from "./mock/Baz";
3
4 t.comment("Declare tests");
5
6 const baz = new Baz();
7
8 const data: string[] = [];
9 baz.writeHello(data);
10 t.pass("Test complete");
11
12 // tslint:disable-next-line: no-console
13 t.comment(data.join("\n"));
@@ -0,0 +1,52
1 import { observe } from "./observable";
2 import * as t from "tap";
3
4 const subj1 = observe<number>(({ next, complete }) => {
5 next(1);
6 complete();
7 next(2);
8 });
9
10 const consumer1 = {
11 sum: 0,
12 next(v: number) {
13 this.sum += v;
14 }
15 }
16
17 subj1.subscribe(consumer1);
18 t.equal(consumer1.sum, 1, "Should get only one value");
19
20 subj1.subscribe(consumer1);
21 t.equal(consumer1.sum, 2, "Should get the value again");
22
23 const consumer2 = {
24 value: 0,
25 completed: false,
26 next(v: number) { this.value = v; },
27 complete() { this.completed = true; }
28 };
29
30 let maps = 0;
31
32 subj1
33 .map(v => {
34 t.comment("map1: " + v * 2);
35 maps++;
36 return v * 2;
37 })
38 .map (v => {
39 t.comment("map2: " + v * 2);
40 maps++;
41 return v * 2;
42 })
43 .map(v => {
44 t.comment("map3: " + v * 2);
45 maps++;
46 return v * 2
47 })
48 .subscribe(consumer2);
49
50 t.equal(consumer2.value, 8, "Should map");
51 t.equal(maps, 3, "The map chain should not be executed after completion");
52 t.ok(consumer2.completed, "The completion signal should pass through"); No newline at end of file
@@ -0,0 +1,1
1 symbols=local No newline at end of file
@@ -0,0 +1,126
1 plugins {
2 id "org.implab.gradle-typescript" version "1.3.4"
3 id "ivy-publish"
4 }
5
6 def container = "djx-playground"
7
8 configurations {
9 npmLocal
10 }
11
12 dependencies {
13 npmLocal project(":djx")
14 }
15
16 def bundleDir = fileTree(layout.buildDirectory.dir("bundle")) {
17 builtBy "bundle"
18 }
19
20 typescript {
21 compilerOptions {
22 lib = ["es5", "dom", "scripthost", "es2015.promise", "es2015.symbol", "es2015.iterable"]
23 // listFiles = true
24 strict = true
25 types = ["requirejs", "@implab/dojo-typings", "@implab/djx"]
26 module = "amd"
27 it.target = "es5"
28 experimentalDecorators = true
29 noUnusedLocals = false
30 jsx = "react"
31 jsxFactory = "createElement"
32 moduleResolution = "node"
33 // dojo-typings are sick
34 skipLibCheck = true
35 // traceResolution = true
36 // baseUrl = "./"
37 // paths = [ "*": [ "$projectDir/src/typings/*" ] ]
38 // baseUrl = "$projectDir/src/typings"
39 // typeRoots = ["$projectDir/src/typings"]
40 }
41 tscCmd = "$projectDir/node_modules/.bin/tsc"
42 tsLintCmd = "$projectDir/node_modules/.bin/tslint"
43 esLintCmd = "$projectDir/node_modules/.bin/eslint"
44 }
45
46 tasks.matching{ it.name =~ /^configureTs/ }.configureEach {
47 compilerOptions {
48 if (symbols != 'none') {
49 sourceMap = true
50 switch(symbols) {
51 case "local":
52 sourceRoot = ( isWindows ? "file:///" : "file://" ) + it.rootDir
53 break;
54 }
55 }
56 }
57 }
58
59 npmInstall {
60 //npmInstall.dependsOn it
61 dependsOn configurations.npmLocal
62
63 doFirst {
64 configurations.npmLocal.each { f ->
65 exec {
66 commandLine "npm", "install", f, "--save-dev"
67 }
68 }
69 }
70 }
71
72 clean {
73 doFirst {
74 delete "$buildDir/bundle"
75 }
76 }
77
78
79 task processResourcesBundle(type: Copy) {
80 from "src/bundle"
81 into layout.buildDirectory.dir("bundle")
82 }
83
84 task copyModules(type: Copy) {
85 dependsOn npmInstall
86 into layout.buildDirectory.dir("bundle/js");
87
88 def pack = { String jsmod ->
89 into(jsmod) {
90 from npm.module(jsmod)
91 }
92 }
93
94
95 pack("@implab/djx")
96 pack("@implab/core-amd")
97 pack("dojo")
98 pack("dijit")
99 into("rxjs") {
100 from(npm.module("rxjs/dist/bundles"))
101 }
102 from npm.module("requirejs/require.js")
103 }
104
105 task copyApp(type: Copy) {
106 dependsOn assemble
107 from typescript.assemblyDir
108 into layout.buildDirectory.dir("bundle/js/app")
109 }
110
111 task bundle {
112 dependsOn copyModules, processResourcesBundle, copyApp
113 }
114
115 task up(type: Exec) {
116 dependsOn bundle
117 commandLine "podman", "run", "--rm", "-d",
118 "--name", container,
119 "-p", "2078:80",
120 "-v", "$buildDir/bundle:/srv/www/htdocs",
121 "registry.implab.org/implab/apache2:latest"
122 }
123
124 task stop(type: Exec) {
125 commandLine "podman", "stop", container
126 } No newline at end of file
@@ -0,0 +1,170
1 {
2 "name": "@implab/djx-playground",
3 "lockfileVersion": 2,
4 "requires": true,
5 "packages": {
6 "": {
7 "name": "@implab/djx-playground",
8 "dependencies": {
9 "dijit": "1.17.3",
10 "dojo": "1.17.3",
11 "requirejs": "2.3.6",
12 "rxjs": "7.5.6"
13 },
14 "devDependencies": {
15 "@implab/core-amd": "1.4.6",
16 "@implab/djx": "file:../djx/build/npm/package",
17 "@implab/dojo-typings": "1.0.2",
18 "@types/requirejs": "2.1.34",
19 "typescript": "4.8.2"
20 }
21 },
22 "../djx/build/npm/package": {
23 "name": "@implab/djx",
24 "dev": true,
25 "license": "BSD-2-Clause",
26 "peerDependencies": {
27 "@implab/core-amd": "^1.4.0",
28 "dojo": "^1.10.0"
29 }
30 },
31 "node_modules/@implab/core-amd": {
32 "version": "1.4.6",
33 "resolved": "https://registry.npmjs.org/@implab/core-amd/-/core-amd-1.4.6.tgz",
34 "integrity": "sha512-I1RwUAxeiodePpiBzveoHaehMSAyk7NFPPPEvDqfphHBC8yXoXWAaUrp7EcOKEzjXAs7lJQVhNpmjCjIqoj6BQ==",
35 "dev": true,
36 "peerDependencies": {
37 "dojo": "^1.10.0"
38 }
39 },
40 "node_modules/@implab/djx": {
41 "resolved": "../djx/build/npm/package",
42 "link": true
43 },
44 "node_modules/@implab/dojo-typings": {
45 "version": "1.0.2",
46 "resolved": "https://registry.npmjs.org/@implab/dojo-typings/-/dojo-typings-1.0.2.tgz",
47 "integrity": "sha512-/lbcMCHdRoHJLKFcT8xdk1KbGazSlb1pGSDJ406io7iMenPm/XbJYcUti+VzXnn71zOJ8aYpGT12T5L0rfOZNA==",
48 "dev": true
49 },
50 "node_modules/@types/requirejs": {
51 "version": "2.1.34",
52 "resolved": "https://registry.npmjs.org/@types/requirejs/-/requirejs-2.1.34.tgz",
53 "integrity": "sha512-iQLGNE1DyIRYih60B47l/hI5X7J0wAnnRBL6Yn85GUYQg8Fm3wl8kvT6NRwncKroUOSx7/lbAagIFNV7y02DiQ==",
54 "dev": true
55 },
56 "node_modules/dijit": {
57 "version": "1.17.3",
58 "resolved": "https://registry.npmjs.org/dijit/-/dijit-1.17.3.tgz",
59 "integrity": "sha512-QS+1bNhPT+BF9E+iomQSi5qI+o3oUNSx1r5TF8WlGH4LybGZP+IIGJBOO5/41YduBPljVXhY7vaPsgrycxC6UQ==",
60 "dependencies": {
61 "dojo": "1.17.3"
62 }
63 },
64 "node_modules/dojo": {
65 "version": "1.17.3",
66 "resolved": "https://registry.npmjs.org/dojo/-/dojo-1.17.3.tgz",
67 "integrity": "sha512-iWDx1oSfCEDnIrs8cMW7Zh9Fbjgxu8iRagFz+Qi2eya3MXIAxFXKhv2A7dpi+bfpMpFozLwcsLV8URLw6BsHsA=="
68 },
69 "node_modules/requirejs": {
70 "version": "2.3.6",
71 "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz",
72 "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==",
73 "bin": {
74 "r_js": "bin/r.js",
75 "r.js": "bin/r.js"
76 },
77 "engines": {
78 "node": ">=0.4.0"
79 }
80 },
81 "node_modules/rxjs": {
82 "version": "7.5.6",
83 "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz",
84 "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==",
85 "dependencies": {
86 "tslib": "^2.1.0"
87 }
88 },
89 "node_modules/tslib": {
90 "version": "2.4.0",
91 "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
92 "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
93 },
94 "node_modules/typescript": {
95 "version": "4.8.2",
96 "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz",
97 "integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==",
98 "dev": true,
99 "bin": {
100 "tsc": "bin/tsc",
101 "tsserver": "bin/tsserver"
102 },
103 "engines": {
104 "node": ">=4.2.0"
105 }
106 }
107 },
108 "dependencies": {
109 "@implab/core-amd": {
110 "version": "1.4.6",
111 "resolved": "https://registry.npmjs.org/@implab/core-amd/-/core-amd-1.4.6.tgz",
112 "integrity": "sha512-I1RwUAxeiodePpiBzveoHaehMSAyk7NFPPPEvDqfphHBC8yXoXWAaUrp7EcOKEzjXAs7lJQVhNpmjCjIqoj6BQ==",
113 "dev": true,
114 "requires": {}
115 },
116 "@implab/djx": {
117 "version": "file:../djx/build/npm/package",
118 "requires": {}
119 },
120 "@implab/dojo-typings": {
121 "version": "1.0.2",
122 "resolved": "https://registry.npmjs.org/@implab/dojo-typings/-/dojo-typings-1.0.2.tgz",
123 "integrity": "sha512-/lbcMCHdRoHJLKFcT8xdk1KbGazSlb1pGSDJ406io7iMenPm/XbJYcUti+VzXnn71zOJ8aYpGT12T5L0rfOZNA==",
124 "dev": true
125 },
126 "@types/requirejs": {
127 "version": "2.1.34",
128 "resolved": "https://registry.npmjs.org/@types/requirejs/-/requirejs-2.1.34.tgz",
129 "integrity": "sha512-iQLGNE1DyIRYih60B47l/hI5X7J0wAnnRBL6Yn85GUYQg8Fm3wl8kvT6NRwncKroUOSx7/lbAagIFNV7y02DiQ==",
130 "dev": true
131 },
132 "dijit": {
133 "version": "1.17.3",
134 "resolved": "https://registry.npmjs.org/dijit/-/dijit-1.17.3.tgz",
135 "integrity": "sha512-QS+1bNhPT+BF9E+iomQSi5qI+o3oUNSx1r5TF8WlGH4LybGZP+IIGJBOO5/41YduBPljVXhY7vaPsgrycxC6UQ==",
136 "requires": {
137 "dojo": "1.17.3"
138 }
139 },
140 "dojo": {
141 "version": "1.17.3",
142 "resolved": "https://registry.npmjs.org/dojo/-/dojo-1.17.3.tgz",
143 "integrity": "sha512-iWDx1oSfCEDnIrs8cMW7Zh9Fbjgxu8iRagFz+Qi2eya3MXIAxFXKhv2A7dpi+bfpMpFozLwcsLV8URLw6BsHsA=="
144 },
145 "requirejs": {
146 "version": "2.3.6",
147 "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz",
148 "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg=="
149 },
150 "rxjs": {
151 "version": "7.5.6",
152 "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz",
153 "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==",
154 "requires": {
155 "tslib": "^2.1.0"
156 }
157 },
158 "tslib": {
159 "version": "2.4.0",
160 "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
161 "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
162 },
163 "typescript": {
164 "version": "4.8.2",
165 "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz",
166 "integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==",
167 "dev": true
168 }
169 }
170 }
@@ -0,0 +1,17
1 {
2 "name": "@implab/djx-playground",
3 "private": true,
4 "dependencies": {
5 "dijit": "1.17.3",
6 "dojo": "1.17.3",
7 "requirejs": "2.3.6",
8 "rxjs": "7.5.6"
9 },
10 "devDependencies": {
11 "@implab/core-amd": "1.4.6",
12 "@implab/djx": "file:../djx/build/npm/package",
13 "@implab/dojo-typings": "1.0.2",
14 "@types/requirejs": "2.1.34",
15 "typescript": "4.8.2"
16 }
17 }
@@ -0,0 +1,16
1 requirejs.config({
2 baseUrl: "js",
3 packages: [
4 "app",
5 "@implab/djx",
6 "@implab/core-amd",
7 "dojo",
8 "dijit",
9 {
10 name: "rxjs",
11 location: "rxjs",
12 main: "rxjs.umd.min"
13 }
14 ],
15 deps: ["app"]
16 });
@@ -0,0 +1,13
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta charset='utf-8'>
5 <meta http-equiv='X-UA-Compatible' content='IE=edge'>
6 <title>Djx playground</title>
7 <meta name='viewport' content='width=device-width, initial-scale=1'>
8 <script data-main="config.js" src='js/require.js'></script>
9 </head>
10 <body>
11
12 </body>
13 </html> No newline at end of file
@@ -0,0 +1,39
1 .progress-bar * {
2 transition: all 0.5s;
3 }
4
5 .progress-bar {
6
7 transition: all 0.5s;
8 height: 1em;
9 line-height: 0;
10 font-size: 10px;
11 white-space: nowrap;
12 }
13
14 .progress-track {
15 background-color: #888;
16 height: 0.2em;
17 box-shadow: 0 0 0.5em 0 #0004;
18 width: 70%;
19 display: inline-block;
20 vertical-align: middle;
21 }
22
23 .progress-indicator {
24 width: 30%;
25 background-color: #0ae;
26 height: 100%;
27 display: inline-block;
28 vertical-align: middle;
29 box-shadow: 0 0.6em 1em -0.4em #000;
30 }
31
32 .progress-bar:hover .progress-indicator {
33 box-shadow: 0 1em 2em -0.4em #000;
34 }
35
36 .progress-bar:hover .progress-track {
37 background-color: #0ae;
38 box-shadow: 0 0 0.5em 0 #0ae;
39 } No newline at end of file
@@ -0,0 +1,82
1 import { djbase, djclass } from "@implab/djx/declare";
2 import { DjxWidgetBase } from "@implab/djx/tsx/DjxWidgetBase";
3 import { createElement, watch, prop, attach, all, bind, toggleClass } from "@implab/djx/tsx";
4 import ProgressBar from "./ProgressBar";
5 import Button = require("dijit/form/Button");
6 import { interval } from "rxjs";
7
8 @djclass
9 export default class MainWidget extends djbase(DjxWidgetBase) {
10
11 titleNode?: HTMLHeadingElement;
12
13 progressBar?: ProgressBar;
14
15 count = 0;
16
17 showCounter = false;
18
19 counterNode?: HTMLInputElement;
20
21 paused = false;
22
23 render() {
24 const Counter = ({ children }: { children: unknown[] }) => <span>Counter: {children}</span>;
25
26 return <div className="tundra">
27 <h2 ref={attach(this, "titleNode")}>Hi!</h2>
28 <ProgressBar ref={attach(this, "progressBar")} />
29 <section style={{ padding: "10px" }}>
30 {watch(prop(this, "showCounter"), flag => flag &&
31 [
32 <Counter><input ref={all(
33 bind("value", prop(this, "count")
34 .map(x => x*10)
35 .map(String)
36 ),
37 attach(this, "counterNode")
38 )} /> <span>ms</span></Counter>,
39 " | ",
40 <span ref={bind("innerHTML", interval(1000))}></span>,
41 " | ",
42 <Button
43 ref={all(
44 bind("label", prop(this, "paused")
45 .map(x => x ? "Unpause" : "Pause")
46 ),
47 toggleClass("paused", prop(this,"paused"))
48 )}
49 onClick={this._onPauseClick}
50 />
51 ]
52
53 )}
54 </section>
55 <Button onClick={this._onToggleCounterClick}>Toggle counter</Button>
56 </div>;
57 }
58
59 postCreate(): void {
60 super.postCreate();
61
62 const h = setInterval(
63 () => {
64 this.set("count", this.count + 1);
65 },
66 10
67 );
68 this.own({
69 destroy: () => {
70 clearInterval(h);
71 }
72 });
73 }
74
75 private _onPauseClick = () => {
76 this.set("paused", !this.paused);
77 }
78
79 private _onToggleCounterClick = () => {
80 this.set("showCounter", !this.showCounter);
81 }
82 }
@@ -0,0 +1,19
1 import { djbase, djclass } from "@implab/djx/declare";
2 import { createElement } from "@implab/djx/tsx";
3 import { DjxWidgetBase } from "@implab/djx/tsx/DjxWidgetBase";
4 import "@implab/djx/css!./css/ProgressBar.css";
5
6 @djclass
7 export default class ProgressBar extends djbase(DjxWidgetBase) {
8
9 constructor(opts?: Partial<ProgressBar> & ThisType<ProgressBar>, refNode?: Node | string) {
10 super(opts, refNode);
11 }
12 render() {
13 return <div class="progress-bar">
14 <div class="progress-indicator"/>
15 <div class="progress-track"/>
16 </div>;
17 }
18
19 } No newline at end of file
@@ -0,0 +1,7
1 import MainWidget from "./MainWidget";
2 import "@implab/djx/css!dojo/resources/dojo.css"
3 import "@implab/djx/css!dijit/themes/dijit.css"
4 import "@implab/djx/css!dijit/themes/tundra/tundra.css"
5
6 const w = new MainWidget();
7 w.placeAt(document.body); No newline at end of file
1 NO CONTENT: new file 100644
@@ -0,0 +1,16
1 {
2 "compilerOptions": {
3 "moduleResolution": "node",
4 "experimentalDecorators": true,
5 "module": "AMD",
6 "jsx": "react",
7 "jsxFactory": "createElement",
8 "strict": true,
9 "types": [
10 "requirejs",
11 "@implab/djx",
12 "@implab/dojo-typings"
13 ],
14 "skipLibCheck": true
15 }
16 } No newline at end of file
@@ -1,8 +1,15
1 1 plugins {
2 id "org.implab.gradle-typescript" version "1.3.3"
2 id "org.implab.gradle-typescript" version "1.3.4"
3 3 id "ivy-publish"
4 4 }
5 5
6 configurations {
7 "default" {
8 canBeConsumed = true
9 canBeResolved = false
10 }
11 }
12
6 13 typescript {
7 14 compilerOptions {
8 15 lib = ["es5", "dom", "scripthost", "es2015.promise", "es2015.symbol", "es2015.iterable"]
@@ -52,6 +59,19 configureTsTest {
52 59 }
53 60 }
54 61
62 tasks.matching{ it.name =~ /^configureTs/ }.configureEach {
63 compilerOptions {
64 if (symbols != 'none') {
65 sourceMap = true
66 switch(symbols) {
67 case "local":
68 sourceRoot = ( isWindows ? "file:///" : "file://" ) + it.rootDir
69 break;
70 }
71 }
72 }
73 }
74
55 75 npmPackMeta {
56 76 meta {
57 77 name = "@$npmScope/$project.name"
@@ -75,4 +95,10 task printVersion {
75 95 println "module: $typescript.compilerOptions.module";
76 96 println "symbols: $symbols";
77 97 }
98 }
99
100 artifacts {
101 "default" (npm.packageDir) {
102 builtBy npmAssemblePackage
103 }
78 104 } No newline at end of file
@@ -5,4 +5,3 description=Create HyperText with Typesc
5 5 license=BSD-2-Clause
6 6 repository=http://hg.code.sf.net/p/implabjs/djx
7 7 npmScope=implab
8 symbols=pack No newline at end of file
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -23,12 +23,14
23 23 "@types/chai": "4.1.3",
24 24 "@types/requirejs": "2.1.31",
25 25 "@types/yaml": "1.2.0",
26 "@types/tap": "15.0.7",
26 27 "dojo": "1.16.0",
27 28 "@implab/dojo-typings": "1.0.0",
28 29 "eslint": "6.8.0",
29 30 "requirejs": "2.3.6",
30 31 "tslint": "^6.1.3",
31 "typescript": "4.2.4",
32 "yaml": "~1.7.2"
32 "typescript": "4.8.2",
33 "yaml": "~1.7.2",
34 "tap": "16.3.0"
33 35 }
34 36 }
@@ -1,11 +1,15
1 import { Constructor, IDestroyable, IRemovable } from "@implab/core-amd/interfaces";
1 import { Constructor } from "@implab/core-amd/interfaces";
2 2 import { HtmlRendition } from "./tsx/HtmlRendition";
3 3 import { WidgetRendition } from "./tsx/WidgetRendition";
4 import { destroy, isWidgetConstructor, Rendition } from "./tsx/traits";
4 import { isElementNode, isWidget, isWidgetConstructor, Rendition } from "./tsx/traits";
5 5 import { FunctionRendition } from "./tsx/FunctionRendition";
6 6 import Stateful = require("dojo/Stateful");
7 7 import _WidgetBase = require("dijit/_WidgetBase");
8 8 import { DjxWidgetBase } from "./tsx/DjxWidgetBase";
9 import { WatchRendition } from "./tsx/WatchRendition";
10 import { Observable, observe, Subscribable } from "./observable";
11 import djAttr = require("dojo/dom-attr");
12 import djClass = require("dojo/dom-class");
9 13
10 14 export function createElement<T extends Constructor | string | ((props: any) => Element)>(elementType: T, ...args: any[]): Rendition {
11 15 if (typeof elementType === "string") {
@@ -42,9 +46,9 export interface EventSelector {
42 46
43 47 export type DojoMouseEvent<T = any> = MouseEvent & EventSelector & EventDetails<T>;
44 48
45 type StatefulProps<T> = T extends Stateful<infer A> ? A : never;
49 type StatefulProps<T> = T extends Stateful<infer A> ? A :
50 T extends _WidgetBase ? T : never;
46 51
47 type CleanFn = (instance: IRemovable | IDestroyable) => void;
48 52
49 53 /**
50 54 * Observers the property and calls render callback each change.
@@ -52,14 +56,12 type CleanFn = (instance: IRemovable | I
52 56 * @param target The target object which property will be observed.
53 57 * @param prop The name of the property.
54 58 * @param render The callback which will be called every time the value is changed
55 * @param cleanupOrOwner The object with method `own` or an callback to register lifecycle for the observer.
56 59 * @returns Rendition which is created instantly
57 60 */
58 61 export function watch<W extends _WidgetBase, K extends keyof W>(
59 62 target: W,
60 63 prop: K,
61 render: (model: W[K]) => any,
62 cleanupOrOwner?: { own: CleanFn } | CleanFn
64 render: (model: W[K]) => any
63 65 ): Rendition;
64 66 /**
65 67 * Observers the property and calls render callback each change.
@@ -67,34 +69,89 export function watch<W extends _WidgetB
67 69 * @param target The target object which property will be observed.
68 70 * @param prop The name of the property.
69 71 * @param render The callback which will be called every time the value is changed
70 * @param cleanupOrOwner The object with method `own` or an callback to register lifecycle for the observer.
71 72 * @returns Rendition which is created instantly
72 73 */
73 74 export function watch<T extends Stateful, K extends keyof StatefulProps<T>>(
74 75 target: T,
75 76 prop: K,
76 render: (model: StatefulProps<T>[K]) => any,
77 cleanupOrOwner?: { own: CleanFn } | CleanFn
77 render: (model: StatefulProps<T>[K]) => any
78 78 ): Rendition;
79 export function watch<T extends Stateful, K extends keyof StatefulProps<T> & string>(
80 target: T,
81 prop: K,
82 render: (model: StatefulProps<T>[K]) => any,
83 cleanupOrOwner: { own: CleanFn } | CleanFn = () => { }
79 export function watch<V>(subj: Subscribable<V>, render: (model: V) => unknown): Rendition;
80 export function watch(
81 ...args: [Stateful, string, (model: unknown) => unknown] |
82 [Subscribable<unknown>, (model: unknown) => unknown]
84 83 ) {
85 let rendition = new FunctionRendition(() => render(target.get(prop)));
86 const _own = cleanupOrOwner instanceof Function ? cleanupOrOwner : (x: IRemovable) => cleanupOrOwner.own(x);
87 _own(target.watch(prop, (_name, oldValue, newValue) => {
88 if (oldValue !== newValue) {
89 const newRendition = new FunctionRendition(() => render(newValue));
90 newRendition.placeAt(rendition.getDomNode(), "replace");
91 destroy(rendition.getDomNode());
92 rendition = newRendition;
84 if (args.length === 3) {
85 const [target, prop, render] = args;
86 return new WatchRendition(
87 render,
88 observe(({next}) => {
89 const h = target.watch<any>(
90 prop,
91 (_prop, oldValue, newValue) => oldValue !== newValue && next(newValue)
92 );
93 next(target.get(prop));
94 return () => h.remove();
95 })
96 );
97 } else {
98 const [subj, render] = args;
99 return new WatchRendition(render, subj);
100 }
101 }
102
103 export const prop: {
104 <T extends Stateful, K extends string & keyof StatefulProps<T>>(target: T, name: K): Observable<StatefulProps<T>[K]>;
105 <T extends _WidgetBase, K extends keyof T>(target: T, name: K): Observable<T[K]>;
106 } = (target: Stateful, name: string) => {
107 return observe(({next}) => {
108 const h = target.watch(
109 name,
110 (_prop, oldValue, newValue) => oldValue !== newValue && next(newValue)
111 );
112 next(target.get(name));
113 return () => h.remove();
114 })
115 };
116
117 export const attach = <W extends DjxWidgetBase, K extends keyof W>(target: W, name: K) => (v: W[K]) => target.set(name, v);
118
119 export const bind = <K extends string, T>(attr: K, subj: Subscribable<T>) => {
120 let h = { unsubscribe() { } };
121
122 return <E extends (HTMLElement & { [p in K]: T }) | { set(name: K, value: T): void; }>(el: E | undefined) => {
123 if (el) {
124 if (isElementNode(el)) {
125 h = subj.subscribe({
126 next: value => djAttr.set(el, attr, value)
127 });
128 } else {
129 h = subj.subscribe({
130 next: value => el.set(attr, value)
131 });
132 }
133 } else {
134 h.unsubscribe();
93 135 }
94 }));
95 return rendition;
136 }
137 };
138
139 export const toggleClass = (className: string, subj: Subscribable<boolean>) => {
140 let h = { unsubscribe() { } };
141 return (elOrWidget: HTMLElement | _WidgetBase | undefined) => {
142 const el = isWidget(elOrWidget) ? elOrWidget.domNode : elOrWidget;
143 if (el) {
144 h = subj.subscribe({
145 next: v => djClass.toggle(el, className, v)
146 });
147 } else {
148 h.unsubscribe();
149 }
150 }
96 151 }
97 152
153 export const all = <T, A extends JSX.Ref<T>[]>(...cbs: A): JSX.Ref<T> => (arg: T | undefined) => cbs.forEach(cb => cb(arg));
154
98 155 /** Decorates the method which will be registered as the handle for the specified event.
99 156 * This decorator can be applied to DjxWidgetBase subclass methods.
100 157 *
@@ -1,9 +1,11
1 1 import { djbase, djclass } from "../declare";
2 2 import _WidgetBase = require("dijit/_WidgetBase");
3 3 import _AttachMixin = require("dijit/_AttachMixin");
4 import { Rendition, isNode } from "./traits";
4 import { Rendition, isNode, isElementNode } from "./traits";
5 5 import registry = require("dijit/registry");
6 6 import on = require("dojo/on");
7 import { Scope } from "./Scope";
8 import { render } from "./render";
7 9
8 10 // type Handle = dojo.Handle;
9 11
@@ -36,13 +38,20 export interface DjxWidgetBase<Attrs = {
36 38
37 39 type _super = {
38 40 startup(): void;
41
42 destroy(preserveDom?: boolean): void;
39 43 };
40 44
41 45 @djclass
42 46 export abstract class DjxWidgetBase<Attrs = {}, Events = {}> extends djbase<_super, _AttachMixin>(_WidgetBase, _AttachMixin) {
47 private readonly _scope = new Scope();
43 48
44 49 buildRendering() {
45 this.domNode = this.render().getDomNode();
50 const node = render(this.render(), this._scope);
51 if (!isElementNode(node))
52 throw new Error("The render method must return a single DOM element");
53 this.domNode = node as HTMLElement;
54
46 55 super.buildRendering();
47 56
48 57 // now we should get assigned data-dojo-attach-points
@@ -111,4 +120,9 export abstract class DjxWidgetBase<Attr
111 120 registry.findWidgets(this.domNode, this.containerNode).forEach(w => w.startup());
112 121 super.startup();
113 122 }
123
124 destroy(preserveDom?: boolean) {
125 this._scope.destroy();
126 super.destroy(preserveDom);
127 }
114 128 }
@@ -1,4 +1,5
1 1 import { argumentNotNull } from "@implab/core-amd/safe";
2 import { getItemDom } from "./render";
2 3 import { RenditionBase } from "./RenditionBase";
3 4
4 5 export class FunctionRendition extends RenditionBase<Node> {
@@ -15,10 +16,9 export class FunctionRendition extends R
15 16
16 17 protected _create(attrs: object, children: any[]) {
17 18 const _attrs: any = attrs || {};
18 const _children = children.map(x => this.getItemDom(x));
19 this._node = this.getItemDom(
20 this._component.call(null, { ..._attrs, children: _children })
21 );
19 const _children = children.map(x => getItemDom(x));
20 this._node = getItemDom(
21 this._component.call(null, { ..._attrs, children: _children }));
22 22 }
23 23
24 24 protected _getDomNode() {
@@ -1,11 +1,14
1 import dom = require("dojo/dom-construct");
1 import djDom = require("dojo/dom-construct");
2 import djAttr = require("dojo/dom-attr");
2 3 import { argumentNotEmptyString } from "@implab/core-amd/safe";
3 4 import { RenditionBase } from "./RenditionBase";
5 import { placeAt } from "./traits";
6 import { getItemDom, refHook } from "./render";
4 7
5 export class HtmlRendition extends RenditionBase<HTMLElement> {
8 export class HtmlRendition extends RenditionBase<Element> {
6 9 elementType: string;
7 10
8 _element: HTMLElement | undefined;
11 _element: Element | undefined;
9 12
10 13 constructor(elementType: string) {
11 14 argumentNotEmptyString(elementType, "elementType");
@@ -14,16 +17,27 export class HtmlRendition extends Rendi
14 17 this.elementType = elementType;
15 18 }
16 19
17 _addChild(child: any): void {
20 _addChild(child: unknown): void {
18 21 if (!this._element)
19 22 throw new Error("The HTML element isn't created");
20 dom.place(this.getItemDom(child), this._element);
23 placeAt(getItemDom(child), this._element);
21 24 }
22 25
23 _create(attrs: object, children: any[]) {
24 this._element = dom.create(this.elementType, attrs);
26 _create({ xmlns, ref, ...attrs }: { xmlns?: string, ref?: JSX.Ref<Element> }, children: unknown[]) {
27
28 if (xmlns) {
29 this._element = document.createElementNS(xmlns, this.elementType);
30 djAttr.set(this._element, attrs);
31 } else {
32 this._element = djDom.create(this.elementType, attrs);
33 }
25 34
26 35 children.forEach(v => this._addChild(v));
36
37 const element = this._element;
38
39 if (ref)
40 refHook(element, ref);
27 41 }
28 42
29 43 _getDomNode() {
@@ -1,9 +1,4
1 import { isNull, mixin } from "@implab/core-amd/safe";
2 import { isPlainObject, isNode, isRendition, DojoNodePosition, Rendition, isInPage, isWidget, isDocumentFragmentNode, startupWidgets } from "./traits";
3
4 import dom = require("dojo/dom-construct");
5 import registry = require("dijit/registry");
6
1 import { isPlainObject, DojoNodePosition, Rendition, isDocumentFragmentNode, placeAt, collectNodes, isMounted, startupWidgets } from "./traits";
7 2
8 3 export abstract class RenditionBase<TNode extends Node> implements Rendition<TNode> {
9 4 private _attrs = {};
@@ -16,12 +11,12 export abstract class RenditionBase<TNod
16 11 if (this._created)
17 12 throw new Error("The Element is already created");
18 13
19 if (isNull(v) || typeof v === "boolean")
14 if (v === null || v === undefined || typeof v === "boolean")
20 15 // skip null, undefined, booleans ( this will work: {value && <span>{value}</span>} )
21 16 return;
22 17
23 18 if (isPlainObject(v)) {
24 mixin(this._attrs, v);
19 this._attrs = {... this._attrs, ...v};
25 20 } else if (v instanceof Array) {
26 21 v.forEach(x => this.visitNext(x));
27 22 } else {
@@ -29,31 +24,6 export abstract class RenditionBase<TNod
29 24 }
30 25 }
31 26
32 /** Renders DOM element for different types of the argument. */
33 protected getItemDom(v: any) {
34 const tv = typeof v;
35
36 if (tv === "string" || tv === "number" || v instanceof RegExp || v instanceof Date) {
37 // primitive types converted to the text nodes
38 return document.createTextNode(v.toString());
39 } else if (isNode(v)) {
40 // nodes are kept as is
41 return v;
42 } else if (isRendition(v)) {
43 // renditions are instantiated
44 return v.getDomNode();
45 } else if (isWidget(v)) {
46 // widgets are converted to it's markup
47 return v.domNode;
48 } else if (tv === "boolean" || v === null || v === undefined) {
49 // null | undefined | boolean are removed, converted to comments
50 return document.createComment(`[${tv} ${String(v)}]`);
51 } else {
52 // bug: explicit error otherwise
53 throw new Error("Invalid parameter: " + v);
54 }
55 }
56
57 27 ensureCreated() {
58 28 if (!this._created) {
59 29 this._create(this._attrs, this._children);
@@ -82,36 +52,19 export abstract class RenditionBase<TNod
82 52 * @param {DojoNodePosition} position Optional parameter, specifies the
83 53 * position relative to refNode. Default is "last" (i.e. last child).
84 54 */
85 placeAt(refNode: string | Node, position?: DojoNodePosition) {
55 placeAt(refNode: string | Node, position: DojoNodePosition = "last") {
86 56 const domNode = this.getDomNode();
87 57
88 const collect = (collection: HTMLCollection) => {
89 const items = [];
90 for (let i = 0, n = collection.length; i < n; i++) {
91 items.push(collection[i]);
92 }
93 return items;
94 };
58 const startupPending = isDocumentFragmentNode(domNode) ? collectNodes(domNode.childNodes) : [domNode];
95 59
96 const startup = (node: Node) => {
97 if (node.parentNode) {
98 const parentWidget = registry.getEnclosingWidget(node.parentNode);
99 if (parentWidget && parentWidget._started)
100 return startupWidgets(node);
101 }
102 if (isInPage(node))
103 startupWidgets(node);
104 };
60 placeAt(domNode, refNode, position);
105 61
106 const startupPending = isDocumentFragmentNode(domNode) ? collect(domNode.children) : [domNode];
107
108 dom.place(domNode, refNode, position);
109
110 startupPending.forEach(startup);
62 if (isMounted(startupPending[0]))
63 startupPending.forEach(n => startupWidgets(n));
111 64
112 65 }
113 66
114 protected abstract _create(attrs: object, children: any[]): void;
67 protected abstract _create(attrs: object, children: unknown[]): void;
115 68
116 69 protected abstract _getDomNode(): TNode;
117 70 }
@@ -1,8 +1,9
1 1 import { IDestroyable, IRemovable } from "@implab/core-amd/interfaces";
2 2 import { isDestroyable, isRemovable } from "@implab/core-amd/safe";
3 import { isUnsubsribable, Unsubscribable } from "../observable";
3 4
4 5 export interface IScope {
5 own(target: (() => void) | IDestroyable | IRemovable): void;
6 own(target: (() => void) | IDestroyable | IRemovable | Unsubscribable): void;
6 7 }
7 8
8 9 export class Scope implements IDestroyable, IScope {
@@ -10,13 +11,15 export class Scope implements IDestroyab
10 11
11 12 static readonly dummy: IScope = { own() { } };
12 13
13 own(target: (() => void) | IDestroyable | IRemovable) {
14 own(target: (() => void) | IDestroyable | IRemovable | Unsubscribable) {
14 15 if (target instanceof Function) {
15 16 this._cleanup.push(target);
16 17 } else if (isDestroyable(target)) {
17 18 this._cleanup.push(() => target.destroy());
18 19 } else if (isRemovable(target)) {
19 20 this._cleanup.push(() => target.remove());
21 } else if (isUnsubsribable(target)) {
22 this._cleanup.push(() => target.unsubscribe());
20 23 }
21 24 }
22 25
@@ -1,51 +1,90
1 1 import { id as mid } from "module";
2 2 import { TraceSource } from "@implab/core-amd/log/TraceSource";
3 3 import { argumentNotNull } from "@implab/core-amd/safe";
4 import { place } from "dojo/dom-construct";
5 import { getScope, render } from "./Renderer";
4 import { getScope, render } from "./render";
6 5 import { RenditionBase } from "./RenditionBase";
7 6 import { Scope } from "./Scope";
8 import { locateNode } from "./traits";
7 import { Subscribable } from "../observable";
8 import { Cancellation } from "@implab/core-amd/Cancellation";
9 import { collectNodes, destroy, isDocumentFragmentNode, isMounted, placeAt, startupWidgets } from "./traits";
9 10
10 11 const trace = TraceSource.get(mid);
11 12
12 13 export class WatchRendition<T> extends RenditionBase<Node> {
13 private readonly _factory: (arg: T) => any;
14 private readonly _component: (arg: T) => unknown;
14 15
15 private _node: Node;
16 private readonly _node: Node;
16 17
17 18 private readonly _scope = new Scope();
18 19
19 constructor(component: (arg: T) => any, subject: any) {
20 private readonly _subject: Subscribable<T>;
21
22 private _renderJob?: { value: T };
23
24 private _ct = Cancellation.none;
25
26 constructor(component: (arg: T) => unknown, subject: Subscribable<T>) {
20 27 super();
21 28 argumentNotNull(component, "component");
22 29
23 this._factory = component;
30 this._component = component;
31
32 this._subject = subject;
33
34 this._node = document.createComment("[Watch]");
35 }
24 36
25 this._node = document.createComment("WatchRendition placeholder");
37 protected _create() {
38 const scope = getScope();
39 scope.own(() => {
40 this._scope.destroy();
41 destroy(this._node);
42 });
43 scope.own(this._subject.subscribe({ next: this._onValue }));
44 this._ct = new Cancellation(cancel => scope.own(cancel));
26 45 }
27 46
28 protected _create(attrs: object, children: any[]) {
29 const _attrs: any = attrs || {};
30 const _children = children.map(x => this.getItemDom(x));
31 this._node = this.getItemDom(
32 this._factory.call(null, { ..._attrs, children: _children })
47 private _onValue = (value: T) => {
48 if (!this._renderJob) {
49 // schedule a new job
50 this._renderJob = { value };
51 this._render().catch(e => trace.error(e));
52 } else {
53 // update existing job
54 this._renderJob = { value };
55 }
56 }
57
58 private async _render() {
59 // fork
60 await Promise.resolve();
61 // don't render destroyed rendition
62 if (this._ct.isRequested())
63 return;
64
65 // remove all previous content
66 this._scope.clean();
67
68 // render the new node
69 const node = render(
70 this._renderJob ? this._component(this._renderJob.value) : undefined,
71 this._scope
33 72 );
34 73
35 const scope = getScope();
36 scope.own(this._scope);
74 // get actual content
75 const pending = isDocumentFragmentNode(node) ?
76 collectNodes(node.childNodes) :
77 [node];
37 78
38 // если отрендерили текст? или DocumentFragment
39 }
79 placeAt(node, this._node, "after");
40 80
41 private async _render(value: T) {
42 const [refNode, position] = locateNode(this._node);
43 this._scope.clean();
81 if (isMounted(this._node))
82 pending.forEach(n => startupWidgets(n));
44 83
45 this._node = await render(() => this._factory(value), this._scope);
84 if (pending.length)
85 this._scope.own(() => pending.forEach(destroy));
46 86
47 if (refNode)
48 place(this._node, refNode, position);
87 this._renderJob = undefined;
49 88 }
50 89
51 90 protected _getDomNode() {
@@ -1,9 +1,9
1 import dom = require("dojo/dom-construct");
2 1 import { argumentNotNull } from "@implab/core-amd/safe";
3 2 import { RenditionBase } from "./RenditionBase";
4 import { DojoNodePosition, isElementNode, isInPage, isWidget } from "./traits";
3 import { DojoNodePosition, isElementNode, isInPage, isWidget, placeAt } from "./traits";
5 4 import registry = require("dijit/registry");
6 5 import ContentPane = require("dijit/layout/ContentPane");
6 import { getItemDom, refHook } from "./render";
7 7
8 8 // tslint:disable-next-line: class-name
9 9 export interface _Widget {
@@ -14,10 +14,10 export interface _Widget {
14 14 placeAt?(refNode: string | Node, position?: DojoNodePosition): void;
15 15 startup?(): void;
16 16
17 addChild?(widget: any, index?: number): void;
17 addChild?(widget: unknown, index?: number): void;
18 18 }
19 19
20 export type _WidgetCtor = new (attrs: any, srcNode?: string | Node) => _Widget;
20 export type _WidgetCtor = new (attrs: {}, srcNode?: string | Node) => _Widget;
21 21
22 22 export class WidgetRendition extends RenditionBase<Node> {
23 23 readonly widgetClass: _WidgetCtor;
@@ -31,7 +31,7 export class WidgetRendition extends Ren
31 31 this.widgetClass = widgetClass;
32 32 }
33 33
34 _addChild(child: any): void {
34 _addChild(child: unknown): void {
35 35 const instance = this._getInstance();
36 36
37 37 if (instance.addChild) {
@@ -41,7 +41,7 export class WidgetRendition extends Ren
41 41 } else if (isWidget(child)) {
42 42 instance.addChild(child);
43 43 } else {
44 const childDom = this.getItemDom(child);
44 const childDom = getItemDom(child);
45 45 const w = isElementNode(childDom) ? registry.byNode(childDom) : undefined;
46 46
47 47 if (w) {
@@ -51,7 +51,7 export class WidgetRendition extends Ren
51 51 throw new Error("Failed to add DOM content. The widget doesn't have a containerNode");
52 52
53 53 // the current widget isn't started, it's children shouldn't start too
54 dom.place(this.getItemDom(child), instance.containerNode);
54 placeAt(getItemDom(child), instance.containerNode, "last");
55 55 }
56 56 }
57 57 } else {
@@ -59,20 +59,20 export class WidgetRendition extends Ren
59 59 throw new Error("The widget doesn't have neither addChild nor containerNode");
60 60
61 61 // the current widget isn't started, it's children shouldn't start too
62 dom.place(this.getItemDom(child), instance.containerNode);
62 placeAt(getItemDom(child), instance.containerNode, "last");
63 63 }
64 64 }
65 65
66 protected _create(attrs: any, children: any[]) {
66 protected _create({ref, ...attrs}: {ref?: JSX.Ref<_Widget>}, children: unknown[]) {
67 67 if (this.widgetClass.prototype instanceof ContentPane) {
68 68 // a special case for the ContentPane this is for
69 // the compatibility with this heavy widget, all
69 // compatibility with that heavy widget, all
70 70 // regular containers could be easily manipulated
71 71 // through `containerNode` property or `addChild` method.
72 72
73 73 // render children to the DocumentFragment
74 74 const content = document.createDocumentFragment();
75 children.forEach(child => content.appendChild(this.getItemDom(child)));
75 children.forEach(child => content.appendChild(getItemDom(child)));
76 76
77 77 // set the content property to the parameters of the widget
78 78 const _attrs = { ...attrs, content };
@@ -82,6 +82,8 export class WidgetRendition extends Ren
82 82 children.forEach(x => this._addChild(x));
83 83 }
84 84
85 if (ref)
86 refHook(this._instance, ref);
85 87 }
86 88
87 89 private _getInstance() {
@@ -1,26 +1,112
1 import { Scope } from "./Scope";
2 import { destroy, Rendition } from "./traits";
1 import { TraceSource } from "@implab/core-amd/log/TraceSource";
2 import { isPromise } from "@implab/core-amd/safe";
3 import { id as mid } from "module";
4 import { IScope, Scope } from "./Scope";
5 import { isNode, isRendition, isWidget } from "./traits";
6
7 const trace = TraceSource.get(mid);
3 8
4 let _scope = Scope.dummy;
9 interface Context {
10 scope: IScope;
5 11
6 const beginRender = async () => {
12 hooks?: (() => void)[];
13 }
14
15 let _context: Context = {
16 scope: Scope.dummy
7 17 }
8 18
9 const endRender = () => {
19 const guard = (cb: () => unknown) => {
20 try {
21 const result = cb()
22 if (isPromise(result)) {
23 const warn = (ret: unknown) => trace.error("The callback {0} competed asynchronously. result = {1}", cb, ret);
24 result.then(warn, warn);
25 }
26 } catch (e) {
27 trace.error(e);
28 }
29 }
30
31 export const beginRender = (scope: IScope = getScope()) => {
32 const prev = _context;
33 _context = {
34 scope,
35 hooks: []
36 };
37 return endRender(prev);
38 }
39
40 /**
41 * Completes render operation
42 */
43 const endRender = (prev: Context) => () => {
44 const { hooks } = _context;
45 if (hooks)
46 hooks.forEach(guard);
47
48 _context = prev;
10 49 }
11 50
12 export const getScope = () => _scope;
51 export const renderHook = (hook: () => void) => {
52 const { hooks } = _context;
53 if (hooks)
54 hooks.push(hook);
55 else
56 guard(hook);
57 }
13 58
14 export const render = async (rendition: () => Rendition, scope = Scope.dummy) => {
15 await beginRender();
16 const prev = _scope;
17 _scope = scope;
59 export const refHook = <T>(value: T, ref: JSX.Ref<T>) => {
60 const { hooks, scope } = _context;
61 if (hooks)
62 hooks.push(() => ref(value));
63 else
64 guard(() => ref(value));
65
66 scope.own(() => ref(undefined));
67 }
68
69 /** Returns the current scope */
70 export const getScope = () => _context.scope;
71
72 /** Schedules the rendition to be rendered to the DOM Node
73 * @param rendition The rendition to be rendered
74 * @param scope The scope
75 */
76 export const render = (rendition: unknown, scope = Scope.dummy) => {
77 const complete = beginRender(scope);
18 78 try {
19 const node = rendition().getDomNode();
20 scope.own(() => destroy(node));
21 return node;
79 return getItemDom(rendition);
22 80 } finally {
23 _scope = prev;
24 endRender();
81 complete();
25 82 }
26 83 }
84
85 /** Renders DOM element for different types of the argument. */
86 export const getItemDom = (v: unknown) => {
87 if (typeof v === "string" || typeof v === "number" || v instanceof RegExp || v instanceof Date) {
88 // primitive types converted to the text nodes
89 return document.createTextNode(v.toString());
90 } else if (isNode(v)) {
91 // nodes are kept as is
92 return v;
93 } else if (isRendition(v)) {
94 // renditions are instantiated
95 return v.getDomNode();
96 } else if (isWidget(v)) {
97 // widgets are converted to it's markup
98 return v.domNode;
99 } else if (typeof v === "boolean" || v === null || v === undefined) {
100 // null | undefined | boolean are removed
101 return document.createDocumentFragment();
102 } else if (v instanceof Array) {
103 // arrays will be translated to document fragments
104 const fragment = document.createDocumentFragment();
105 v.map(item => getItemDom(item))
106 .forEach(node => fragment.appendChild(node));
107 return fragment;
108 } else {
109 // bug: explicit error otherwise
110 throw new Error("Invalid parameter: " + v);
111 }
112 }
@@ -2,13 +2,15 import { IDestroyable } from "@implab/co
2 2 import { isDestroyable } from "@implab/core-amd/safe";
3 3 import _WidgetBase = require("dijit/_WidgetBase");
4 4 import registry = require("dijit/registry");
5 import dom = require("dojo/dom-construct");
6 5
7 type _WidgetBaseConstructor = typeof _WidgetBase;
6 interface _WidgetBaseConstructor {
7 new <A = {}, E extends { [k in keyof E]: Event } = {}>(params?: Partial<_WidgetBase<E> & A>, srcNodeRef?: dojo.NodeOrString): _WidgetBase<E> & dojo._base.DeclareCreatedObject;
8 prototype: _WidgetBase<any>;
9 }
8 10
9 11 export type DojoNodePosition = "first" | "after" | "before" | "last" | "replace" | "only" | number;
10 12
11 export type DojoNodeLocation = [Node | null, DojoNodePosition];
13 export type DojoNodeLocation = [Node, DojoNodePosition];
12 14
13 15 export interface Rendition<TNode extends Node = Node> {
14 16 getDomNode(): TNode;
@@ -25,52 +27,32 export interface IRecursivelyDestroyable
25 27 destroyRecursive(): void;
26 28 }
27 29
28 export function isNode(el: any): el is Node {
29 return el && el.nodeName && el.nodeType;
30 }
30 export const isNode = (el: unknown): el is Node => !!(el && (el as Node).nodeName && (el as Node).nodeType);
31 31
32 export function isElementNode(el: any): el is Element {
33 return isNode(el) && el.nodeType === 1;
34 }
32 export const isElementNode = (el: unknown): el is Element => isNode(el) && el.nodeType === 1;
35 33
36 export function isTextNode(el: any): el is Text {
37 return isNode(el) && el.nodeType === 3;
38 }
34 export const isTextNode = (el: unknown): el is Text => isNode(el) && el.nodeType === 3;
39 35
40 export function isProcessingInstructionNode(el: any): el is ProcessingInstruction {
41 return isNode(el) && el.nodeType === 7;
42 }
36 export const isProcessingInstructionNode = (el: unknown): el is ProcessingInstruction => isNode(el) && el.nodeType === 7;
43 37
44 export function isCommentNode(el: any): el is Comment {
45 return isNode(el) && el.nodeType === 8;
46 }
38 export const isCommentNode = (el: unknown): el is Comment => isNode(el) && el.nodeType === 8;
47 39
48 export function isDocumentNode(el: any): el is Document {
49 return isNode(el) && el.nodeType === 9;
50 }
40 export const isDocumentNode = (el: unknown): el is Document => isNode(el) && el.nodeType === 9;
51 41
52 export function isDocumentTypeNode(el: any): el is DocumentType {
53 return isNode(el) && el.nodeType === 10;
54 }
42 export const isDocumentTypeNode = (el: unknown): el is DocumentType => isNode(el) && el.nodeType === 10;
55 43
56 export function isDocumentFragmentNode(el: any): el is DocumentFragment {
57 return isNode(el) && el.nodeType === 11;
58 }
44 export const isDocumentFragmentNode = (el: any): el is DocumentFragment => isNode(el) && el.nodeType === 11;
59 45
60 export function isWidget(v: any): v is _WidgetBase {
61 return v && "domNode" in v;
62 }
46 export const isWidget = (v: unknown): v is _WidgetBase => !!(v && "domNode" in (v as _WidgetBase));
63 47
64 export function isRendition(v: any): v is Rendition {
65 return v && typeof v.getDomElement === "function";
66 }
48 export const isRendition = (v: unknown): v is Rendition => !!(v && typeof (v as Rendition).getDomNode === "function");
67 49
68 50 /**
69 51 * @deprecated use isRendition
70 52 */
71 53 export const isBuildContext = isRendition;
72 54
73 export function isPlainObject(v: object) {
55 export const isPlainObject = (v: object) => {
74 56 if (typeof v !== "object")
75 57 return false;
76 58
@@ -78,23 +60,21 export function isPlainObject(v: object)
78 60 return !vp || vp === Object.prototype;
79 61 }
80 62
81 export function isWidgetConstructor(v: any): v is _WidgetBaseConstructor {
82 return typeof v === "function" && v.prototype && (
63 export const isWidgetConstructor = (v: unknown): v is _WidgetBaseConstructor =>
64 typeof v === "function" && v.prototype && (
83 65 "domNode" in v.prototype ||
84 66 "buildRendering" in v.prototype
85 67 );
86 }
68
87 69
88 70 /** Tests whether the specified node is placed in visible dom.
89 71 * @param {Node} node The node to test
90 72 */
91 export function isInPage(node: Node) {
92 return (node === document.body) ? false : document.body.contains(node);
93 }
73 export const isInPage = (node: Node) => node === document.body ? false : document.body.contains(node);
94 74
95 export function isRecursivelyDestroyable(target: any): target is IRecursivelyDestroyable {
96 return target && typeof target.destroyRecursive === "function";
97 }
75 export const isRecursivelyDestroyable = (target: unknown): target is IRecursivelyDestroyable =>
76 !!(target && typeof (target as IRecursivelyDestroyable).destroyRecursive === "function");
77
98 78
99 79
100 80 /** Destroys DOM Node with all contained widgets.
@@ -103,7 +83,7 export function isRecursivelyDestroyable
103 83 *
104 84 * @param target DOM Node or widget to destroy
105 85 */
106 export function destroy(target: Node | IDestroyable | IRecursivelyDestroyable) {
86 export const destroy = (target: Node | IDestroyable | IRecursivelyDestroyable) => {
107 87 if (isRecursivelyDestroyable(target)) {
108 88 target.destroyRecursive();
109 89 } else if (isDestroyable(target)) {
@@ -113,21 +93,28 export function destroy(target: Node | I
113 93 const w = registry.byNode(target);
114 94 if (w) {
115 95 w.destroyRecursive();
96 return;
116 97 } else {
117 registry.findWidgets(target).forEach(destroy);
118 dom.destroy(target);
98 emptyNode(target);
119 99 }
120 100 }
101 const parent = target.parentNode;
102 if (parent)
103 parent.removeChild(target);
104
121 105 }
122 106 }
123 107
124 108 /** Empties a content of the specified node and destroys all contained widgets.
125 109 *
126 * @param target DOM node to .
110 * @param target DOM node to empty.
127 111 */
128 export function emptyNode(target: Node) {
112 export const emptyNode = (target: Node) => {
129 113 registry.findWidgets(target).forEach(destroy);
130 dom.empty(target);
114
115 for (let c; c = target.lastChild;) { // intentional assignment
116 target.removeChild(c);
117 }
131 118 }
132 119
133 120 /** This function starts all widgets inside the DOM node if the target is a node
@@ -136,7 +123,7 export function emptyNode(target: Node)
136 123 *
137 124 * @param target DOM node to find and start widgets or the widget itself.
138 125 */
139 export function startupWidgets(target: Node | _WidgetBase, skipNode?: Node) {
126 export const startupWidgets = (target: Node | _WidgetBase, skipNode?: Node) => {
140 127 if (isNode(target)) {
141 128 if (isElementNode(target)) {
142 129 const w = registry.byNode(target);
@@ -153,9 +140,75 export function startupWidgets(target: N
153 140 }
154 141 }
155 142
156 export function locateNode(node: Node): DojoNodeLocation {
157 const next = node.nextSibling;
158 return next ?
159 [next, "before"] :
160 [node.parentNode, "last"];
161 } No newline at end of file
143 /** Places the specified DOM node at the specified location.
144 *
145 * @param node The node which should be placed
146 * @param refNodeOrId The reference node where the created
147 * DOM should be placed.
148 * @param position Optional parameter, specifies the
149 * position relative to refNode. Default is "last" (i.e. last child).
150 */
151 export const placeAt = (node: Node, refNodeOrId: string | Node, position: DojoNodePosition = "last") => {
152 const ref = typeof refNodeOrId == "string" ? document.getElementById(refNodeOrId) : refNodeOrId;
153 if (!ref)
154 return;
155
156 const parent = ref.parentNode;
157
158 if (typeof position == "number") {
159 if (ref.childNodes.length <= position) {
160 ref.appendChild(node);
161 } else {
162 ref.insertBefore(node, ref.childNodes[position]);
163 }
164 } else {
165 switch (position) {
166 case "before":
167 parent && parent.insertBefore(node, ref);
168 break;
169 case "after":
170 parent && parent.insertBefore(node, ref.nextSibling);
171 break;
172 case "first":
173 ref.insertBefore(node, ref.firstChild);
174 break;
175 case "last":
176 ref.appendChild(node);
177 break;
178 case "only":
179 emptyNode(ref);
180 ref.appendChild(node);
181 break;
182 case "replace":
183 if (parent)
184 parent.replaceChild(node, ref);
185 destroy(ref);
186 break;
187 }
188 }
189 }
190
191 /** Collects nodes from collection to an array.
192 *
193 * @param collection The collection of nodes.
194 * @returns The array of nodes.
195 */
196 export const collectNodes = (collection: NodeListOf<ChildNode>) => {
197 const items = [];
198 for (let i = 0, n = collection.length; i < n; i++) {
199 items.push(collection[i]);
200 }
201 return items;
202 };
203
204
205 export const isMounted = (node: Node) => {
206 if (node.parentNode) {
207 const parentWidget = registry.getEnclosingWidget(node.parentNode);
208 if (parentWidget && parentWidget._started)
209 return true;
210 }
211 if (isInPage(node))
212 return true;
213 return false;
214 }; No newline at end of file
1 NO CONTENT: file renamed from djx/src/main/typings/css.d.ts to djx/src/main/typings/css-plugin.d.ts
@@ -1,8 +1,10
1 /// <reference path="./css.d.ts"/>
1 /// <reference path="./css-plugin.d.ts"/>
2 2
3 3 declare namespace JSX {
4 4
5 interface DjxIntrinsicAttributes {
5 type Ref<T> = ((value: T | undefined) => void);
6
7 interface DjxIntrinsicAttributes<E> {
6 8 /** alias for className */
7 9 class: string;
8 10
@@ -14,6 +16,8 declare namespace JSX {
14 16 /** specifies handlers map for the events */
15 17 "data-dojo-attach-event": string;
16 18
19 ref: Ref<E>;
20
17 21 /** @deprecated */
18 22 [attr: string]: any;
19 23 }
@@ -56,7 +60,7 declare namespace JSX {
56 60
57 61 type LaxElement<E extends object> =
58 62 Pick<E, AssignableElementAttrNames<E>> &
59 DjxIntrinsicAttributes;
63 DjxIntrinsicAttributes<E>;
60 64
61 65 type LaxIntrinsicElementsMap = {
62 66 [tag in keyof HTMLElementTagNameMap]: LaxElement<HTMLElementTagNameMap[tag]>
@@ -65,4 +69,13 declare namespace JSX {
65 69 type IntrinsicElements = {
66 70 [tag in keyof LaxIntrinsicElementsMap]: RecursivePartial<LaxIntrinsicElementsMap[tag]>;
67 71 }
72
73 interface ElementChildrenAttribute {
74 children: {};
75 }
76
77 interface IntrinsicClassAttributes<T> {
78 ref?: (value: T) => void;
79 children?: unknown;
80 }
68 81 }
@@ -1,1 +1,2
1 import "./DeclareTests"; No newline at end of file
1 import "./declare-tests";
2 import "./observable-tests"; No newline at end of file
@@ -30,10 +30,10 export class MyWidget extends djbase(Djx
30 30 counter = 0;
31 31
32 32 render() {
33 const Frame = (props: any) => <div>{props.children}</div>;
33 const Frame = ({children, ref}: {ref: JSX.Ref<HTMLDivElement>, children: any[]}) => <div ref={ref} >{children}</div>;
34 34 return <div className="myWidget" onsubmit={e => this._onSubmit(e)} tabIndex={3} style={{ alignContent: "center", border: "1px solid" }} >
35 35 <h1 data-dojo-attach-point="titleNode"></h1>
36 <Frame>
36 <Frame ref={ v => {}}>
37 37 <span class="up-button" onclick={e => this._onIncClick(e)}>[+]</span>
38 38 <span class="down-button" onclick={() => this._onDecClick()}>[-]</span>
39 39 </Frame>
1 NO CONTENT: modified file, binary diff hidden
@@ -1,5 +1,5
1 1 distributionBase=GRADLE_USER_HOME
2 2 distributionPath=wrapper/dists
3 distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-bin.zip
3 distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
4 4 zipStoreBase=GRADLE_USER_HOME
5 5 zipStorePath=wrapper/dists
@@ -154,19 +154,19 if [ "$cygwin" = "true" -o "$msys" = "tr
154 154 else
155 155 eval `echo args$i`="\"$arg\""
156 156 fi
157 i=$((i+1))
157 i=`expr $i + 1`
158 158 done
159 159 case $i in
160 (0) set -- ;;
161 (1) set -- "$args0" ;;
162 (2) set -- "$args0" "$args1" ;;
163 (3) set -- "$args0" "$args1" "$args2" ;;
164 (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
160 0) set -- ;;
161 1) set -- "$args0" ;;
162 2) set -- "$args0" "$args1" ;;
163 3) set -- "$args0" "$args1" "$args2" ;;
164 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 170 esac
171 171 fi
172 172
@@ -175,14 +175,9 save () {
175 175 for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176 176 echo " "
177 177 }
178 APP_ARGS=$(save "$@")
178 APP_ARGS=`save "$@"`
179 179
180 180 # Collect all arguments for the java command, following the shell quoting and substitution rules
181 181 eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 182
183 # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
184 if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
185 cd "$(dirname "$0")"
186 fi
187
188 183 exec "$JAVACMD" "$@"
@@ -14,4 +14,5
14 14
15 15 rootProject.name = 'implabjs-djx'
16 16
17 include 'djx' No newline at end of file
17 include 'djx'
18 include 'playground' No newline at end of file
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now