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 0x |
|
|
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 |
|
|
183 | " print(\"Loading %s\" % file_widget.filename)\n", | |
|
182 | 184 | "\n", |
|
183 | 185 | "def file_loaded():\n", |
|
184 |
" print |
|
|
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 |
|
|
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 |
" |
|
|
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 |
|
|
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 |
"2 |
|
|
120 | "28\n" | |
|
119 | 121 | ] |
|
120 | 122 | }, |
|
121 | 123 | { |
|
122 | 124 | "output_type": "stream", |
|
123 | 125 | "stream": "stdout", |
|
124 | 126 | "text": [ |
|
125 |
" |
|
|
127 | "55\n" | |
|
126 | 128 | ] |
|
127 | 129 | }, |
|
128 | 130 | { |
|
129 | 131 | "output_type": "stream", |
|
130 | 132 | "stream": "stdout", |
|
131 | 133 | "text": [ |
|
132 |
"9 |
|
|
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": |
|
|
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 0x |
|
|
163 | "<IPython.core.display.Javascript at 0x7f8c679289d0>" | |
|
143 | 164 | ] |
|
144 | 165 | } |
|
145 | 166 | ], |
|
146 |
"prompt_number": |
|
|
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 0x |
|
|
205 | "<IPython.core.display.Javascript at 0x7f8c67928510>" | |
|
185 | 206 | ] |
|
186 | 207 | } |
|
187 | 208 | ], |
|
188 |
"prompt_number": |
|
|
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 0x |
|
|
273 | "<IPython.core.display.Javascript at 0x7f8c67928a10>" | |
|
253 | 274 | ] |
|
254 | 275 | } |
|
255 | 276 | ], |
|
256 |
"prompt_number": |
|
|
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": |
|
|
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": |
|
|
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 0x |
|
|
429 | "<IPython.core.display.Javascript at 0x7f8c67928590>" | |
|
409 | 430 | ] |
|
410 | 431 | } |
|
411 | 432 | ], |
|
412 |
"prompt_number": |
|
|
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 0x |
|
|
519 | "<IPython.core.display.Javascript at 0x7f8c648ce8d0>" | |
|
499 | 520 | ] |
|
500 | 521 | } |
|
501 | 522 | ], |
|
502 |
"prompt_number": |
|
|
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 0x |
|
|
637 | "<IPython.core.display.Javascript at 0x7f8c648ce150>" | |
|
617 | 638 | ] |
|
618 | 639 | } |
|
619 | 640 | ], |
|
620 |
"prompt_number": 1 |
|
|
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": 1 |
|
|
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": 1 |
|
|
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": 1 |
|
|
707 | "prompt_number": 14, | |
|
687 | 708 | "text": [ |
|
688 |
" |
|
|
709 | "'2013-11-28'" | |
|
689 | 710 | ] |
|
690 | 711 | } |
|
691 | 712 | ], |
|
692 |
"prompt_number": 1 |
|
|
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": 1 |
|
|
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": 1 |
|
|
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": 1 |
|
|
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": 1 |
|
|
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": 1 |
|
|
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 0x |
|
|
1116 | "<IPython.core.display.Javascript at 0x7f8c679134d0>" | |
|
1098 | 1117 | ] |
|
1099 | 1118 | } |
|
1100 | 1119 | ], |
|
1101 |
"prompt_number": |
|
|
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": 2 |
|
|
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": 2 |
|
|
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": 2 |
|
|
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": 2 |
|
|
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": 2 |
|
|
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 |
"_ |
|
|
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_ |
|
|
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=_ |
|
|
154 |
"_modal_body. |
|
|
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=_ |
|
|
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(_ |
|
|
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": |
|
|
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(\"&\", \"&\").replace(\"<\", \"<\")\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": |
|
|
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": |
|
|
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": |
|
|
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": |
|
|
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": |
|
|
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": |
|
|
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": |
|
|
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