Skip to content

Commit a3f1770

Browse files
committed
Iterate on the custom widget tutorial
1 parent 85b424d commit a3f1770

File tree

3 files changed

+125
-109
lines changed

3 files changed

+125
-109
lines changed

docs/source/examples/Widget Custom.ipynb

Lines changed: 124 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,27 @@
141141
"jupyter nbextension enable --sys-prefix --py ipyemail\n",
142142
"```\n",
143143
"\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+
"![hello-world](./images/custom-widget-hello.png)\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."
145165
]
146166
},
147167
{
@@ -152,7 +172,7 @@
152172
}
153173
},
154174
"source": [
155-
"## Building a Custom Widget"
175+
"## Implementing the widget"
156176
]
157177
},
158178
{
@@ -203,24 +223,45 @@
203223
"source": [
204224
"Inheriting from the DOMWidget does not tell the widget framework what front end widget to associate with your back end widget.\n",
205225
"\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+
"```"
207248
]
208249
},
209250
{
210-
"cell_type": "code",
211-
"execution_count": null,
251+
"cell_type": "markdown",
212252
"metadata": {},
213-
"outputs": [],
214253
"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",
217255
"\n",
256+
"```python\n",
257+
"from .example import ExampleWidget\n",
258+
"```\n",
218259
"\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+
"```"
224265
]
225266
},
226267
{
@@ -300,7 +341,7 @@
300341
}
301342
},
302343
"source": [
303-
"## Front end (JavaScript)"
344+
"## Front end (TypeScript)"
304345
]
305346
},
306347
{
@@ -325,66 +366,52 @@
325366
}
326367
},
327368
"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",
383385
"\n",
384-
" return {\n",
385-
" EmailView: EmailView\n",
386+
" static serializers: ISerializers = {\n",
387+
" ...DOMWidgetModel.serializers,\n",
388+
" // Add any extra serializers here\n",
386389
" }\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+
"```"
388415
]
389416
},
390417
{
@@ -402,37 +429,29 @@
402429
"cell_type": "markdown",
403430
"metadata": {},
404431
"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",
416433
"\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",
418435
"\n",
419-
" var EmailView = widgets.DOMWidgetView.extend({\n",
436+
"In `src/widget.ts`, define the `email_input` attribute:\n",
420437
"\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",
427443
"\n",
428-
" this.el.appendChild(this.email_input); \n",
429-
" },\n",
430-
" });\n",
444+
"Then, add the following logic for the `render`:\n",
431445
"\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+
"```"
436455
]
437456
},
438457
{
@@ -450,16 +469,13 @@
450469
"cell_type": "markdown",
451470
"metadata": {},
452471
"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+
"```"
463479
]
464480
},
465481
{

docs/source/examples/Widget Styling.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1413,7 +1413,7 @@
14131413
"name": "python",
14141414
"nbconvert_exporter": "python",
14151415
"pygments_lexer": "ipython3",
1416-
"version": "3.6.8"
1416+
"version": "3.8.1"
14171417
}
14181418
},
14191419
"nbformat": 4,
9.76 KB
Loading

0 commit comments

Comments
 (0)