##// END OF EJS Templates
Widget examples Python 3.x compatability.
Jonathan Frederic -
Show More
@@ -1,220 +1,222 b''
1 1 {
2 2 "metadata": {
3 3 "cell_tags": [
4 4 [
5 5 "<None>",
6 6 null
7 7 ]
8 8 ],
9 9 "name": ""
10 10 },
11 11 "nbformat": 3,
12 12 "nbformat_minor": 0,
13 13 "worksheets": [
14 14 {
15 15 "cells": [
16 16 {
17 17 "cell_type": "code",
18 18 "collapsed": false,
19 19 "input": [
20 "from __future__ import print_function # py 2.7 compat\n",
21 "\n",
20 22 "from IPython.html import widgets # Widget definitions\n",
21 23 "from IPython.display import display # Used to display widgets in the notebook"
22 24 ],
23 25 "language": "python",
24 26 "metadata": {},
25 27 "outputs": [],
26 28 "prompt_number": 1
27 29 },
28 30 {
29 31 "cell_type": "heading",
30 32 "level": 1,
31 33 "metadata": {},
32 34 "source": [
33 35 "Custom Widget"
34 36 ]
35 37 },
36 38 {
37 39 "cell_type": "code",
38 40 "collapsed": false,
39 41 "input": [
40 42 "# Import the base Widget class and the traitlets Unicode class.\n",
41 43 "from IPython.html.widgets import Widget\n",
42 44 "from IPython.utils.traitlets import Unicode, Int\n",
43 45 "\n",
44 46 "# Define our FileWidget and its target model and default view.\n",
45 47 "class FileWidget(Widget):\n",
46 48 " target_name = Unicode('FileWidgetModel')\n",
47 49 " default_view_name = Unicode('FilePickerView')\n",
48 50 " \n",
49 51 " # Define the custom state properties to sync with the front-end\n",
50 52 " _keys = ['value', 'filename']\n",
51 53 " value = Unicode('')\n",
52 54 " filename = Unicode('')\n",
53 55 " on_failed = Int(0)"
54 56 ],
55 57 "language": "python",
56 58 "metadata": {},
57 59 "outputs": [],
58 60 "prompt_number": 2
59 61 },
60 62 {
61 63 "cell_type": "code",
62 64 "collapsed": false,
63 65 "input": [
64 66 "%%javascript\n",
65 67 "\n",
66 68 "require([\"notebook/js/widget\"], function(){\n",
67 69 " \n",
68 70 " // Define the FileModel and register it with the widget manager.\n",
69 71 " var FileModel = IPython.WidgetModel.extend({});\n",
70 72 " IPython.widget_manager.register_widget_model('FileWidgetModel', FileModel);\n",
71 73 " \n",
72 74 " // Define the FilePickerView\n",
73 75 " var FilePickerView = IPython.WidgetView.extend({\n",
74 76 " \n",
75 77 " render: function(){\n",
76 78 " var that = this;\n",
77 79 " this.$el = $('<input />')\n",
78 80 " .attr('type', 'file')\n",
79 81 " .change(function(evt){ that.handleFileChange(evt) });\n",
80 82 " },\n",
81 83 " \n",
82 84 " // Handles: User input\n",
83 85 " handleFileChange: function(evt) { \n",
84 86 " \n",
85 87 " //Retrieve the first (and only!) File from the FileList object\n",
86 88 " var that = this;\n",
87 89 " var f = evt.target.files[0];\n",
88 90 " if (f) {\n",
89 91 " var r = new FileReader();\n",
90 92 " r.onload = function(e) {\n",
91 93 " that.model.set('value', e.target.result);\n",
92 94 " that.model.update_other_views(that);\n",
93 95 " }\n",
94 96 " r.readAsText(f);\n",
95 97 " } else {\n",
96 98 " this.model.set('on_failed', this.model.get('on_failed') + 1);\n",
97 99 " this.model.update_other_views(this);\n",
98 100 " }\n",
99 101 " this.model.set('filename', f.name);\n",
100 102 " this.model.update_other_views(this);\n",
101 103 " },\n",
102 104 " });\n",
103 105 " \n",
104 106 " // Register the DatePickerView with the widget manager.\n",
105 107 " IPython.widget_manager.register_widget_view('FilePickerView', FilePickerView);\n",
106 108 "});"
107 109 ],
108 110 "language": "python",
109 111 "metadata": {},
110 112 "outputs": [
111 113 {
112 114 "javascript": [
113 115 "\n",
114 116 "require([\"notebook/js/widget\"], function(){\n",
115 117 " \n",
116 118 " // Define the FileModel and register it with the widget manager.\n",
117 119 " var FileModel = IPython.WidgetModel.extend({});\n",
118 120 " IPython.widget_manager.register_widget_model('FileWidgetModel', FileModel);\n",
119 121 " \n",
120 122 " // Define the FilePickerView\n",
121 123 " var FilePickerView = IPython.WidgetView.extend({\n",
122 124 " \n",
123 125 " render: function(){\n",
124 126 " var that = this;\n",
125 127 " this.$el = $('<input />')\n",
126 128 " .attr('type', 'file')\n",
127 129 " .change(function(evt){ that.handleFileChange(evt) });\n",
128 130 " },\n",
129 131 " \n",
130 132 " // Handles: User input\n",
131 133 " handleFileChange: function(evt) { \n",
132 134 " \n",
133 135 " //Retrieve the first (and only!) File from the FileList object\n",
134 136 " var that = this;\n",
135 137 " var f = evt.target.files[0];\n",
136 138 " if (f) {\n",
137 139 " var r = new FileReader();\n",
138 140 " r.onload = function(e) {\n",
139 141 " that.model.set('value', e.target.result);\n",
140 142 " that.model.update_other_views(that);\n",
141 143 " }\n",
142 144 " r.readAsText(f);\n",
143 145 " } else {\n",
144 146 " this.model.set('on_failed', this.model.get('on_failed') + 1);\n",
145 147 " this.model.update_other_views(this);\n",
146 148 " }\n",
147 149 " this.model.set('filename', f.name);\n",
148 150 " this.model.update_other_views(this);\n",
149 151 " },\n",
150 152 " });\n",
151 153 " \n",
152 154 " // Register the DatePickerView with the widget manager.\n",
153 155 " IPython.widget_manager.register_widget_view('FilePickerView', FilePickerView);\n",
154 156 "});"
155 157 ],
156 158 "metadata": {},
157 159 "output_type": "display_data",
158 160 "text": [
159 "<IPython.core.display.Javascript at 0x319fe90>"
161 "<IPython.core.display.Javascript at 0x7f14f1c78cd0>"
160 162 ]
161 163 }
162 164 ],
163 165 "prompt_number": 3
164 166 },
165 167 {
166 168 "cell_type": "heading",
167 169 "level": 1,
168 170 "metadata": {},
169 171 "source": [
170 172 "Usage"
171 173 ]
172 174 },
173 175 {
174 176 "cell_type": "code",
175 177 "collapsed": false,
176 178 "input": [
177 179 "file_widget = FileWidget()\n",
178 180 "display(file_widget)\n",
179 181 "\n",
180 182 "def file_loading():\n",
181 " print \"Loading %s\" % file_widget.filename\n",
183 " print(\"Loading %s\" % file_widget.filename)\n",
182 184 "\n",
183 185 "def file_loaded():\n",
184 " print \"Loaded, file contents: %s\" % file_widget.value\n",
186 " print(\"Loaded, file contents: %s\" % file_widget.value)\n",
185 187 "\n",
186 188 "def file_failed(name, old_value, new_value):\n",
187 189 " if new_value > old_value:\n",
188 " print \"Could not load file contents of %s\" % file_widget.filename\n",
190 " print(\"Could not load file contents of %s\" % file_widget.filename)\n",
189 191 "\n",
190 192 "\n",
191 193 "file_widget.on_trait_change(file_loading, 'filename')\n",
192 194 "file_widget.on_trait_change(file_loaded, 'value')\n",
193 195 "file_widget.on_trait_change(file_failed, 'on_failed')"
194 196 ],
195 197 "language": "python",
196 198 "metadata": {},
197 199 "outputs": [
198 200 {
199 201 "output_type": "stream",
200 202 "stream": "stdout",
201 203 "text": [
202 204 "Loading test.txt\n"
203 205 ]
204 206 },
205 207 {
206 208 "output_type": "stream",
207 209 "stream": "stdout",
208 210 "text": [
209 "Loaded, file contents: \n",
210 "hello world!\n"
211 "Loaded, file contents: Hello World!\n",
212 "\n"
211 213 ]
212 214 }
213 215 ],
214 216 "prompt_number": 4
215 217 }
216 218 ],
217 219 "metadata": {}
218 220 }
219 221 ]
220 222 } No newline at end of file
@@ -1,217 +1,223 b''
1 1 {
2 2 "metadata": {
3 3 "name": ""
4 4 },
5 5 "nbformat": 3,
6 6 "nbformat_minor": 0,
7 7 "worksheets": [
8 8 {
9 9 "cells": [
10 10 {
11 11 "cell_type": "code",
12 12 "collapsed": false,
13 13 "input": [
14 14 "from subprocess import Popen, PIPE\n",
15 15 "import fcntl\n",
16 16 "import os\n",
17 17 "\n",
18 18 "from IPython.html import widgets\n",
19 "from IPython.display import display"
19 "from IPython.display import display\n",
20 "from IPython.utils.py3compat import bytes_to_str, string_types"
20 21 ],
21 22 "language": "python",
22 23 "metadata": {},
23 24 "outputs": [],
24 25 "prompt_number": 1
25 26 },
26 27 {
27 28 "cell_type": "markdown",
28 29 "metadata": {},
29 30 "source": [
30 31 "Create the output, input, and console toggle widgets."
31 32 ]
32 33 },
33 34 {
34 35 "cell_type": "code",
35 36 "collapsed": false,
36 37 "input": [
37 38 "console_container = widgets.ContainerWidget(visible=False)\n",
38 39 "console_container.set_css('padding', '10px')\n",
39 40 "\n",
40 41 "console_style = {\n",
41 42 " 'font-family': 'monospace',\n",
42 43 " 'color': '#AAAAAA',\n",
43 44 " 'background': 'black',\n",
44 45 " 'width': '800px',\n",
45 46 "}\n",
46 47 "\n",
47 48 "output_box = widgets.StringWidget(parent=console_container, default_view_name='TextAreaView')\n",
48 49 "output_box.set_css(console_style)\n",
49 50 "output_box.set_css('height', '400px')\n",
50 51 "\n",
51 52 "input_box = widgets.StringWidget(parent=console_container)\n",
52 53 "input_box.set_css(console_style)\n",
53 54 "\n",
54 55 "toggle_button = widgets.ButtonWidget(description=\"Start Console\")\n",
55 56 "def toggle_console():\n",
56 57 " console_container.visible = not console_container.visible\n",
57 58 " if console_container.visible:\n",
58 59 " toggle_button.description=\"Stop Console\"\n",
59 60 " input_box.disabled = False\n",
60 61 " else:\n",
61 62 " toggle_button.description=\"Start Console\"\n",
62 63 "toggle_button.on_click(toggle_console)\n"
63 64 ],
64 65 "language": "python",
65 66 "metadata": {},
66 67 "outputs": [],
67 68 "prompt_number": 2
68 69 },
69 70 {
70 71 "cell_type": "markdown",
71 72 "metadata": {},
72 73 "source": [
73 74 "Define function to run a process without blocking the input."
74 75 ]
75 76 },
76 77 {
77 78 "cell_type": "code",
78 79 "collapsed": false,
79 80 "input": [
80 81 "def read_process(process, append_output):\n",
81 82 " \"\"\" Try to read the stdout and stderr of a process and render it using \n",
82 83 " the append_output method provided\n",
83 84 " \n",
84 85 " Parameters\n",
85 86 " ----------\n",
86 87 " process: Popen handle\n",
87 88 " append_output: method handle\n",
88 89 " Callback to render output. Signature of\n",
89 90 " append_output(output, [prefix=])\"\"\"\n",
90 91 " \n",
91 92 " try:\n",
92 93 " stdout = process.stdout.read()\n",
93 94 " if stdout is not None and len(stdout) > 0:\n",
94 " append_output(stdout)\n",
95 " append_output(stdout, prefix=' ')\n",
95 96 " except:\n",
96 97 " pass\n",
97 98 " \n",
98 99 " try:\n",
99 100 " stderr = process.stderr.read()\n",
100 101 " if stderr is not None and len(stderr) > 0:\n",
101 102 " append_output(stderr, prefix='ERR ')\n",
102 103 " except:\n",
103 104 " pass\n",
104 105 "\n",
105 106 "\n",
106 107 "def set_pipe_nonblocking(pipe):\n",
107 108 " \"\"\"Set a pipe as non-blocking\"\"\"\n",
108 109 " fl = fcntl.fcntl(pipe, fcntl.F_GETFL)\n",
109 110 " fcntl.fcntl(pipe, fcntl.F_SETFL, fl | os.O_NONBLOCK)\n",
110 111 "\n",
111 112 "\n",
112 113 "kernel = get_ipython().kernel\n",
113 114 "def run_command(command, append_output, has_user_exited=None):\n",
114 115 " \"\"\"Run a command asyncronously\n",
115 116 " \n",
116 117 " Parameters\n",
117 118 " ----------\n",
118 119 " command: str\n",
119 120 " Shell command to launch a process with.\n",
120 121 " append_output: method handle\n",
121 122 " Callback to render output. Signature of\n",
122 123 " append_output(output, [prefix=])\n",
123 124 " has_user_exited: method handle\n",
124 125 " Check to see if the user wants to stop the command.\n",
125 126 " Must return a boolean.\"\"\"\n",
126 127 " \n",
127 128 " # Echo input.\n",
128 129 " append_output(command, prefix='>>> ')\n",
129 130 " \n",
130 131 " # Create the process. Make sure the pipes are set as non-blocking.\n",
131 132 " process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)\n",
132 133 " set_pipe_nonblocking(process.stdout)\n",
133 134 " set_pipe_nonblocking(process.stderr)\n",
134 135 " \n",
135 136 " # Only continue to read from the command \n",
136 137 " while (has_user_exited is None or not has_user_exited()) and process.poll() is None:\n",
137 138 " read_process(process, append_output)\n",
138 139 " kernel.do_one_iteration() # Run IPython iteration. This is the code that\n",
139 140 " # makes this operation non-blocking. This will\n",
140 141 " # allow widget messages and callbacks to be \n",
141 142 " # processed.\n",
142 143 " \n",
143 144 " # If the process is still running, the user must have exited.\n",
144 145 " if process.poll() is None:\n",
145 146 " process.kill()\n",
146 147 " else:\n",
147 148 " read_process(process, append_output) # Read remainer\n",
148 149 " \n",
149 150 " \n",
150 151 " \n",
151 152 " "
152 153 ],
153 154 "language": "python",
154 155 "metadata": {},
155 156 "outputs": [],
156 157 "prompt_number": 3
157 158 },
158 159 {
159 160 "cell_type": "markdown",
160 161 "metadata": {},
161 162 "source": [
162 163 "Hook the process execution methods up to our console widgets."
163 164 ]
164 165 },
165 166 {
166 167 "cell_type": "code",
167 168 "collapsed": false,
168 169 "input": [
169 "def append_output(output, prefix=' '):\n",
170 " output_lines = output.split('\\n')\n",
170 "\n",
171 "def append_output(output, prefix):\n",
172 " if isinstance(output, string_types):\n",
173 " output_str = output\n",
174 " else:\n",
175 " output_str = bytes_to_str(output)\n",
176 " output_lines = output_str.split('\\n')\n",
171 177 " formatted_output = '\\n'.join([prefix + line for line in output_lines if len(line) > 0]) + '\\n'\n",
172 178 " output_box.value += formatted_output\n",
173 179 " output_box.scroll_to_bottom()\n",
174 180 " \n",
175 181 "def has_user_exited():\n",
176 182 " return not console_container.visible\n",
177 183 "\n",
178 184 "def handle_input(sender):\n",
179 185 " sender.disabled = True\n",
180 186 " try:\n",
181 187 " command = sender.value\n",
182 188 " sender.value = ''\n",
183 189 " run_command(command, append_output=append_output, has_user_exited=has_user_exited)\n",
184 190 " finally:\n",
185 191 " sender.disabled = False\n",
186 192 " \n",
187 193 "input_box.on_submit(handle_input)"
188 194 ],
189 195 "language": "python",
190 196 "metadata": {},
191 197 "outputs": [],
192 198 "prompt_number": 4
193 199 },
194 200 {
195 201 "cell_type": "markdown",
196 202 "metadata": {},
197 203 "source": [
198 204 "Show the console"
199 205 ]
200 206 },
201 207 {
202 208 "cell_type": "code",
203 209 "collapsed": false,
204 210 "input": [
205 211 "display(toggle_button)\n",
206 212 "display(console_container)"
207 213 ],
208 214 "language": "python",
209 215 "metadata": {},
210 216 "outputs": [],
211 217 "prompt_number": 5
212 218 }
213 219 ],
214 220 "metadata": {}
215 221 }
216 222 ]
217 223 } No newline at end of file
@@ -1,269 +1,271 b''
1 1 {
2 2 "metadata": {
3 3 "cell_tags": [
4 4 [
5 5 "<None>",
6 6 null
7 7 ]
8 8 ],
9 9 "name": ""
10 10 },
11 11 "nbformat": 3,
12 12 "nbformat_minor": 0,
13 13 "worksheets": [
14 14 {
15 15 "cells": [
16 16 {
17 17 "cell_type": "code",
18 18 "collapsed": false,
19 19 "input": [
20 "from __future__ import print_function # 2.7 compatability\n",
21 "\n",
20 22 "from IPython.html import widgets # Widget definitions\n",
21 23 "from IPython.display import display # Used to display widgets in the notebook"
22 24 ],
23 25 "language": "python",
24 26 "metadata": {},
25 27 "outputs": [],
26 28 "prompt_number": 1
27 29 },
28 30 {
29 31 "cell_type": "heading",
30 32 "level": 1,
31 33 "metadata": {},
32 34 "source": [
33 35 "Traitlet Events"
34 36 ]
35 37 },
36 38 {
37 39 "cell_type": "markdown",
38 40 "metadata": {},
39 41 "source": [
40 42 "The widget properties are IPython traitlets. Traitlets are eventful. To handle property value changes, the `on_trait_change` method of the widget can be used to register an event handling callback. The doc string for `on_trait_change` can be seen below. Both the `name` and `remove` properties are optional."
41 43 ]
42 44 },
43 45 {
44 46 "cell_type": "code",
45 47 "collapsed": false,
46 48 "input": [
47 49 "print(widgets.Widget.on_trait_change.__doc__)"
48 50 ],
49 51 "language": "python",
50 52 "metadata": {},
51 53 "outputs": [
52 54 {
53 55 "output_type": "stream",
54 56 "stream": "stdout",
55 57 "text": [
56 58 "Setup a handler to be called when a trait changes.\n",
57 59 "\n",
58 60 " This is used to setup dynamic notifications of trait changes.\n",
59 61 "\n",
60 62 " Static handlers can be created by creating methods on a HasTraits\n",
61 63 " subclass with the naming convention '_[traitname]_changed'. Thus,\n",
62 64 " to create static handler for the trait 'a', create the method\n",
63 65 " _a_changed(self, name, old, new) (fewer arguments can be used, see\n",
64 66 " below).\n",
65 67 "\n",
66 68 " Parameters\n",
67 69 " ----------\n",
68 70 " handler : callable\n",
69 71 " A callable that is called when a trait changes. Its\n",
70 72 " signature can be handler(), handler(name), handler(name, new)\n",
71 73 " or handler(name, old, new).\n",
72 74 " name : list, str, None\n",
73 75 " If None, the handler will apply to all traits. If a list\n",
74 76 " of str, handler will apply to all names in the list. If a\n",
75 77 " str, the handler will apply just to that name.\n",
76 78 " remove : bool\n",
77 79 " If False (the default), then install the handler. If True\n",
78 80 " then unintall it.\n",
79 81 " \n"
80 82 ]
81 83 }
82 84 ],
83 85 "prompt_number": 2
84 86 },
85 87 {
86 88 "cell_type": "markdown",
87 89 "metadata": {},
88 90 "source": [
89 91 "Mentioned in the doc string, the callback registered can have 4 possible signatures:\n",
90 92 "\n",
91 93 "- callback()\n",
92 94 "- callback(trait_name)\n",
93 95 "- callback(trait_name, new_value)\n",
94 96 "- callback(trait_name, old_value, new_value)\n",
95 97 "\n",
96 98 "An example of how to output an IntRangeWiget's value as it is changed can be seen below."
97 99 ]
98 100 },
99 101 {
100 102 "cell_type": "code",
101 103 "collapsed": false,
102 104 "input": [
103 105 "intrange = widgets.IntRangeWidget()\n",
104 106 "display(intrange)\n",
105 107 "\n",
106 108 "def on_value_change(name, value):\n",
107 " print value\n",
109 " print(value)\n",
108 110 "\n",
109 111 "intrange.on_trait_change(on_value_change, 'value')"
110 112 ],
111 113 "language": "python",
112 114 "metadata": {},
113 115 "outputs": [
114 116 {
115 117 "output_type": "stream",
116 118 "stream": "stdout",
117 119 "text": [
118 "25\n"
120 "28\n"
119 121 ]
120 122 },
121 123 {
122 124 "output_type": "stream",
123 125 "stream": "stdout",
124 126 "text": [
125 "73\n"
127 "55\n"
126 128 ]
127 129 },
128 130 {
129 131 "output_type": "stream",
130 132 "stream": "stdout",
131 133 "text": [
132 "99\n"
134 "94\n"
133 135 ]
134 136 }
135 137 ],
136 138 "prompt_number": 3
137 139 },
138 140 {
139 141 "cell_type": "heading",
140 142 "level": 1,
141 143 "metadata": {},
142 144 "source": [
143 145 "Specialized Events"
144 146 ]
145 147 },
146 148 {
147 149 "cell_type": "heading",
148 150 "level": 2,
149 151 "metadata": {},
150 152 "source": [
151 153 "Button On Click Event"
152 154 ]
153 155 },
154 156 {
155 157 "cell_type": "markdown",
156 158 "metadata": {},
157 159 "source": [
158 160 "The `ButtonWidget` is a special widget, like the `ContainerWidget` and `MulticontainerWidget`, that isn't used to represent a data type. Instead the button widget is used to handle mouse clicks. The `on_click` method of the `ButtonWidget` can be used to register a click even handler. The doc string of the `on_click` can be seen below."
159 161 ]
160 162 },
161 163 {
162 164 "cell_type": "code",
163 165 "collapsed": false,
164 166 "input": [
165 167 "print(widgets.ButtonWidget.on_click.__doc__)"
166 168 ],
167 169 "language": "python",
168 170 "metadata": {},
169 171 "outputs": [
170 172 {
171 173 "output_type": "stream",
172 174 "stream": "stdout",
173 175 "text": [
174 176 "Register a callback to execute when the button is clicked. The\n",
175 177 " callback can either accept no parameters or one sender parameter:\n",
176 178 " - callback()\n",
177 179 " - callback(sender)\n",
178 180 " If the callback has a sender parameter, the ButtonWidget instance that\n",
179 181 " called the callback will be passed into the method as the sender.\n",
180 182 "\n",
181 183 " Parameters\n",
182 184 " ----------\n",
183 185 " remove : bool (optional)\n",
184 186 " Set to true to remove the callback from the list of callbacks.\n"
185 187 ]
186 188 }
187 189 ],
188 190 "prompt_number": 4
189 191 },
190 192 {
191 193 "cell_type": "markdown",
192 194 "metadata": {},
193 195 "source": [
194 196 "Button clicks are transmitted from the front-end to the back-end using custom messages. By using the `on_click` method, a button that prints a message when it has been clicked is shown below."
195 197 ]
196 198 },
197 199 {
198 200 "cell_type": "code",
199 201 "collapsed": false,
200 202 "input": [
201 203 "button = widgets.ButtonWidget(description=\"Click Me!\")\n",
202 204 "display(button)\n",
203 205 "\n",
204 206 "def on_button_clicked(sender):\n",
205 207 " print(\"Button clicked.\")\n",
206 208 "\n",
207 209 "button.on_click(on_button_clicked)"
208 210 ],
209 211 "language": "python",
210 212 "metadata": {},
211 213 "outputs": [
212 214 {
213 215 "output_type": "stream",
214 216 "stream": "stdout",
215 217 "text": [
216 218 "Button clicked.\n"
217 219 ]
218 220 },
219 221 {
220 222 "output_type": "stream",
221 223 "stream": "stdout",
222 224 "text": [
223 225 "Button clicked.\n"
224 226 ]
225 227 },
226 228 {
227 229 "output_type": "stream",
228 230 "stream": "stdout",
229 231 "text": [
230 232 "Button clicked.\n"
231 233 ]
232 234 }
233 235 ],
234 236 "prompt_number": 5
235 237 },
236 238 {
237 239 "cell_type": "markdown",
238 240 "metadata": {},
239 241 "source": [
240 242 "Event handlers can also be used to create widgets. In the example below, clicking a button spawns another button with a description equal to how many times the parent button had been clicked at the time."
241 243 ]
242 244 },
243 245 {
244 246 "cell_type": "code",
245 247 "collapsed": false,
246 248 "input": [
247 249 "def show_button(sender=None):\n",
248 250 " button = widgets.ButtonWidget()\n",
249 251 " button.clicks = 0\n",
250 252 " if sender is None:\n",
251 253 " button.description = \"0\"\n",
252 254 " else:\n",
253 255 " sender.clicks += 1\n",
254 256 " button.description = \"%d\" % sender.clicks\n",
255 257 " display(button)\n",
256 258 " button.on_click(show_button)\n",
257 259 "show_button()\n",
258 260 " "
259 261 ],
260 262 "language": "python",
261 263 "metadata": {},
262 264 "outputs": [],
263 265 "prompt_number": 6
264 266 }
265 267 ],
266 268 "metadata": {}
267 269 }
268 270 ]
269 271 } No newline at end of file
@@ -1,1203 +1,1222 b''
1 1 {
2 2 "metadata": {
3 3 "cell_tags": [
4 4 [
5 5 "<None>",
6 6 null
7 7 ]
8 8 ],
9 9 "name": ""
10 10 },
11 11 "nbformat": 3,
12 12 "nbformat_minor": 0,
13 13 "worksheets": [
14 14 {
15 15 "cells": [
16 16 {
17 17 "cell_type": "markdown",
18 18 "metadata": {},
19 19 "source": [
20 20 "Before reading, the author recommends the reader to review\n",
21 21 "\n",
22 22 "- [MVC prgramming](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)\n",
23 23 "- [Backbone.js](https://www.codeschool.com/courses/anatomy-of-backbonejs)\n",
24 24 "- [The widget IPEP](https://github.com/ipython/ipython/wiki/IPEP-23%3A-Backbone.js-Widgets)\n",
25 25 "- [The original widget PR discussion](https://github.com/ipython/ipython/pull/4374)"
26 26 ]
27 27 },
28 28 {
29 29 "cell_type": "code",
30 30 "collapsed": false,
31 31 "input": [
32 "from __future__ import print_function # For py 2.7 compat\n",
33 "\n",
32 34 "from IPython.html import widgets # Widget definitions\n",
33 35 "from IPython.display import display # Used to display widgets in the notebook"
34 36 ],
35 37 "language": "python",
36 38 "metadata": {},
37 39 "outputs": [],
38 40 "prompt_number": 1
39 41 },
40 42 {
43 "cell_type": "markdown",
44 "metadata": {},
45 "source": [
46 "The 3 part of this tutorial requires the 3rd party `dateutil` library. https://pypi.python.org/pypi/python-dateutil"
47 ]
48 },
49 {
50 "cell_type": "code",
51 "collapsed": false,
52 "input": [
53 "# Import the dateutil library to parse date strings.\n",
54 "from dateutil import parser"
55 ],
56 "language": "python",
57 "metadata": {},
58 "outputs": [],
59 "prompt_number": 2
60 },
61 {
41 62 "cell_type": "heading",
42 63 "level": 1,
43 64 "metadata": {},
44 65 "source": [
45 66 "Abstract"
46 67 ]
47 68 },
48 69 {
49 70 "cell_type": "markdown",
50 71 "metadata": {},
51 72 "source": [
52 73 "This notebook implements a custom date picker widget. The purpose of this notebook is to demonstrate the widget creation process. To create a custom widget, custom Python and JavaScript is required."
53 74 ]
54 75 },
55 76 {
56 77 "cell_type": "heading",
57 78 "level": 1,
58 79 "metadata": {},
59 80 "source": [
60 81 "Section 1 - Basics"
61 82 ]
62 83 },
63 84 {
64 85 "cell_type": "heading",
65 86 "level": 2,
66 87 "metadata": {},
67 88 "source": [
68 89 "Python"
69 90 ]
70 91 },
71 92 {
72 93 "cell_type": "markdown",
73 94 "metadata": {},
74 95 "source": [
75 96 "When starting a project like this, it is often easiest to make an overly simplified base to verify that the underlying framework is working as expected. To start we will create an empty widget and make sure that it can be rendered. The first step is to create the widget in Python."
76 97 ]
77 98 },
78 99 {
79 100 "cell_type": "code",
80 101 "collapsed": false,
81 102 "input": [
82 103 "# Import the base Widget class and the traitlets Unicode class.\n",
83 104 "from IPython.html.widgets import Widget\n",
84 105 "from IPython.utils.traitlets import Unicode\n",
85 106 "\n",
86 107 "# Define our DateWidget and its target model and default view.\n",
87 108 "class DateWidget(Widget):\n",
88 109 " target_name = Unicode('DateWidgetModel')\n",
89 110 " default_view_name = Unicode('DatePickerView')"
90 111 ],
91 112 "language": "python",
92 113 "metadata": {},
93 114 "outputs": [],
94 "prompt_number": 2
115 "prompt_number": 3
95 116 },
96 117 {
97 118 "cell_type": "markdown",
98 119 "metadata": {},
99 120 "source": [
100 121 "- **target_name** is a special `Widget` property that tells the widget framework which Backbone model in the front-end corresponds to this widget.\n",
101 122 "- **default_view_name** is the default Backbone view to display when the user calls `display` to display an instance of this widget.\n"
102 123 ]
103 124 },
104 125 {
105 126 "cell_type": "heading",
106 127 "level": 2,
107 128 "metadata": {},
108 129 "source": [
109 130 "JavaScript"
110 131 ]
111 132 },
112 133 {
113 134 "cell_type": "markdown",
114 135 "metadata": {},
115 136 "source": [
116 137 "In the IPython notebook [require.js](http://requirejs.org/) is used to load JavaScript dependencies. All IPython widget code depends on `notebook/js/widget.js`. In it the base widget model, base view, and widget manager are defined. We need to use require.js to include this file:"
117 138 ]
118 139 },
119 140 {
120 141 "cell_type": "code",
121 142 "collapsed": false,
122 143 "input": [
123 144 "%%javascript\n",
124 145 "\n",
125 146 "require([\"notebook/js/widget\"], function(){\n",
126 147 "\n",
127 148 "});"
128 149 ],
129 150 "language": "python",
130 151 "metadata": {},
131 152 "outputs": [
132 153 {
133 154 "javascript": [
134 155 "\n",
135 156 "require([\"notebook/js/widget\"], function(){\n",
136 157 "\n",
137 158 "});"
138 159 ],
139 160 "metadata": {},
140 161 "output_type": "display_data",
141 162 "text": [
142 "<IPython.core.display.Javascript at 0x21f8f10>"
163 "<IPython.core.display.Javascript at 0x7f8c679289d0>"
143 164 ]
144 165 }
145 166 ],
146 "prompt_number": 3
167 "prompt_number": 4
147 168 },
148 169 {
149 170 "cell_type": "markdown",
150 171 "metadata": {},
151 172 "source": [
152 173 "The next step is to add a definition for the widget's model. It's important to extend the `IPython.WidgetModel` which extends the Backbone.js base model instead of trying to extend the Backbone.js base model directly. After defining the model, it needs to be registed with the widget manager using the `target_name` used in the Python code."
153 174 ]
154 175 },
155 176 {
156 177 "cell_type": "code",
157 178 "collapsed": false,
158 179 "input": [
159 180 "%%javascript\n",
160 181 "\n",
161 182 "require([\"notebook/js/widget\"], function(){\n",
162 183 " \n",
163 184 " // Define the DateModel and register it with the widget manager.\n",
164 185 " var DateModel = IPython.WidgetModel.extend({});\n",
165 186 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
166 187 "});"
167 188 ],
168 189 "language": "python",
169 190 "metadata": {},
170 191 "outputs": [
171 192 {
172 193 "javascript": [
173 194 "\n",
174 195 "require([\"notebook/js/widget\"], function(){\n",
175 196 " \n",
176 197 " // Define the DateModel and register it with the widget manager.\n",
177 198 " var DateModel = IPython.WidgetModel.extend({});\n",
178 199 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
179 200 "});"
180 201 ],
181 202 "metadata": {},
182 203 "output_type": "display_data",
183 204 "text": [
184 "<IPython.core.display.Javascript at 0x21f8ed0>"
205 "<IPython.core.display.Javascript at 0x7f8c67928510>"
185 206 ]
186 207 }
187 208 ],
188 "prompt_number": 4
209 "prompt_number": 5
189 210 },
190 211 {
191 212 "cell_type": "markdown",
192 213 "metadata": {},
193 214 "source": [
194 215 "Now that the model is defined, we need to define a view that can be used to represent the model. To do this, the `IPython.WidgetView` is extended. A render function must be defined. The render function is used to render a widget view instance to the DOM. For now the render function renders a div that contains the text *Hello World!* Lastly, the view needs to be registered with the widget manager like the model was.\n",
195 216 "\n",
196 217 "**Final JavaScript code below:**"
197 218 ]
198 219 },
199 220 {
200 221 "cell_type": "code",
201 222 "collapsed": false,
202 223 "input": [
203 224 "%%javascript\n",
204 225 "\n",
205 226 "require([\"notebook/js/widget\"], function(){\n",
206 227 " \n",
207 228 " // Define the DateModel and register it with the widget manager.\n",
208 229 " var DateModel = IPython.WidgetModel.extend({});\n",
209 230 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
210 231 " \n",
211 232 " // Define the DatePickerView\n",
212 233 " var DatePickerView = IPython.WidgetView.extend({\n",
213 234 " \n",
214 235 " render: function(){\n",
215 236 " this.$el = $('<div />')\n",
216 237 " .html('Hello World!');\n",
217 238 " },\n",
218 239 " });\n",
219 240 " \n",
220 241 " // Register the DatePickerView with the widget manager.\n",
221 242 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
222 243 "});"
223 244 ],
224 245 "language": "python",
225 246 "metadata": {},
226 247 "outputs": [
227 248 {
228 249 "javascript": [
229 250 "\n",
230 251 "require([\"notebook/js/widget\"], function(){\n",
231 252 " \n",
232 253 " // Define the DateModel and register it with the widget manager.\n",
233 254 " var DateModel = IPython.WidgetModel.extend({});\n",
234 255 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
235 256 " \n",
236 257 " // Define the DatePickerView\n",
237 258 " var DatePickerView = IPython.WidgetView.extend({\n",
238 259 " \n",
239 260 " render: function(){\n",
240 261 " this.$el = $('<div />')\n",
241 262 " .html('Hello World!');\n",
242 263 " },\n",
243 264 " });\n",
244 265 " \n",
245 266 " // Register the DatePickerView with the widget manager.\n",
246 267 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
247 268 "});"
248 269 ],
249 270 "metadata": {},
250 271 "output_type": "display_data",
251 272 "text": [
252 "<IPython.core.display.Javascript at 0x21f8cd0>"
273 "<IPython.core.display.Javascript at 0x7f8c67928a10>"
253 274 ]
254 275 }
255 276 ],
256 "prompt_number": 5
277 "prompt_number": 6
257 278 },
258 279 {
259 280 "cell_type": "heading",
260 281 "level": 2,
261 282 "metadata": {},
262 283 "source": [
263 284 "Test"
264 285 ]
265 286 },
266 287 {
267 288 "cell_type": "markdown",
268 289 "metadata": {},
269 290 "source": [
270 291 "To test, create the widget the same way that the other widgets are created."
271 292 ]
272 293 },
273 294 {
274 295 "cell_type": "code",
275 296 "collapsed": false,
276 297 "input": [
277 298 "my_widget = DateWidget()\n",
278 299 "display(my_widget)"
279 300 ],
280 301 "language": "python",
281 302 "metadata": {},
282 303 "outputs": [],
283 "prompt_number": 6
304 "prompt_number": 7
284 305 },
285 306 {
286 307 "cell_type": "heading",
287 308 "level": 1,
288 309 "metadata": {},
289 310 "source": [
290 311 "Section 2 - Something useful"
291 312 ]
292 313 },
293 314 {
294 315 "cell_type": "heading",
295 316 "level": 2,
296 317 "metadata": {},
297 318 "source": [
298 319 "Python"
299 320 ]
300 321 },
301 322 {
302 323 "cell_type": "markdown",
303 324 "metadata": {},
304 325 "source": [
305 326 "In the last section we created a simple widget that displayed *Hello World!* There was no custom state information associated with the widget. To make an actual date widget, we need to add a property that will be synced between the Python model and the JavaScript model. The new property must be a traitlet property so the widget machinery can automatically handle it. The property needs to be added to the the `_keys` list. The `_keys` list tells the widget machinery what traitlets should be synced with the front-end. Adding this to the code from the last section:"
306 327 ]
307 328 },
308 329 {
309 330 "cell_type": "code",
310 331 "collapsed": false,
311 332 "input": [
312 333 "# Import the base Widget class and the traitlets Unicode class.\n",
313 334 "from IPython.html.widgets import Widget\n",
314 335 "from IPython.utils.traitlets import Unicode\n",
315 336 "\n",
316 337 "# Define our DateWidget and its target model and default view.\n",
317 338 "class DateWidget(Widget):\n",
318 339 " target_name = Unicode('DateWidgetModel')\n",
319 340 " default_view_name = Unicode('DatePickerView')\n",
320 341 " \n",
321 342 " # Define the custom state properties to sync with the front-end\n",
322 343 " _keys = ['value']\n",
323 344 " value = Unicode()"
324 345 ],
325 346 "language": "python",
326 347 "metadata": {},
327 348 "outputs": [],
328 "prompt_number": 7
349 "prompt_number": 8
329 350 },
330 351 {
331 352 "cell_type": "heading",
332 353 "level": 2,
333 354 "metadata": {},
334 355 "source": [
335 356 "JavaScript"
336 357 ]
337 358 },
338 359 {
339 360 "cell_type": "markdown",
340 361 "metadata": {},
341 362 "source": [
342 363 "In the JavaScript there is no need to define the same properties in the JavaScript model. When the JavaScript model is created for the first time, it copies all of the attributes from the Python model. We need to replace *Hello World!* with an actual HTML date picker widget."
343 364 ]
344 365 },
345 366 {
346 367 "cell_type": "code",
347 368 "collapsed": false,
348 369 "input": [
349 370 "%%javascript\n",
350 371 "\n",
351 372 "require([\"notebook/js/widget\"], function(){\n",
352 373 " \n",
353 374 " // Define the DateModel and register it with the widget manager.\n",
354 375 " var DateModel = IPython.WidgetModel.extend({});\n",
355 376 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
356 377 " \n",
357 378 " // Define the DatePickerView\n",
358 379 " var DatePickerView = IPython.WidgetView.extend({\n",
359 380 " \n",
360 381 " render: function(){\n",
361 382 " \n",
362 383 " // Create a div to hold our widget.\n",
363 384 " this.$el = $('<div />');\n",
364 385 " \n",
365 386 " // Create the date picker control.\n",
366 387 " this.$date = $('<input />')\n",
367 388 " .attr('type', 'date');\n",
368 389 " },\n",
369 390 " });\n",
370 391 " \n",
371 392 " // Register the DatePickerView with the widget manager.\n",
372 393 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
373 394 "});"
374 395 ],
375 396 "language": "python",
376 397 "metadata": {},
377 398 "outputs": [
378 399 {
379 400 "javascript": [
380 401 "\n",
381 402 "require([\"notebook/js/widget\"], function(){\n",
382 403 " \n",
383 404 " // Define the DateModel and register it with the widget manager.\n",
384 405 " var DateModel = IPython.WidgetModel.extend({});\n",
385 406 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
386 407 " \n",
387 408 " // Define the DatePickerView\n",
388 409 " var DatePickerView = IPython.WidgetView.extend({\n",
389 410 " \n",
390 411 " render: function(){\n",
391 412 " \n",
392 413 " // Create a div to hold our widget.\n",
393 414 " this.$el = $('<div />');\n",
394 415 " \n",
395 416 " // Create the date picker control.\n",
396 417 " this.$date = $('<input />')\n",
397 418 " .attr('type', 'date');\n",
398 419 " },\n",
399 420 " });\n",
400 421 " \n",
401 422 " // Register the DatePickerView with the widget manager.\n",
402 423 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
403 424 "});"
404 425 ],
405 426 "metadata": {},
406 427 "output_type": "display_data",
407 428 "text": [
408 "<IPython.core.display.Javascript at 0x21fc310>"
429 "<IPython.core.display.Javascript at 0x7f8c67928590>"
409 430 ]
410 431 }
411 432 ],
412 "prompt_number": 8
433 "prompt_number": 9
413 434 },
414 435 {
415 436 "cell_type": "markdown",
416 437 "metadata": {},
417 438 "source": [
418 439 "In order to get the HTML date picker to update itself with the value set in the back-end, we need to implement an `update()` method."
419 440 ]
420 441 },
421 442 {
422 443 "cell_type": "code",
423 444 "collapsed": false,
424 445 "input": [
425 446 "%%javascript\n",
426 447 "\n",
427 448 "require([\"notebook/js/widget\"], function(){\n",
428 449 " \n",
429 450 " // Define the DateModel and register it with the widget manager.\n",
430 451 " var DateModel = IPython.WidgetModel.extend({});\n",
431 452 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
432 453 " \n",
433 454 " // Define the DatePickerView\n",
434 455 " var DatePickerView = IPython.WidgetView.extend({\n",
435 456 " \n",
436 457 " render: function(){\n",
437 458 " \n",
438 459 " // Create a div to hold our widget.\n",
439 460 " this.$el = $('<div />');\n",
440 461 " \n",
441 462 " // Create the date picker control.\n",
442 463 " this.$date = $('<input />')\n",
443 464 " .attr('type', 'date');\n",
444 465 " },\n",
445 466 " \n",
446 467 " update: function() {\n",
447 468 " \n",
448 469 " // Set the value of the date control and then call base.\n",
449 470 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
450 471 " return IPython.WidgetView.prototype.update.call(this);\n",
451 472 " },\n",
452 473 " });\n",
453 474 " \n",
454 475 " // Register the DatePickerView with the widget manager.\n",
455 476 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
456 477 "});"
457 478 ],
458 479 "language": "python",
459 480 "metadata": {},
460 481 "outputs": [
461 482 {
462 483 "javascript": [
463 484 "\n",
464 485 "require([\"notebook/js/widget\"], function(){\n",
465 486 " \n",
466 487 " // Define the DateModel and register it with the widget manager.\n",
467 488 " var DateModel = IPython.WidgetModel.extend({});\n",
468 489 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
469 490 " \n",
470 491 " // Define the DatePickerView\n",
471 492 " var DatePickerView = IPython.WidgetView.extend({\n",
472 493 " \n",
473 494 " render: function(){\n",
474 495 " \n",
475 496 " // Create a div to hold our widget.\n",
476 497 " this.$el = $('<div />');\n",
477 498 " \n",
478 499 " // Create the date picker control.\n",
479 500 " this.$date = $('<input />')\n",
480 501 " .attr('type', 'date');\n",
481 502 " },\n",
482 503 " \n",
483 504 " update: function() {\n",
484 505 " \n",
485 506 " // Set the value of the date control and then call base.\n",
486 507 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
487 508 " return IPython.WidgetView.prototype.update.call(this);\n",
488 509 " },\n",
489 510 " });\n",
490 511 " \n",
491 512 " // Register the DatePickerView with the widget manager.\n",
492 513 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
493 514 "});"
494 515 ],
495 516 "metadata": {},
496 517 "output_type": "display_data",
497 518 "text": [
498 "<IPython.core.display.Javascript at 0x21fc290>"
519 "<IPython.core.display.Javascript at 0x7f8c648ce8d0>"
499 520 ]
500 521 }
501 522 ],
502 "prompt_number": 9
523 "prompt_number": 10
503 524 },
504 525 {
505 526 "cell_type": "markdown",
506 527 "metadata": {},
507 528 "source": [
508 529 "To get the changed value from the front-end to publish itself to the back-end, we need to listen to the change event triggered by the HTM date control and set the value in the model. By setting the `this.$el` property of the view, we break the Backbone powered event handling. To fix this, a call to `this.delegateEvents()` must be added after `this.$el` is set. \n",
509 530 "\n",
510 531 "After the date change event fires and the new value is set in the model, it's very important that we call `update_other_views(this)` to make the other views on the page update and to let the widget machinery know which view changed the model. This is important because the widget machinery needs to know which cell to route the message callbacks to.\n",
511 532 "\n",
512 533 "**Final JavaScript code below:**"
513 534 ]
514 535 },
515 536 {
516 537 "cell_type": "code",
517 538 "collapsed": false,
518 539 "input": [
519 540 "%%javascript\n",
520 541 "\n",
521 542 "require([\"notebook/js/widget\"], function(){\n",
522 543 " \n",
523 544 " // Define the DateModel and register it with the widget manager.\n",
524 545 " var DateModel = IPython.WidgetModel.extend({});\n",
525 546 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
526 547 " \n",
527 548 " // Define the DatePickerView\n",
528 549 " var DatePickerView = IPython.WidgetView.extend({\n",
529 550 " \n",
530 551 " render: function(){\n",
531 552 " \n",
532 553 " // Create a div to hold our widget.\n",
533 554 " this.$el = $('<div />');\n",
534 555 " this.delegateEvents();\n",
535 556 " \n",
536 557 " // Create the date picker control.\n",
537 558 " this.$date = $('<input />')\n",
538 559 " .attr('type', 'date')\n",
539 560 " .appendTo(this.$el);\n",
540 561 " },\n",
541 562 " \n",
542 563 " update: function() {\n",
543 564 " \n",
544 565 " // Set the value of the date control and then call base.\n",
545 566 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
546 567 " return IPython.WidgetView.prototype.update.call(this);\n",
547 568 " },\n",
548 569 " \n",
549 570 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
550 571 " events: {\"change\": \"handle_date_change\"},\n",
551 572 " \n",
552 573 " // Callback for when the date is changed.\n",
553 574 " handle_date_change: function(event) {\n",
554 575 " this.model.set('value', this.$date.val());\n",
555 576 " this.model.update_other_views(this);\n",
556 577 " },\n",
557 578 " \n",
558 579 " });\n",
559 580 " \n",
560 581 " // Register the DatePickerView with the widget manager.\n",
561 582 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
562 583 "});"
563 584 ],
564 585 "language": "python",
565 586 "metadata": {},
566 587 "outputs": [
567 588 {
568 589 "javascript": [
569 590 "\n",
570 591 "require([\"notebook/js/widget\"], function(){\n",
571 592 " \n",
572 593 " // Define the DateModel and register it with the widget manager.\n",
573 594 " var DateModel = IPython.WidgetModel.extend({});\n",
574 595 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
575 596 " \n",
576 597 " // Define the DatePickerView\n",
577 598 " var DatePickerView = IPython.WidgetView.extend({\n",
578 599 " \n",
579 600 " render: function(){\n",
580 601 " \n",
581 602 " // Create a div to hold our widget.\n",
582 603 " this.$el = $('<div />');\n",
583 604 " this.delegateEvents();\n",
584 605 " \n",
585 606 " // Create the date picker control.\n",
586 607 " this.$date = $('<input />')\n",
587 608 " .attr('type', 'date')\n",
588 609 " .appendTo(this.$el);\n",
589 610 " },\n",
590 611 " \n",
591 612 " update: function() {\n",
592 613 " \n",
593 614 " // Set the value of the date control and then call base.\n",
594 615 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
595 616 " return IPython.WidgetView.prototype.update.call(this);\n",
596 617 " },\n",
597 618 " \n",
598 619 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
599 620 " events: {\"change\": \"handle_date_change\"},\n",
600 621 " \n",
601 622 " // Callback for when the date is changed.\n",
602 623 " handle_date_change: function(event) {\n",
603 624 " this.model.set('value', this.$date.val());\n",
604 625 " this.model.update_other_views(this);\n",
605 626 " },\n",
606 627 " \n",
607 628 " });\n",
608 629 " \n",
609 630 " // Register the DatePickerView with the widget manager.\n",
610 631 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
611 632 "});"
612 633 ],
613 634 "metadata": {},
614 635 "output_type": "display_data",
615 636 "text": [
616 "<IPython.core.display.Javascript at 0x21fc3d0>"
637 "<IPython.core.display.Javascript at 0x7f8c648ce150>"
617 638 ]
618 639 }
619 640 ],
620 "prompt_number": 10
641 "prompt_number": 11
621 642 },
622 643 {
623 644 "cell_type": "heading",
624 645 "level": 2,
625 646 "metadata": {},
626 647 "source": [
627 648 "Test"
628 649 ]
629 650 },
630 651 {
631 652 "cell_type": "markdown",
632 653 "metadata": {},
633 654 "source": [
634 655 "To test, create the widget the same way that the other widgets are created."
635 656 ]
636 657 },
637 658 {
638 659 "cell_type": "code",
639 660 "collapsed": false,
640 661 "input": [
641 662 "my_widget = DateWidget()\n",
642 663 "display(my_widget)"
643 664 ],
644 665 "language": "python",
645 666 "metadata": {},
646 667 "outputs": [],
647 "prompt_number": 11
668 "prompt_number": 12
648 669 },
649 670 {
650 671 "cell_type": "markdown",
651 672 "metadata": {},
652 673 "source": [
653 674 "Display the widget again to make sure that both views remain in sync."
654 675 ]
655 676 },
656 677 {
657 678 "cell_type": "code",
658 679 "collapsed": false,
659 680 "input": [
660 681 "display(my_widget)"
661 682 ],
662 683 "language": "python",
663 684 "metadata": {},
664 685 "outputs": [],
665 "prompt_number": 12
686 "prompt_number": 13
666 687 },
667 688 {
668 689 "cell_type": "markdown",
669 690 "metadata": {},
670 691 "source": [
671 692 "Read the date from Python"
672 693 ]
673 694 },
674 695 {
675 696 "cell_type": "code",
676 697 "collapsed": false,
677 698 "input": [
678 699 "my_widget.value"
679 700 ],
680 701 "language": "python",
681 702 "metadata": {},
682 703 "outputs": [
683 704 {
684 705 "metadata": {},
685 706 "output_type": "pyout",
686 "prompt_number": 13,
707 "prompt_number": 14,
687 708 "text": [
688 "u'2013-11-14'"
709 "'2013-11-28'"
689 710 ]
690 711 }
691 712 ],
692 "prompt_number": 13
713 "prompt_number": 14
693 714 },
694 715 {
695 716 "cell_type": "markdown",
696 717 "metadata": {},
697 718 "source": [
698 719 "Set the date from Python"
699 720 ]
700 721 },
701 722 {
702 723 "cell_type": "code",
703 724 "collapsed": false,
704 725 "input": [
705 726 "my_widget.value = \"1999-12-01\" # December 1st, 1999"
706 727 ],
707 728 "language": "python",
708 729 "metadata": {},
709 730 "outputs": [],
710 "prompt_number": 14
731 "prompt_number": 15
711 732 },
712 733 {
713 734 "cell_type": "heading",
714 735 "level": 1,
715 736 "metadata": {},
716 737 "source": [
717 738 "Section 3 - Extra credit"
718 739 ]
719 740 },
720 741 {
721 742 "cell_type": "markdown",
722 743 "metadata": {},
723 744 "source": [
724 745 "In the last section we created a fully working date picker widget. Now we will add custom validation and support for labels. Currently only the ISO date format \"YYYY-MM-DD\" is supported. We will add support for all of the date formats recognized by the 3rd party Python dateutil library."
725 746 ]
726 747 },
727 748 {
728 749 "cell_type": "heading",
729 750 "level": 2,
730 751 "metadata": {},
731 752 "source": [
732 753 "Python"
733 754 ]
734 755 },
735 756 {
736 757 "cell_type": "markdown",
737 758 "metadata": {},
738 759 "source": [
739 760 "The traitlet machinery searches the class that the trait is defined in for methods with \"`_changed`\" suffixed onto their names. Any method with the format \"`X_changed`\" will be called when \"`X`\" is modified. We can take advantage of this to perform validation and parsing of different date string formats. Below a method that listens to value has been added to the DateWidget."
740 761 ]
741 762 },
742 763 {
743 764 "cell_type": "code",
744 765 "collapsed": false,
745 766 "input": [
746 767 "# Import the base Widget class and the traitlets Unicode class.\n",
747 768 "from IPython.html.widgets import Widget\n",
748 769 "from IPython.utils.traitlets import Unicode\n",
749 770 "\n",
750 771 "# Define our DateWidget and its target model and default view.\n",
751 772 "class DateWidget(Widget):\n",
752 773 " target_name = Unicode('DateWidgetModel')\n",
753 774 " default_view_name = Unicode('DatePickerView')\n",
754 775 " \n",
755 776 " # Define the custom state properties to sync with the front-end\n",
756 777 " _keys = ['value']\n",
757 778 " value = Unicode()\n",
758 779 " \n",
759 780 " # This function automatically gets called by the traitlet machinery when\n",
760 781 " # value is modified because of this function's name.\n",
761 782 " def _value_changed(self, name, old_value, new_value):\n",
762 783 " pass\n",
763 784 " "
764 785 ],
765 786 "language": "python",
766 787 "metadata": {},
767 788 "outputs": [],
768 "prompt_number": 15
789 "prompt_number": 16
769 790 },
770 791 {
771 792 "cell_type": "markdown",
772 793 "metadata": {},
773 794 "source": [
774 795 "Now the function that parses the date string and only sets it in the correct format can be added."
775 796 ]
776 797 },
777 798 {
778 799 "cell_type": "code",
779 800 "collapsed": false,
780 801 "input": [
781 "# Import the dateutil library to parse date strings.\n",
782 "from dateutil import parser\n",
783 802 "\n",
784 803 "# Import the base Widget class and the traitlets Unicode class.\n",
785 804 "from IPython.html.widgets import Widget\n",
786 805 "from IPython.utils.traitlets import Unicode\n",
787 806 "\n",
788 807 "# Define our DateWidget and its target model and default view.\n",
789 808 "class DateWidget(Widget):\n",
790 809 " target_name = Unicode('DateWidgetModel')\n",
791 810 " default_view_name = Unicode('DatePickerView')\n",
792 811 " \n",
793 812 " # Define the custom state properties to sync with the front-end\n",
794 813 " _keys = ['value']\n",
795 814 " value = Unicode()\n",
796 815 " \n",
797 816 " # This function automatically gets called by the traitlet machinery when\n",
798 817 " # value is modified because of this function's name.\n",
799 818 " def _value_changed(self, name, old_value, new_value):\n",
800 819 " \n",
801 820 " # Parse the date time value.\n",
802 821 " try:\n",
803 822 " parsed_date = parser.parse(new_value)\n",
804 823 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
805 824 " except:\n",
806 825 " parsed_date_string = ''\n",
807 826 " \n",
808 827 " # Set the parsed date string if the current date string is different.\n",
809 828 " if self.value != parsed_date_string:\n",
810 829 " self.value = parsed_date_string"
811 830 ],
812 831 "language": "python",
813 832 "metadata": {},
814 833 "outputs": [],
815 "prompt_number": 16
834 "prompt_number": 17
816 835 },
817 836 {
818 837 "cell_type": "markdown",
819 838 "metadata": {},
820 839 "source": [
821 840 "The standard property name used for widget labels is `description`. In the code block below, `description` has been added to the Python widget."
822 841 ]
823 842 },
824 843 {
825 844 "cell_type": "code",
826 845 "collapsed": false,
827 846 "input": [
828 847 "# Import the dateutil library to parse date strings.\n",
829 848 "from dateutil import parser\n",
830 849 "\n",
831 850 "# Import the base Widget class and the traitlets Unicode class.\n",
832 851 "from IPython.html.widgets import Widget\n",
833 852 "from IPython.utils.traitlets import Unicode\n",
834 853 "\n",
835 854 "# Define our DateWidget and its target model and default view.\n",
836 855 "class DateWidget(Widget):\n",
837 856 " target_name = Unicode('DateWidgetModel')\n",
838 857 " default_view_name = Unicode('DatePickerView')\n",
839 858 " \n",
840 859 " # Define the custom state properties to sync with the front-end\n",
841 860 " _keys = ['value', 'description']\n",
842 861 " value = Unicode()\n",
843 862 " description = Unicode()\n",
844 863 " \n",
845 864 " # This function automatically gets called by the traitlet machinery when\n",
846 865 " # value is modified because of this function's name.\n",
847 866 " def _value_changed(self, name, old_value, new_value):\n",
848 867 " \n",
849 868 " # Parse the date time value.\n",
850 869 " try:\n",
851 870 " parsed_date = parser.parse(new_value)\n",
852 871 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
853 872 " except:\n",
854 873 " parsed_date_string = ''\n",
855 874 " \n",
856 875 " # Set the parsed date string if the current date string is different.\n",
857 876 " if self.value != parsed_date_string:\n",
858 877 " self.value = parsed_date_string"
859 878 ],
860 879 "language": "python",
861 880 "metadata": {},
862 881 "outputs": [],
863 "prompt_number": 17
882 "prompt_number": 18
864 883 },
865 884 {
866 885 "cell_type": "markdown",
867 886 "metadata": {},
868 887 "source": [
869 888 "Finally, a callback list is added so the user can perform custom validation. If any one of the callbacks returns False, the new date time is not set.\n",
870 889 "\n",
871 890 "**Final Python code below:**"
872 891 ]
873 892 },
874 893 {
875 894 "cell_type": "code",
876 895 "collapsed": false,
877 896 "input": [
878 897 "# Import the dateutil library to parse date strings.\n",
879 898 "from dateutil import parser\n",
880 899 "\n",
881 900 "# Import the base Widget class and the traitlets Unicode class.\n",
882 901 "from IPython.html.widgets import Widget\n",
883 902 "from IPython.utils.traitlets import Unicode\n",
884 903 "\n",
885 904 "# Define our DateWidget and its target model and default view.\n",
886 905 "class DateWidget(Widget):\n",
887 906 " target_name = Unicode('DateWidgetModel')\n",
888 907 " default_view_name = Unicode('DatePickerView')\n",
889 908 " \n",
890 909 " # Define the custom state properties to sync with the front-end\n",
891 910 " _keys = ['value', 'description']\n",
892 911 " value = Unicode()\n",
893 912 " description = Unicode()\n",
894 913 " \n",
895 914 " def __init__(self, **kwargs):\n",
896 915 " super(DateWidget, self).__init__(**kwargs)\n",
897 916 " self._validation_callbacks = []\n",
898 917 " \n",
899 918 " # This function automatically gets called by the traitlet machinery when\n",
900 919 " # value is modified because of this function's name.\n",
901 920 " def _value_changed(self, name, old_value, new_value):\n",
902 921 " \n",
903 922 " # Parse the date time value.\n",
904 923 " try:\n",
905 924 " parsed_date = parser.parse(new_value)\n",
906 925 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
907 926 " except:\n",
908 927 " parsed_date = None\n",
909 928 " parsed_date_string = ''\n",
910 929 " \n",
911 930 " # Set the parsed date string if the current date string is different.\n",
912 931 " if old_value != new_value:\n",
913 932 " if self.handle_validate(parsed_date):\n",
914 933 " self.value = parsed_date_string\n",
915 934 " else:\n",
916 935 " self.value = old_value\n",
917 936 " self.send_state() # The traitlet event won't fire since the value isn't changing.\n",
918 937 " # We need to force the back-end to send the front-end the state\n",
919 938 " # to make sure that the date control date doesn't change.\n",
920 939 " \n",
921 940 " \n",
922 941 " # Allow the user to register custom validation callbacks.\n",
923 942 " # callback(new value as a datetime object)\n",
924 943 " def on_validate(self, callback, remove=False):\n",
925 944 " if remove and callback in self._validation_callbacks:\n",
926 945 " self._validation_callbacks.remove(callback)\n",
927 946 " elif (not remove) and (not callback in self._validation_callbacks):\n",
928 947 " self._validation_callbacks.append(callback)\n",
929 948 " \n",
930 949 " # Call user validation callbacks. Return True if valid.\n",
931 950 " def handle_validate(self, new_value):\n",
932 951 " for callback in self._validation_callbacks:\n",
933 952 " if not callback(new_value):\n",
934 953 " return False\n",
935 954 " return True\n",
936 955 " "
937 956 ],
938 957 "language": "python",
939 958 "metadata": {},
940 959 "outputs": [],
941 "prompt_number": 18
960 "prompt_number": 19
942 961 },
943 962 {
944 963 "cell_type": "heading",
945 964 "level": 2,
946 965 "metadata": {},
947 966 "source": [
948 967 "JavaScript"
949 968 ]
950 969 },
951 970 {
952 971 "cell_type": "markdown",
953 972 "metadata": {},
954 973 "source": [
955 974 "Using the Javascript code from the last section, we add a label to the date time object. The label is a div with the `widget-hlabel` class applied to it. The `widget-hlabel` is a class provided by the widget framework that applies special styling to a div to make it look like the rest of the horizontal labels used with the built in widgets. Similar to the `widget-hlabel` class is the `widget-hbox-single` class. The `widget-hbox-single` class applies special styling to widget containers that store a single line horizontal widget. \n",
956 975 "\n",
957 976 "We hide the label if the description value is blank."
958 977 ]
959 978 },
960 979 {
961 980 "cell_type": "code",
962 981 "collapsed": false,
963 982 "input": [
964 983 "%%javascript\n",
965 984 "\n",
966 985 "require([\"notebook/js/widget\"], function(){\n",
967 986 " \n",
968 987 " // Define the DateModel and register it with the widget manager.\n",
969 988 " var DateModel = IPython.WidgetModel.extend({});\n",
970 989 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
971 990 " \n",
972 991 " // Define the DatePickerView\n",
973 992 " var DatePickerView = IPython.WidgetView.extend({\n",
974 993 " \n",
975 994 " render: function(){\n",
976 995 " \n",
977 996 " // Create a div to hold our widget.\n",
978 997 " this.$el = $('<div />')\n",
979 998 " .addClass('widget-hbox-single'); // Apply this class to the widget container to make\n",
980 999 " // it fit with the other built in widgets.\n",
981 1000 " this.delegateEvents();\n",
982 1001 " \n",
983 1002 " // Create a label.\n",
984 1003 " this.$label = $('<div />')\n",
985 1004 " .addClass('widget-hlabel')\n",
986 1005 " .appendTo(this.$el)\n",
987 1006 " .hide(); // Hide the label by default.\n",
988 1007 " \n",
989 1008 " // Create the date picker control.\n",
990 1009 " this.$date = $('<input />')\n",
991 1010 " .attr('type', 'date')\n",
992 1011 " .appendTo(this.$el);\n",
993 1012 " },\n",
994 1013 " \n",
995 1014 " update: function() {\n",
996 1015 " \n",
997 1016 " // Set the value of the date control and then call base.\n",
998 1017 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
999 1018 " \n",
1000 1019 " // Hide or show the label depending on the existance of a description.\n",
1001 1020 " var description = this.model.get('description');\n",
1002 1021 " if (description == undefined || description == '') {\n",
1003 1022 " this.$label.hide();\n",
1004 1023 " } else {\n",
1005 1024 " this.$label.show();\n",
1006 1025 " this.$label.html(description);\n",
1007 1026 " }\n",
1008 1027 " \n",
1009 1028 " return IPython.WidgetView.prototype.update.call(this);\n",
1010 1029 " },\n",
1011 1030 " \n",
1012 1031 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
1013 1032 " events: {\"change\": \"handle_date_change\"},\n",
1014 1033 " \n",
1015 1034 " // Callback for when the date is changed.\n",
1016 1035 " handle_date_change: function(event) {\n",
1017 1036 " this.model.set('value', this.$date.val());\n",
1018 1037 " this.model.update_other_views(this);\n",
1019 1038 " },\n",
1020 1039 " \n",
1021 1040 " });\n",
1022 1041 " \n",
1023 1042 " // Register the DatePickerView with the widget manager.\n",
1024 1043 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
1025 1044 "});"
1026 1045 ],
1027 1046 "language": "python",
1028 1047 "metadata": {},
1029 1048 "outputs": [
1030 1049 {
1031 1050 "javascript": [
1032 1051 "\n",
1033 1052 "require([\"notebook/js/widget\"], function(){\n",
1034 1053 " \n",
1035 1054 " // Define the DateModel and register it with the widget manager.\n",
1036 1055 " var DateModel = IPython.WidgetModel.extend({});\n",
1037 1056 " IPython.widget_manager.register_widget_model('DateWidgetModel', DateModel);\n",
1038 1057 " \n",
1039 1058 " // Define the DatePickerView\n",
1040 1059 " var DatePickerView = IPython.WidgetView.extend({\n",
1041 1060 " \n",
1042 1061 " render: function(){\n",
1043 1062 " \n",
1044 1063 " // Create a div to hold our widget.\n",
1045 1064 " this.$el = $('<div />')\n",
1046 1065 " .addClass('widget-hbox-single'); // Apply this class to the widget container to make\n",
1047 1066 " // it fit with the other built in widgets.\n",
1048 1067 " this.delegateEvents();\n",
1049 1068 " \n",
1050 1069 " // Create a label.\n",
1051 1070 " this.$label = $('<div />')\n",
1052 1071 " .addClass('widget-hlabel')\n",
1053 1072 " .appendTo(this.$el)\n",
1054 1073 " .hide(); // Hide the label by default.\n",
1055 1074 " \n",
1056 1075 " // Create the date picker control.\n",
1057 1076 " this.$date = $('<input />')\n",
1058 1077 " .attr('type', 'date')\n",
1059 1078 " .appendTo(this.$el);\n",
1060 1079 " },\n",
1061 1080 " \n",
1062 1081 " update: function() {\n",
1063 1082 " \n",
1064 1083 " // Set the value of the date control and then call base.\n",
1065 1084 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
1066 1085 " \n",
1067 1086 " // Hide or show the label depending on the existance of a description.\n",
1068 1087 " var description = this.model.get('description');\n",
1069 1088 " if (description == undefined || description == '') {\n",
1070 1089 " this.$label.hide();\n",
1071 1090 " } else {\n",
1072 1091 " this.$label.show();\n",
1073 1092 " this.$label.html(description);\n",
1074 1093 " }\n",
1075 1094 " \n",
1076 1095 " return IPython.WidgetView.prototype.update.call(this);\n",
1077 1096 " },\n",
1078 1097 " \n",
1079 1098 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
1080 1099 " events: {\"change\": \"handle_date_change\"},\n",
1081 1100 " \n",
1082 1101 " // Callback for when the date is changed.\n",
1083 1102 " handle_date_change: function(event) {\n",
1084 1103 " this.model.set('value', this.$date.val());\n",
1085 1104 " this.model.update_other_views(this);\n",
1086 1105 " },\n",
1087 1106 " \n",
1088 1107 " });\n",
1089 1108 " \n",
1090 1109 " // Register the DatePickerView with the widget manager.\n",
1091 1110 " IPython.widget_manager.register_widget_view('DatePickerView', DatePickerView);\n",
1092 1111 "});"
1093 1112 ],
1094 1113 "metadata": {},
1095 1114 "output_type": "display_data",
1096 1115 "text": [
1097 "<IPython.core.display.Javascript at 0x221a850>"
1116 "<IPython.core.display.Javascript at 0x7f8c679134d0>"
1098 1117 ]
1099 1118 }
1100 1119 ],
1101 "prompt_number": 19
1120 "prompt_number": 20
1102 1121 },
1103 1122 {
1104 1123 "cell_type": "heading",
1105 1124 "level": 2,
1106 1125 "metadata": {},
1107 1126 "source": [
1108 1127 "Test"
1109 1128 ]
1110 1129 },
1111 1130 {
1112 1131 "cell_type": "markdown",
1113 1132 "metadata": {},
1114 1133 "source": [
1115 1134 "To test the drawing of the label we create the widget like normal but supply the additional description property a value."
1116 1135 ]
1117 1136 },
1118 1137 {
1119 1138 "cell_type": "code",
1120 1139 "collapsed": false,
1121 1140 "input": [
1122 1141 "# Add some additional widgets for aesthetic purpose\n",
1123 1142 "display(widgets.StringWidget(description=\"First:\"))\n",
1124 1143 "display(widgets.StringWidget(description=\"Last:\"))\n",
1125 1144 "\n",
1126 1145 "my_widget = DateWidget(description=\"DOB:\")\n",
1127 1146 "display(my_widget)"
1128 1147 ],
1129 1148 "language": "python",
1130 1149 "metadata": {},
1131 1150 "outputs": [],
1132 "prompt_number": 20
1151 "prompt_number": 21
1133 1152 },
1134 1153 {
1135 1154 "cell_type": "markdown",
1136 1155 "metadata": {},
1137 1156 "source": [
1138 1157 "Since the date widget uses `value` and `description`, we can also display its value using a `TextBoxView`. The allows us to look at the raw date value being passed to and from the back-end and front-end."
1139 1158 ]
1140 1159 },
1141 1160 {
1142 1161 "cell_type": "code",
1143 1162 "collapsed": false,
1144 1163 "input": [
1145 1164 "display(my_widget, view_name=\"TextBoxView\")"
1146 1165 ],
1147 1166 "language": "python",
1148 1167 "metadata": {},
1149 1168 "outputs": [],
1150 "prompt_number": 21
1169 "prompt_number": 22
1151 1170 },
1152 1171 {
1153 1172 "cell_type": "markdown",
1154 1173 "metadata": {},
1155 1174 "source": [
1156 1175 "Now we will try to create a widget that only accepts dates in the year 2013. We render the widget without a description to verify that it can still render without a label."
1157 1176 ]
1158 1177 },
1159 1178 {
1160 1179 "cell_type": "code",
1161 1180 "collapsed": false,
1162 1181 "input": [
1163 1182 "my_widget = DateWidget()\n",
1164 1183 "display(my_widget)\n",
1165 1184 "\n",
1166 1185 "def validate_date(date):\n",
1167 1186 " return not date is None and date.year == 2013\n",
1168 1187 "my_widget.on_validate(validate_date)"
1169 1188 ],
1170 1189 "language": "python",
1171 1190 "metadata": {},
1172 1191 "outputs": [],
1173 "prompt_number": 22
1192 "prompt_number": 23
1174 1193 },
1175 1194 {
1176 1195 "cell_type": "code",
1177 1196 "collapsed": false,
1178 1197 "input": [
1179 1198 "# Try setting a valid date\n",
1180 1199 "my_widget.value = \"December 2, 2013\""
1181 1200 ],
1182 1201 "language": "python",
1183 1202 "metadata": {},
1184 1203 "outputs": [],
1185 "prompt_number": 23
1204 "prompt_number": 24
1186 1205 },
1187 1206 {
1188 1207 "cell_type": "code",
1189 1208 "collapsed": false,
1190 1209 "input": [
1191 1210 "# Try setting an invalid date\n",
1192 1211 "my_widget.value = \"June 12, 1999\""
1193 1212 ],
1194 1213 "language": "python",
1195 1214 "metadata": {},
1196 1215 "outputs": [],
1197 "prompt_number": 24
1216 "prompt_number": 25
1198 1217 }
1199 1218 ],
1200 1219 "metadata": {}
1201 1220 }
1202 1221 ]
1203 1222 } No newline at end of file
@@ -1,354 +1,249 b''
1 1 {
2 2 "metadata": {
3 3 "name": ""
4 4 },
5 5 "nbformat": 3,
6 6 "nbformat_minor": 0,
7 7 "worksheets": [
8 8 {
9 9 "cells": [
10 10 {
11 11 "cell_type": "heading",
12 12 "level": 1,
13 13 "metadata": {},
14 14 "source": [
15 15 "Build the Variable Inspector"
16 16 ]
17 17 },
18 18 {
19 19 "cell_type": "code",
20 20 "collapsed": false,
21 21 "input": [
22 22 "from IPython.html import widgets\n",
23 23 "from IPython.display import display\n",
24 24 "import re"
25 25 ],
26 26 "language": "python",
27 27 "metadata": {},
28 28 "outputs": [],
29 29 "prompt_number": 1
30 30 },
31 31 {
32 32 "cell_type": "heading",
33 33 "level": 3,
34 34 "metadata": {},
35 35 "source": [
36 "Custom SidePanel View"
37 ]
38 },
39 {
40 "cell_type": "code",
41 "collapsed": false,
42 "input": [
43 "%%javascript\n",
44 "\n",
45 "require([\"notebook/js/widget\"], function(){\n",
46 "\n",
47 " // Define the FilePickerView\n",
48 " var SidePanelView = IPython.WidgetView.extend({\n",
49 " \n",
50 " render: function(){ \n",
51 " var table_div = $('<div />', {id: 'var_inspect'})\n",
52 " .addClass('hbox');\n",
53 " var body_div = $('<div />')\n",
54 " .css('width', '80%')\n",
55 " .css('height', '100%')\n",
56 " .appendTo(table_div);\n",
57 " this.panel_div = $('<div />')\n",
58 " .css('width', '20%')\n",
59 " .css('height', '100%')\n",
60 " .appendTo(table_div);\n",
61 " \n",
62 " var body = $('body');\n",
63 " var site = body.find('#site');\n",
64 " site.detach();\n",
65 " body.find('#var_inspect').remove();\n",
66 " body.append(table_div);\n",
67 " site.appendTo(body_div);\n",
68 " },\n",
69 "\n",
70 " display_child: function(view) {\n",
71 " this.panel_div.append(view.$el);\n",
72 " },\n",
73 " });\n",
74 " \n",
75 " // Register the DatePickerView with the widget manager.\n",
76 " IPython.widget_manager.register_widget_view('SidePanelView', SidePanelView);\n",
77 "});"
78 ],
79 "language": "python",
80 "metadata": {},
81 "outputs": [
82 {
83 "javascript": [
84 "\n",
85 "require([\"notebook/js/widget\"], function(){\n",
86 "\n",
87 " // Define the FilePickerView\n",
88 " var SidePanelView = IPython.WidgetView.extend({\n",
89 " \n",
90 " render: function(){ \n",
91 " var table_div = $('<div />', {id: 'var_inspect'})\n",
92 " .addClass('hbox');\n",
93 " var body_div = $('<div />')\n",
94 " .css('width', '80%')\n",
95 " .css('height', '100%')\n",
96 " .appendTo(table_div);\n",
97 " this.panel_div = $('<div />')\n",
98 " .css('width', '20%')\n",
99 " .css('height', '100%')\n",
100 " .appendTo(table_div);\n",
101 " \n",
102 " var body = $('body');\n",
103 " var site = body.find('#site');\n",
104 " site.detach();\n",
105 " body.find('#var_inspect').remove();\n",
106 " body.append(table_div);\n",
107 " site.appendTo(body_div);\n",
108 " },\n",
109 "\n",
110 " display_child: function(view) {\n",
111 " this.panel_div.append(view.$el);\n",
112 " },\n",
113 " });\n",
114 " \n",
115 " // Register the DatePickerView with the widget manager.\n",
116 " IPython.widget_manager.register_widget_view('SidePanelView', SidePanelView);\n",
117 "});"
118 ],
119 "metadata": {},
120 "output_type": "display_data",
121 "text": [
122 "<IPython.core.display.Javascript at 0x7f4e70015050>"
123 ]
124 }
125 ],
126 "prompt_number": 2
127 },
128 {
129 "cell_type": "heading",
130 "level": 3,
131 "metadata": {},
132 "source": [
133 36 "Create Variable Inspector Controls"
134 37 ]
135 38 },
136 39 {
137 40 "cell_type": "code",
138 41 "collapsed": false,
139 42 "input": [
140 "_side_panel = widgets.ContainerWidget(default_view_name=\"SidePanelView\")\n",
43 "_popout = widgets.ContainerWidget(default_view_name=\"ModalView\")\n",
44 "_popout.description = \"Variable Inspector\"\n",
45 "_popout.button_text = _popout.description\n",
46 "_popout.vbox()\n",
141 47 "\n",
142 "_modal_div = widgets.ContainerWidget(parent=_side_panel)\n",
143 "_modal_div.set_css({'padding-top': '60px',\n",
144 " 'padding-right': '40px',\n",
145 " 'padding-left': '10px',})\n",
146 "\n",
147 "_modal_header = widgets.ContainerWidget(parent=_modal_div)\n",
148 "_modal_header_label = widgets.StringWidget(parent=_modal_header, default_view_name=\"LabelView\")\n",
149 "_modal_header_label.value = '<h3>Variable Inspector</h3>'\n",
150 "_modal_header_execs_label = widgets.StringWidget(parent=_modal_header, default_view_name=\"LabelView\")\n",
48 "_modal_header_execs_label = widgets.StringWidget(parent=_popout, default_view_name=\"LabelView\")\n",
151 49 "_modal_header_execs_label.execs = 0\n",
152 50 "\n",
153 "_modal_body = widgets.ContainerWidget(parent=_modal_div)\n",
154 "_modal_body.vbox()\n",
155 "\n",
51 "_modal_body = widgets.ContainerWidget(parent=_popout)\n",
52 "_modal_body.flex1()\n",
53 "_modal_body.set_css('overflow-y', 'scroll')\n",
156 54 "_modal_body_label = widgets.StringWidget(parent=_modal_body, default_view_name=\"LabelView\")\n",
157 55 "_modal_body_label.value = 'Not hooked'\n",
158 56 "\n",
159 "_modal_footer = widgets.ContainerWidget(parent=_modal_div)\n",
160 "_modal_footer.vbox()\n",
57 "_modal_footer = widgets.ContainerWidget(parent=_popout)\n",
161 58 "_var_filter = widgets.SelectionWidget(values=['Public', 'Private', 'IPython'], parent=_modal_footer, value='Public', default_view_name='ToggleButtonsView')\n",
162 59 "\n",
163 "display(_side_panel)\n",
60 "display(_popout)\n",
164 61 "\n",
165 "_modal_header.add_class('modal-header')\n",
166 "_modal_body.add_class('modal-body')\n",
167 62 "_modal_footer.add_class('modal-footer')\n"
168 63 ],
169 64 "language": "python",
170 65 "metadata": {},
171 66 "outputs": [],
172 "prompt_number": 3
67 "prompt_number": 2
173 68 },
174 69 {
175 70 "cell_type": "heading",
176 71 "level": 3,
177 72 "metadata": {},
178 73 "source": [
179 74 "Method that Fills the Inspector"
180 75 ]
181 76 },
182 77 {
183 78 "cell_type": "code",
184 79 "collapsed": false,
185 80 "input": [
186 81 "_ipython_input = re.compile('_i[0-9]*')\n",
187 82 "\n",
188 83 "def _fill_inspector():\n",
189 84 " \n",
190 85 " # Apply filter to variable names.\n",
191 86 " names = []\n",
192 87 " for name in sorted(_ipython.user_ns):\n",
193 88 " \n",
194 89 " match = _ipython_input.match(name)\n",
195 90 " is_ipython = (match is not None and match.group() == name) or \\\n",
196 91 " name == '_dh' or \\\n",
197 92 " name == '_ih' or \\\n",
198 93 " name == '_ii' or \\\n",
199 94 " name == '_iii' or \\\n",
200 95 " name == '_oh' or \\\n",
201 96 " name == '_sh' or \\\n",
202 97 " name == 'get_ipython' or \\\n",
203 98 " name == 'In' or \\\n",
204 99 " name == 'Out' or \\\n",
205 100 " name == 'exit' or \\\n",
206 101 " name == 'help' or \\\n",
207 102 " name == 'quit' or \\\n",
208 103 " name == '_' or \\\n",
209 104 " name == '__' or \\\n",
210 105 " name == '___'\n",
211 106 " \n",
212 107 " is_private = name.startswith('_')\n",
213 108 " is_public = not is_private\n",
214 109 " \n",
215 110 " var_filter = _var_filter.value\n",
216 111 " if var_filter == 'IPython' and is_ipython:\n",
217 112 " names.append(name)\n",
218 113 " elif var_filter == 'Private' and (is_private and not is_ipython):\n",
219 114 " names.append(name)\n",
220 115 " elif var_filter == 'Public' and (is_public and not is_ipython):\n",
221 116 " names.append(name)\n",
222 117 " \n",
223 118 " # Render each name and it's value.\n",
224 119 " variable_list_html = \"\"\"\n",
225 120 "<table class=\"table table-bordered table-striped\" style=\"width: 100%; overflow: hidden; table-layout:fixed;\">\n",
226 121 " <tr><th>Name</th><th>Type</th><th>Value</th>\"\"\"\n",
227 122 " for name in names:\n",
228 123 " var_value = _ipython.user_ns[name]\n",
229 124 " var_type = type(var_value)\n",
230 125 " var_small_value = str(var_value)[:100].replace(\"&\", \"&amp;\").replace(\"<\", \"&lt;\")\n",
231 126 " \n",
232 127 " if str(var_value) != var_small_value:\n",
233 128 " var_small_value += '<br><div class=\"label label-info\">...</div>'\n",
234 129 " \n",
235 130 " row = \"\"\"\n",
236 131 "<tr style='overflow: hidden;'>\n",
237 132 " <td style='width: 30%; overflow: hidden;'>{name}</td>\n",
238 133 " <td style='width: 30%; overflow: hidden;'>{type}</td>\n",
239 134 " <td style='width: 40%; overflow: hidden;'>{small_value}</td>\n",
240 135 "</tr>\n",
241 136 "\"\"\".format(name=name, type=var_type.__name__, small_value=var_small_value, value=str(var_value))\n",
242 137 " variable_list_html += row + '\\n'\n",
243 138 " variable_list_html += '</table>'\n",
244 139 " _modal_body_label.value = variable_list_html\n",
245 140 " \n",
246 141 " "
247 142 ],
248 143 "language": "python",
249 144 "metadata": {},
250 145 "outputs": [],
251 "prompt_number": 4
146 "prompt_number": 3
252 147 },
253 148 {
254 149 "cell_type": "heading",
255 150 "level": 3,
256 151 "metadata": {},
257 152 "source": [
258 153 "Hook Cell Execute and Filter Change"
259 154 ]
260 155 },
261 156 {
262 157 "cell_type": "code",
263 158 "collapsed": false,
264 159 "input": [
265 160 "_ipython = get_ipython()\n",
266 161 "\n",
267 162 "try:\n",
268 163 " del _ipython._post_execute[handle_cell_executed]\n",
269 164 "except:\n",
270 165 " pass\n",
271 166 "\n",
272 167 "def _handle_cell_executed():\n",
273 168 " _modal_header_execs_label.execs += 1\n",
274 169 " _modal_header_execs_label.value = '%d cell executions captured' % _modal_header_execs_label.execs\n",
275 170 " _fill_inspector()\n",
276 171 "_ipython.register_post_execute(_handle_cell_executed)\n",
277 172 "\n",
278 173 "def _handle_filter_changed():\n",
279 174 " _fill_inspector()\n",
280 175 "_var_filter.on_trait_change(_handle_filter_changed, 'value')"
281 176 ],
282 177 "language": "python",
283 178 "metadata": {},
284 179 "outputs": [],
285 "prompt_number": 5
180 "prompt_number": 4
286 181 },
287 182 {
288 183 "cell_type": "heading",
289 184 "level": 1,
290 185 "metadata": {},
291 186 "source": [
292 187 "Test"
293 188 ]
294 189 },
295 190 {
296 191 "cell_type": "code",
297 192 "collapsed": false,
298 193 "input": [
299 194 "a = 5"
300 195 ],
301 196 "language": "python",
302 197 "metadata": {},
303 198 "outputs": [],
304 "prompt_number": 6
199 "prompt_number": 5
305 200 },
306 201 {
307 202 "cell_type": "code",
308 203 "collapsed": false,
309 204 "input": [
310 205 "b = 3.0"
311 206 ],
312 207 "language": "python",
313 208 "metadata": {},
314 209 "outputs": [],
315 "prompt_number": 7
210 "prompt_number": 6
316 211 },
317 212 {
318 213 "cell_type": "code",
319 214 "collapsed": false,
320 215 "input": [
321 216 "c = a * b"
322 217 ],
323 218 "language": "python",
324 219 "metadata": {},
325 220 "outputs": [],
326 "prompt_number": 8
221 "prompt_number": 7
327 222 },
328 223 {
329 224 "cell_type": "code",
330 225 "collapsed": false,
331 226 "input": [
332 227 "d = \"String\""
333 228 ],
334 229 "language": "python",
335 230 "metadata": {},
336 231 "outputs": [],
337 "prompt_number": 9
232 "prompt_number": 8
338 233 },
339 234 {
340 235 "cell_type": "code",
341 236 "collapsed": false,
342 237 "input": [
343 238 "del b"
344 239 ],
345 240 "language": "python",
346 241 "metadata": {},
347 242 "outputs": [],
348 "prompt_number": 10
243 "prompt_number": 9
349 244 }
350 245 ],
351 246 "metadata": {}
352 247 }
353 248 ]
354 249 } No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now