1
1
# Signals
2
2
3
- > Use Signals to allow Widgets communicate with each others.
3
+ > Use Signals to allow Widgets to communicate with each others.
4
4
5
5
- [ Lumino Signaling 101] ( #lumino-signaling-101 )
6
- - [ A simple React Button] ( #a-simple-react -button )
6
+ - [ A simple HTML Button] ( #a-simple-html -button )
7
7
- [ Subscribing to a Signal] ( #subscribing-to-a-signal )
8
8
9
9
![ Button with Signal] ( preview.png )
10
10
11
11
## Lumino Signaling 101
12
12
13
- Communication between different components of JupyterLab is a key ingredient in building an
14
- extension.
13
+ Communication between different components of JupyterLab is a key ingredient in building an extension.
15
14
16
- In this extension, a simple button will be added to print something to the console.
15
+ In this extension, a simple HTML button will be added to print something to the console.
17
16
18
17
JupyterLab's Lumino engine uses the ` ISignal ` interface and the
19
18
` Signal ` class that implements this interface for communication
20
19
(read more on the [ documentation] ( https://jupyterlab.github.io/lumino/signaling/index.html ) page).
21
20
22
21
The basic concept is as follows:
23
22
24
- First, a widget (` button.tsx ` ), in this case the one that contains
23
+ First, a widget (` ButtonWidget ` in ` button.ts ` ), in this case the one that contains
25
24
some visual elements such as a button, defines a ` _stateChanged ` signal:
26
25
27
26
``` ts
28
- // src/button.tsx#L36-L36
27
+ // src/button.ts#L32-L32
29
28
30
- private _stateChanged = new Signal <this , ICount >(this );
29
+ private _stateChanged = new Signal <ButtonWidget , ICount >(this );
31
30
```
32
31
33
32
That private signal is exposed to other widgets via a public accessor method.
34
33
35
34
``` ts
36
- // src/button.tsx#L15-L17
35
+ // src/button.ts#L34-L36
37
36
38
- public get stateChanged (): ISignal < this , ICount > {
37
+ public get stateChanged (): ISignal < ButtonWidget , ICount > {
39
38
return this._stateChanged;
40
39
}
41
40
```
42
41
43
- Another widget, in this case the panel (` panel.ts ` ) that boxes several different widgets,
44
- subscribes to the ` stateChanged ` signal and links some function to it:
42
+ Another widget, in this case the panel (` SignalExamplePanel ` in ` panel.ts ` ) that can box several different widgets,
43
+ subscribes to the ` stateChanged ` signal and links a function to it:
45
44
46
45
``` ts
47
- // src/panel.ts#L29-L29
46
+ // src/panel.ts#L33-L33
48
47
49
48
this ._widget .stateChanged .connect (this ._logMessage , this );
50
49
```
51
50
52
51
The ` _logMessage ` is executed when the signal is triggered from the first widget with:
53
52
54
53
``` ts
55
- // src/button.tsx#L28-L28
54
+ // src/button.ts#L24-L24
56
55
57
56
this ._stateChanged .emit (this ._count );
58
57
```
59
58
60
59
Let's look at the implementations details.
61
60
62
- ## A simple React Button
61
+ ## A Simple HTML Button
63
62
64
- Start with a file called ` src/button.tsx ` . The ` tsx ` extension allows to use
65
- HTML-like syntax with the tag notation ` <> ` to represent some visual elements
66
- (note that you have to add a line: ` "jsx": "react", ` to the
67
- ` tsconfig.json ` file). This is a special syntax used by [ React] ( https://reactjs.org/tutorial/tutorial.html ) .
63
+ Start with a file called ` src/button.ts ` .
68
64
69
- You can also try the [ React Widget example] ( ./../../react/react-widget ) for more details.
65
+ NB: For a React widget, you can try the [ React Widget example] ( ./../../react/react-widget ) for more details.
70
66
71
- ` button.tsx ` contains one major class ` ButtonWidget ` that extends the
72
- ` ReactWidget ` class provided by JupyterLab .
67
+ ` button.ts ` contains one class ` ButtonWidget ` that extends the
68
+ ` Widget ` class provided by Lumino .
73
69
74
- ` ReactWidget ` defines a ` render() ` method that defines some React elements such as a button.
75
- This is the recommended way to include React component inside the JupyterLab widget based UI.
70
+ The constructor argument of the ` ButtonWidget ` class is assigned a default ` HTMLButtonElement ` node (e.g., ` <button></button> ` ). The Widget's ` node ` property references its respective ` HTMLElement ` . For example, you can set the content of the button with ` this.node.textContent = 'Click me' ` .
76
71
77
72
``` ts
78
- // src/button.tsx#L19-L34
79
-
80
- protected render (): React .ReactElement < any > {
81
- return (
82
- <button
83
- key = " header-thread"
84
- className = " jp-example-button"
85
- onClick = {(): void => {
86
- this ._count = {
87
- clickCount: this ._count .clickCount + 1
88
- };
89
- this ._stateChanged .emit (this ._count );
90
- }}
91
- >
92
- Clickme
93
- </button >
94
- );
95
- }
73
+ // src/button.ts#L11-L11
74
+
75
+ constructor (options = { node: document .createElement (' button' ) }) {
96
76
` ` `
97
77
98
- ` ButtonWidget ` also contain a private attribute ` _count ` of type ` ICount ` .
78
+ ` ButtonWidget ` also contains a private attribute ` _count ` of type ` ICount ` .
99
79
100
80
` ` ` ts
101
- // src/button.tsx#L11-L13
81
+ // src/button.ts#L28-L30
102
82
103
- protected _count : ICount = {
83
+ private _count : ICount = {
104
84
clickCount: 0
105
85
};
106
86
` ` `
@@ -109,29 +89,27 @@ protected _count: ICount = {
109
89
` Signal ` .
110
90
111
91
` ` ` ts
112
- // src/button.tsx#L36-L36
92
+ // src/button.ts#L32-L32
113
93
114
- private _stateChanged = new Signal <this , ICount >(this );
94
+ private _stateChanged = new Signal <ButtonWidget , ICount >(this );
115
95
` ` `
116
96
117
97
A signal object can be triggered and then emits an actual signal.
118
98
119
99
Other Widgets can subscribe to such a signal and react when a message is
120
100
emitted.
121
101
122
- The button ` onClick ` event will increment the ` _count `
102
+ The button ` click ` event will increment the ` _count `
123
103
private attribute and will trigger the ` _stateChanged ` signal passing
124
104
the ` _count ` variable.
125
105
126
106
` ` ` ts
127
- // src/button.tsx#L24-L29
107
+ // src/button.ts#L22-L25
128
108
129
- onClick = {(): void => {
130
- this ._count = {
131
- clickCount: this ._count .clickCount + 1
132
- };
109
+ this .node .addEventListener (' click' , () => {
110
+ this ._count .clickCount = this ._count .clickCount + 1 ;
133
111
this ._stateChanged .emit (this ._count );
134
- }}
112
+ });
135
113
` ` `
136
114
137
115
## Subscribing to a Signal
@@ -141,14 +119,17 @@ The `panel.ts` class defines an extension panel that displays the
141
119
This is done in the constructor.
142
120
143
121
` ` ` ts
144
- // src/panel.ts#L18-L30
122
+ // src/panel.ts#L19-L34
145
123
146
124
constructor (translator ?: ITranslator ) {
147
125
super ();
148
126
this ._translator = translator || nullTranslator ;
149
127
this ._trans = this ._translator .load (' jupyterlab' );
150
128
this .addClass (PANEL_CLASS );
151
- this .id = ' SignalExamplePanel' ;
129
+
130
+ // This ensures the id of the DOM node is unique for each Panel instance.
131
+ this .id = ' SignalExamplePanel_' + SignalExamplePanel ._id ++ ;
132
+
152
133
this .title .label = this ._trans .__ (' Signal Example View' );
153
134
this .title .closable = true ;
154
135
@@ -162,7 +143,7 @@ Subscription to a signal is done using the `connect` method of the
162
143
` stateChanged ` attribute.
163
144
164
145
` ` ` ts
165
- // src/panel.ts#L29-L29
146
+ // src/panel.ts#L33-L33
166
147
167
148
this ._widget .stateChanged .connect (this ._logMessage , this );
168
149
` ` `
@@ -171,7 +152,8 @@ It registers the `_logMessage` function which is triggered when the signal is em
171
152
172
153
**Note**
173
154
174
- > From the official [ JupyterLab Documentation] ( https://jupyterlab.readthedocs.io/en/stable/developer/patterns.html#signals ) :
155
+ From the official [JupyterLab Documentation](https://jupyterlab.readthedocs.io/en/stable/developer/patterns.html#signals):
156
+
175
157
> Wherever possible as signal connection should be made with the pattern ` .connect (this ._onFoo , this )` .
176
158
> Providing the ` this ` context enables the connection to be properly cleared by ` clearSignalData (this )` .
177
159
> Using a private method avoids allocating a closure for each connection.
@@ -180,16 +162,16 @@ The `_logMessage` function receives as parameters the emitter (of type `ButtonWi
180
162
and the count (of type ` ICount ` ) sent by the signal emitter.
181
163
182
164
` ` ` ts
183
- // src/panel.ts#L32-L32
165
+ // src/panel.ts#L36-L36
184
166
185
167
private _logMessage (emitter : ButtonWidget , count : ICount ): void {
186
168
` ` `
187
169
188
- In our case, that function writes ` Button has been clicked ... times .` text
170
+ In our case, that function writes ` The big red button has been clicked ... times .` text
189
171
to the browser console and in an alert when the big red button is clicked.
190
172
191
173
` ` ` ts
192
- // src/panel.ts#L32-L39
174
+ // src/panel.ts#L36-L44
193
175
194
176
private _logMessage (emitter : ButtonWidget , count : ICount ): void {
195
177
console .log (' Hey, a Signal has been received from' , emitter );
@@ -199,6 +181,7 @@ private _logMessage(emitter: ButtonWidget, count: ICount): void {
199
181
window .alert (
200
182
` The big red button has been clicked ${count .clickCount } times. `
201
183
);
184
+ }
202
185
` ` `
203
186
204
187
There it is. Signaling is conceptually important for building extensions.
0 commit comments