##// END OF EJS Templates
Added images
Jonathan Frederic -
Show More
@@ -1,1011 +1,1011 b''
1 1 {
2 2 "metadata": {
3 3 "celltoolbar": "Slideshow",
4 4 "name": "",
5 "signature": "sha256:a53951979397cd38785846c18854053eaa5093f6f08246dbcce76b7d243e2153"
5 "signature": "sha256:4fc2f8717ea4752070ed0e10a8997c2a5f45851ba9b20293339335894264021c"
6 6 },
7 7 "nbformat": 3,
8 8 "nbformat_minor": 0,
9 9 "worksheets": [
10 10 {
11 11 "cells": [
12 12 {
13 13 "cell_type": "markdown",
14 14 "metadata": {},
15 15 "source": [
16 16 "[Index](Index.ipynb) - [Back](Widget Styling.ipynb)"
17 17 ]
18 18 },
19 19 {
20 20 "cell_type": "code",
21 21 "collapsed": false,
22 22 "input": [
23 23 "from __future__ import print_function # For py 2.7 compat"
24 24 ],
25 25 "language": "python",
26 26 "metadata": {},
27 27 "outputs": [],
28 28 "prompt_number": 3
29 29 },
30 30 {
31 31 "cell_type": "heading",
32 32 "level": 1,
33 33 "metadata": {
34 34 "slideshow": {
35 35 "slide_type": "slide"
36 36 }
37 37 },
38 38 "source": [
39 39 "Building a Custom Widget"
40 40 ]
41 41 },
42 42 {
43 43 "cell_type": "markdown",
44 44 "metadata": {},
45 45 "source": [
46 46 "The widget framework is built **on top of the Comm framework** (short for communication). The Comm framework is a framework that **allows you send/receive JSON messages** to/from the front-end (as seen below).\n",
47 47 "\n",
48 "** Insert framework layer image here. **\n",
48 "![Widget layer](images/WidgetArch.png)\n",
49 49 "\n",
50 50 "To create a custom widget, you need to **define the widget both in the back-end and in the front-end**. "
51 51 ]
52 52 },
53 53 {
54 54 "cell_type": "heading",
55 55 "level": 1,
56 56 "metadata": {
57 57 "slideshow": {
58 58 "slide_type": "slide"
59 59 }
60 60 },
61 61 "source": [
62 62 "Building a Custom Widget"
63 63 ]
64 64 },
65 65 {
66 66 "cell_type": "markdown",
67 67 "metadata": {},
68 68 "source": [
69 69 "To get started, you'll create a **simple hello world widget**. Later you'll build on this foundation to make more complex widgets."
70 70 ]
71 71 },
72 72 {
73 73 "cell_type": "heading",
74 74 "level": 2,
75 75 "metadata": {
76 76 "slideshow": {
77 77 "slide_type": "slide"
78 78 }
79 79 },
80 80 "source": [
81 81 "Back-end (Python)"
82 82 ]
83 83 },
84 84 {
85 85 "cell_type": "heading",
86 86 "level": 3,
87 87 "metadata": {},
88 88 "source": [
89 89 "DOMWidget and Widget"
90 90 ]
91 91 },
92 92 {
93 93 "cell_type": "markdown",
94 94 "metadata": {},
95 95 "source": [
96 96 "To define a widget, you must inherit from the **Widget or DOMWidget** base class. If you intend for your widget to be **displayed in the IPython notebook**, you'll need to **inherit from the DOMWidget**. The DOMWidget class itself inherits from the Widget class. The Widget class is useful for cases in which the **Widget is not meant to be displayed directly in the notebook**, but **instead as a child of another rendering environment**. For example, if you wanted to create a three.js widget (a popular WebGL library), you would implement the rendering window as a DOMWidget and any 3D objects or lights meant to be rendered in that window as Widgets."
97 97 ]
98 98 },
99 99 {
100 100 "cell_type": "heading",
101 101 "level": 3,
102 102 "metadata": {
103 103 "slideshow": {
104 104 "slide_type": "slide"
105 105 }
106 106 },
107 107 "source": [
108 108 "_view_name"
109 109 ]
110 110 },
111 111 {
112 112 "cell_type": "markdown",
113 113 "metadata": {},
114 114 "source": [
115 115 "Inheriting from the DOMWidget does not tell the widget framework what front-end widget to associate with your back-end widget. Instead, you must tell it yourself by defining a **specially named Traitlet, `_view_name`** (as seen below)."
116 116 ]
117 117 },
118 118 {
119 119 "cell_type": "code",
120 120 "collapsed": false,
121 121 "input": [
122 122 "from IPython.html import widgets\n",
123 123 "from IPython.utils.traitlets import Unicode\n",
124 124 "\n",
125 125 "class HelloWidget(widgets.DOMWidget):\n",
126 126 " _view_name = Unicode('HelloView', sync=True)"
127 127 ],
128 128 "language": "python",
129 129 "metadata": {},
130 130 "outputs": [],
131 131 "prompt_number": 1
132 132 },
133 133 {
134 134 "cell_type": "heading",
135 135 "level": 3,
136 136 "metadata": {
137 137 "slideshow": {
138 138 "slide_type": "slide"
139 139 }
140 140 },
141 141 "source": [
142 142 "sync=True traitlets"
143 143 ]
144 144 },
145 145 {
146 146 "cell_type": "markdown",
147 147 "metadata": {},
148 148 "source": [
149 149 "**Traitlets is** an IPython library for defining **type-safe properties** on configurable objects. For this tutorial you do not need to worry about the *configurable* piece of the traitlets machinery. The **`sync=True` keyword argument** tells the widget framework to **handle synchronizing that value to the front-end**. Without `sync=True`, the front-end would have no knowledge of `_view_name`."
150 150 ]
151 151 },
152 152 {
153 153 "cell_type": "heading",
154 154 "level": 3,
155 155 "metadata": {
156 156 "slideshow": {
157 157 "slide_type": "slide"
158 158 }
159 159 },
160 160 "source": [
161 161 "Other traitlet types"
162 162 ]
163 163 },
164 164 {
165 165 "cell_type": "markdown",
166 166 "metadata": {},
167 167 "source": [
168 168 "Unicode, used for _view_name, is not the only Traitlet type, there are many more some of which are listed below: \n",
169 169 "\n",
170 170 "- Any\n",
171 171 "- Bool\n",
172 172 "- Bytes\n",
173 173 "- CBool\n",
174 174 "- CBytes\n",
175 175 "- CComplex\n",
176 176 "- CFloat\n",
177 177 "- CInt\n",
178 178 "- CLong\n",
179 179 "- CRegExp\n",
180 180 "- CUnicode\n",
181 181 "- CaselessStrEnum\n",
182 182 "- Complex\n",
183 183 "- Dict\n",
184 184 "- DottedObjectName\n",
185 185 "- Enum\n",
186 186 "- Float\n",
187 187 "- FunctionType\n",
188 188 "- Instance\n",
189 189 "- InstanceType\n",
190 190 "- Int\n",
191 191 "- List\n",
192 192 "- Long\n",
193 193 "- Set\n",
194 194 "- TCPAddress\n",
195 195 "- Tuple\n",
196 196 "- Type\n",
197 197 "- Unicode\n",
198 198 "\n",
199 199 "**Not all of these traitlets can be synchronized** across the network, **only the JSON-able** traits and **Widget instances** will be synchronized."
200 200 ]
201 201 },
202 202 {
203 203 "cell_type": "heading",
204 204 "level": 2,
205 205 "metadata": {
206 206 "slideshow": {
207 207 "slide_type": "slide"
208 208 }
209 209 },
210 210 "source": [
211 211 "Front-end (JavaScript)"
212 212 ]
213 213 },
214 214 {
215 215 "cell_type": "heading",
216 216 "level": 3,
217 217 "metadata": {},
218 218 "source": [
219 219 "Models and views"
220 220 ]
221 221 },
222 222 {
223 223 "cell_type": "markdown",
224 224 "metadata": {},
225 225 "source": [
226 226 "The IPython widget framework front-end relies heavily on [Backbone.js](http://backbonejs.org/). **Backbone.js is an MVC (model view controller) framework**. Widgets defined in the back-end are automatically **synchronized with generic Backbone.js models** in the front-end. The traitlets are added to the front-end instance **automatically on first state push**. The **`_view_name` trait** that you defined earlier is used by the widget framework to create the corresponding Backbone.js view and **link that view to the model**."
227 227 ]
228 228 },
229 229 {
230 230 "cell_type": "heading",
231 231 "level": 3,
232 232 "metadata": {
233 233 "slideshow": {
234 234 "slide_type": "slide"
235 235 }
236 236 },
237 237 "source": [
238 238 "Import the WidgetManager"
239 239 ]
240 240 },
241 241 {
242 242 "cell_type": "markdown",
243 243 "metadata": {},
244 244 "source": [
245 245 "You first need to **import the WidgetManager**. You will use it later to register your view by name (the same name you used in the back-end). To import the widget manager, use the `require` method of [require.js](http://requirejs.org/) (as seen below)."
246 246 ]
247 247 },
248 248 {
249 249 "cell_type": "code",
250 250 "collapsed": false,
251 251 "input": [
252 252 "%%javascript\n",
253 253 "\n",
254 254 "require([\"widgets/js/widget\"], function(WidgetManager){\n",
255 255 " \n",
256 256 "});"
257 257 ],
258 258 "language": "python",
259 259 "metadata": {},
260 260 "outputs": [
261 261 {
262 262 "javascript": [
263 263 "\n",
264 264 "require([\"widgets/js/widget\"], function(WidgetManager){\n",
265 265 " \n",
266 266 "});"
267 267 ],
268 268 "metadata": {},
269 269 "output_type": "display_data",
270 270 "text": [
271 271 "<IPython.core.display.Javascript object>"
272 272 ]
273 273 }
274 274 ],
275 275 "prompt_number": 2
276 276 },
277 277 {
278 278 "cell_type": "heading",
279 279 "level": 3,
280 280 "metadata": {
281 281 "slideshow": {
282 282 "slide_type": "slide"
283 283 }
284 284 },
285 285 "source": [
286 286 "Define the view"
287 287 ]
288 288 },
289 289 {
290 290 "cell_type": "markdown",
291 291 "metadata": {},
292 292 "source": [
293 293 "Next define your widget view class. **Inherit from the `DOMWidgetView`** by using the `.extend` method. Register the view class with the widget manager by calling **`.register_widget_view`**. The **first parameter is the widget view name** (`_view_name` that you defined earlier in Python) and the **second is a handle to the class type**."
294 294 ]
295 295 },
296 296 {
297 297 "cell_type": "code",
298 298 "collapsed": false,
299 299 "input": [
300 300 "%%javascript\n",
301 301 "\n",
302 302 "require([\"widgets/js/widget\"], function(WidgetManager){\n",
303 303 " \n",
304 304 " // Define the HelloView\n",
305 305 " var HelloView = IPython.DOMWidgetView.extend({\n",
306 306 " \n",
307 307 " });\n",
308 308 " \n",
309 309 " // Register the HelloView with the widget manager.\n",
310 310 " WidgetManager.register_widget_view('HelloView', HelloView);\n",
311 311 "});"
312 312 ],
313 313 "language": "python",
314 314 "metadata": {},
315 315 "outputs": [
316 316 {
317 317 "javascript": [
318 318 "\n",
319 319 "require([\"widgets/js/widget\"], function(WidgetManager){\n",
320 320 " \n",
321 321 " // Define the HelloView\n",
322 322 " var HelloView = IPython.DOMWidgetView.extend({\n",
323 323 " \n",
324 324 " });\n",
325 325 " \n",
326 326 " // Register the HelloView with the widget manager.\n",
327 327 " WidgetManager.register_widget_view('HelloView', HelloView);\n",
328 328 "});"
329 329 ],
330 330 "metadata": {},
331 331 "output_type": "display_data",
332 332 "text": [
333 333 "<IPython.core.display.Javascript object>"
334 334 ]
335 335 }
336 336 ],
337 337 "prompt_number": 3
338 338 },
339 339 {
340 340 "cell_type": "heading",
341 341 "level": 3,
342 342 "metadata": {
343 343 "slideshow": {
344 344 "slide_type": "slide"
345 345 }
346 346 },
347 347 "source": [
348 348 "Render method"
349 349 ]
350 350 },
351 351 {
352 352 "cell_type": "markdown",
353 353 "metadata": {},
354 354 "source": [
355 355 "Lastly, **override the base `render` method** of the view to define custom rendering logic. A handle to the widget's default div element can be acquired via **`this.$el`**. The `$el` property is a **[jQuery](http://jquery.com/) object handle** (which can be thought of as a supercharged version of the normal DOM element's handle)."
356 356 ]
357 357 },
358 358 {
359 359 "cell_type": "code",
360 360 "collapsed": false,
361 361 "input": [
362 362 "%%javascript\n",
363 363 "\n",
364 364 "require([\"widgets/js/widget\"], function(WidgetManager){ \n",
365 365 " \n",
366 366 " var HelloView = IPython.DOMWidgetView.extend({\n",
367 367 " \n",
368 368 " // Render the view.\n",
369 369 " render: function(){ \n",
370 370 " this.$el.text('Hello World!'); \n",
371 371 " },\n",
372 372 " });\n",
373 373 " \n",
374 374 " WidgetManager.register_widget_view('HelloView', HelloView);\n",
375 375 "});"
376 376 ],
377 377 "language": "python",
378 378 "metadata": {},
379 379 "outputs": [
380 380 {
381 381 "javascript": [
382 382 "\n",
383 383 "require([\"widgets/js/widget\"], function(WidgetManager){ \n",
384 384 " \n",
385 385 " var HelloView = IPython.DOMWidgetView.extend({\n",
386 386 " \n",
387 387 " // Render the view.\n",
388 388 " render: function(){ \n",
389 389 " this.$el.text('Hello World!'); \n",
390 390 " },\n",
391 391 " });\n",
392 392 " \n",
393 393 " WidgetManager.register_widget_view('HelloView', HelloView);\n",
394 394 "});"
395 395 ],
396 396 "metadata": {},
397 397 "output_type": "display_data",
398 398 "text": [
399 399 "<IPython.core.display.Javascript object>"
400 400 ]
401 401 }
402 402 ],
403 403 "prompt_number": 4
404 404 },
405 405 {
406 406 "cell_type": "heading",
407 407 "level": 2,
408 408 "metadata": {
409 409 "slideshow": {
410 410 "slide_type": "slide"
411 411 }
412 412 },
413 413 "source": [
414 414 "Test"
415 415 ]
416 416 },
417 417 {
418 418 "cell_type": "markdown",
419 419 "metadata": {},
420 420 "source": [
421 421 "You should be able to display your widget just like any other widget now."
422 422 ]
423 423 },
424 424 {
425 425 "cell_type": "code",
426 426 "collapsed": false,
427 427 "input": [
428 428 "HelloWidget()"
429 429 ],
430 430 "language": "python",
431 431 "metadata": {},
432 432 "outputs": [],
433 433 "prompt_number": 5
434 434 },
435 435 {
436 436 "cell_type": "heading",
437 437 "level": 2,
438 438 "metadata": {
439 439 "slideshow": {
440 440 "slide_type": "slide"
441 441 }
442 442 },
443 443 "source": [
444 444 "Making the widget stateful"
445 445 ]
446 446 },
447 447 {
448 448 "cell_type": "markdown",
449 449 "metadata": {},
450 450 "source": [
451 451 "There is not much that you can do with the above example that you can't do with the IPython display framework. To change this, you will make the widget stateful. Instead of displaying a static \"hello world\" message, it will **display a string set by the back-end**. First you need to **add a traitlet in the back-end**. Use the name of **`value` to stay consistent** with the rest of the widget framework and to **allow your widget to be used with interact**."
452 452 ]
453 453 },
454 454 {
455 455 "cell_type": "code",
456 456 "collapsed": false,
457 457 "input": [
458 458 "class HelloWidget(widgets.DOMWidget):\n",
459 459 " _view_name = Unicode('HelloView', sync=True)\n",
460 460 " value = Unicode('Hello World!', sync=True)"
461 461 ],
462 462 "language": "python",
463 463 "metadata": {},
464 464 "outputs": [],
465 465 "prompt_number": 6
466 466 },
467 467 {
468 468 "cell_type": "heading",
469 469 "level": 3,
470 470 "metadata": {
471 471 "slideshow": {
472 472 "slide_type": "slide"
473 473 }
474 474 },
475 475 "source": [
476 476 "Accessing the model from the view"
477 477 ]
478 478 },
479 479 {
480 480 "cell_type": "markdown",
481 481 "metadata": {},
482 482 "source": [
483 483 "To access the model associate with a view instance, use the **`model` property** of the view. **`get` and `set`** methods are used to interact with the Backbone model. **`get` is trivial**, however you have to **be careful when using `set`**. **After calling the model `set`** you need call the **view's `touch` method**. This associates the `set` operation with a particular view so **output will be routed to the correct cell**. The model also has a **`on` method** which allows you to listen to events triggered by the model (like value changes)."
484 484 ]
485 485 },
486 486 {
487 487 "cell_type": "heading",
488 488 "level": 3,
489 489 "metadata": {
490 490 "slideshow": {
491 491 "slide_type": "slide"
492 492 }
493 493 },
494 494 "source": [
495 495 "Rendering model contents"
496 496 ]
497 497 },
498 498 {
499 499 "cell_type": "markdown",
500 500 "metadata": {},
501 501 "source": [
502 502 "By **replacing the string literal with a call to `model.get`**, the view will now display the **value of the back-end upon display**. However, it will not update itself to a new value when the value changes."
503 503 ]
504 504 },
505 505 {
506 506 "cell_type": "code",
507 507 "collapsed": false,
508 508 "input": [
509 509 "%%javascript\n",
510 510 "\n",
511 511 "require([\"widgets/js/widget\"], function(WidgetManager){ \n",
512 512 " \n",
513 513 " var HelloView = IPython.DOMWidgetView.extend({\n",
514 514 " \n",
515 515 " render: function(){ \n",
516 516 " this.$el.text(this.model.get('value')); \n",
517 517 " },\n",
518 518 " });\n",
519 519 " \n",
520 520 " WidgetManager.register_widget_view('HelloView', HelloView);\n",
521 521 "});"
522 522 ],
523 523 "language": "python",
524 524 "metadata": {},
525 525 "outputs": [
526 526 {
527 527 "javascript": [
528 528 "\n",
529 529 "require([\"widgets/js/widget\"], function(WidgetManager){ \n",
530 530 " \n",
531 531 " var HelloView = IPython.DOMWidgetView.extend({\n",
532 532 " \n",
533 533 " render: function(){ \n",
534 534 " this.$el.text(this.model.get('value')); \n",
535 535 " },\n",
536 536 " });\n",
537 537 " \n",
538 538 " WidgetManager.register_widget_view('HelloView', HelloView);\n",
539 539 "});"
540 540 ],
541 541 "metadata": {},
542 542 "output_type": "display_data",
543 543 "text": [
544 544 "<IPython.core.display.Javascript object>"
545 545 ]
546 546 }
547 547 ],
548 548 "prompt_number": 7
549 549 },
550 550 {
551 551 "cell_type": "heading",
552 552 "level": 3,
553 553 "metadata": {
554 554 "slideshow": {
555 555 "slide_type": "slide"
556 556 }
557 557 },
558 558 "source": [
559 559 "Dynamic updates"
560 560 ]
561 561 },
562 562 {
563 563 "cell_type": "markdown",
564 564 "metadata": {},
565 565 "source": [
566 566 "To get the view to **update itself dynamically**, register a function to update the view's value when the model's `value` property changes. This can be done using the **`model.on` method**. The `on` method takes three parameters, an event name, callback handle, and callback context. The Backbone **event named `change`** will fire whenever the model changes. By **appending `:value`** to it, you tell Backbone to only listen to the change event of the `value` property (as seen below)."
567 567 ]
568 568 },
569 569 {
570 570 "cell_type": "code",
571 571 "collapsed": false,
572 572 "input": [
573 573 "%%javascript\n",
574 574 "\n",
575 575 "require([\"widgets/js/widget\"], function(WidgetManager){ \n",
576 576 " \n",
577 577 " var HelloView = IPython.DOMWidgetView.extend({\n",
578 578 " \n",
579 579 " \n",
580 580 " render: function(){ \n",
581 581 " this.value_changed();\n",
582 582 " this.model.on('change:value', this.value_changed, this);\n",
583 583 " },\n",
584 584 " \n",
585 585 " value_changed: function() {\n",
586 586 " this.$el.text(this.model.get('value')); \n",
587 587 " },\n",
588 588 " });\n",
589 589 " \n",
590 590 " WidgetManager.register_widget_view('HelloView', HelloView);\n",
591 591 "});"
592 592 ],
593 593 "language": "python",
594 594 "metadata": {},
595 595 "outputs": [
596 596 {
597 597 "javascript": [
598 598 "\n",
599 599 "require([\"widgets/js/widget\"], function(WidgetManager){ \n",
600 600 " \n",
601 601 " var HelloView = IPython.DOMWidgetView.extend({\n",
602 602 " \n",
603 603 " \n",
604 604 " render: function(){ \n",
605 605 " this.value_changed();\n",
606 606 " this.model.on('change:value', this.value_changed, this);\n",
607 607 " },\n",
608 608 " \n",
609 609 " value_changed: function() {\n",
610 610 " this.$el.text(this.model.get('value')); \n",
611 611 " },\n",
612 612 " });\n",
613 613 " \n",
614 614 " WidgetManager.register_widget_view('HelloView', HelloView);\n",
615 615 "});"
616 616 ],
617 617 "metadata": {},
618 618 "output_type": "display_data",
619 619 "text": [
620 620 "<IPython.core.display.Javascript object>"
621 621 ]
622 622 }
623 623 ],
624 624 "prompt_number": 8
625 625 },
626 626 {
627 627 "cell_type": "heading",
628 628 "level": 2,
629 629 "metadata": {
630 630 "slideshow": {
631 631 "slide_type": "slide"
632 632 }
633 633 },
634 634 "source": [
635 635 "Test"
636 636 ]
637 637 },
638 638 {
639 639 "cell_type": "code",
640 640 "collapsed": false,
641 641 "input": [
642 642 "w = HelloWidget()\n",
643 643 "w"
644 644 ],
645 645 "language": "python",
646 646 "metadata": {},
647 647 "outputs": [],
648 648 "prompt_number": 9
649 649 },
650 650 {
651 651 "cell_type": "code",
652 652 "collapsed": false,
653 653 "input": [
654 654 "w.value = 'test'"
655 655 ],
656 656 "language": "python",
657 657 "metadata": {},
658 658 "outputs": [],
659 659 "prompt_number": 10
660 660 },
661 661 {
662 662 "cell_type": "heading",
663 663 "level": 1,
664 664 "metadata": {
665 665 "slideshow": {
666 666 "slide_type": "slide"
667 667 }
668 668 },
669 669 "source": [
670 670 "Finishing"
671 671 ]
672 672 },
673 673 {
674 674 "cell_type": "heading",
675 675 "level": 2,
676 676 "metadata": {},
677 677 "source": [
678 678 "Bidirectional communication"
679 679 ]
680 680 },
681 681 {
682 682 "cell_type": "markdown",
683 683 "metadata": {},
684 684 "source": [
685 685 "The examples above dump the value directly into the DOM. There is no way for you to interact with this dumped data in the front-end. To create an example that **accepts input**, you will have to do something more than blindly dumping the contents of value into the DOM. In this part of the tutorial, you will **use a jQuery spinner** to display and accept input in the front-end. IPython currently lacks a spinner implementation so this widget will be unique."
686 686 ]
687 687 },
688 688 {
689 689 "cell_type": "heading",
690 690 "level": 3,
691 691 "metadata": {
692 692 "slideshow": {
693 693 "slide_type": "slide"
694 694 }
695 695 },
696 696 "source": [
697 697 "Update the Python code"
698 698 ]
699 699 },
700 700 {
701 701 "cell_type": "markdown",
702 702 "metadata": {},
703 703 "source": [
704 704 "You will need to change the type of the **value traitlet to `Int`**. It also makes sense to **change the name of the widget** to something more appropriate, like `SpinnerWidget`."
705 705 ]
706 706 },
707 707 {
708 708 "cell_type": "code",
709 709 "collapsed": false,
710 710 "input": [
711 711 "from IPython.utils.traitlets import CInt\n",
712 712 "class SpinnerWidget(widgets.DOMWidget):\n",
713 713 " _view_name = Unicode('SpinnerView', sync=True)\n",
714 714 " value = CInt(0, sync=True)"
715 715 ],
716 716 "language": "python",
717 717 "metadata": {},
718 718 "outputs": [],
719 719 "prompt_number": 11
720 720 },
721 721 {
722 722 "cell_type": "heading",
723 723 "level": 3,
724 724 "metadata": {
725 725 "slideshow": {
726 726 "slide_type": "slide"
727 727 }
728 728 },
729 729 "source": [
730 730 "Updating the Javascript code"
731 731 ]
732 732 },
733 733 {
734 734 "cell_type": "markdown",
735 735 "metadata": {},
736 736 "source": [
737 737 "The [jQuery docs for the spinner control](https://jqueryui.com/spinner/) say to use **`.spinner` to create a spinner** in an element. Calling **`.spinner` on `$el` will create a spinner inside `$el`**. Make sure to **update the widget name here too** so it's the same as `_view_name` in the back-end."
738 738 ]
739 739 },
740 740 {
741 741 "cell_type": "code",
742 742 "collapsed": false,
743 743 "input": [
744 744 "%%javascript\n",
745 745 "\n",
746 746 "require([\"widgets/js/widget\"], function(WidgetManager){ \n",
747 747 " \n",
748 748 " var SpinnerView = IPython.DOMWidgetView.extend({\n",
749 749 " \n",
750 750 " render: function(){ \n",
751 751 " \n",
752 752 " // jQuery code to create a spinner and append it to $el\n",
753 753 " this.$input = $('<input />');\n",
754 754 " this.$el.append(this.$input);\n",
755 755 " this.$spinner = this.$input.spinner({\n",
756 756 " change: function( event, ui ) {}\n",
757 757 " });\n",
758 758 " \n",
759 759 " this.value_changed();\n",
760 760 " this.model.on('change:value', this.value_changed, this);\n",
761 761 " },\n",
762 762 " \n",
763 763 " value_changed: function() {\n",
764 764 " \n",
765 765 " },\n",
766 766 " });\n",
767 767 " \n",
768 768 " WidgetManager.register_widget_view('SpinnerView', SpinnerView);\n",
769 769 "});"
770 770 ],
771 771 "language": "python",
772 772 "metadata": {},
773 773 "outputs": [
774 774 {
775 775 "javascript": [
776 776 "\n",
777 777 "require([\"widgets/js/widget\"], function(WidgetManager){ \n",
778 778 " \n",
779 779 " var SpinnerView = IPython.DOMWidgetView.extend({\n",
780 780 " \n",
781 781 " render: function(){ \n",
782 782 " \n",
783 783 " // jQuery code to create a spinner and append it to $el\n",
784 784 " this.$input = $('<input />');\n",
785 785 " this.$el.append(this.$input);\n",
786 786 " this.$spinner = this.$input.spinner({\n",
787 787 " change: function( event, ui ) {}\n",
788 788 " });\n",
789 789 " \n",
790 790 " this.value_changed();\n",
791 791 " this.model.on('change:value', this.value_changed, this);\n",
792 792 " },\n",
793 793 " \n",
794 794 " value_changed: function() {\n",
795 795 " \n",
796 796 " },\n",
797 797 " });\n",
798 798 " \n",
799 799 " WidgetManager.register_widget_view('SpinnerView', SpinnerView);\n",
800 800 "});"
801 801 ],
802 802 "metadata": {},
803 803 "output_type": "display_data",
804 804 "text": [
805 805 "<IPython.core.display.Javascript object>"
806 806 ]
807 807 }
808 808 ],
809 809 "prompt_number": 12
810 810 },
811 811 {
812 812 "cell_type": "heading",
813 813 "level": 3,
814 814 "metadata": {
815 815 "slideshow": {
816 816 "slide_type": "slide"
817 817 }
818 818 },
819 819 "source": [
820 820 "Getting and setting the value"
821 821 ]
822 822 },
823 823 {
824 824 "cell_type": "markdown",
825 825 "metadata": {},
826 826 "source": [
827 827 "To **set the value of the spinner on update from the back-end**, you need to use **jQuery's `spinner` API**. `spinner.spinner('value', new)` will set the value of the spinner. Add that code to the **`value_changed` method** to make the spinner **update with the value stored in the back-end((. Using jQuery's spinner API, you can add a function to handle the **spinner `change` event** by passing it in when constructing the spinner. Inside the `change` event, call **`model.set`** to set the value and then **`touch`** to inform the framework that this view was the view that caused the change to the model. **Note: The `var that = this;` is a JavaScript trick to pass the current context into closures.**"
828 828 ]
829 829 },
830 830 {
831 831 "cell_type": "code",
832 832 "collapsed": false,
833 833 "input": [
834 834 "%%javascript\n",
835 835 "\n",
836 836 "require([\"widgets/js/widget\"], function(WidgetManager){ \n",
837 837 " \n",
838 838 " var SpinnerView = IPython.DOMWidgetView.extend({\n",
839 839 " \n",
840 840 " render: function(){ \n",
841 841 "\n",
842 842 " var that = this;\n",
843 843 " this.$input = $('<input />');\n",
844 844 " this.$el.append(this.$input);\n",
845 845 " this.$spinner = this.$input.spinner({\n",
846 846 " change: function( event, ui ) {\n",
847 847 " that.handle_spin();\n",
848 848 " },\n",
849 849 " spin: function( event, ui ) {\n",
850 850 " that.handle_spin();\n",
851 851 " }\n",
852 852 " });\n",
853 853 " \n",
854 854 " this.value_changed();\n",
855 855 " this.model.on('change:value', this.value_changed, this);\n",
856 856 " },\n",
857 857 " \n",
858 858 " value_changed: function() {\n",
859 859 " this.$spinner.spinner('value', this.model.get('value'));\n",
860 860 " },\n",
861 861 " \n",
862 862 " handle_spin: function() {\n",
863 863 " this.model.set('value', this.$spinner.spinner('value'));\n",
864 864 " this.touch();\n",
865 865 " },\n",
866 866 " });\n",
867 867 " \n",
868 868 " WidgetManager.register_widget_view('SpinnerView', SpinnerView);\n",
869 869 "});"
870 870 ],
871 871 "language": "python",
872 872 "metadata": {},
873 873 "outputs": [
874 874 {
875 875 "javascript": [
876 876 "\n",
877 877 "require([\"widgets/js/widget\"], function(WidgetManager){ \n",
878 878 " \n",
879 879 " var SpinnerView = IPython.DOMWidgetView.extend({\n",
880 880 " \n",
881 881 " render: function(){ \n",
882 882 "\n",
883 883 " var that = this;\n",
884 884 " this.$input = $('<input />');\n",
885 885 " this.$el.append(this.$input);\n",
886 886 " this.$spinner = this.$input.spinner({\n",
887 887 " change: function( event, ui ) {\n",
888 888 " that.handle_spin();\n",
889 889 " },\n",
890 890 " spin: function( event, ui ) {\n",
891 891 " that.handle_spin();\n",
892 892 " }\n",
893 893 " });\n",
894 894 " \n",
895 895 " this.value_changed();\n",
896 896 " this.model.on('change:value', this.value_changed, this);\n",
897 897 " },\n",
898 898 " \n",
899 899 " value_changed: function() {\n",
900 900 " this.$spinner.spinner('value', this.model.get('value'));\n",
901 901 " },\n",
902 902 " \n",
903 903 " handle_spin: function() {\n",
904 904 " this.model.set('value', this.$spinner.spinner('value'));\n",
905 905 " this.touch();\n",
906 906 " },\n",
907 907 " });\n",
908 908 " \n",
909 909 " WidgetManager.register_widget_view('SpinnerView', SpinnerView);\n",
910 910 "});"
911 911 ],
912 912 "metadata": {},
913 913 "output_type": "display_data",
914 914 "text": [
915 915 "<IPython.core.display.Javascript object>"
916 916 ]
917 917 }
918 918 ],
919 919 "prompt_number": 13
920 920 },
921 921 {
922 922 "cell_type": "heading",
923 923 "level": 2,
924 924 "metadata": {
925 925 "slideshow": {
926 926 "slide_type": "slide"
927 927 }
928 928 },
929 929 "source": [
930 930 "Test"
931 931 ]
932 932 },
933 933 {
934 934 "cell_type": "code",
935 935 "collapsed": false,
936 936 "input": [
937 937 "w = SpinnerWidget(value=5)\n",
938 938 "w"
939 939 ],
940 940 "language": "python",
941 941 "metadata": {},
942 942 "outputs": [],
943 943 "prompt_number": 15
944 944 },
945 945 {
946 946 "cell_type": "code",
947 947 "collapsed": false,
948 948 "input": [
949 949 "w.value"
950 950 ],
951 951 "language": "python",
952 952 "metadata": {},
953 953 "outputs": [
954 954 {
955 955 "metadata": {},
956 956 "output_type": "pyout",
957 957 "prompt_number": 16,
958 958 "text": [
959 959 "5"
960 960 ]
961 961 }
962 962 ],
963 963 "prompt_number": 16
964 964 },
965 965 {
966 966 "cell_type": "code",
967 967 "collapsed": false,
968 968 "input": [
969 969 "w.value = 20"
970 970 ],
971 971 "language": "python",
972 972 "metadata": {},
973 973 "outputs": [],
974 974 "prompt_number": 17
975 975 },
976 976 {
977 977 "cell_type": "markdown",
978 978 "metadata": {},
979 979 "source": [
980 980 "Trying to **use the spinner with another widget**."
981 981 ]
982 982 },
983 983 {
984 984 "cell_type": "code",
985 985 "collapsed": false,
986 986 "input": [
987 987 "from IPython.display import display\n",
988 988 "w1 = SpinnerWidget(value=0)\n",
989 989 "w2 = widgets.IntSliderWidget()\n",
990 990 "display(w1,w2)\n",
991 991 "\n",
992 992 "from IPython.utils.traitlets import link\n",
993 993 "mylink = link((w1, 'value'), (w2, 'value'))"
994 994 ],
995 995 "language": "python",
996 996 "metadata": {},
997 997 "outputs": [],
998 998 "prompt_number": 18
999 999 },
1000 1000 {
1001 1001 "cell_type": "markdown",
1002 1002 "metadata": {},
1003 1003 "source": [
1004 1004 "[Index](Index.ipynb) - [Back](Widget Styling.ipynb)"
1005 1005 ]
1006 1006 }
1007 1007 ],
1008 1008 "metadata": {}
1009 1009 }
1010 1010 ]
1011 1011 } No newline at end of file
@@ -1,468 +1,478 b''
1 1 {
2 2 "metadata": {
3 3 "celltoolbar": "Slideshow",
4 4 "name": "",
5 "signature": "sha256:b8ade017615ae4e656f0740a85c77764ff451dd651110af0b5dc92fd5cb409ff"
5 "signature": "sha256:4ca68d6f219809b3b1c4e84665384a4069dbc1c8496fdbdcba1508bbe1266b44"
6 6 },
7 7 "nbformat": 3,
8 8 "nbformat_minor": 0,
9 9 "worksheets": [
10 10 {
11 11 "cells": [
12 12 {
13 13 "cell_type": "markdown",
14 14 "metadata": {},
15 15 "source": [
16 16 "[Index](Index.ipynb) - [Next](Widget List.ipynb)"
17 17 ]
18 18 },
19 19 {
20 20 "cell_type": "heading",
21 21 "level": 1,
22 22 "metadata": {},
23 23 "source": [
24 24 "Simple Widget Introduction"
25 25 ]
26 26 },
27 27 {
28 28 "cell_type": "heading",
29 29 "level": 2,
30 30 "metadata": {},
31 31 "source": [
32 32 "What are widgets?"
33 33 ]
34 34 },
35 35 {
36 36 "cell_type": "markdown",
37 37 "metadata": {
38 38 "slideshow": {
39 39 "slide_type": "slide"
40 40 }
41 41 },
42 42 "source": [
43 43 "Widgets are elements that exists in both the front-end and the back-end.\n",
44 44 "\n",
45 "** Insert Frontend-Backend Picture **"
45 "![Kernel & front-end diagram](../images/FrontendKernel.png)"
46 46 ]
47 47 },
48 48 {
49 49 "cell_type": "heading",
50 50 "level": 2,
51 51 "metadata": {},
52 52 "source": [
53 53 "What can they be used for?"
54 54 ]
55 55 },
56 56 {
57 57 "cell_type": "markdown",
58 58 "metadata": {
59 59 "slideshow": {
60 60 "slide_type": "slide"
61 61 }
62 62 },
63 63 "source": [
64 64 "You can use widgets to build **interactive GUIs** for your notebooks. \n",
65 65 "You can also use widgets to **synchronize stateful and stateless information** between Python and JavaScript."
66 66 ]
67 67 },
68 68 {
69 69 "cell_type": "heading",
70 70 "level": 2,
71 71 "metadata": {},
72 72 "source": [
73 73 "Using widgets "
74 74 ]
75 75 },
76 76 {
77 77 "cell_type": "markdown",
78 78 "metadata": {
79 79 "slideshow": {
80 80 "slide_type": "slide"
81 81 }
82 82 },
83 83 "source": [
84 84 "To use the widget framework, you need to **import `IPython.html.widgets`**."
85 85 ]
86 86 },
87 87 {
88 88 "cell_type": "code",
89 89 "collapsed": false,
90 90 "input": [
91 91 "from IPython.html.widgets import *"
92 92 ],
93 93 "language": "python",
94 94 "metadata": {},
95 95 "outputs": [],
96 96 "prompt_number": 1
97 97 },
98 98 {
99 99 "cell_type": "heading",
100 100 "level": 3,
101 101 "metadata": {
102 102 "slideshow": {
103 103 "slide_type": "slide"
104 104 }
105 105 },
106 106 "source": [
107 107 "repr"
108 108 ]
109 109 },
110 110 {
111 111 "cell_type": "markdown",
112 112 "metadata": {},
113 113 "source": [
114 114 "Widgets have their own display `repr` which allows them to be displayed using IPython's display framework. Constructing and returning an `IntSliderWidget` automatically displays the widget (as seen below). Widgets are **displayed inside the `widget area`**, which sits between the code cell and output. **You can hide all of the widgets** in the `widget area` by clicking the grey *x* in the margin."
115 115 ]
116 116 },
117 117 {
118 118 "cell_type": "code",
119 119 "collapsed": false,
120 120 "input": [
121 121 "IntSliderWidget()"
122 122 ],
123 123 "language": "python",
124 124 "metadata": {},
125 125 "outputs": [],
126 126 "prompt_number": 2
127 127 },
128 128 {
129 129 "cell_type": "heading",
130 130 "level": 3,
131 131 "metadata": {
132 132 "slideshow": {
133 133 "slide_type": "slide"
134 134 }
135 135 },
136 136 "source": [
137 137 "display()"
138 138 ]
139 139 },
140 140 {
141 141 "cell_type": "markdown",
142 142 "metadata": {},
143 143 "source": [
144 144 "You can also explicitly display the widget using `display(...)`."
145 145 ]
146 146 },
147 147 {
148 148 "cell_type": "code",
149 149 "collapsed": false,
150 150 "input": [
151 151 "from IPython.display import display\n",
152 152 "w = IntSliderWidget()\n",
153 153 "display(w)"
154 154 ],
155 155 "language": "python",
156 156 "metadata": {},
157 157 "outputs": [],
158 158 "prompt_number": 3
159 159 },
160 160 {
161 161 "cell_type": "heading",
162 162 "level": 3,
163 163 "metadata": {
164 164 "slideshow": {
165 165 "slide_type": "slide"
166 166 }
167 167 },
168 168 "source": [
169 169 "Multiple display() calls"
170 170 ]
171 171 },
172 172 {
173 173 "cell_type": "markdown",
174 174 "metadata": {},
175 175 "source": [
176 176 "If you display the same widget twice, the displayed instances in the front-end **will remain in sync** with each other."
177 177 ]
178 178 },
179 179 {
180 180 "cell_type": "code",
181 181 "collapsed": false,
182 182 "input": [
183 183 "display(w)"
184 184 ],
185 185 "language": "python",
186 186 "metadata": {},
187 187 "outputs": [],
188 188 "prompt_number": 4
189 189 },
190 190 {
191 191 "cell_type": "heading",
192 192 "level": 2,
193 193 "metadata": {},
194 194 "source": [
195 195 "Why does displaying the same widget twice work?"
196 196 ]
197 197 },
198 198 {
199 199 "cell_type": "markdown",
200 200 "metadata": {
201 201 "slideshow": {
202 202 "slide_type": "slide"
203 203 }
204 204 },
205 205 "source": [
206 206 "Widgets are **represented in the back-end by a single object**. Each time a widget is displayed, **a new representation** of that same object is created in the front-end. These representations are called **views**.\n",
207 207 "\n",
208 "** Insert Backend-Frontend Views Figure **"
208 "![Kernel & front-end diagram](images/WidgetModelView.png)"
209 209 ]
210 210 },
211 211 {
212 212 "cell_type": "heading",
213 213 "level": 3,
214 214 "metadata": {
215 215 "slideshow": {
216 216 "slide_type": "slide"
217 217 }
218 218 },
219 219 "source": [
220 220 "Closing widgets"
221 221 ]
222 222 },
223 223 {
224 224 "cell_type": "markdown",
225 225 "metadata": {},
226 226 "source": [
227 227 "You can close a widget by calling its `close()` method."
228 228 ]
229 229 },
230 230 {
231 231 "cell_type": "code",
232 232 "collapsed": false,
233 233 "input": [
234 "display(w)"
235 ],
236 "language": "python",
237 "metadata": {},
238 "outputs": []
239 },
240 {
241 "cell_type": "code",
242 "collapsed": false,
243 "input": [
234 244 "w.close()"
235 245 ],
236 246 "language": "python",
237 247 "metadata": {},
238 248 "outputs": [],
239 249 "prompt_number": 5
240 250 },
241 251 {
242 252 "cell_type": "heading",
243 253 "level": 2,
244 254 "metadata": {},
245 255 "source": [
246 256 "Widget properties"
247 257 ]
248 258 },
249 259 {
250 260 "cell_type": "markdown",
251 261 "metadata": {
252 262 "slideshow": {
253 263 "slide_type": "slide"
254 264 }
255 265 },
256 266 "source": [
257 267 "All of the IPython widgets **share a similar naming scheme**. To read the value of a widget, you can query its `value` property."
258 268 ]
259 269 },
260 270 {
261 271 "cell_type": "code",
262 272 "collapsed": false,
263 273 "input": [
264 274 "w = IntSliderWidget()\n",
265 275 "display(w)"
266 276 ],
267 277 "language": "python",
268 278 "metadata": {},
269 279 "outputs": [],
270 280 "prompt_number": 9
271 281 },
272 282 {
273 283 "cell_type": "code",
274 284 "collapsed": false,
275 285 "input": [
276 286 "w.value"
277 287 ],
278 288 "language": "python",
279 289 "metadata": {},
280 290 "outputs": [
281 291 {
282 292 "metadata": {},
283 293 "output_type": "pyout",
284 294 "prompt_number": 11,
285 295 "text": [
286 296 "40"
287 297 ]
288 298 }
289 299 ],
290 300 "prompt_number": 11
291 301 },
292 302 {
293 303 "cell_type": "markdown",
294 304 "metadata": {},
295 305 "source": [
296 306 "Similarly, to set a widget's value, you can set its `value` property."
297 307 ]
298 308 },
299 309 {
300 310 "cell_type": "code",
301 311 "collapsed": false,
302 312 "input": [
303 313 "w.value = 100"
304 314 ],
305 315 "language": "python",
306 316 "metadata": {},
307 317 "outputs": [],
308 318 "prompt_number": 12
309 319 },
310 320 {
311 321 "cell_type": "heading",
312 322 "level": 3,
313 323 "metadata": {
314 324 "slideshow": {
315 325 "slide_type": "slide"
316 326 }
317 327 },
318 328 "source": [
319 329 "Keys"
320 330 ]
321 331 },
322 332 {
323 333 "cell_type": "markdown",
324 334 "metadata": {},
325 335 "source": [
326 336 "In addition to `value`, most widgets share `keys`, `description`, `disabled`, and `visible`. To see the entire list of synchronized, stateful properties, of any specific widget, you can **query the `keys` property**."
327 337 ]
328 338 },
329 339 {
330 340 "cell_type": "code",
331 341 "collapsed": false,
332 342 "input": [
333 343 "w.keys"
334 344 ],
335 345 "language": "python",
336 346 "metadata": {},
337 347 "outputs": [
338 348 {
339 349 "metadata": {},
340 350 "output_type": "pyout",
341 351 "prompt_number": 13,
342 352 "text": [
343 353 "['_view_name',\n",
344 354 " 'orientation',\n",
345 355 " 'msg_throttle',\n",
346 356 " 'min',\n",
347 357 " 'max',\n",
348 358 " '_css',\n",
349 359 " 'value',\n",
350 360 " 'readout',\n",
351 361 " 'disabled',\n",
352 362 " 'visible',\n",
353 363 " 'step',\n",
354 364 " 'description']"
355 365 ]
356 366 }
357 367 ],
358 368 "prompt_number": 13
359 369 },
360 370 {
361 371 "cell_type": "heading",
362 372 "level": 3,
363 373 "metadata": {},
364 374 "source": [
365 375 "Shorthand for setting the initial values of widget properties"
366 376 ]
367 377 },
368 378 {
369 379 "cell_type": "markdown",
370 380 "metadata": {
371 381 "slideshow": {
372 382 "slide_type": "slide"
373 383 }
374 384 },
375 385 "source": [
376 386 "While creating a widget, you can set some or all of the initial values of that widget by **defining them as keyword arguments in the widget's constructor** (as seen below)."
377 387 ]
378 388 },
379 389 {
380 390 "cell_type": "code",
381 391 "collapsed": false,
382 392 "input": [
383 393 "TextWidget(value='Hello World!', disabled=True)"
384 394 ],
385 395 "language": "python",
386 396 "metadata": {},
387 397 "outputs": [],
388 398 "prompt_number": 14
389 399 },
390 400 {
391 401 "cell_type": "heading",
392 402 "level": 2,
393 403 "metadata": {},
394 404 "source": [
395 405 "Linking two similar widgets"
396 406 ]
397 407 },
398 408 {
399 409 "cell_type": "markdown",
400 410 "metadata": {
401 411 "slideshow": {
402 412 "slide_type": "slide"
403 413 }
404 414 },
405 415 "source": [
406 416 "If you need to display the same value two different ways, you'll have to use two different widgets. Instead of **attempting to manually synchronize the values** of the two widgets, you can use the `traitlet` `link` function **to link two properties together**. Below, the values of three widgets are linked together."
407 417 ]
408 418 },
409 419 {
410 420 "cell_type": "code",
411 421 "collapsed": false,
412 422 "input": [
413 423 "from IPython.utils.traitlets import link\n",
414 424 "a = FloatTextWidget()\n",
415 425 "b = FloatSliderWidget()\n",
416 426 "c = FloatProgressWidget()\n",
417 427 "display(a,b,c)\n",
418 428 "\n",
419 429 "\n",
420 430 "mylink = link((a, 'value'), (b, 'value'), (c, 'value'))"
421 431 ],
422 432 "language": "python",
423 433 "metadata": {},
424 434 "outputs": [],
425 435 "prompt_number": 15
426 436 },
427 437 {
428 438 "cell_type": "heading",
429 439 "level": 3,
430 440 "metadata": {},
431 441 "source": [
432 442 "Unlinking widgets"
433 443 ]
434 444 },
435 445 {
436 446 "cell_type": "markdown",
437 447 "metadata": {
438 448 "slideshow": {
439 449 "slide_type": "slide"
440 450 }
441 451 },
442 452 "source": [
443 453 "Unlinking the widgets is simple. All you have to do is call `.unlink` on the link object."
444 454 ]
445 455 },
446 456 {
447 457 "cell_type": "code",
448 458 "collapsed": false,
449 459 "input": [
450 460 "mylink.unlink()"
451 461 ],
452 462 "language": "python",
453 463 "metadata": {},
454 464 "outputs": [],
455 465 "prompt_number": 16
456 466 },
457 467 {
458 468 "cell_type": "markdown",
459 469 "metadata": {},
460 470 "source": [
461 471 "[Index](Index.ipynb) - [Next](Widget List.ipynb)"
462 472 ]
463 473 }
464 474 ],
465 475 "metadata": {}
466 476 }
467 477 ]
468 478 } No newline at end of file
@@ -1,1465 +1,1464 b''
1 1 {
2 2 "metadata": {
3 3 "cell_tags": [
4 4 [
5 5 "<None>",
6 6 null
7 7 ]
8 8 ],
9 9 "celltoolbar": "Slideshow",
10 10 "name": "",
11 "signature": "sha256:ea0c2f71869ec3d5fb1b007e1f13023fa5fc580ad7bee737d5cbbce8a1490169"
11 "signature": "sha256:25ac05059b7d8d60e6ff6a9098db0fb06a6c0b0e67c457b1d362f34d9cecef18"
12 12 },
13 13 "nbformat": 3,
14 14 "nbformat_minor": 0,
15 15 "worksheets": [
16 16 {
17 17 "cells": [
18 18 {
19 19 "cell_type": "markdown",
20 20 "metadata": {},
21 21 "source": [
22 22 "[Index](Index.ipynb) - [Back](Widget Events.ipynb) - [Next](Custom Widget - Hello World.ipynb)"
23 23 ]
24 24 },
25 25 {
26 26 "cell_type": "code",
27 27 "collapsed": false,
28 28 "input": [
29 29 "%%html\n",
30 30 "<style>\n",
31 31 ".example-container { background: #999999; padding: 2px; min-height: 100px; }\n",
32 32 ".example-container.sm { min-height: 50px; }\n",
33 33 ".example-box { background: #9999FF; width: 50px; height: 50px; text-align: center; vertical-align: middle; color: white; font-weight: bold; margin: 2px;}\n",
34 34 ".example-box.med { width: 65px; height: 65px; } \n",
35 35 ".example-box.lrg { width: 80px; height: 80px; } \n",
36 36 "</style>"
37 37 ],
38 38 "language": "python",
39 39 "metadata": {},
40 40 "outputs": [
41 41 {
42 42 "html": [
43 43 "<style>\n",
44 44 ".example-container { background: #999999; padding: 2px; min-height: 100px; }\n",
45 45 ".example-container.sm { min-height: 50px; }\n",
46 46 ".example-box { background: #9999FF; width: 50px; height: 50px; text-align: center; vertical-align: middle; color: white; font-weight: bold; margin: 2px;}\n",
47 47 ".example-box.med { width: 65px; height: 65px; } \n",
48 48 ".example-box.lrg { width: 80px; height: 80px; } \n",
49 49 "</style>"
50 50 ],
51 51 "metadata": {},
52 52 "output_type": "display_data",
53 53 "text": [
54 54 "<IPython.core.display.HTML object>"
55 55 ]
56 56 }
57 57 ],
58 58 "prompt_number": 1
59 59 },
60 60 {
61 61 "cell_type": "heading",
62 62 "level": 1,
63 63 "metadata": {
64 64 "slideshow": {
65 65 "slide_type": "slide"
66 66 }
67 67 },
68 68 "source": [
69 69 "Widget Styling"
70 70 ]
71 71 },
72 72 {
73 73 "cell_type": "heading",
74 74 "level": 2,
75 75 "metadata": {},
76 76 "source": [
77 77 "CSS"
78 78 ]
79 79 },
80 80 {
81 81 "cell_type": "markdown",
82 82 "metadata": {},
83 83 "source": [
84 84 "Since the representation of the widget you see is a **browser element**, **Cascading Style Sheets (CSS)** are used for styling. Widgets have a **`set_css` method** that allows you to **add and remove CSS properties** from your elements. The following example shows had `set_css` **can be used to set the background color** of a `TextWidget`."
85 85 ]
86 86 },
87 87 {
88 88 "cell_type": "code",
89 89 "collapsed": false,
90 90 "input": [
91 91 "from IPython.html import widgets\n",
92 92 "text = widgets.TextWidget(value=\"Hello World!\")\n",
93 93 "text.set_css('background', 'lime')\n",
94 94 "text "
95 95 ],
96 96 "language": "python",
97 97 "metadata": {},
98 98 "outputs": [],
99 99 "prompt_number": 5
100 100 },
101 101 {
102 102 "cell_type": "heading",
103 103 "level": 3,
104 104 "metadata": {
105 105 "slideshow": {
106 106 "slide_type": "slide"
107 107 }
108 108 },
109 109 "source": [
110 110 "Color codes"
111 111 ]
112 112 },
113 113 {
114 114 "cell_type": "markdown",
115 115 "metadata": {},
116 116 "source": [
117 117 "In the example above, **the color `lime` is specified by name**. CSS also supports specifying colors by a **3 byte hexadecimal string**. The first byte is red, second green, and third blue (**RGB**). The following example sets the `TextWidget`'s background to blue."
118 118 ]
119 119 },
120 120 {
121 121 "cell_type": "code",
122 122 "collapsed": false,
123 123 "input": [
124 124 "text.set_css('background', '#0000FF')"
125 125 ],
126 126 "language": "python",
127 127 "metadata": {},
128 128 "outputs": [],
129 129 "prompt_number": 6
130 130 },
131 131 {
132 132 "cell_type": "heading",
133 133 "level": 3,
134 134 "metadata": {
135 135 "slideshow": {
136 136 "slide_type": "slide"
137 137 }
138 138 },
139 139 "source": [
140 140 "Forecolor"
141 141 ]
142 142 },
143 143 {
144 144 "cell_type": "markdown",
145 145 "metadata": {},
146 146 "source": [
147 147 "In CSS the **font color is `color`.**"
148 148 ]
149 149 },
150 150 {
151 151 "cell_type": "code",
152 152 "collapsed": false,
153 153 "input": [
154 154 "text.set_css('color', '#FFFFFF')"
155 155 ],
156 156 "language": "python",
157 157 "metadata": {},
158 158 "outputs": [],
159 159 "prompt_number": 7
160 160 },
161 161 {
162 162 "cell_type": "heading",
163 163 "level": 3,
164 164 "metadata": {
165 165 "slideshow": {
166 166 "slide_type": "slide"
167 167 }
168 168 },
169 169 "source": [
170 170 "Size"
171 171 ]
172 172 },
173 173 {
174 174 "cell_type": "markdown",
175 175 "metadata": {},
176 176 "source": [
177 177 "CSS is also used to set the **height and width** of controls. The `set_css` method also **can accept a single dictionary with multiple CSS properties** (as seen below)."
178 178 ]
179 179 },
180 180 {
181 181 "cell_type": "code",
182 182 "collapsed": false,
183 183 "input": [
184 184 "btn = widgets.ButtonWidget()\n",
185 185 "btn.set_css({\n",
186 186 " 'width': '100px',\n",
187 187 " 'height': '100px',\n",
188 188 " 'background': 'red',\n",
189 189 "})\n",
190 190 "btn"
191 191 ],
192 192 "language": "python",
193 193 "metadata": {},
194 194 "outputs": [],
195 195 "prompt_number": 8
196 196 },
197 197 {
198 198 "cell_type": "heading",
199 199 "level": 3,
200 200 "metadata": {
201 201 "slideshow": {
202 202 "slide_type": "slide"
203 203 }
204 204 },
205 205 "source": [
206 206 "Removing"
207 207 ]
208 208 },
209 209 {
210 210 "cell_type": "markdown",
211 211 "metadata": {},
212 212 "source": [
213 213 "To remove the styling, you can call `set_css` again, but use an empty string instead of a color value."
214 214 ]
215 215 },
216 216 {
217 217 "cell_type": "code",
218 218 "collapsed": false,
219 219 "input": [
220 220 "btn.set_css('background', '')"
221 221 ],
222 222 "language": "python",
223 223 "metadata": {},
224 224 "outputs": [],
225 225 "prompt_number": 9
226 226 },
227 227 {
228 228 "cell_type": "markdown",
229 229 "metadata": {},
230 230 "source": [
231 231 "For more information about what can be done with CSS, please refer to the [Mozilla Developer Network's series on it](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_started).\n"
232 232 ]
233 233 },
234 234 {
235 235 "cell_type": "heading",
236 236 "level": 2,
237 237 "metadata": {
238 238 "slideshow": {
239 239 "slide_type": "slide"
240 240 }
241 241 },
242 242 "source": [
243 243 "Parent/child relationships"
244 244 ]
245 245 },
246 246 {
247 247 "cell_type": "markdown",
248 248 "metadata": {},
249 249 "source": [
250 250 "To display widget A inside widget B, widget A must be a child of widget B. **Only one instance of any particular widget can be child of another (this limitation will be removed in IPython 3.0).** In other words, *widget A* cannot have *widget B* listed twice in it's list of children.\n",
251 251 "\n",
252 252 "Widgets that can contain other widgets have a **`children` attribute**. This attribute can be **set via a keyword argument** in the widget's constructor **or after construction**. Calling display on an **object with children automatically displays those children**, too."
253 253 ]
254 254 },
255 255 {
256 256 "cell_type": "code",
257 257 "collapsed": false,
258 258 "input": [
259 "\n",
260 259 "from IPython.display import display\n",
261 260 "\n",
262 261 "float_range = widgets.FloatSliderWidget()\n",
263 262 "string = widgets.TextWidget(value='hi')\n",
264 263 "container = widgets.ContainerWidget(children=[float_range, string])\n",
265 264 "\n",
266 265 "container.set_css('border', '3px dotted red')\n",
267 266 "display(container) # Displays the `container` and all of it's children."
268 267 ],
269 268 "language": "python",
270 269 "metadata": {},
271 270 "outputs": [],
272 271 "prompt_number": 10
273 272 },
274 273 {
275 274 "cell_type": "heading",
276 275 "level": 3,
277 276 "metadata": {},
278 277 "source": [
279 278 "After the parent is displayed"
280 279 ]
281 280 },
282 281 {
283 282 "cell_type": "markdown",
284 283 "metadata": {
285 284 "slideshow": {
286 285 "slide_type": "slide"
287 286 }
288 287 },
289 288 "source": [
290 289 "Children **can be added to parents** after the parent has been displayed. The **parent is responsible for rendering its children**."
291 290 ]
292 291 },
293 292 {
294 293 "cell_type": "code",
295 294 "collapsed": false,
296 295 "input": [
297 296 "container = widgets.ContainerWidget()\n",
298 297 "container.set_css('border', '3px dotted red')\n",
299 298 "display(container)\n",
300 299 "\n",
301 300 "int_range = widgets.IntSliderWidget()\n",
302 301 "container.children=[int_range]"
303 302 ],
304 303 "language": "python",
305 304 "metadata": {},
306 305 "outputs": [],
307 306 "prompt_number": 11
308 307 },
309 308 {
310 309 "cell_type": "heading",
311 310 "level": 2,
312 311 "metadata": {
313 312 "slideshow": {
314 313 "slide_type": "slide"
315 314 }
316 315 },
317 316 "source": [
318 317 "Fancy containers"
319 318 ]
320 319 },
321 320 {
322 321 "cell_type": "markdown",
323 322 "metadata": {},
324 323 "source": [
325 324 "If you need to display a more complicated set of widgets, there are **specialized containers** that you can use. To display **multiple sets of widgets**, you can use an **`AccordionWidget` or a `TabWidget` in combination with one `ContainerWidget` per set of widgets** (as seen below). The \"pages\" of these widgets are their children. To set the titles of the pages, one must **call `set_title` after the widget has been displayed**."
326 325 ]
327 326 },
328 327 {
329 328 "cell_type": "heading",
330 329 "level": 3,
331 330 "metadata": {},
332 331 "source": [
333 332 "AccordionWidget"
334 333 ]
335 334 },
336 335 {
337 336 "cell_type": "code",
338 337 "collapsed": false,
339 338 "input": [
340 339 "name1 = widgets.TextWidget(description='Location:')\n",
341 340 "zip1 = widgets.BoundedIntTextWidget(description='Zip:', min=0, max=99999)\n",
342 341 "page1 = widgets.ContainerWidget(children=[name1, zip1])\n",
343 342 "\n",
344 343 "name2 = widgets.TextWidget(description='Location:')\n",
345 344 "zip2 = widgets.BoundedIntTextWidget(description='Zip:', min=0, max=99999)\n",
346 345 "page2 = widgets.ContainerWidget(children=[name2, zip2])\n",
347 346 "\n",
348 347 "accord = widgets.AccordionWidget(children=[page1, page2])\n",
349 348 "display(accord)\n",
350 349 "\n",
351 350 "accord.set_title(0, 'From')\n",
352 351 "accord.set_title(1, 'To')"
353 352 ],
354 353 "language": "python",
355 354 "metadata": {},
356 355 "outputs": [],
357 356 "prompt_number": 12
358 357 },
359 358 {
360 359 "cell_type": "heading",
361 360 "level": 3,
362 361 "metadata": {
363 362 "slideshow": {
364 363 "slide_type": "slide"
365 364 }
366 365 },
367 366 "source": [
368 367 "TabWidget"
369 368 ]
370 369 },
371 370 {
372 371 "cell_type": "code",
373 372 "collapsed": false,
374 373 "input": [
375 374 "name = widgets.TextWidget(description='Name:')\n",
376 375 "color = widgets.DropdownWidget(description='Color:', values=['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'])\n",
377 376 "page1 = widgets.ContainerWidget(children=[name, color])\n",
378 377 "\n",
379 378 "age = widgets.IntSliderWidget(description='Age:', min=0, max=120, value=50)\n",
380 379 "gender = widgets.RadioButtonsWidget(description='Gender:', values=['male', 'female'])\n",
381 380 "page2 = widgets.ContainerWidget(children=[age, gender])\n",
382 381 "\n",
383 382 "tabs = widgets.TabWidget(children=[page1, page2])\n",
384 383 "display(tabs)\n",
385 384 "\n",
386 385 "tabs.set_title(0, 'Name')\n",
387 386 "tabs.set_title(1, 'Details')"
388 387 ],
389 388 "language": "python",
390 389 "metadata": {},
391 390 "outputs": [],
392 391 "prompt_number": 13
393 392 },
394 393 {
395 394 "cell_type": "heading",
396 395 "level": 3,
397 396 "metadata": {
398 397 "slideshow": {
399 398 "slide_type": "slide"
400 399 }
401 400 },
402 401 "source": [
403 402 "PopupWidget"
404 403 ]
405 404 },
406 405 {
407 406 "cell_type": "markdown",
408 407 "metadata": {},
409 408 "source": [
410 409 "Unlike the other two special containers, the `PopupWidget` is only **designed to display one set of widgets**. The `PopupWidget` can be used to **display widgets outside of the widget area**. "
411 410 ]
412 411 },
413 412 {
414 413 "cell_type": "code",
415 414 "collapsed": false,
416 415 "input": [
417 416 "counter = widgets.IntTextWidget(description='Counter:')\n",
418 417 "popup = widgets.PopupWidget(children=[counter], description='Popup Demo', button_text='Popup Button')\n",
419 418 "display(popup)"
420 419 ],
421 420 "language": "python",
422 421 "metadata": {},
423 422 "outputs": [],
424 423 "prompt_number": 14
425 424 },
426 425 {
427 426 "cell_type": "code",
428 427 "collapsed": false,
429 428 "input": [
430 429 "counter.value += 1"
431 430 ],
432 431 "language": "python",
433 432 "metadata": {},
434 433 "outputs": [],
435 434 "prompt_number": 15
436 435 },
437 436 {
438 437 "cell_type": "code",
439 438 "collapsed": false,
440 439 "input": [],
441 440 "language": "python",
442 441 "metadata": {},
443 442 "outputs": [],
444 443 "prompt_number": 15
445 444 },
446 445 {
447 446 "cell_type": "code",
448 447 "collapsed": false,
449 448 "input": [],
450 449 "language": "python",
451 450 "metadata": {},
452 451 "outputs": [],
453 452 "prompt_number": 15
454 453 },
455 454 {
456 455 "cell_type": "code",
457 456 "collapsed": false,
458 457 "input": [],
459 458 "language": "python",
460 459 "metadata": {},
461 460 "outputs": [],
462 461 "prompt_number": 15
463 462 },
464 463 {
465 464 "cell_type": "code",
466 465 "collapsed": false,
467 466 "input": [],
468 467 "language": "python",
469 468 "metadata": {},
470 469 "outputs": [],
471 470 "prompt_number": 15
472 471 },
473 472 {
474 473 "cell_type": "code",
475 474 "collapsed": false,
476 475 "input": [],
477 476 "language": "python",
478 477 "metadata": {},
479 478 "outputs": [],
480 479 "prompt_number": 15
481 480 },
482 481 {
483 482 "cell_type": "code",
484 483 "collapsed": false,
485 484 "input": [],
486 485 "language": "python",
487 486 "metadata": {},
488 487 "outputs": [],
489 488 "prompt_number": 15
490 489 },
491 490 {
492 491 "cell_type": "code",
493 492 "collapsed": false,
494 493 "input": [],
495 494 "language": "python",
496 495 "metadata": {},
497 496 "outputs": [],
498 497 "prompt_number": 15
499 498 },
500 499 {
501 500 "cell_type": "code",
502 501 "collapsed": false,
503 502 "input": [],
504 503 "language": "python",
505 504 "metadata": {},
506 505 "outputs": [],
507 506 "prompt_number": 15
508 507 },
509 508 {
510 509 "cell_type": "code",
511 510 "collapsed": false,
512 511 "input": [],
513 512 "language": "python",
514 513 "metadata": {},
515 514 "outputs": [],
516 515 "prompt_number": 15
517 516 },
518 517 {
519 518 "cell_type": "code",
520 519 "collapsed": false,
521 520 "input": [],
522 521 "language": "python",
523 522 "metadata": {},
524 523 "outputs": [],
525 524 "prompt_number": 15
526 525 },
527 526 {
528 527 "cell_type": "code",
529 528 "collapsed": false,
530 529 "input": [],
531 530 "language": "python",
532 531 "metadata": {},
533 532 "outputs": [],
534 533 "prompt_number": 15
535 534 },
536 535 {
537 536 "cell_type": "code",
538 537 "collapsed": false,
539 538 "input": [],
540 539 "language": "python",
541 540 "metadata": {},
542 541 "outputs": [],
543 542 "prompt_number": 15
544 543 },
545 544 {
546 545 "cell_type": "code",
547 546 "collapsed": false,
548 547 "input": [],
549 548 "language": "python",
550 549 "metadata": {},
551 550 "outputs": [],
552 551 "prompt_number": 15
553 552 },
554 553 {
555 554 "cell_type": "code",
556 555 "collapsed": false,
557 556 "input": [],
558 557 "language": "python",
559 558 "metadata": {},
560 559 "outputs": [],
561 560 "prompt_number": 15
562 561 },
563 562 {
564 563 "cell_type": "code",
565 564 "collapsed": false,
566 565 "input": [
567 566 "counter.value += 1"
568 567 ],
569 568 "language": "python",
570 569 "metadata": {},
571 570 "outputs": [],
572 571 "prompt_number": 16
573 572 },
574 573 {
575 574 "cell_type": "code",
576 575 "collapsed": false,
577 576 "input": [
578 577 "popup.close()"
579 578 ],
580 579 "language": "python",
581 580 "metadata": {},
582 581 "outputs": [],
583 582 "prompt_number": 17
584 583 },
585 584 {
586 585 "cell_type": "heading",
587 586 "level": 1,
588 587 "metadata": {
589 588 "slideshow": {
590 589 "slide_type": "slide"
591 590 }
592 591 },
593 592 "source": [
594 593 "Alignment"
595 594 ]
596 595 },
597 596 {
598 597 "cell_type": "markdown",
599 598 "metadata": {},
600 599 "source": [
601 600 "Most widgets have a **`description` attribute**, which allows a label for the widget to be defined.\n",
602 601 "The label of the widget **has a fixed minimum width**.\n",
603 602 "The text of the label is **always right aligned and the widget is left aligned**:"
604 603 ]
605 604 },
606 605 {
607 606 "cell_type": "code",
608 607 "collapsed": false,
609 608 "input": [
610 609 "display(widgets.TextWidget(description=\"a:\"))\n",
611 610 "display(widgets.TextWidget(description=\"aa:\"))\n",
612 611 "display(widgets.TextWidget(description=\"aaa:\"))"
613 612 ],
614 613 "language": "python",
615 614 "metadata": {},
616 615 "outputs": [],
617 616 "prompt_number": 18
618 617 },
619 618 {
620 619 "cell_type": "markdown",
621 620 "metadata": {
622 621 "slideshow": {
623 622 "slide_type": "slide"
624 623 }
625 624 },
626 625 "source": [
627 626 "If a **label is longer** than the minimum width, the **widget is shifted to the right**:"
628 627 ]
629 628 },
630 629 {
631 630 "cell_type": "code",
632 631 "collapsed": false,
633 632 "input": [
634 633 "display(widgets.TextWidget(description=\"a:\"))\n",
635 634 "display(widgets.TextWidget(description=\"aa:\"))\n",
636 635 "display(widgets.TextWidget(description=\"aaa:\"))\n",
637 636 "display(widgets.TextWidget(description=\"aaaaaaaaaaaaaaaaaa:\"))"
638 637 ],
639 638 "language": "python",
640 639 "metadata": {},
641 640 "outputs": [],
642 641 "prompt_number": 19
643 642 },
644 643 {
645 644 "cell_type": "markdown",
646 645 "metadata": {
647 646 "slideshow": {
648 647 "slide_type": "slide"
649 648 }
650 649 },
651 650 "source": [
652 651 "If a `description` is **not set** for the widget, the **label is not displayed**:"
653 652 ]
654 653 },
655 654 {
656 655 "cell_type": "code",
657 656 "collapsed": false,
658 657 "input": [
659 658 "display(widgets.TextWidget(description=\"a:\"))\n",
660 659 "display(widgets.TextWidget(description=\"aa:\"))\n",
661 660 "display(widgets.TextWidget(description=\"aaa:\"))\n",
662 661 "display(widgets.TextWidget())"
663 662 ],
664 663 "language": "python",
665 664 "metadata": {},
666 665 "outputs": [],
667 666 "prompt_number": 20
668 667 },
669 668 {
670 669 "cell_type": "heading",
671 670 "level": 1,
672 671 "metadata": {
673 672 "slideshow": {
674 673 "slide_type": "slide"
675 674 }
676 675 },
677 676 "source": [
678 677 "DOM Classes"
679 678 ]
680 679 },
681 680 {
682 681 "cell_type": "markdown",
683 682 "metadata": {},
684 683 "source": [
685 684 "IPython defines a large number of **DOM (document object model) classes** that you can apply to your widgets. Applying a DOM class causes all of the **CSS associated with that class** to be applied to the element. Classes can be applied and removed using the **`add_class` and `remove_class`** methods **after a widget has been displayed**. The majority of DOM classes defined by IPython are actually **Bootstrap classes**. For more information on Bootstrap classes and CSS, please refer to [Bootstrap's website](http://getbootstrap.com/2.3.2/)."
686 685 ]
687 686 },
688 687 {
689 688 "cell_type": "heading",
690 689 "level": 2,
691 690 "metadata": {
692 691 "slideshow": {
693 692 "slide_type": "slide"
694 693 }
695 694 },
696 695 "source": [
697 696 "Path dependent"
698 697 ]
699 698 },
700 699 {
701 700 "cell_type": "markdown",
702 701 "metadata": {},
703 702 "source": [
704 703 "Both `add_class` and `remove_class` allow you to use **CSS selectors** to pick which sub elements of your widget get styled. Because of this, the `add_class` and `remove_class` methods are **path dependent (order specific)**. The following example shows the **same three calls** made in three **different orders** and the resulting output. **All three differ.**"
705 704 ]
706 705 },
707 706 {
708 707 "cell_type": "code",
709 708 "collapsed": false,
710 709 "input": [
711 710 "%%html\n",
712 711 "<style>\n",
713 712 " div.cube { display: inline; padding: 5px; }\n",
714 713 " div.red { background: red; }\n",
715 714 " div.blue { background: blue; }\n",
716 715 "</style>"
717 716 ],
718 717 "language": "python",
719 718 "metadata": {},
720 719 "outputs": [
721 720 {
722 721 "html": [
723 722 "<style>\n",
724 723 " div.cube { display: inline; padding: 5px; }\n",
725 724 " div.red { background: red; }\n",
726 725 " div.blue { background: blue; }\n",
727 726 "</style>"
728 727 ],
729 728 "metadata": {},
730 729 "output_type": "display_data",
731 730 "text": [
732 731 "<IPython.core.display.HTML object>"
733 732 ]
734 733 }
735 734 ],
736 735 "prompt_number": 21
737 736 },
738 737 {
739 738 "cell_type": "code",
740 739 "collapsed": false,
741 740 "input": [
742 741 "from IPython.html import widgets\n",
743 742 "from IPython.display import display\n",
744 743 "html = '<br />'.join([''.join(['<div class=\"cube\">x</div>' for i in range(8)]) for j in range(8)])\n",
745 744 "widget = [widgets.HTMLWidget(value=html) for i in range(3)]\n",
746 745 "\n",
747 746 "display(widget[0])\n",
748 747 "widget[0].add_class('red', 'div.cube:nth-child(even)')\n",
749 748 "widget[0].remove_class('red', 'div.red:nth-child(7n+1)')\n",
750 749 "widget[0].add_class('blue', 'div.red:nth-child(3n+1)')"
751 750 ],
752 751 "language": "python",
753 752 "metadata": {},
754 753 "outputs": [],
755 754 "prompt_number": 22
756 755 },
757 756 {
758 757 "cell_type": "code",
759 758 "collapsed": false,
760 759 "input": [
761 760 "display(widget[1])\n",
762 761 "widget[1].remove_class('red', 'div.red:nth-child(7n+1)')\n",
763 762 "widget[1].add_class('blue', 'div.red:nth-child(3n+1)')\n",
764 763 "widget[1].add_class('red', 'div.cube:nth-child(even)')"
765 764 ],
766 765 "language": "python",
767 766 "metadata": {},
768 767 "outputs": [],
769 768 "prompt_number": 23
770 769 },
771 770 {
772 771 "cell_type": "code",
773 772 "collapsed": false,
774 773 "input": [
775 774 "display(widget[2])\n",
776 775 "widget[2].add_class('red', 'div.cube:nth-child(even)')\n",
777 776 "widget[2].add_class('blue', 'div.red:nth-child(3n+1)')\n",
778 777 "widget[2].remove_class('red', 'div.red:nth-child(7n+1)')"
779 778 ],
780 779 "language": "python",
781 780 "metadata": {},
782 781 "outputs": [],
783 782 "prompt_number": 24
784 783 },
785 784 {
786 785 "cell_type": "heading",
787 786 "level": 2,
788 787 "metadata": {
789 788 "slideshow": {
790 789 "slide_type": "slide"
791 790 }
792 791 },
793 792 "source": [
794 793 "Alignment classes"
795 794 ]
796 795 },
797 796 {
798 797 "cell_type": "markdown",
799 798 "metadata": {},
800 799 "source": [
801 800 "Widgets can be aligned using IPython **alignment classes**. These classes should work with most widgets, but were **designed to be applied to `ContainerWidget`s**. Examples of these classes follow:"
802 801 ]
803 802 },
804 803 {
805 804 "cell_type": "heading",
806 805 "level": 3,
807 806 "metadata": {},
808 807 "source": [
809 808 "Orientation classes"
810 809 ]
811 810 },
812 811 {
813 812 "cell_type": "heading",
814 813 "level": 4,
815 814 "metadata": {},
816 815 "source": [
817 816 "\"vbox\""
818 817 ]
819 818 },
820 819 {
821 820 "cell_type": "markdown",
822 821 "metadata": {},
823 822 "source": [
824 823 "Widget containers default to this orientation.\n",
825 824 "<div class=\"example-container vbox\">\n",
826 825 "<div class=\"example-box\">A</div>\n",
827 826 "<div class=\"example-box med\">B</div>\n",
828 827 "<div class=\"example-box lrg\">C</div>\n",
829 828 "</div>"
830 829 ]
831 830 },
832 831 {
833 832 "cell_type": "heading",
834 833 "level": 4,
835 834 "metadata": {},
836 835 "source": [
837 836 "\"hbox\""
838 837 ]
839 838 },
840 839 {
841 840 "cell_type": "markdown",
842 841 "metadata": {},
843 842 "source": [
844 843 "<div class=\"example-container hbox\">\n",
845 844 "<div class=\"example-box\">A</div>\n",
846 845 "<div class=\"example-box med\">B</div>\n",
847 846 "<div class=\"example-box lrg\">C</div>\n",
848 847 "</div>"
849 848 ]
850 849 },
851 850 {
852 851 "cell_type": "heading",
853 852 "level": 3,
854 853 "metadata": {},
855 854 "source": [
856 855 "Packing classes"
857 856 ]
858 857 },
859 858 {
860 859 "cell_type": "markdown",
861 860 "metadata": {},
862 861 "source": [
863 862 "These examples use the **hbox layout** to show packing. Packing is the alignment of the widgets along the the **axis that they are displayed on**."
864 863 ]
865 864 },
866 865 {
867 866 "cell_type": "heading",
868 867 "level": 4,
869 868 "metadata": {},
870 869 "source": [
871 870 "\"start\""
872 871 ]
873 872 },
874 873 {
875 874 "cell_type": "markdown",
876 875 "metadata": {},
877 876 "source": [
878 877 "<div class=\"example-container hbox start\">\n",
879 878 "<div class=\"example-box\">A</div>\n",
880 879 "<div class=\"example-box med\">B</div>\n",
881 880 "<div class=\"example-box lrg\">C</div>\n",
882 881 "</div>"
883 882 ]
884 883 },
885 884 {
886 885 "cell_type": "heading",
887 886 "level": 4,
888 887 "metadata": {},
889 888 "source": [
890 889 "\"center\""
891 890 ]
892 891 },
893 892 {
894 893 "cell_type": "markdown",
895 894 "metadata": {},
896 895 "source": [
897 896 "<div class=\"example-container hbox center\">\n",
898 897 "<div class=\"example-box\">A</div>\n",
899 898 "<div class=\"example-box med\">B</div>\n",
900 899 "<div class=\"example-box lrg\">C</div>\n",
901 900 "</div>"
902 901 ]
903 902 },
904 903 {
905 904 "cell_type": "heading",
906 905 "level": 4,
907 906 "metadata": {},
908 907 "source": [
909 908 "\"end\""
910 909 ]
911 910 },
912 911 {
913 912 "cell_type": "markdown",
914 913 "metadata": {
915 914 "slideshow": {
916 915 "slide_type": "slide"
917 916 }
918 917 },
919 918 "source": [
920 919 "<div class=\"example-container hbox end\">\n",
921 920 "<div class=\"example-box\">A</div>\n",
922 921 "<div class=\"example-box med\">B</div>\n",
923 922 "<div class=\"example-box lrg\">C</div>\n",
924 923 "</div>"
925 924 ]
926 925 },
927 926 {
928 927 "cell_type": "heading",
929 928 "level": 3,
930 929 "metadata": {},
931 930 "source": [
932 931 "Aligning classes"
933 932 ]
934 933 },
935 934 {
936 935 "cell_type": "markdown",
937 936 "metadata": {},
938 937 "source": [
939 938 "These examples use the **hbox layout** to show alignment. Packing is the alignment of the widgets along the the **axis perpendicular to the one that they are displayed on**."
940 939 ]
941 940 },
942 941 {
943 942 "cell_type": "heading",
944 943 "level": 4,
945 944 "metadata": {},
946 945 "source": [
947 946 "\"align-start\""
948 947 ]
949 948 },
950 949 {
951 950 "cell_type": "markdown",
952 951 "metadata": {},
953 952 "source": [
954 953 "<div class=\"example-container hbox align-start\">\n",
955 954 "<div class=\"example-box\">A</div>\n",
956 955 "<div class=\"example-box med\">B</div>\n",
957 956 "<div class=\"example-box lrg\">C</div>\n",
958 957 "</div>"
959 958 ]
960 959 },
961 960 {
962 961 "cell_type": "heading",
963 962 "level": 4,
964 963 "metadata": {},
965 964 "source": [
966 965 "\"align-center\""
967 966 ]
968 967 },
969 968 {
970 969 "cell_type": "markdown",
971 970 "metadata": {},
972 971 "source": [
973 972 "<div class=\"example-container hbox align-center\">\n",
974 973 "<div class=\"example-box\">A</div>\n",
975 974 "<div class=\"example-box med\">B</div>\n",
976 975 "<div class=\"example-box lrg\">C</div>\n",
977 976 "</div>"
978 977 ]
979 978 },
980 979 {
981 980 "cell_type": "heading",
982 981 "level": 4,
983 982 "metadata": {},
984 983 "source": [
985 984 "\"align-end\""
986 985 ]
987 986 },
988 987 {
989 988 "cell_type": "markdown",
990 989 "metadata": {
991 990 "slideshow": {
992 991 "slide_type": "slide"
993 992 }
994 993 },
995 994 "source": [
996 995 "<div class=\"example-container hbox align-end\">\n",
997 996 "<div class=\"example-box\">A</div>\n",
998 997 "<div class=\"example-box med\">B</div>\n",
999 998 "<div class=\"example-box lrg\">C</div>\n",
1000 999 "</div>"
1001 1000 ]
1002 1001 },
1003 1002 {
1004 1003 "cell_type": "heading",
1005 1004 "level": 3,
1006 1005 "metadata": {},
1007 1006 "source": [
1008 1007 "Flex classes"
1009 1008 ]
1010 1009 },
1011 1010 {
1012 1011 "cell_type": "markdown",
1013 1012 "metadata": {},
1014 1013 "source": [
1015 1014 "To specify **how \"greedy\" a container is** when filling in the remaining space of its parent, the **`box-flexN`** classes are used (where N is 0, 1, or 2). The **higher the value of N, the more greedy** the child is. **`box-flex0` is the default behavior**, which is to not fill the parent."
1016 1015 ]
1017 1016 },
1018 1017 {
1019 1018 "cell_type": "heading",
1020 1019 "level": 4,
1021 1020 "metadata": {},
1022 1021 "source": [
1023 1022 "Example 1"
1024 1023 ]
1025 1024 },
1026 1025 {
1027 1026 "cell_type": "markdown",
1028 1027 "metadata": {},
1029 1028 "source": [
1030 1029 "<div class=\"example-container sm hbox center\">\n",
1031 1030 "<div class=\"example-box box-flex0\">box-flex0</div>\n",
1032 1031 "<div class=\"example-box box-flex0\">box-flex0</div>\n",
1033 1032 "<div class=\"example-box box-flex0\">box-flex0</div>\n",
1034 1033 "</div>"
1035 1034 ]
1036 1035 },
1037 1036 {
1038 1037 "cell_type": "heading",
1039 1038 "level": 4,
1040 1039 "metadata": {},
1041 1040 "source": [
1042 1041 "Example 2"
1043 1042 ]
1044 1043 },
1045 1044 {
1046 1045 "cell_type": "markdown",
1047 1046 "metadata": {},
1048 1047 "source": [
1049 1048 "<div class=\"example-container sm hbox center\">\n",
1050 1049 "<div class=\"example-box box-flex0\">box-flex0</div>\n",
1051 1050 "<div class=\"example-box box-flex1\">box-flex1</div>\n",
1052 1051 "<div class=\"example-box box-flex0\">box-flex0</div>\n",
1053 1052 "</div>"
1054 1053 ]
1055 1054 },
1056 1055 {
1057 1056 "cell_type": "heading",
1058 1057 "level": 4,
1059 1058 "metadata": {},
1060 1059 "source": [
1061 1060 "Example 3"
1062 1061 ]
1063 1062 },
1064 1063 {
1065 1064 "cell_type": "markdown",
1066 1065 "metadata": {},
1067 1066 "source": [
1068 1067 "<div class=\"example-container sm hbox center\">\n",
1069 1068 "<div class=\"example-box box-flex0\">box-flex0</div>\n",
1070 1069 "<div class=\"example-box box-flex1\">box-flex1</div>\n",
1071 1070 "<div class=\"example-box box-flex1\">box-flex1</div>\n",
1072 1071 "</div>"
1073 1072 ]
1074 1073 },
1075 1074 {
1076 1075 "cell_type": "heading",
1077 1076 "level": 4,
1078 1077 "metadata": {},
1079 1078 "source": [
1080 1079 "Example 4"
1081 1080 ]
1082 1081 },
1083 1082 {
1084 1083 "cell_type": "markdown",
1085 1084 "metadata": {},
1086 1085 "source": [
1087 1086 "<div class=\"example-container sm hbox center\">\n",
1088 1087 "<div class=\"example-box box-flex1\">box-flex1</div>\n",
1089 1088 "<div class=\"example-box box-flex1\">box-flex1</div>\n",
1090 1089 "<div class=\"example-box box-flex1\">box-flex1</div>\n",
1091 1090 "</div>"
1092 1091 ]
1093 1092 },
1094 1093 {
1095 1094 "cell_type": "heading",
1096 1095 "level": 4,
1097 1096 "metadata": {},
1098 1097 "source": [
1099 1098 "Example 5"
1100 1099 ]
1101 1100 },
1102 1101 {
1103 1102 "cell_type": "markdown",
1104 1103 "metadata": {},
1105 1104 "source": [
1106 1105 "<div class=\"example-container sm hbox center\">\n",
1107 1106 "<div class=\"example-box box-flex2\">box-flex2</div>\n",
1108 1107 "<div class=\"example-box box-flex1\">box-flex1</div>\n",
1109 1108 "<div class=\"example-box box-flex1\">box-flex1</div>\n",
1110 1109 "</div>"
1111 1110 ]
1112 1111 },
1113 1112 {
1114 1113 "cell_type": "heading",
1115 1114 "level": 4,
1116 1115 "metadata": {},
1117 1116 "source": [
1118 1117 "Example 6"
1119 1118 ]
1120 1119 },
1121 1120 {
1122 1121 "cell_type": "markdown",
1123 1122 "metadata": {
1124 1123 "slideshow": {
1125 1124 "slide_type": "slide"
1126 1125 }
1127 1126 },
1128 1127 "source": [
1129 1128 "<div class=\"example-container sm hbox center\">\n",
1130 1129 "<div class=\"example-box box-flex0\">box-flex0</div>\n",
1131 1130 "<div class=\"example-box box-flex1\">box-flex1</div>\n",
1132 1131 "<div class=\"example-box box-flex2\">box-flex2</div>\n",
1133 1132 "</div>"
1134 1133 ]
1135 1134 },
1136 1135 {
1137 1136 "cell_type": "heading",
1138 1137 "level": 3,
1139 1138 "metadata": {
1140 1139 "slideshow": {
1141 1140 "slide_type": "slide"
1142 1141 }
1143 1142 },
1144 1143 "source": [
1145 1144 "Application to widgets"
1146 1145 ]
1147 1146 },
1148 1147 {
1149 1148 "cell_type": "markdown",
1150 1149 "metadata": {},
1151 1150 "source": [
1152 1151 "Widget containers **default to vbox** alignment."
1153 1152 ]
1154 1153 },
1155 1154 {
1156 1155 "cell_type": "code",
1157 1156 "collapsed": false,
1158 1157 "input": [
1159 1158 "buttons = [widgets.ButtonWidget(description=str(i)) for i in range(3)]\n",
1160 1159 "\n",
1161 1160 "container = widgets.ContainerWidget(children=buttons)\n",
1162 1161 "display(container)"
1163 1162 ],
1164 1163 "language": "python",
1165 1164 "metadata": {},
1166 1165 "outputs": [],
1167 1166 "prompt_number": 25
1168 1167 },
1169 1168 {
1170 1169 "cell_type": "heading",
1171 1170 "level": 3,
1172 1171 "metadata": {
1173 1172 "slideshow": {
1174 1173 "slide_type": "slide"
1175 1174 }
1176 1175 },
1177 1176 "source": [
1178 1177 "Using hbox"
1179 1178 ]
1180 1179 },
1181 1180 {
1182 1181 "cell_type": "markdown",
1183 1182 "metadata": {},
1184 1183 "source": [
1185 1184 "To make a widget container display its widgets horizontally, you need to **remove the `vbox` class** from the container and **add the `hbox` class** in its place."
1186 1185 ]
1187 1186 },
1188 1187 {
1189 1188 "cell_type": "code",
1190 1189 "collapsed": false,
1191 1190 "input": [
1192 1191 "container = widgets.ContainerWidget(children=buttons)\n",
1193 1192 "display(container)\n",
1194 1193 "container.remove_class('vbox')\n",
1195 1194 "container.add_class('hbox')"
1196 1195 ],
1197 1196 "language": "python",
1198 1197 "metadata": {},
1199 1198 "outputs": [],
1200 1199 "prompt_number": 26
1201 1200 },
1202 1201 {
1203 1202 "cell_type": "markdown",
1204 1203 "metadata": {},
1205 1204 "source": [
1206 1205 "By setting the width of the container to 100% and adding the `center` class to it, you can center the buttons."
1207 1206 ]
1208 1207 },
1209 1208 {
1210 1209 "cell_type": "code",
1211 1210 "collapsed": false,
1212 1211 "input": [
1213 1212 "container.set_css('width', '100%')\n",
1214 1213 "container.add_class('center')"
1215 1214 ],
1216 1215 "language": "python",
1217 1216 "metadata": {},
1218 1217 "outputs": [],
1219 1218 "prompt_number": 27
1220 1219 },
1221 1220 {
1222 1221 "cell_type": "heading",
1223 1222 "level": 2,
1224 1223 "metadata": {
1225 1224 "slideshow": {
1226 1225 "slide_type": "slide"
1227 1226 }
1228 1227 },
1229 1228 "source": [
1230 1229 "Style classes"
1231 1230 ]
1232 1231 },
1233 1232 {
1234 1233 "cell_type": "markdown",
1235 1234 "metadata": {},
1236 1235 "source": [
1237 1236 "In addition to alignment classes, the classes defined by Bootstrap can also be used. This tutorial will only cover a few of the most common classes. For a full list of Bootstrap classes, please refer to [Bootstrap's website](http://getbootstrap.com/2.3.2/)."
1238 1237 ]
1239 1238 },
1240 1239 {
1241 1240 "cell_type": "heading",
1242 1241 "level": 3,
1243 1242 "metadata": {},
1244 1243 "source": [
1245 1244 "ButtonWidgets"
1246 1245 ]
1247 1246 },
1248 1247 {
1249 1248 "cell_type": "code",
1250 1249 "collapsed": false,
1251 1250 "input": [
1252 1251 "# List of the bootstrap button styles\n",
1253 1252 "classes = [\n",
1254 1253 " 'btn', \n",
1255 1254 " 'btn-primary', \n",
1256 1255 " 'btn-info', \n",
1257 1256 " 'btn-success', \n",
1258 1257 " 'btn-warning', \n",
1259 1258 " 'btn-danger', \n",
1260 1259 " 'btn-inverse', \n",
1261 1260 " 'btn-link'\n",
1262 1261 "]\n",
1263 1262 "\n",
1264 1263 "# Display the buttons in a hbox\n",
1265 1264 "container = widgets.ContainerWidget(children=[widgets.ButtonWidget(description=c) for c in classes])\n",
1266 1265 "display(container)\n",
1267 1266 "\n",
1268 1267 "# Apply classes after display\n",
1269 1268 "container.remove_class('vbox')\n",
1270 1269 "container.add_class('hbox')\n",
1271 1270 "ret = [container.children[i].add_class(c) for i, c in enumerate(classes)]"
1272 1271 ],
1273 1272 "language": "python",
1274 1273 "metadata": {},
1275 1274 "outputs": [],
1276 1275 "prompt_number": 28
1277 1276 },
1278 1277 {
1279 1278 "cell_type": "heading",
1280 1279 "level": 3,
1281 1280 "metadata": {
1282 1281 "slideshow": {
1283 1282 "slide_type": "slide"
1284 1283 }
1285 1284 },
1286 1285 "source": [
1287 1286 "ContainerWidgets"
1288 1287 ]
1289 1288 },
1290 1289 {
1291 1290 "cell_type": "code",
1292 1291 "collapsed": false,
1293 1292 "input": [
1294 1293 "def create_label(cls):\n",
1295 1294 " class_name = widgets.HTMLWidget(value=cls)\n",
1296 1295 " container = widgets.ContainerWidget(children=[class_name])\n",
1297 1296 " display(container)\n",
1298 1297 " container.add_class(cls)\n",
1299 1298 "\n",
1300 1299 "ret = [create_label(c) for c in [\n",
1301 1300 " 'alert', \n",
1302 1301 " 'alert alert-error', \n",
1303 1302 " 'alert alert-success', \n",
1304 1303 " 'alert alert-info'\n",
1305 1304 "]]"
1306 1305 ],
1307 1306 "language": "python",
1308 1307 "metadata": {},
1309 1308 "outputs": [],
1310 1309 "prompt_number": 29
1311 1310 },
1312 1311 {
1313 1312 "cell_type": "heading",
1314 1313 "level": 3,
1315 1314 "metadata": {
1316 1315 "slideshow": {
1317 1316 "slide_type": "slide"
1318 1317 }
1319 1318 },
1320 1319 "source": [
1321 1320 "ProgressWidgets"
1322 1321 ]
1323 1322 },
1324 1323 {
1325 1324 "cell_type": "code",
1326 1325 "collapsed": false,
1327 1326 "input": [
1328 1327 "classes = [\n",
1329 1328 " 'progress-info', \n",
1330 1329 " 'progress-success', \n",
1331 1330 " 'progress-warning', \n",
1332 1331 " 'progress-danger',\n",
1333 1332 " 'progress-info progress-striped', \n",
1334 1333 " 'progress-success progress-striped', \n",
1335 1334 " 'progress-warning progress-striped', \n",
1336 1335 " 'progress-danger progress-striped',\n",
1337 1336 " 'active progress-info progress-striped', \n",
1338 1337 " 'active progress-success progress-striped', \n",
1339 1338 " 'active progress-warning progress-striped', \n",
1340 1339 " 'active progress-danger progress-striped',\n",
1341 1340 "]\n",
1342 1341 "ws = [widgets.IntProgressWidget(value=50, description=c) for c in classes]\n",
1343 1342 "ret = [display(w) for w in ws]\n",
1344 1343 "ret = [ws[i].add_class(c) for i, cs in enumerate(classes) for c in cs.split(' ')]"
1345 1344 ],
1346 1345 "language": "python",
1347 1346 "metadata": {},
1348 1347 "outputs": [],
1349 1348 "prompt_number": 31
1350 1349 },
1351 1350 {
1352 1351 "cell_type": "heading",
1353 1352 "level": 2,
1354 1353 "metadata": {
1355 1354 "slideshow": {
1356 1355 "slide_type": "slide"
1357 1356 }
1358 1357 },
1359 1358 "source": [
1360 1359 "Visibility"
1361 1360 ]
1362 1361 },
1363 1362 {
1364 1363 "cell_type": "markdown",
1365 1364 "metadata": {},
1366 1365 "source": [
1367 1366 "Sometimes it is necessary to **hide or show widgets** in place, **without having to re-display** the widget.\n",
1368 1367 "The `visibility` property of widgets can be used to hide or show **widgets that have already been displayed** (as seen below)."
1369 1368 ]
1370 1369 },
1371 1370 {
1372 1371 "cell_type": "code",
1373 1372 "collapsed": false,
1374 1373 "input": [
1375 1374 "string = widgets.LatexWidget(value=\"Hello World!\")\n",
1376 1375 "display(string) "
1377 1376 ],
1378 1377 "language": "python",
1379 1378 "metadata": {},
1380 1379 "outputs": [],
1381 1380 "prompt_number": 32
1382 1381 },
1383 1382 {
1384 1383 "cell_type": "code",
1385 1384 "collapsed": false,
1386 1385 "input": [
1387 1386 "string.visible=False"
1388 1387 ],
1389 1388 "language": "python",
1390 1389 "metadata": {},
1391 1390 "outputs": [],
1392 1391 "prompt_number": 33
1393 1392 },
1394 1393 {
1395 1394 "cell_type": "code",
1396 1395 "collapsed": false,
1397 1396 "input": [
1398 1397 "string.visible=True"
1399 1398 ],
1400 1399 "language": "python",
1401 1400 "metadata": {},
1402 1401 "outputs": [],
1403 1402 "prompt_number": 34
1404 1403 },
1405 1404 {
1406 1405 "cell_type": "heading",
1407 1406 "level": 3,
1408 1407 "metadata": {
1409 1408 "slideshow": {
1410 1409 "slide_type": "slide"
1411 1410 }
1412 1411 },
1413 1412 "source": [
1414 1413 "Another example"
1415 1414 ]
1416 1415 },
1417 1416 {
1418 1417 "cell_type": "markdown",
1419 1418 "metadata": {},
1420 1419 "source": [
1421 1420 "In the example below, a form is rendered, which conditionally displays widgets depending on the state of other widgets. Try toggling the student checkbox."
1422 1421 ]
1423 1422 },
1424 1423 {
1425 1424 "cell_type": "code",
1426 1425 "collapsed": false,
1427 1426 "input": [
1428 1427 "form = widgets.ContainerWidget()\n",
1429 1428 "first = widgets.TextWidget(description=\"First Name:\")\n",
1430 1429 "last = widgets.TextWidget(description=\"Last Name:\")\n",
1431 1430 "\n",
1432 1431 "student = widgets.CheckboxWidget(description=\"Student:\", value=False)\n",
1433 1432 "school_info = widgets.ContainerWidget(visible=False, children=[\n",
1434 1433 " widgets.TextWidget(description=\"School:\"),\n",
1435 1434 " widgets.IntTextWidget(description=\"Grade:\", min=0, max=12)\n",
1436 1435 " ])\n",
1437 1436 "\n",
1438 1437 "pet = widgets.TextWidget(description=\"Pet's Name:\")\n",
1439 1438 "form.children = [first, last, student, school_info, pet]\n",
1440 1439 "display(form)\n",
1441 1440 "\n",
1442 1441 "def on_student_toggle(name, value):\n",
1443 1442 " if value:\n",
1444 1443 " school_info.visible = True\n",
1445 1444 " else:\n",
1446 1445 " school_info.visible = False\n",
1447 1446 "student.on_trait_change(on_student_toggle, 'value')\n"
1448 1447 ],
1449 1448 "language": "python",
1450 1449 "metadata": {},
1451 1450 "outputs": [],
1452 1451 "prompt_number": 35
1453 1452 },
1454 1453 {
1455 1454 "cell_type": "markdown",
1456 1455 "metadata": {},
1457 1456 "source": [
1458 1457 "[Index](Index.ipynb) - [Back](Widget Events.ipynb) - [Next](Custom Widget - Hello World.ipynb)"
1459 1458 ]
1460 1459 }
1461 1460 ],
1462 1461 "metadata": {}
1463 1462 }
1464 1463 ]
1465 1464 } No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now