|
141 | 141 | "jupyter nbextension enable --sys-prefix --py ipyemail\n",
|
142 | 142 | "```\n",
|
143 | 143 | "\n",
|
144 |
| - "You are now ready to implement the core functionality of the widget!" |
| 144 | + "### Testing the installation\n", |
| 145 | + "\n", |
| 146 | + "At this point, you should be able to open a notebook and create a new `ExampleWidget`.\n", |
| 147 | + "\n", |
| 148 | + "To test it, execute the following in a terminal:\n", |
| 149 | + "\n", |
| 150 | + "```bash\n", |
| 151 | + "# if you are using the classic notebook\n", |
| 152 | + "jupyter notebook\n", |
| 153 | + "\n", |
| 154 | + "# if you are using JupyterLab\n", |
| 155 | + "jupyter lab\n", |
| 156 | + "```\n", |
| 157 | + "\n", |
| 158 | + "And open `examples/introduction.ipynb`.\n", |
| 159 | + "\n", |
| 160 | + "By default, the widget displays the string `Hello World` with a colored background:\n", |
| 161 | + "\n", |
| 162 | + "\n", |
| 163 | + "\n", |
| 164 | + "The next step will walk you through how to modify the existing code to transform the widget into an email widget." |
145 | 165 | ]
|
146 | 166 | },
|
147 | 167 | {
|
|
152 | 172 | }
|
153 | 173 | },
|
154 | 174 | "source": [
|
155 |
| - "## Building a Custom Widget" |
| 175 | + "## Implementing the widget" |
156 | 176 | ]
|
157 | 177 | },
|
158 | 178 | {
|
|
203 | 223 | "source": [
|
204 | 224 | "Inheriting from the DOMWidget does not tell the widget framework what front end widget to associate with your back end widget.\n",
|
205 | 225 | "\n",
|
206 |
| - "Instead, you must tell it yourself by defining specially named trait attributes, `_view_name`, `_view_module`, and `_view_module_version` (as seen below) and optionally `_model_name` and `_model_module`." |
| 226 | + "Instead, you must tell it yourself by defining specially named trait attributes, `_view_name`, `_view_module`, and `_view_module_version` (as seen below) and optionally `_model_name` and `_model_module`.\n", |
| 227 | + "\n", |
| 228 | + "\n", |
| 229 | + "In `ipyemail/example.py`, replace the example code with the following:\n", |
| 230 | + "\n", |
| 231 | + "```python\n", |
| 232 | + "from ipywidgets import DOMWidget, ValueWidget, register\n", |
| 233 | + "from traitlets import Unicode, Bool, validate, TraitError\n", |
| 234 | + "\n", |
| 235 | + "from ._frontend import module_name, module_version\n", |
| 236 | + "\n", |
| 237 | + "\n", |
| 238 | + "@register\n", |
| 239 | + "class EmailWidget(DOMWidget, ValueWidget):\n", |
| 240 | + " _model_name = Unicode('EmailModel').tag(sync=True)\n", |
| 241 | + " _model_module = Unicode(module_name).tag(sync=True)\n", |
| 242 | + " _model_module_version = Unicode(module_version).tag(sync=True)\n", |
| 243 | + "\n", |
| 244 | + " _view_name = Unicode('EmailView').tag(sync=True)\n", |
| 245 | + " _view_module = Unicode(module_name).tag(sync=True)\n", |
| 246 | + " _view_module_version = Unicode(module_version).tag(sync=True)\n", |
| 247 | + "```" |
207 | 248 | ]
|
208 | 249 | },
|
209 | 250 | {
|
210 |
| - "cell_type": "code", |
211 |
| - "execution_count": null, |
| 251 | + "cell_type": "markdown", |
212 | 252 | "metadata": {},
|
213 |
| - "outputs": [], |
214 | 253 | "source": [
|
215 |
| - "from traitlets import Unicode, Bool, validate, TraitError\n", |
216 |
| - "from ipywidgets import DOMWidget, ValueWidget, register\n", |
| 254 | + "In `ipyemail/__init__.py`, change the import from:\n", |
217 | 255 | "\n",
|
| 256 | + "```python\n", |
| 257 | + "from .example import ExampleWidget\n", |
| 258 | + "```\n", |
218 | 259 | "\n",
|
219 |
| - "@register\n", |
220 |
| - "class Email(DOMWidget, ValueWidget):\n", |
221 |
| - " _view_name = Unicode('EmailView').tag(sync=True)\n", |
222 |
| - " _view_module = Unicode('email_widget').tag(sync=True)\n", |
223 |
| - " _view_module_version = Unicode('0.1.0').tag(sync=True)" |
| 260 | + "To:\n", |
| 261 | + "\n", |
| 262 | + "```python\n", |
| 263 | + "from .example import EmailWidget\n", |
| 264 | + "```" |
224 | 265 | ]
|
225 | 266 | },
|
226 | 267 | {
|
|
300 | 341 | }
|
301 | 342 | },
|
302 | 343 | "source": [
|
303 |
| - "## Front end (JavaScript)" |
| 344 | + "## Front end (TypeScript)" |
304 | 345 | ]
|
305 | 346 | },
|
306 | 347 | {
|
|
325 | 366 | }
|
326 | 367 | },
|
327 | 368 | "source": [
|
328 |
| - "### Import @jupyter-widgets/base" |
329 |
| - ] |
330 |
| - }, |
331 |
| - { |
332 |
| - "cell_type": "markdown", |
333 |
| - "metadata": {}, |
334 |
| - "source": [ |
335 |
| - "You first need to import the `@jupyter-widgets/base` module. To import modules, use the `define` method of [require.js](http://requirejs.org/) (as seen below)." |
336 |
| - ] |
337 |
| - }, |
338 |
| - { |
339 |
| - "cell_type": "code", |
340 |
| - "execution_count": null, |
341 |
| - "metadata": {}, |
342 |
| - "outputs": [], |
343 |
| - "source": [ |
344 |
| - "%%javascript\n", |
345 |
| - "define('email_widget', [\"@jupyter-widgets/base\"], function(widgets) {\n", |
346 |
| - " \n", |
347 |
| - "});" |
348 |
| - ] |
349 |
| - }, |
350 |
| - { |
351 |
| - "cell_type": "markdown", |
352 |
| - "metadata": { |
353 |
| - "slideshow": { |
354 |
| - "slide_type": "slide" |
355 |
| - } |
356 |
| - }, |
357 |
| - "source": [ |
358 |
| - "### Define the view" |
359 |
| - ] |
360 |
| - }, |
361 |
| - { |
362 |
| - "cell_type": "markdown", |
363 |
| - "metadata": {}, |
364 |
| - "source": [ |
365 |
| - "Next, define your widget view class. Inherit from the `DOMWidgetView` by using the `.extend` method." |
366 |
| - ] |
367 |
| - }, |
368 |
| - { |
369 |
| - "cell_type": "code", |
370 |
| - "execution_count": null, |
371 |
| - "metadata": {}, |
372 |
| - "outputs": [], |
373 |
| - "source": [ |
374 |
| - "%%javascript\n", |
375 |
| - "require.undef('email_widget');\n", |
376 |
| - "\n", |
377 |
| - "define('email_widget', [\"@jupyter-widgets/base\"], function(widgets) {\n", |
378 |
| - " \n", |
379 |
| - " // Define the EmailView\n", |
380 |
| - " var EmailView = widgets.DOMWidgetView.extend({\n", |
381 |
| - " \n", |
382 |
| - " });\n", |
| 369 | + "The TypeScript cookiecutter generates a file `src/widget.ts`. Open the file and rename `ExampleModel` to `EmailModel` and `ExampleView` to `EmailView`:\n", |
| 370 | + "\n", |
| 371 | + "```typescript\n", |
| 372 | + "export\n", |
| 373 | + "class EmailModel extends DOMWidgetModel {\n", |
| 374 | + " defaults() {\n", |
| 375 | + " return {...super.defaults(),\n", |
| 376 | + " _model_name: EmailModel.model_name,\n", |
| 377 | + " _model_module: EmailModel.model_module,\n", |
| 378 | + " _model_module_version: EmailModel.model_module_version,\n", |
| 379 | + " _view_name: EmailModel.view_name,\n", |
| 380 | + " _view_module: EmailModel.view_module,\n", |
| 381 | + " _view_module_version: EmailModel.view_module_version,\n", |
| 382 | + " value : 'Hello World'\n", |
| 383 | + " };\n", |
| 384 | + " }\n", |
383 | 385 | "\n",
|
384 |
| - " return {\n", |
385 |
| - " EmailView: EmailView\n", |
| 386 | + " static serializers: ISerializers = {\n", |
| 387 | + " ...DOMWidgetModel.serializers,\n", |
| 388 | + " // Add any extra serializers here\n", |
386 | 389 | " }\n",
|
387 |
| - "});" |
| 390 | + "\n", |
| 391 | + " static model_name = 'EmailModel';\n", |
| 392 | + " static model_module = MODULE_NAME;\n", |
| 393 | + " static model_module_version = MODULE_VERSION;\n", |
| 394 | + " static view_name = 'EmailView'; // Set to null if no view\n", |
| 395 | + " static view_module = MODULE_NAME; // Set to null if no view\n", |
| 396 | + " static view_module_version = MODULE_VERSION;\n", |
| 397 | + "}\n", |
| 398 | + "\n", |
| 399 | + "\n", |
| 400 | + "export\n", |
| 401 | + "class EmailView extends DOMWidgetView {\n", |
| 402 | + " render() {\n", |
| 403 | + " this.el.classList.add('custom-widget');\n", |
| 404 | + "\n", |
| 405 | + " this.value_changed();\n", |
| 406 | + " this.model.on('change:value', this.value_changed, this);\n", |
| 407 | + " }\n", |
| 408 | + "\n", |
| 409 | + " value_changed() {\n", |
| 410 | + " this.el.textContent = this.model.get('value');\n", |
| 411 | + " }\n", |
| 412 | + "}\n", |
| 413 | + "\n", |
| 414 | + "```" |
388 | 415 | ]
|
389 | 416 | },
|
390 | 417 | {
|
|
402 | 429 | "cell_type": "markdown",
|
403 | 430 | "metadata": {},
|
404 | 431 | "source": [
|
405 |
| - "Lastly, override the base `render` method of the view to define custom rendering logic. A handle to the widget's default DOM element can be acquired via `this.el`. The `el` property is the DOM element associated with the view." |
406 |
| - ] |
407 |
| - }, |
408 |
| - { |
409 |
| - "cell_type": "code", |
410 |
| - "execution_count": null, |
411 |
| - "metadata": {}, |
412 |
| - "outputs": [], |
413 |
| - "source": [ |
414 |
| - "%%javascript\n", |
415 |
| - "require.undef('email_widget');\n", |
| 432 | + "Now, override the base `render` method of the view to define custom rendering logic.\n", |
416 | 433 | "\n",
|
417 |
| - "define('email_widget', [\"@jupyter-widgets/base\"], function(widgets) {\n", |
| 434 | + "A handle to the widget's default DOM element can be acquired via `this.el`. The `el` property is the DOM element associated with the view.\n", |
418 | 435 | "\n",
|
419 |
| - " var EmailView = widgets.DOMWidgetView.extend({\n", |
| 436 | + "In `src/widget.ts`, define the `email_input` attribute:\n", |
420 | 437 | "\n",
|
421 |
| - " // Render the view.\n", |
422 |
| - " render: function() { \n", |
423 |
| - " this.email_input = document.createElement('input');\n", |
424 |
| - " this.email_input.type = 'email';\n", |
425 |
| - " this.email_input.value = '[email protected]';\n", |
426 |
| - " this.email_input.disabled = true;\n", |
| 438 | + "```typescript\n", |
| 439 | + "export class EmailView extends DOMWidgetView {\n", |
| 440 | + " private _emailInput: HTMLInputElement;\n", |
| 441 | + "}\n", |
| 442 | + "```\n", |
427 | 443 | "\n",
|
428 |
| - " this.el.appendChild(this.email_input); \n", |
429 |
| - " },\n", |
430 |
| - " });\n", |
| 444 | + "Then, add the following logic for the `render`:\n", |
431 | 445 | "\n",
|
432 |
| - " return {\n", |
433 |
| - " EmailView: EmailView\n", |
434 |
| - " };\n", |
435 |
| - "});" |
| 446 | + "```typescript\n", |
| 447 | + "render: function() { \n", |
| 448 | + " this._emailInput = document.createElement('input');\n", |
| 449 | + " this._emailInput.type = 'email';\n", |
| 450 | + " this._emailInput.value = '[email protected]';\n", |
| 451 | + " this._emailInput.disabled = true;\n", |
| 452 | + " this.el.appendChild(this._emailInput);\n", |
| 453 | + "},\n", |
| 454 | + "```" |
436 | 455 | ]
|
437 | 456 | },
|
438 | 457 | {
|
|
450 | 469 | "cell_type": "markdown",
|
451 | 470 | "metadata": {},
|
452 | 471 | "source": [
|
453 |
| - "You should be able to display your widget just like any other widget now." |
454 |
| - ] |
455 |
| - }, |
456 |
| - { |
457 |
| - "cell_type": "code", |
458 |
| - "execution_count": null, |
459 |
| - "metadata": {}, |
460 |
| - "outputs": [], |
461 |
| - "source": [ |
462 |
| - "Email()" |
| 472 | + "You should be able to display your widget just like any other widget now:\n", |
| 473 | + "\n", |
| 474 | + "```python\n", |
| 475 | + "from ipyemail import EmailWidget\n", |
| 476 | + "\n", |
| 477 | + "EmailWidget()\n", |
| 478 | + "```" |
463 | 479 | ]
|
464 | 480 | },
|
465 | 481 | {
|
|
0 commit comments