##// 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,78 +1,104
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"]
9 16 // listFiles = true
10 17 declaration = true
11 18 strict = true
12 19 types = []
13 20 module = "amd"
14 21 it.target = "es5"
15 22 experimentalDecorators = true
16 23 noUnusedLocals = false
17 24 jsx = "react"
18 25 jsxFactory = "createElement"
19 26 moduleResolution = "node"
20 27 // dojo-typings are sick
21 28 skipLibCheck = true
22 29 // traceResolution = true
23 30 // baseUrl = "./"
24 31 // paths = [ "*": [ "$projectDir/src/typings/*" ] ]
25 32 // baseUrl = "$projectDir/src/typings"
26 33 // typeRoots = ["$projectDir/src/typings"]
27 34 }
28 35
29 36 tscCmd = "$projectDir/node_modules/.bin/tsc"
30 37 tsLintCmd = "$projectDir/node_modules/.bin/tslint"
31 38 esLintCmd = "$projectDir/node_modules/.bin/eslint"
32 39 }
33 40
34 41 configureTsMain {
35 42 sourceFiles {
36 43 from sources.main.typings
37 44 }
38 45 compilerOptions {
39 46 // baseUrl = "$projectDir/src"
40 47 /*paths = [
41 48 "dojo/*" : [ "typings/dojo/*" ],
42 49 "dijit/*" : [ "typings/dijit/*" ]
43 50 ]*/
44 51 types = ["requirejs", "@implab/dojo-typings"]
45 52 }
46 53 }
47 54
48 55 configureTsTest {
49 56 compilerOptions {
50 57 typeRoots = []
51 58 types = ["requirejs", sources.main.output.typingsDir.get().toString() ]
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"
58 78 }
59 79 }
60 80
61 81 task npmPackTypings(type: Copy) {
62 82 dependsOn typings
63 83
64 84 npmPackContents.dependsOn it
65 85
66 86 from typescript.typingsDir
67 87 into npm.packageDir
68 88 }
69 89
70 90 task printVersion {
71 91 doLast {
72 92 println "packageName: ${npmPackMeta.metadata.get().name}";
73 93 println "version: $version";
74 94 println "target: $typescript.compilerOptions.target";
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
@@ -1,8 +1,7
1 1 group=org.implab.implabjs
2 2 version=
3 3 author=Implab team
4 4 description=Create HyperText with Typescript, integrate Dojo1 widgets in your .tsx scripts.
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
@@ -1,34 +1,36
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.0",
19 19 "dojo": "^1.10.0"
20 20 },
21 21 "devDependencies": {
22 22 "@implab/core-amd": "^1.4.0",
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,119 +1,176
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") {
12 16 const ctx = new HtmlRendition(elementType);
13 17 if (args)
14 18 args.forEach(x => ctx.visitNext(x));
15 19
16 20 return ctx;
17 21 } else if (isWidgetConstructor(elementType)) {
18 22 const ctx = new WidgetRendition(elementType);
19 23 if (args)
20 24 args.forEach(x => ctx.visitNext(x));
21 25
22 26 return ctx;
23 27 } else if (typeof elementType === "function") {
24 28 const ctx = new FunctionRendition(elementType as (props: any) => Element);
25 29 if (args)
26 30 args.forEach(x => ctx.visitNext(x));
27 31
28 32 return ctx;
29 33 } else {
30 34 throw new Error(`The element type '${elementType}' is unsupported`);
31 35 }
32 36 }
33 37
34 38 export interface EventDetails<T = any> {
35 39 detail: T;
36 40 }
37 41
38 42 export interface EventSelector {
39 43 selectorTarget: HTMLElement;
40 44 target: HTMLElement;
41 45 }
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.
51 55 *
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.
66 68 *
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 }
93 101 }
94 }));
95 return rendition;
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 });
96 132 }
133 } else {
134 h.unsubscribe();
135 }
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 }
151 }
152
153 export const all = <T, A extends JSX.Ref<T>[]>(...cbs: A): JSX.Ref<T> => (arg: T | undefined) => cbs.forEach(cb => cb(arg));
97 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 *
101 158 * ```
102 159 * @on("click")
103 160 * _onClick(eventObj: MouseEvent) {
104 161 * // ...
105 162 * }
106 163 * ```
107 164 */
108 165 export const on = <E extends string>(...eventNames: E[]) =>
109 166 <K extends string,
110 167 T extends DjxWidgetBase<any, { [p in E]: EV }>,
111 168 EV extends Event
112 169 >(
113 170 target: T,
114 171 key: K,
115 172 _descriptor: TypedPropertyDescriptor<(eventObj: EV) => void> | TypedPropertyDescriptor<() => void>
116 173 ): any => {
117 174 const handlers = eventNames.map(eventName => ({ eventName, handlerMethod: key }));
118 175 target._eventHandlers = target._eventHandlers ? target._eventHandlers.concat(handlers) : handlers;
119 176 };
@@ -1,114 +1,128
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
10 12 export interface EventArgs {
11 13 bubbles?: boolean;
12 14
13 15 cancelable?: boolean;
14 16
15 17 composed?: boolean;
16 18 }
17 19
18 20 export interface DjxWidgetBase<Attrs = {}, Events extends { [name in keyof Events]: Event } = {}> extends
19 21 _WidgetBase<Events> {
20 22
21 23 /** This property is declared only for type inference to work, it is never assigned
22 24 * and should not be used.
23 25 */
24 26 readonly _eventMap: Events & GlobalEventHandlersEventMap;
25 27
26 28 /** The list of pairs of event and method names. When the widget is created all methods from
27 29 * this list will be connected to corresponding events.
28 30 *
29 31 * This property is maintained in the prototype
30 32 */
31 33 _eventHandlers: Array<{
32 34 eventName: string,
33 35 handlerMethod: keyof any;
34 36 }>;
35 37 }
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
49 58 // place the contents of the original srcNode to the containerNode
50 59 const src = this.srcNodeRef;
51 60 const dest = this.containerNode;
52 61
53 62 // the donNode is constructed now we need to connect event handlers
54 63 this._connectEventHandlers();
55 64
56 65 if (src && dest) {
57 66 while (src.firstChild)
58 67 dest.appendChild(src.firstChild);
59 68 }
60 69 }
61 70
62 71 abstract render(): Rendition<HTMLElement>;
63 72
64 73 private _connectEventHandlers() {
65 74 if (this._eventHandlers)
66 75 this._eventHandlers.forEach(({ eventName, handlerMethod }) => {
67 76 const handler = this[handlerMethod as keyof this];
68 77 if (typeof handler === "function")
69 78 on(this.domNode, eventName, handler.bind(this));
70 79 });
71 80 }
72 81
73 82 _processTemplateNode<T extends (Element | Node | _WidgetBase)>(
74 83 baseNode: T,
75 84 getAttrFunc: (baseNode: T, attr: string) => any,
76 85 // tslint:disable-next-line: ban-types
77 86 attachFunc: (node: T, type: string, func?: Function) => dojo.Handle
78 87 ): boolean {
79 88 if (isNode(baseNode)) {
80 89 const w = registry.byNode(baseNode);
81 90 if (w) {
82 91 // from dijit/_WidgetsInTemplateMixin
83 92 this._processTemplateNode(w,
84 93 (n, p) => n.get(p as any), // callback to get a property of a widget
85 94 (widget, type, callback) => {
86 95 if (!callback)
87 96 throw new Error("The callback must be specified");
88 97
89 98 // callback to do data-dojo-attach-event to a widget
90 99 if (type in widget) {
91 100 // back-compat, remove for 2.0
92 101 return widget.connect(widget, type, callback as EventListener);
93 102 } else {
94 103 // 1.x may never hit this branch, but it's the default for 2.0
95 104 return widget.on(type, callback);
96 105 }
97 106
98 107 });
99 108 // don't process widgets internals
100 109 return false;
101 110 }
102 111 }
103 112 return super._processTemplateNode(baseNode, getAttrFunc, attachFunc);
104 113 }
105 114
106 115 /** Starts current widget and all its supporting widgets (placed outside
107 116 * `containerNode`) and child widgets (placed inside `containerNode`)
108 117 */
109 118 startup() {
110 119 // startup supporting widgets
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);
114 127 }
128 }
@@ -1,30 +1,30
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> {
5 6 private _component: (...args: any[]) => any;
6 7
7 8 private _node: Node | undefined;
8 9
9 10 constructor(component: (...args: any[]) => any) {
10 11 super();
11 12 argumentNotNull(component, "component");
12 13
13 14 this._component = component;
14 15 }
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() {
25 25 if (!this._node)
26 26 throw new Error("The instance of the widget isn't created");
27 27 return this._node;
28 28 }
29 29
30 30 }
@@ -1,36 +1,50
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");
12 15 super();
13 16
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() {
30 44 if (!this._element)
31 45 throw new Error("The HTML element isn't created");
32 46
33 47 return this._element;
34 48 }
35 49
36 50 }
@@ -1,117 +1,70
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 = {};
10 5
11 6 private _children = new Array();
12 7
13 8 private _created: boolean = false;
14 9
15 10 visitNext(v: any) {
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 {
28 23 this._children.push(v);
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);
60 30 this._children = [];
61 31 this._attrs = {};
62 32 this._created = true;
63 33 }
64 34 }
65 35
66 36 /** Is rendition was instantiated to the DOM node */
67 37 isCreated() {
68 38 return this._created;
69 39 }
70 40
71 41 /** Creates DOM node if not created. No additional actions are taken. */
72 42 getDomNode() {
73 43 this.ensureCreated();
74 44 return this._getDomNode();
75 45 }
76 46
77 47 /** Creates DOM node if not created, places it to the specified position
78 48 * and calls startup() method for all widgets contained by this node.
79 49 *
80 50 * @param {string | Node} refNode The reference node where the created
81 51 * DOM should be placed.
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,40 +1,43
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 {
9 10 private readonly _cleanup: (() => void)[] = [];
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
23 26 clean() {
24 27 const guard = (cb: () => void) => {
25 28 try {
26 29 cb();
27 30 } catch {
28 31 // guard
29 32 }
30 33 }
31 34
32 35 this._cleanup.forEach(guard);
33 36 this._cleanup.length = 0;
34 37 }
35 38
36 39 destroy() {
37 40 this.clean();
38 41 }
39 42
40 43 } No newline at end of file
@@ -1,58 +1,97
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() {
52 91 if (!this._node)
53 92 throw new Error("The instance of the widget isn't created");
54 93 return this._node;
55 94 }
56 95
57 96
58 97 }
@@ -1,128 +1,130
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 {
10 10 domNode: Node;
11 11
12 12 containerNode?: Node;
13 13
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;
24 24
25 25 _instance: _Widget | undefined;
26 26
27 27 constructor(widgetClass: _WidgetCtor) {
28 28 super();
29 29 argumentNotNull(widgetClass, "widgetClass");
30 30
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) {
38 38 if (child instanceof WidgetRendition) {
39 39 // layout containers add custom logic to addChild methods
40 40 instance.addChild(child.getWidgetInstance());
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) {
48 48 instance.addChild(w);
49 49 } else {
50 50 if (!instance.containerNode)
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 {
58 58 if (!instance.containerNode)
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 };
79 79 this._instance = new this.widgetClass(_attrs);
80 80 } else {
81 81 this._instance = new this.widgetClass(attrs);
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() {
88 90 if (!this._instance)
89 91 throw new Error("The instance of the widget isn't created");
90 92 return this._instance;
91 93 }
92 94
93 95 protected _getDomNode() {
94 96 if (!this._instance)
95 97 throw new Error("The instance of the widget isn't created");
96 98 return this._instance.domNode;
97 99 }
98 100
99 101 /** Overrides default placeAt implementation. Calls placeAt of the
100 102 * widget and then starts it.
101 103 *
102 104 * @param refNode A node or id of the node where the widget should be placed.
103 105 * @param position A position relative to refNode.
104 106 */
105 107 placeAt(refNode: string | Node, position?: DojoNodePosition) {
106 108 this.ensureCreated();
107 109 const instance = this._getInstance();
108 110 if (typeof instance.placeAt === "function") {
109 111 instance.placeAt(refNode, position);
110 112
111 113 // fix the dojo startup behavior when the widget is placed
112 114 // directly to the document and doesn't have any enclosing widgets
113 115 const parentWidget = instance.domNode.parentNode ?
114 116 registry.getEnclosingWidget(instance.domNode.parentNode) : null;
115 117 if (!parentWidget && isInPage(instance.domNode) && typeof instance.startup === "function")
116 118 instance.startup();
117 119 } else {
118 120 // the widget doesn't have a placeAt method, strange but whatever
119 121 super.placeAt(refNode, position);
120 122 }
121 123 }
122 124
123 125 getWidgetInstance() {
124 126 this.ensureCreated();
125 127 return this._getInstance();
126 128 }
127 129
128 130 }
@@ -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 }
@@ -1,161 +1,214
1 1 import { IDestroyable } from "@implab/core-amd/interfaces";
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;
15 17
16 18 placeAt(refNode: string | Node, position?: DojoNodePosition): void;
17 19 }
18 20
19 21 /**
20 22 * @deprecated use Rendition
21 23 */
22 24 export type BuildContext<TNode extends Node = Node> = Rendition<TNode>;
23 25
24 26 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
77 59 const vp = Object.getPrototypeOf(v);
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.
101 81 * If the specified node is the root node of a widget, then the
102 82 * widget will be destroyed.
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)) {
110 90 target.destroy();
111 91 } else if (isNode(target)) {
112 92 if (isElementNode(target)) {
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
134 121 * or starts widget itself if the target is the widget. If the specified node
135 122 * associated with the widget that widget will be started.
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);
143 130 if (w) {
144 131 if (w.startup)
145 132 w.startup();
146 133 } else {
147 134 registry.findWidgets(target, skipNode).forEach(x => x.startup());
148 135 }
149 136 }
150 137 } else {
151 138 if (target.startup)
152 139 target.startup();
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,68 +1,81
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
9 11 /** specifies the name of the property in the widget where the the
10 12 * reference to the current object will be stored
11 13 */
12 14 "data-dojo-attach-point": string;
13 15
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 }
20 24
21 25 interface DjxIntrinsicElements {
22 26 }
23 27
24 28 type RecursivePartial<T> = T extends string | number | boolean | null | undefined | Function ?
25 29 T :
26 30 { [k in keyof T]?: RecursivePartial<T[k]> };
27 31
28 32 type MatchingMemberKeys<T, U> = {
29 33 [K in keyof T]: T[K] extends U ? K : never;
30 34 }[keyof T];
31 35 type NotMatchingMemberKeys<T, U> = {
32 36 [K in keyof T]: T[K] extends U ? never : K;
33 37 }[keyof T];
34 38
35 39 type ExtractMembers<T, U> = Pick<T, MatchingMemberKeys<T, U>>;
36 40
37 41 type ExcludeMembers<T, U> = Pick<T, NotMatchingMemberKeys<T, U>>;
38 42
39 43 type ElementAttrNames<E> = NotMatchingMemberKeys<E, (...args: any[]) => any>;
40 44
41 45 type ElementAttrType<E, K extends keyof any> = K extends keyof E ? RecursivePartial<E[K]> : string;
42 46
43 47
44 48 type ElementAttrNamesBlacklist = "children" | "getRootNode" | keyof EventTarget;
45 49
46 50 /** This type extracts keys of the specified parameter E by the following rule:
47 51 * 1. skips all ElementAttrNamesBlacklist
48 52 * 2. skips all methods except with the signature of event handlers
49 53 */
50 54 type AssignableElementAttrNames<E> = {
51 55 [K in keyof E]: K extends ElementAttrNamesBlacklist ? never :
52 56 ((evt: Event) => any) extends E[K] ? K :
53 57 E[K] extends ((...args: any[]) => any) ? never :
54 58 K;
55 59 }[keyof E];
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]>
63 67 } & DjxIntrinsicElements;
64 68
65 69 type IntrinsicElements = {
66 70 [tag in keyof LaxIntrinsicElementsMap]: RecursivePartial<LaxIntrinsicElementsMap[tag]>;
67 71 }
72
73 interface ElementChildrenAttribute {
74 children: {};
68 75 }
76
77 interface IntrinsicClassAttributes<T> {
78 ref?: (value: T) => void;
79 children?: unknown;
80 }
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
@@ -1,70 +1,70
1 1 import { djbase, djclass, bind, prototype, AbstractConstructor } from "../declare";
2 2
3 3 import { DjxWidgetBase } from "../tsx/DjxWidgetBase";
4 4 import { createElement, on } from "../tsx";
5 5
6 6 interface MyWidgetAttrs {
7 7 title: string;
8 8
9 9 counter: number;
10 10 }
11 11
12 12 interface MyWidgetEvents {
13 13 "count-inc": Event & {
14 14 detail: number;
15 15 };
16 16
17 17 "count-dec": Event & {
18 18 detail: number;
19 19 };
20 20 }
21 21
22 22
23 23 @djclass
24 24 export class MyWidget extends djbase(DjxWidgetBase as AbstractConstructor<DjxWidgetBase<MyWidgetAttrs, MyWidgetEvents>>) {
25 25
26 26 @bind({ node: "titleNode", type: "innerHTML" })
27 27 title = "";
28 28
29 29 @prototype()
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>
40 40 </div>;
41 41 }
42 42
43 43 postCreate() {
44 44 super.postCreate();
45 45
46 46 this.on("click", () => {});
47 47 }
48 48
49 49 _onSubmit(e: Event) {
50 50 }
51 51
52 52 _onIncClick(e: MouseEvent) {
53 53 this.set("counter", this.counter + 1);
54 54
55 55 this.emit("count-inc", { bubbles: false });
56 56 }
57 57
58 58 _onDecClick() {
59 59 this.emit("count-dec", { bubbles: false, detail: this.counter });
60 60 }
61 61
62 62 @on("count-inc")
63 63 private _onCounterInc(evt: Event & { detail: number; x?: number; }) {
64 64 }
65 65
66 66 @on("click", "keydown")
67 67 protected _onClick(event: MouseEvent | KeyboardEvent) {
68 68
69 69 }
70 70 } No newline at end of file
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
@@ -1,188 +1,183
1 1 #!/usr/bin/env sh
2 2
3 3 #
4 4 # Copyright 2015 the original author or authors.
5 5 #
6 6 # Licensed under the Apache License, Version 2.0 (the "License");
7 7 # you may not use this file except in compliance with the License.
8 8 # You may obtain a copy of the License at
9 9 #
10 10 # https://www.apache.org/licenses/LICENSE-2.0
11 11 #
12 12 # Unless required by applicable law or agreed to in writing, software
13 13 # distributed under the License is distributed on an "AS IS" BASIS,
14 14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 15 # See the License for the specific language governing permissions and
16 16 # limitations under the License.
17 17 #
18 18
19 19 ##############################################################################
20 20 ##
21 21 ## Gradle start up script for UN*X
22 22 ##
23 23 ##############################################################################
24 24
25 25 # Attempt to set APP_HOME
26 26 # Resolve links: $0 may be a link
27 27 PRG="$0"
28 28 # Need this for relative symlinks.
29 29 while [ -h "$PRG" ] ; do
30 30 ls=`ls -ld "$PRG"`
31 31 link=`expr "$ls" : '.*-> \(.*\)$'`
32 32 if expr "$link" : '/.*' > /dev/null; then
33 33 PRG="$link"
34 34 else
35 35 PRG=`dirname "$PRG"`"/$link"
36 36 fi
37 37 done
38 38 SAVED="`pwd`"
39 39 cd "`dirname \"$PRG\"`/" >/dev/null
40 40 APP_HOME="`pwd -P`"
41 41 cd "$SAVED" >/dev/null
42 42
43 43 APP_NAME="Gradle"
44 44 APP_BASE_NAME=`basename "$0"`
45 45
46 46 # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 47 DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 48
49 49 # Use the maximum available, or set MAX_FD != -1 to use that value.
50 50 MAX_FD="maximum"
51 51
52 52 warn () {
53 53 echo "$*"
54 54 }
55 55
56 56 die () {
57 57 echo
58 58 echo "$*"
59 59 echo
60 60 exit 1
61 61 }
62 62
63 63 # OS specific support (must be 'true' or 'false').
64 64 cygwin=false
65 65 msys=false
66 66 darwin=false
67 67 nonstop=false
68 68 case "`uname`" in
69 69 CYGWIN* )
70 70 cygwin=true
71 71 ;;
72 72 Darwin* )
73 73 darwin=true
74 74 ;;
75 75 MINGW* )
76 76 msys=true
77 77 ;;
78 78 NONSTOP* )
79 79 nonstop=true
80 80 ;;
81 81 esac
82 82
83 83 CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 84
85 85 # Determine the Java command to use to start the JVM.
86 86 if [ -n "$JAVA_HOME" ] ; then
87 87 if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
88 88 # IBM's JDK on AIX uses strange locations for the executables
89 89 JAVACMD="$JAVA_HOME/jre/sh/java"
90 90 else
91 91 JAVACMD="$JAVA_HOME/bin/java"
92 92 fi
93 93 if [ ! -x "$JAVACMD" ] ; then
94 94 die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 95
96 96 Please set the JAVA_HOME variable in your environment to match the
97 97 location of your Java installation."
98 98 fi
99 99 else
100 100 JAVACMD="java"
101 101 which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 102
103 103 Please set the JAVA_HOME variable in your environment to match the
104 104 location of your Java installation."
105 105 fi
106 106
107 107 # Increase the maximum file descriptors if we can.
108 108 if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
109 109 MAX_FD_LIMIT=`ulimit -H -n`
110 110 if [ $? -eq 0 ] ; then
111 111 if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
112 112 MAX_FD="$MAX_FD_LIMIT"
113 113 fi
114 114 ulimit -n $MAX_FD
115 115 if [ $? -ne 0 ] ; then
116 116 warn "Could not set maximum file descriptor limit: $MAX_FD"
117 117 fi
118 118 else
119 119 warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 120 fi
121 121 fi
122 122
123 123 # For Darwin, add options to specify how the application appears in the dock
124 124 if $darwin; then
125 125 GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 126 fi
127 127
128 128 # For Cygwin or MSYS, switch paths to Windows format before running java
129 129 if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
130 130 APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131 131 CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
132 132 JAVACMD=`cygpath --unix "$JAVACMD"`
133 133
134 134 # We build the pattern for arguments to be converted via cygpath
135 135 ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
136 136 SEP=""
137 137 for dir in $ROOTDIRSRAW ; do
138 138 ROOTDIRS="$ROOTDIRS$SEP$dir"
139 139 SEP="|"
140 140 done
141 141 OURCYGPATTERN="(^($ROOTDIRS))"
142 142 # Add a user-defined pattern to the cygpath arguments
143 143 if [ "$GRADLE_CYGPATTERN" != "" ] ; then
144 144 OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 145 fi
146 146 # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 147 i=0
148 148 for arg in "$@" ; do
149 149 CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
150 150 CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
151 151
152 152 if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
153 153 eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
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
173 173 # Escape application args
174 174 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" "$@"
@@ -1,17 +1,18
1 1 /*
2 2 * This settings file was generated by the Gradle 'init' task.
3 3 *
4 4 * The settings file is used to specify which projects to include in your build.
5 5 * In a single project build this file can be empty or even removed.
6 6 *
7 7 * Detailed information about configuring a multi-project build in Gradle can be found
8 8 * in the user guide at https://docs.gradle.org/3.5/userguide/multi_project_builds.html
9 9 */
10 10
11 11 // To declare projects as part of a multi-project build use the 'include' method
12 12
13 13 //include 'sub-project-name'
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