##// END OF EJS Templates
Merge pull request #5289 from ellisonbg/widget-path...
Jonathan Frederic -
r15747:3f608e35 merge
parent child Browse files
Show More
@@ -1,248 +1,248 b''
1 {
1 {
2 "metadata": {
2 "metadata": {
3 "cell_tags": [
3 "cell_tags": [
4 [
4 [
5 "<None>",
5 "<None>",
6 null
6 null
7 ]
7 ]
8 ],
8 ],
9 "name": ""
9 "name": ""
10 },
10 },
11 "nbformat": 3,
11 "nbformat": 3,
12 "nbformat_minor": 0,
12 "nbformat_minor": 0,
13 "worksheets": [
13 "worksheets": [
14 {
14 {
15 "cells": [
15 "cells": [
16 {
16 {
17 "cell_type": "code",
17 "cell_type": "code",
18 "collapsed": false,
18 "collapsed": false,
19 "input": [
19 "input": [
20 "import base64\n",
20 "import base64\n",
21 "from __future__ import print_function # py 2.7 compat.\n",
21 "from __future__ import print_function # py 2.7 compat.\n",
22 "from IPython.html import widgets # Widget definitions.\n",
22 "from IPython.html import widgets # Widget definitions.\n",
23 "from IPython.utils.traitlets import Unicode # Traitlet needed to add synced attributes to the widget."
23 "from IPython.utils.traitlets import Unicode # Traitlet needed to add synced attributes to the widget."
24 ],
24 ],
25 "language": "python",
25 "language": "python",
26 "metadata": {},
26 "metadata": {},
27 "outputs": [],
27 "outputs": [],
28 "prompt_number": 1
28 "prompt_number": 1
29 },
29 },
30 {
30 {
31 "cell_type": "markdown",
31 "cell_type": "markdown",
32 "metadata": {},
32 "metadata": {},
33 "source": [
33 "source": [
34 "This is a custom widget that allows the user to upload file data to the notebook server. The file data is sent via a statefull `value` attribute of the widget. The widget has an upload failed event that fires in the front-end and is echoed to the back-end using a custom msg."
34 "This is a custom widget that allows the user to upload file data to the notebook server. The file data is sent via a statefull `value` attribute of the widget. The widget has an upload failed event that fires in the front-end and is echoed to the back-end using a custom msg."
35 ]
35 ]
36 },
36 },
37 {
37 {
38 "cell_type": "code",
38 "cell_type": "code",
39 "collapsed": false,
39 "collapsed": false,
40 "input": [
40 "input": [
41 "class FileWidget(widgets.DOMWidget):\n",
41 "class FileWidget(widgets.DOMWidget):\n",
42 " _view_name = Unicode('FilePickerView', sync=True)\n",
42 " _view_name = Unicode('FilePickerView', sync=True)\n",
43 " value = Unicode(sync=True)\n",
43 " value = Unicode(sync=True)\n",
44 " filename = Unicode(sync=True)\n",
44 " filename = Unicode(sync=True)\n",
45 " \n",
45 " \n",
46 " def __init__(self, **kwargs):\n",
46 " def __init__(self, **kwargs):\n",
47 " \"\"\"Constructor\"\"\"\n",
47 " \"\"\"Constructor\"\"\"\n",
48 " widgets.DOMWidget.__init__(self, **kwargs) # Call the base.\n",
48 " widgets.DOMWidget.__init__(self, **kwargs) # Call the base.\n",
49 " \n",
49 " \n",
50 " # Allow the user to register error callbacks with the following signatures:\n",
50 " # Allow the user to register error callbacks with the following signatures:\n",
51 " # callback()\n",
51 " # callback()\n",
52 " # callback(sender)\n",
52 " # callback(sender)\n",
53 " self.errors = widgets.CallbackDispatcher(accepted_nargs=[0, 1])\n",
53 " self.errors = widgets.CallbackDispatcher(accepted_nargs=[0, 1])\n",
54 " \n",
54 " \n",
55 " # Listen for custom msgs\n",
55 " # Listen for custom msgs\n",
56 " self.on_msg(self._handle_custom_msg)\n",
56 " self.on_msg(self._handle_custom_msg)\n",
57 "\n",
57 "\n",
58 " def _handle_custom_msg(self, content):\n",
58 " def _handle_custom_msg(self, content):\n",
59 " \"\"\"Handle a msg from the front-end.\n",
59 " \"\"\"Handle a msg from the front-end.\n",
60 "\n",
60 "\n",
61 " Parameters\n",
61 " Parameters\n",
62 " ----------\n",
62 " ----------\n",
63 " content: dict\n",
63 " content: dict\n",
64 " Content of the msg.\"\"\"\n",
64 " Content of the msg.\"\"\"\n",
65 " if 'event' in content and content['event'] == 'error':\n",
65 " if 'event' in content and content['event'] == 'error':\n",
66 " self.errors()\n",
66 " self.errors()\n",
67 " self.errors(self)\n",
67 " self.errors(self)\n",
68 " "
68 " "
69 ],
69 ],
70 "language": "python",
70 "language": "python",
71 "metadata": {},
71 "metadata": {},
72 "outputs": [],
72 "outputs": [],
73 "prompt_number": 2
73 "prompt_number": 2
74 },
74 },
75 {
75 {
76 "cell_type": "code",
76 "cell_type": "code",
77 "collapsed": false,
77 "collapsed": false,
78 "input": [
78 "input": [
79 "%%javascript\n",
79 "%%javascript\n",
80 "\n",
80 "\n",
81 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
81 "require([\"widgets/js/widget\"], function(WidgetManager){\n",
82 "\n",
82 "\n",
83 " var FilePickerView = IPython.WidgetView.extend({\n",
83 " var FilePickerView = IPython.WidgetView.extend({\n",
84 " render: function(){\n",
84 " render: function(){\n",
85 " // Render the view.\n",
85 " // Render the view.\n",
86 " this.setElement($('<input />')\n",
86 " this.setElement($('<input />')\n",
87 " .attr('type', 'file'));\n",
87 " .attr('type', 'file'));\n",
88 " },\n",
88 " },\n",
89 " \n",
89 " \n",
90 " events: {\n",
90 " events: {\n",
91 " // List of events and their handlers.\n",
91 " // List of events and their handlers.\n",
92 " 'change': 'handle_file_change',\n",
92 " 'change': 'handle_file_change',\n",
93 " },\n",
93 " },\n",
94 " \n",
94 " \n",
95 " handle_file_change: function(evt) { \n",
95 " handle_file_change: function(evt) { \n",
96 " // Handle when the user has changed the file.\n",
96 " // Handle when the user has changed the file.\n",
97 " \n",
97 " \n",
98 " // Retrieve the first (and only!) File from the FileList object\n",
98 " // Retrieve the first (and only!) File from the FileList object\n",
99 " var file = evt.target.files[0];\n",
99 " var file = evt.target.files[0];\n",
100 " if (file) {\n",
100 " if (file) {\n",
101 "\n",
101 "\n",
102 " // Read the file's textual content and set value to those contents.\n",
102 " // Read the file's textual content and set value to those contents.\n",
103 " var that = this;\n",
103 " var that = this;\n",
104 " var file_reader = new FileReader();\n",
104 " var file_reader = new FileReader();\n",
105 " file_reader.onload = function(e) {\n",
105 " file_reader.onload = function(e) {\n",
106 " that.model.set('value', e.target.result);\n",
106 " that.model.set('value', e.target.result);\n",
107 " that.touch();\n",
107 " that.touch();\n",
108 " }\n",
108 " }\n",
109 " file_reader.readAsText(file);\n",
109 " file_reader.readAsText(file);\n",
110 " } else {\n",
110 " } else {\n",
111 "\n",
111 "\n",
112 " // The file couldn't be opened. Send an error msg to the\n",
112 " // The file couldn't be opened. Send an error msg to the\n",
113 " // back-end.\n",
113 " // back-end.\n",
114 " this.send({ 'event': 'error' });\n",
114 " this.send({ 'event': 'error' });\n",
115 " }\n",
115 " }\n",
116 "\n",
116 "\n",
117 " // Set the filename of the file.\n",
117 " // Set the filename of the file.\n",
118 " this.model.set('filename', file.name);\n",
118 " this.model.set('filename', file.name);\n",
119 " this.touch();\n",
119 " this.touch();\n",
120 " },\n",
120 " },\n",
121 " });\n",
121 " });\n",
122 " \n",
122 " \n",
123 " // Register the DatePickerView with the widget manager.\n",
123 " // Register the DatePickerView with the widget manager.\n",
124 " WidgetManager.register_widget_view('FilePickerView', FilePickerView);\n",
124 " WidgetManager.register_widget_view('FilePickerView', FilePickerView);\n",
125 "});"
125 "});"
126 ],
126 ],
127 "language": "python",
127 "language": "python",
128 "metadata": {},
128 "metadata": {},
129 "outputs": [
129 "outputs": [
130 {
130 {
131 "javascript": [
131 "javascript": [
132 "\n",
132 "\n",
133 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
133 "require([\"widgets/js/widget\"], function(WidgetManager){\n",
134 "\n",
134 "\n",
135 " var FilePickerView = IPython.WidgetView.extend({\n",
135 " var FilePickerView = IPython.WidgetView.extend({\n",
136 " render: function(){\n",
136 " render: function(){\n",
137 " // Render the view.\n",
137 " // Render the view.\n",
138 " this.setElement($('<input />')\n",
138 " this.setElement($('<input />')\n",
139 " .attr('type', 'file'));\n",
139 " .attr('type', 'file'));\n",
140 " },\n",
140 " },\n",
141 " \n",
141 " \n",
142 " events: {\n",
142 " events: {\n",
143 " // List of events and their handlers.\n",
143 " // List of events and their handlers.\n",
144 " 'change': 'handle_file_change',\n",
144 " 'change': 'handle_file_change',\n",
145 " },\n",
145 " },\n",
146 " \n",
146 " \n",
147 " handle_file_change: function(evt) { \n",
147 " handle_file_change: function(evt) { \n",
148 " // Handle when the user has changed the file.\n",
148 " // Handle when the user has changed the file.\n",
149 " \n",
149 " \n",
150 " // Retrieve the first (and only!) File from the FileList object\n",
150 " // Retrieve the first (and only!) File from the FileList object\n",
151 " var file = evt.target.files[0];\n",
151 " var file = evt.target.files[0];\n",
152 " if (file) {\n",
152 " if (file) {\n",
153 "\n",
153 "\n",
154 " // Read the file's textual content and set value to those contents.\n",
154 " // Read the file's textual content and set value to those contents.\n",
155 " var that = this;\n",
155 " var that = this;\n",
156 " var file_reader = new FileReader();\n",
156 " var file_reader = new FileReader();\n",
157 " file_reader.onload = function(e) {\n",
157 " file_reader.onload = function(e) {\n",
158 " that.model.set('value', e.target.result);\n",
158 " that.model.set('value', e.target.result);\n",
159 " that.touch();\n",
159 " that.touch();\n",
160 " }\n",
160 " }\n",
161 " file_reader.readAsText(file);\n",
161 " file_reader.readAsText(file);\n",
162 " } else {\n",
162 " } else {\n",
163 "\n",
163 "\n",
164 " // The file couldn't be opened. Send an error msg to the\n",
164 " // The file couldn't be opened. Send an error msg to the\n",
165 " // back-end.\n",
165 " // back-end.\n",
166 " this.send({ 'event': 'error' });\n",
166 " this.send({ 'event': 'error' });\n",
167 " }\n",
167 " }\n",
168 "\n",
168 "\n",
169 " // Set the filename of the file.\n",
169 " // Set the filename of the file.\n",
170 " this.model.set('filename', file.name);\n",
170 " this.model.set('filename', file.name);\n",
171 " this.touch();\n",
171 " this.touch();\n",
172 " },\n",
172 " },\n",
173 " });\n",
173 " });\n",
174 " \n",
174 " \n",
175 " // Register the DatePickerView with the widget manager.\n",
175 " // Register the DatePickerView with the widget manager.\n",
176 " WidgetManager.register_widget_view('FilePickerView', FilePickerView);\n",
176 " WidgetManager.register_widget_view('FilePickerView', FilePickerView);\n",
177 "});"
177 "});"
178 ],
178 ],
179 "metadata": {},
179 "metadata": {},
180 "output_type": "display_data",
180 "output_type": "display_data",
181 "text": [
181 "text": [
182 "<IPython.core.display.Javascript at 0x36df2d0>"
182 "<IPython.core.display.Javascript at 0x36df2d0>"
183 ]
183 ]
184 }
184 }
185 ],
185 ],
186 "prompt_number": 3
186 "prompt_number": 3
187 },
187 },
188 {
188 {
189 "cell_type": "markdown",
189 "cell_type": "markdown",
190 "metadata": {},
190 "metadata": {},
191 "source": [
191 "source": [
192 "The following shows how the file widget can be used."
192 "The following shows how the file widget can be used."
193 ]
193 ]
194 },
194 },
195 {
195 {
196 "cell_type": "code",
196 "cell_type": "code",
197 "collapsed": false,
197 "collapsed": false,
198 "input": [
198 "input": [
199 "file_widget = FileWidget()\n",
199 "file_widget = FileWidget()\n",
200 "\n",
200 "\n",
201 "# Register an event to echo the filename when it has been changed.\n",
201 "# Register an event to echo the filename when it has been changed.\n",
202 "def file_loading():\n",
202 "def file_loading():\n",
203 " print(\"Loading %s\" % file_widget.filename)\n",
203 " print(\"Loading %s\" % file_widget.filename)\n",
204 "file_widget.on_trait_change(file_loading, 'filename')\n",
204 "file_widget.on_trait_change(file_loading, 'filename')\n",
205 "\n",
205 "\n",
206 "# Register an event to echo the filename and contents when a file\n",
206 "# Register an event to echo the filename and contents when a file\n",
207 "# has been uploaded.\n",
207 "# has been uploaded.\n",
208 "def file_loaded():\n",
208 "def file_loaded():\n",
209 " print(\"Loaded, file contents: %s\" % file_widget.value)\n",
209 " print(\"Loaded, file contents: %s\" % file_widget.value)\n",
210 "file_widget.on_trait_change(file_loaded, 'value')\n",
210 "file_widget.on_trait_change(file_loaded, 'value')\n",
211 "\n",
211 "\n",
212 "# Register an event to print an error message when a file could not\n",
212 "# Register an event to print an error message when a file could not\n",
213 "# be opened. Since the error messages are not handled through\n",
213 "# be opened. Since the error messages are not handled through\n",
214 "# traitlets but instead handled through custom msgs, the registration\n",
214 "# traitlets but instead handled through custom msgs, the registration\n",
215 "# of the handler is different than the two examples above. Instead\n",
215 "# of the handler is different than the two examples above. Instead\n",
216 "# the API provided by the CallbackDispatcher must be used.\n",
216 "# the API provided by the CallbackDispatcher must be used.\n",
217 "def file_failed():\n",
217 "def file_failed():\n",
218 " print(\"Could not load file contents of %s\" % file_widget.filename)\n",
218 " print(\"Could not load file contents of %s\" % file_widget.filename)\n",
219 "file_widget.errors.register_callback(file_failed)\n",
219 "file_widget.errors.register_callback(file_failed)\n",
220 "\n",
220 "\n",
221 "file_widget"
221 "file_widget"
222 ],
222 ],
223 "language": "python",
223 "language": "python",
224 "metadata": {},
224 "metadata": {},
225 "outputs": [
225 "outputs": [
226 {
226 {
227 "output_type": "stream",
227 "output_type": "stream",
228 "stream": "stdout",
228 "stream": "stdout",
229 "text": [
229 "text": [
230 "Loading test.txt\n"
230 "Loading test.txt\n"
231 ]
231 ]
232 },
232 },
233 {
233 {
234 "output_type": "stream",
234 "output_type": "stream",
235 "stream": "stdout",
235 "stream": "stdout",
236 "text": [
236 "text": [
237 "Loaded, file contents: Hello World!\n",
237 "Loaded, file contents: Hello World!\n",
238 "\n"
238 "\n"
239 ]
239 ]
240 }
240 }
241 ],
241 ],
242 "prompt_number": 4
242 "prompt_number": 4
243 }
243 }
244 ],
244 ],
245 "metadata": {}
245 "metadata": {}
246 }
246 }
247 ]
247 ]
248 } No newline at end of file
248 }
@@ -1,1058 +1,1058 b''
1 {
1 {
2 "metadata": {
2 "metadata": {
3 "cell_tags": [
3 "cell_tags": [
4 [
4 [
5 "<None>",
5 "<None>",
6 null
6 null
7 ]
7 ]
8 ],
8 ],
9 "name": ""
9 "name": ""
10 },
10 },
11 "nbformat": 3,
11 "nbformat": 3,
12 "nbformat_minor": 0,
12 "nbformat_minor": 0,
13 "worksheets": [
13 "worksheets": [
14 {
14 {
15 "cells": [
15 "cells": [
16 {
16 {
17 "cell_type": "markdown",
17 "cell_type": "markdown",
18 "metadata": {},
18 "metadata": {},
19 "source": [
19 "source": [
20 "[< Back to Part 5](Part 5 - Alignment.ipynb) or [Index](index.ipynb)\n",
20 "[< Back to Part 5](Part 5 - Alignment.ipynb) or [Index](index.ipynb)\n",
21 "\n",
21 "\n",
22 "Before reading, make sure to review\n",
22 "Before reading, make sure to review\n",
23 "\n",
23 "\n",
24 "- [MVC prgramming](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)\n",
24 "- [MVC prgramming](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)\n",
25 "- [Backbone.js](https://www.codeschool.com/courses/anatomy-of-backbonejs)\n",
25 "- [Backbone.js](https://www.codeschool.com/courses/anatomy-of-backbonejs)\n",
26 "- [The widget IPEP](https://github.com/ipython/ipython/wiki/IPEP-23%3A-Backbone.js-Widgets)\n",
26 "- [The widget IPEP](https://github.com/ipython/ipython/wiki/IPEP-23%3A-Backbone.js-Widgets)\n",
27 "- [The original widget PR discussion](https://github.com/ipython/ipython/pull/4374)"
27 "- [The original widget PR discussion](https://github.com/ipython/ipython/pull/4374)"
28 ]
28 ]
29 },
29 },
30 {
30 {
31 "cell_type": "code",
31 "cell_type": "code",
32 "collapsed": false,
32 "collapsed": false,
33 "input": [
33 "input": [
34 "from __future__ import print_function # For py 2.7 compat\n",
34 "from __future__ import print_function # For py 2.7 compat\n",
35 "\n",
35 "\n",
36 "from IPython.html import widgets # Widget definitions\n",
36 "from IPython.html import widgets # Widget definitions\n",
37 "from IPython.display import display # Used to display widgets in the notebook\n",
37 "from IPython.display import display # Used to display widgets in the notebook\n",
38 "from IPython.utils.traitlets import Unicode # Used to declare attributes of our widget"
38 "from IPython.utils.traitlets import Unicode # Used to declare attributes of our widget"
39 ],
39 ],
40 "language": "python",
40 "language": "python",
41 "metadata": {},
41 "metadata": {},
42 "outputs": [],
42 "outputs": [],
43 "prompt_number": 1
43 "prompt_number": 1
44 },
44 },
45 {
45 {
46 "cell_type": "heading",
46 "cell_type": "heading",
47 "level": 1,
47 "level": 1,
48 "metadata": {},
48 "metadata": {},
49 "source": [
49 "source": [
50 "Abstract"
50 "Abstract"
51 ]
51 ]
52 },
52 },
53 {
53 {
54 "cell_type": "markdown",
54 "cell_type": "markdown",
55 "metadata": {},
55 "metadata": {},
56 "source": [
56 "source": [
57 "This notebook implements a custom date picker widget,\n",
57 "This notebook implements a custom date picker widget,\n",
58 "in order to demonstrate the widget creation process.\n",
58 "in order to demonstrate the widget creation process.\n",
59 "\n",
59 "\n",
60 "To create a custom widget, both Python and JavaScript code is required."
60 "To create a custom widget, both Python and JavaScript code is required."
61 ]
61 ]
62 },
62 },
63 {
63 {
64 "cell_type": "heading",
64 "cell_type": "heading",
65 "level": 1,
65 "level": 1,
66 "metadata": {},
66 "metadata": {},
67 "source": [
67 "source": [
68 "Section 1 - Basics"
68 "Section 1 - Basics"
69 ]
69 ]
70 },
70 },
71 {
71 {
72 "cell_type": "heading",
72 "cell_type": "heading",
73 "level": 2,
73 "level": 2,
74 "metadata": {},
74 "metadata": {},
75 "source": [
75 "source": [
76 "Python"
76 "Python"
77 ]
77 ]
78 },
78 },
79 {
79 {
80 "cell_type": "markdown",
80 "cell_type": "markdown",
81 "metadata": {},
81 "metadata": {},
82 "source": [
82 "source": [
83 "When starting a project like this, it is often easiest to make a simple base implementation,\n",
83 "When starting a project like this, it is often easiest to make a simple base implementation,\n",
84 "to verify that the underlying framework is working as expected.\n",
84 "to verify that the underlying framework is working as expected.\n",
85 "To start, we will create an empty widget and make sure that it can be rendered.\n",
85 "To start, we will create an empty widget and make sure that it can be rendered.\n",
86 "The first step is to define the widget in Python."
86 "The first step is to define the widget in Python."
87 ]
87 ]
88 },
88 },
89 {
89 {
90 "cell_type": "code",
90 "cell_type": "code",
91 "collapsed": false,
91 "collapsed": false,
92 "input": [
92 "input": [
93 "class DateWidget(widgets.DOMWidget):\n",
93 "class DateWidget(widgets.DOMWidget):\n",
94 " _view_name = Unicode('DatePickerView', sync=True)"
94 " _view_name = Unicode('DatePickerView', sync=True)"
95 ],
95 ],
96 "language": "python",
96 "language": "python",
97 "metadata": {},
97 "metadata": {},
98 "outputs": [],
98 "outputs": [],
99 "prompt_number": 2
99 "prompt_number": 2
100 },
100 },
101 {
101 {
102 "cell_type": "markdown",
102 "cell_type": "markdown",
103 "metadata": {},
103 "metadata": {},
104 "source": [
104 "source": [
105 "Our widget inherits from `widgets.DOMWidget` since it is intended that it will be displayed in the notebook directly.\n",
105 "Our widget inherits from `widgets.DOMWidget` since it is intended that it will be displayed in the notebook directly.\n",
106 "The `_view_name` trait is special; the widget framework will read the `_view_name` trait to determine what Backbone view the widget is associated with.\n",
106 "The `_view_name` trait is special; the widget framework will read the `_view_name` trait to determine what Backbone view the widget is associated with.\n",
107 "**Using `sync=True` is very important** because it tells the widget framework that that specific traitlet should be synced between the front- and back-ends."
107 "**Using `sync=True` is very important** because it tells the widget framework that that specific traitlet should be synced between the front- and back-ends."
108 ]
108 ]
109 },
109 },
110 {
110 {
111 "cell_type": "heading",
111 "cell_type": "heading",
112 "level": 2,
112 "level": 2,
113 "metadata": {},
113 "metadata": {},
114 "source": [
114 "source": [
115 "JavaScript"
115 "JavaScript"
116 ]
116 ]
117 },
117 },
118 {
118 {
119 "cell_type": "markdown",
119 "cell_type": "markdown",
120 "metadata": {},
120 "metadata": {},
121 "source": [
121 "source": [
122 "In the IPython notebook [require.js](http://requirejs.org/) is used to load JavaScript dependencies.\n",
122 "In the IPython notebook [require.js](http://requirejs.org/) is used to load JavaScript dependencies.\n",
123 "All IPython widget code depends on `notebook/js/widgets/widget.js`,\n",
123 "All IPython widget code depends on `widgets/js/widget.js`,\n",
124 "where the base widget model and base view are defined.\n",
124 "where the base widget model and base view are defined.\n",
125 "We use require.js to load this file:"
125 "We use require.js to load this file:"
126 ]
126 ]
127 },
127 },
128 {
128 {
129 "cell_type": "code",
129 "cell_type": "code",
130 "collapsed": false,
130 "collapsed": false,
131 "input": [
131 "input": [
132 "%%javascript\n",
132 "%%javascript\n",
133 "\n",
133 "\n",
134 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
134 "require([\"widgets/js/widget\"], function(WidgetManager){\n",
135 "\n",
135 "\n",
136 "});"
136 "});"
137 ],
137 ],
138 "language": "python",
138 "language": "python",
139 "metadata": {},
139 "metadata": {},
140 "outputs": [
140 "outputs": [
141 {
141 {
142 "javascript": [
142 "javascript": [
143 "\n",
143 "\n",
144 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
144 "require([\"widgets/js/widget\"], function(WidgetManager){\n",
145 "\n",
145 "\n",
146 "});"
146 "});"
147 ],
147 ],
148 "metadata": {},
148 "metadata": {},
149 "output_type": "display_data",
149 "output_type": "display_data",
150 "text": [
150 "text": [
151 "<IPython.core.display.Javascript at 0x109491690>"
151 "<IPython.core.display.Javascript at 0x109491690>"
152 ]
152 ]
153 }
153 }
154 ],
154 ],
155 "prompt_number": 3
155 "prompt_number": 3
156 },
156 },
157 {
157 {
158 "cell_type": "markdown",
158 "cell_type": "markdown",
159 "metadata": {},
159 "metadata": {},
160 "source": [
160 "source": [
161 "Now we need to define a view that can be used to represent the model.\n",
161 "Now we need to define a view that can be used to represent the model.\n",
162 "To do this, the `IPython.DOMWidgetView` is extended.\n",
162 "To do this, the `IPython.DOMWidgetView` is extended.\n",
163 "**A render function must be defined**.\n",
163 "**A render function must be defined**.\n",
164 "The render function is used to render a widget view instance to the DOM.\n",
164 "The render function is used to render a widget view instance to the DOM.\n",
165 "For now, the render function renders a div that contains the text *Hello World!*\n",
165 "For now, the render function renders a div that contains the text *Hello World!*\n",
166 "Lastly, the view needs to be registered with the widget manager.\n",
166 "Lastly, the view needs to be registered with the widget manager.\n",
167 "\n",
167 "\n",
168 "**Final JavaScript code below:**"
168 "**Final JavaScript code below:**"
169 ]
169 ]
170 },
170 },
171 {
171 {
172 "cell_type": "code",
172 "cell_type": "code",
173 "collapsed": false,
173 "collapsed": false,
174 "input": [
174 "input": [
175 "%%javascript\n",
175 "%%javascript\n",
176 "\n",
176 "\n",
177 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
177 "require([\"widgets/js/widget\"], function(WidgetManager){\n",
178 " \n",
178 " \n",
179 " // Define the DatePickerView\n",
179 " // Define the DatePickerView\n",
180 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
180 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
181 " render: function(){ this.$el.text('Hello World!'); },\n",
181 " render: function(){ this.$el.text('Hello World!'); },\n",
182 " });\n",
182 " });\n",
183 " \n",
183 " \n",
184 " // Register the DatePickerView with the widget manager.\n",
184 " // Register the DatePickerView with the widget manager.\n",
185 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
185 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
186 "});"
186 "});"
187 ],
187 ],
188 "language": "python",
188 "language": "python",
189 "metadata": {},
189 "metadata": {},
190 "outputs": [
190 "outputs": [
191 {
191 {
192 "javascript": [
192 "javascript": [
193 "\n",
193 "\n",
194 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
194 "require([\"widgets/js/widget\"], function(WidgetManager){\n",
195 " \n",
195 " \n",
196 " // Define the DatePickerView\n",
196 " // Define the DatePickerView\n",
197 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
197 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
198 " render: function(){ this.$el.text('Hello World!'); },\n",
198 " render: function(){ this.$el.text('Hello World!'); },\n",
199 " });\n",
199 " });\n",
200 " \n",
200 " \n",
201 " // Register the DatePickerView with the widget manager.\n",
201 " // Register the DatePickerView with the widget manager.\n",
202 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
202 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
203 "});"
203 "});"
204 ],
204 ],
205 "metadata": {},
205 "metadata": {},
206 "output_type": "display_data",
206 "output_type": "display_data",
207 "text": [
207 "text": [
208 "<IPython.core.display.Javascript at 0x1094917d0>"
208 "<IPython.core.display.Javascript at 0x1094917d0>"
209 ]
209 ]
210 }
210 }
211 ],
211 ],
212 "prompt_number": 4
212 "prompt_number": 4
213 },
213 },
214 {
214 {
215 "cell_type": "heading",
215 "cell_type": "heading",
216 "level": 2,
216 "level": 2,
217 "metadata": {},
217 "metadata": {},
218 "source": [
218 "source": [
219 "Test"
219 "Test"
220 ]
220 ]
221 },
221 },
222 {
222 {
223 "cell_type": "markdown",
223 "cell_type": "markdown",
224 "metadata": {},
224 "metadata": {},
225 "source": [
225 "source": [
226 "To test what we have so far, create the widget, just like you would the builtin widgets:"
226 "To test what we have so far, create the widget, just like you would the builtin widgets:"
227 ]
227 ]
228 },
228 },
229 {
229 {
230 "cell_type": "code",
230 "cell_type": "code",
231 "collapsed": false,
231 "collapsed": false,
232 "input": [
232 "input": [
233 "DateWidget()"
233 "DateWidget()"
234 ],
234 ],
235 "language": "python",
235 "language": "python",
236 "metadata": {},
236 "metadata": {},
237 "outputs": [],
237 "outputs": [],
238 "prompt_number": 5
238 "prompt_number": 5
239 },
239 },
240 {
240 {
241 "cell_type": "heading",
241 "cell_type": "heading",
242 "level": 1,
242 "level": 1,
243 "metadata": {},
243 "metadata": {},
244 "source": [
244 "source": [
245 "Section 2 - Something useful"
245 "Section 2 - Something useful"
246 ]
246 ]
247 },
247 },
248 {
248 {
249 "cell_type": "heading",
249 "cell_type": "heading",
250 "level": 2,
250 "level": 2,
251 "metadata": {},
251 "metadata": {},
252 "source": [
252 "source": [
253 "Python"
253 "Python"
254 ]
254 ]
255 },
255 },
256 {
256 {
257 "cell_type": "markdown",
257 "cell_type": "markdown",
258 "metadata": {},
258 "metadata": {},
259 "source": [
259 "source": [
260 "In the last section we created a simple widget that displayed *Hello World!*\n",
260 "In the last section we created a simple widget that displayed *Hello World!*\n",
261 "To make an actual date widget, we need to add a property that will be synced between the Python model and the JavaScript model.\n",
261 "To make an actual date widget, we need to add a property that will be synced between the Python model and the JavaScript model.\n",
262 "The new attribute must be a traitlet, so the widget machinery can handle it.\n",
262 "The new attribute must be a traitlet, so the widget machinery can handle it.\n",
263 "The traitlet must be constructed with a `sync=True` keyword argument, to tell the widget machinery knows to synchronize it with the front-end.\n",
263 "The traitlet must be constructed with a `sync=True` keyword argument, to tell the widget machinery knows to synchronize it with the front-end.\n",
264 "Adding this to the code from the last section:"
264 "Adding this to the code from the last section:"
265 ]
265 ]
266 },
266 },
267 {
267 {
268 "cell_type": "code",
268 "cell_type": "code",
269 "collapsed": false,
269 "collapsed": false,
270 "input": [
270 "input": [
271 "class DateWidget(widgets.DOMWidget):\n",
271 "class DateWidget(widgets.DOMWidget):\n",
272 " _view_name = Unicode('DatePickerView', sync=True)\n",
272 " _view_name = Unicode('DatePickerView', sync=True)\n",
273 " value = Unicode(sync=True)"
273 " value = Unicode(sync=True)"
274 ],
274 ],
275 "language": "python",
275 "language": "python",
276 "metadata": {},
276 "metadata": {},
277 "outputs": [],
277 "outputs": [],
278 "prompt_number": 6
278 "prompt_number": 6
279 },
279 },
280 {
280 {
281 "cell_type": "heading",
281 "cell_type": "heading",
282 "level": 2,
282 "level": 2,
283 "metadata": {},
283 "metadata": {},
284 "source": [
284 "source": [
285 "JavaScript"
285 "JavaScript"
286 ]
286 ]
287 },
287 },
288 {
288 {
289 "cell_type": "markdown",
289 "cell_type": "markdown",
290 "metadata": {},
290 "metadata": {},
291 "source": [
291 "source": [
292 "In the JavaScript, there is no need to define counterparts to the traitlets.\n",
292 "In the JavaScript, there is no need to define counterparts to the traitlets.\n",
293 "When the JavaScript model is created for the first time,\n",
293 "When the JavaScript model is created for the first time,\n",
294 "it copies all of the traitlet `sync=True` attributes from the Python model.\n",
294 "it copies all of the traitlet `sync=True` attributes from the Python model.\n",
295 "We need to replace *Hello World!* with an actual HTML date picker widget."
295 "We need to replace *Hello World!* with an actual HTML date picker widget."
296 ]
296 ]
297 },
297 },
298 {
298 {
299 "cell_type": "code",
299 "cell_type": "code",
300 "collapsed": false,
300 "collapsed": false,
301 "input": [
301 "input": [
302 "%%javascript\n",
302 "%%javascript\n",
303 "\n",
303 "\n",
304 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
304 "require([\"widgets/js/widget\"], function(WidgetManager){\n",
305 " \n",
305 " \n",
306 " // Define the DatePickerView\n",
306 " // Define the DatePickerView\n",
307 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
307 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
308 " render: function(){\n",
308 " render: function(){\n",
309 " \n",
309 " \n",
310 " // Create the date picker control.\n",
310 " // Create the date picker control.\n",
311 " this.$date = $('<input />')\n",
311 " this.$date = $('<input />')\n",
312 " .attr('type', 'date')\n",
312 " .attr('type', 'date')\n",
313 " .appendTo(this.$el);\n",
313 " .appendTo(this.$el);\n",
314 " },\n",
314 " },\n",
315 " });\n",
315 " });\n",
316 " \n",
316 " \n",
317 " // Register the DatePickerView with the widget manager.\n",
317 " // Register the DatePickerView with the widget manager.\n",
318 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
318 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
319 "});"
319 "});"
320 ],
320 ],
321 "language": "python",
321 "language": "python",
322 "metadata": {},
322 "metadata": {},
323 "outputs": [
323 "outputs": [
324 {
324 {
325 "javascript": [
325 "javascript": [
326 "\n",
326 "\n",
327 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
327 "require([\"widgets/js/widget\"], function(WidgetManager){\n",
328 " \n",
328 " \n",
329 " // Define the DatePickerView\n",
329 " // Define the DatePickerView\n",
330 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
330 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
331 " render: function(){\n",
331 " render: function(){\n",
332 " \n",
332 " \n",
333 " // Create the date picker control.\n",
333 " // Create the date picker control.\n",
334 " this.$date = $('<input />')\n",
334 " this.$date = $('<input />')\n",
335 " .attr('type', 'date')\n",
335 " .attr('type', 'date')\n",
336 " .appendTo(this.$el);\n",
336 " .appendTo(this.$el);\n",
337 " },\n",
337 " },\n",
338 " });\n",
338 " });\n",
339 " \n",
339 " \n",
340 " // Register the DatePickerView with the widget manager.\n",
340 " // Register the DatePickerView with the widget manager.\n",
341 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
341 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
342 "});"
342 "});"
343 ],
343 ],
344 "metadata": {},
344 "metadata": {},
345 "output_type": "display_data",
345 "output_type": "display_data",
346 "text": [
346 "text": [
347 "<IPython.core.display.Javascript at 0x109491750>"
347 "<IPython.core.display.Javascript at 0x109491750>"
348 ]
348 ]
349 }
349 }
350 ],
350 ],
351 "prompt_number": 7
351 "prompt_number": 7
352 },
352 },
353 {
353 {
354 "cell_type": "markdown",
354 "cell_type": "markdown",
355 "metadata": {},
355 "metadata": {},
356 "source": [
356 "source": [
357 "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."
357 "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."
358 ]
358 ]
359 },
359 },
360 {
360 {
361 "cell_type": "code",
361 "cell_type": "code",
362 "collapsed": false,
362 "collapsed": false,
363 "input": [
363 "input": [
364 "%%javascript\n",
364 "%%javascript\n",
365 "\n",
365 "\n",
366 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
366 "require([\"widgets/js/widget\"], function(WidgetManager){\n",
367 " \n",
367 " \n",
368 " // Define the DatePickerView\n",
368 " // Define the DatePickerView\n",
369 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
369 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
370 " render: function(){\n",
370 " render: function(){\n",
371 " \n",
371 " \n",
372 " // Create the date picker control.\n",
372 " // Create the date picker control.\n",
373 " this.$date = $('<input />')\n",
373 " this.$date = $('<input />')\n",
374 " .attr('type', 'date')\n",
374 " .attr('type', 'date')\n",
375 " .appendTo(this.$el);\n",
375 " .appendTo(this.$el);\n",
376 " },\n",
376 " },\n",
377 " \n",
377 " \n",
378 " update: function() {\n",
378 " update: function() {\n",
379 " \n",
379 " \n",
380 " // Set the value of the date control and then call base.\n",
380 " // Set the value of the date control and then call base.\n",
381 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
381 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
382 " return DatePickerView.__super__.update.apply(this);\n",
382 " return DatePickerView.__super__.update.apply(this);\n",
383 " },\n",
383 " },\n",
384 " });\n",
384 " });\n",
385 " \n",
385 " \n",
386 " // Register the DatePickerView with the widget manager.\n",
386 " // Register the DatePickerView with the widget manager.\n",
387 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
387 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
388 "});"
388 "});"
389 ],
389 ],
390 "language": "python",
390 "language": "python",
391 "metadata": {},
391 "metadata": {},
392 "outputs": [
392 "outputs": [
393 {
393 {
394 "javascript": [
394 "javascript": [
395 "\n",
395 "\n",
396 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
396 "require([\"widgets/js/widget\"], function(WidgetManager){\n",
397 " \n",
397 " \n",
398 " // Define the DatePickerView\n",
398 " // Define the DatePickerView\n",
399 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
399 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
400 " render: function(){\n",
400 " render: function(){\n",
401 " \n",
401 " \n",
402 " // Create the date picker control.\n",
402 " // Create the date picker control.\n",
403 " this.$date = $('<input />')\n",
403 " this.$date = $('<input />')\n",
404 " .attr('type', 'date')\n",
404 " .attr('type', 'date')\n",
405 " .appendTo(this.$el);\n",
405 " .appendTo(this.$el);\n",
406 " },\n",
406 " },\n",
407 " \n",
407 " \n",
408 " update: function() {\n",
408 " update: function() {\n",
409 " \n",
409 " \n",
410 " // Set the value of the date control and then call base.\n",
410 " // Set the value of the date control and then call base.\n",
411 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
411 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
412 " return DatePickerView.__super__.update.apply(this);\n",
412 " return DatePickerView.__super__.update.apply(this);\n",
413 " },\n",
413 " },\n",
414 " });\n",
414 " });\n",
415 " \n",
415 " \n",
416 " // Register the DatePickerView with the widget manager.\n",
416 " // Register the DatePickerView with the widget manager.\n",
417 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
417 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
418 "});"
418 "});"
419 ],
419 ],
420 "metadata": {},
420 "metadata": {},
421 "output_type": "display_data",
421 "output_type": "display_data",
422 "text": [
422 "text": [
423 "<IPython.core.display.Javascript at 0x109491750>"
423 "<IPython.core.display.Javascript at 0x109491750>"
424 ]
424 ]
425 }
425 }
426 ],
426 ],
427 "prompt_number": 8
427 "prompt_number": 8
428 },
428 },
429 {
429 {
430 "cell_type": "markdown",
430 "cell_type": "markdown",
431 "metadata": {},
431 "metadata": {},
432 "source": [
432 "source": [
433 "To get the changed value from the frontend to publish itself to the backend,\n",
433 "To get the changed value from the frontend to publish itself to the backend,\n",
434 "we need to listen to the change event triggered by the HTM date control and set the value in the model.\n",
434 "we need to listen to the change event triggered by the HTM date control and set the value in the model.\n",
435 "After the date change event fires and the new value is set in the model,\n",
435 "After the date change event fires and the new value is set in the model,\n",
436 "it is very important that we call `this.touch()` to let the widget machinery know which view changed the model.\n",
436 "it is very important that we call `this.touch()` to let the widget machinery know which view changed the model.\n",
437 "This is important because the widget machinery needs to know which cell to route the message callbacks to.\n",
437 "This is important because the widget machinery needs to know which cell to route the message callbacks to.\n",
438 "\n",
438 "\n",
439 "**Final JavaScript code below:**"
439 "**Final JavaScript code below:**"
440 ]
440 ]
441 },
441 },
442 {
442 {
443 "cell_type": "code",
443 "cell_type": "code",
444 "collapsed": false,
444 "collapsed": false,
445 "input": [
445 "input": [
446 "%%javascript\n",
446 "%%javascript\n",
447 "\n",
447 "\n",
448 "\n",
448 "\n",
449 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
449 "require([\"widgets/js/widget\"], function(WidgetManager){\n",
450 " \n",
450 " \n",
451 " // Define the DatePickerView\n",
451 " // Define the DatePickerView\n",
452 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
452 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
453 " render: function(){\n",
453 " render: function(){\n",
454 " \n",
454 " \n",
455 " // Create the date picker control.\n",
455 " // Create the date picker control.\n",
456 " this.$date = $('<input />')\n",
456 " this.$date = $('<input />')\n",
457 " .attr('type', 'date')\n",
457 " .attr('type', 'date')\n",
458 " .appendTo(this.$el);\n",
458 " .appendTo(this.$el);\n",
459 " },\n",
459 " },\n",
460 " \n",
460 " \n",
461 " update: function() {\n",
461 " update: function() {\n",
462 " \n",
462 " \n",
463 " // Set the value of the date control and then call base.\n",
463 " // Set the value of the date control and then call base.\n",
464 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
464 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
465 " return DatePickerView.__super__.update.apply(this);\n",
465 " return DatePickerView.__super__.update.apply(this);\n",
466 " },\n",
466 " },\n",
467 " \n",
467 " \n",
468 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
468 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
469 " events: {\"change\": \"handle_date_change\"},\n",
469 " events: {\"change\": \"handle_date_change\"},\n",
470 " \n",
470 " \n",
471 " // Callback for when the date is changed.\n",
471 " // Callback for when the date is changed.\n",
472 " handle_date_change: function(event) {\n",
472 " handle_date_change: function(event) {\n",
473 " this.model.set('value', this.$date.val());\n",
473 " this.model.set('value', this.$date.val());\n",
474 " this.touch();\n",
474 " this.touch();\n",
475 " },\n",
475 " },\n",
476 " });\n",
476 " });\n",
477 " \n",
477 " \n",
478 " // Register the DatePickerView with the widget manager.\n",
478 " // Register the DatePickerView with the widget manager.\n",
479 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
479 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
480 "});"
480 "});"
481 ],
481 ],
482 "language": "python",
482 "language": "python",
483 "metadata": {},
483 "metadata": {},
484 "outputs": [
484 "outputs": [
485 {
485 {
486 "javascript": [
486 "javascript": [
487 "\n",
487 "\n",
488 "\n",
488 "\n",
489 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
489 "require([\"widgets/js/widget\"], function(WidgetManager){\n",
490 " \n",
490 " \n",
491 " // Define the DatePickerView\n",
491 " // Define the DatePickerView\n",
492 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
492 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
493 " render: function(){\n",
493 " render: function(){\n",
494 " \n",
494 " \n",
495 " // Create the date picker control.\n",
495 " // Create the date picker control.\n",
496 " this.$date = $('<input />')\n",
496 " this.$date = $('<input />')\n",
497 " .attr('type', 'date')\n",
497 " .attr('type', 'date')\n",
498 " .appendTo(this.$el);\n",
498 " .appendTo(this.$el);\n",
499 " },\n",
499 " },\n",
500 " \n",
500 " \n",
501 " update: function() {\n",
501 " update: function() {\n",
502 " \n",
502 " \n",
503 " // Set the value of the date control and then call base.\n",
503 " // Set the value of the date control and then call base.\n",
504 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
504 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
505 " return DatePickerView.__super__.update.apply(this);\n",
505 " return DatePickerView.__super__.update.apply(this);\n",
506 " },\n",
506 " },\n",
507 " \n",
507 " \n",
508 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
508 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
509 " events: {\"change\": \"handle_date_change\"},\n",
509 " events: {\"change\": \"handle_date_change\"},\n",
510 " \n",
510 " \n",
511 " // Callback for when the date is changed.\n",
511 " // Callback for when the date is changed.\n",
512 " handle_date_change: function(event) {\n",
512 " handle_date_change: function(event) {\n",
513 " this.model.set('value', this.$date.val());\n",
513 " this.model.set('value', this.$date.val());\n",
514 " this.touch();\n",
514 " this.touch();\n",
515 " },\n",
515 " },\n",
516 " });\n",
516 " });\n",
517 " \n",
517 " \n",
518 " // Register the DatePickerView with the widget manager.\n",
518 " // Register the DatePickerView with the widget manager.\n",
519 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
519 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
520 "});"
520 "});"
521 ],
521 ],
522 "metadata": {},
522 "metadata": {},
523 "output_type": "display_data",
523 "output_type": "display_data",
524 "text": [
524 "text": [
525 "<IPython.core.display.Javascript at 0x109491b10>"
525 "<IPython.core.display.Javascript at 0x109491b10>"
526 ]
526 ]
527 }
527 }
528 ],
528 ],
529 "prompt_number": 9
529 "prompt_number": 9
530 },
530 },
531 {
531 {
532 "cell_type": "heading",
532 "cell_type": "heading",
533 "level": 2,
533 "level": 2,
534 "metadata": {},
534 "metadata": {},
535 "source": [
535 "source": [
536 "Test"
536 "Test"
537 ]
537 ]
538 },
538 },
539 {
539 {
540 "cell_type": "markdown",
540 "cell_type": "markdown",
541 "metadata": {},
541 "metadata": {},
542 "source": [
542 "source": [
543 "To test, create the widget the same way that the other widgets are created."
543 "To test, create the widget the same way that the other widgets are created."
544 ]
544 ]
545 },
545 },
546 {
546 {
547 "cell_type": "code",
547 "cell_type": "code",
548 "collapsed": false,
548 "collapsed": false,
549 "input": [
549 "input": [
550 "my_widget = DateWidget()\n",
550 "my_widget = DateWidget()\n",
551 "display(my_widget)"
551 "display(my_widget)"
552 ],
552 ],
553 "language": "python",
553 "language": "python",
554 "metadata": {},
554 "metadata": {},
555 "outputs": [],
555 "outputs": [],
556 "prompt_number": 10
556 "prompt_number": 10
557 },
557 },
558 {
558 {
559 "cell_type": "markdown",
559 "cell_type": "markdown",
560 "metadata": {},
560 "metadata": {},
561 "source": [
561 "source": [
562 "Display the widget again to make sure that both views remain in sync."
562 "Display the widget again to make sure that both views remain in sync."
563 ]
563 ]
564 },
564 },
565 {
565 {
566 "cell_type": "code",
566 "cell_type": "code",
567 "collapsed": false,
567 "collapsed": false,
568 "input": [
568 "input": [
569 "my_widget"
569 "my_widget"
570 ],
570 ],
571 "language": "python",
571 "language": "python",
572 "metadata": {},
572 "metadata": {},
573 "outputs": [],
573 "outputs": [],
574 "prompt_number": 11
574 "prompt_number": 11
575 },
575 },
576 {
576 {
577 "cell_type": "markdown",
577 "cell_type": "markdown",
578 "metadata": {},
578 "metadata": {},
579 "source": [
579 "source": [
580 "Read the date from Python"
580 "Read the date from Python"
581 ]
581 ]
582 },
582 },
583 {
583 {
584 "cell_type": "code",
584 "cell_type": "code",
585 "collapsed": false,
585 "collapsed": false,
586 "input": [
586 "input": [
587 "my_widget.value"
587 "my_widget.value"
588 ],
588 ],
589 "language": "python",
589 "language": "python",
590 "metadata": {},
590 "metadata": {},
591 "outputs": [
591 "outputs": [
592 {
592 {
593 "metadata": {},
593 "metadata": {},
594 "output_type": "pyout",
594 "output_type": "pyout",
595 "prompt_number": 12,
595 "prompt_number": 12,
596 "text": [
596 "text": [
597 "u''"
597 "u''"
598 ]
598 ]
599 }
599 }
600 ],
600 ],
601 "prompt_number": 12
601 "prompt_number": 12
602 },
602 },
603 {
603 {
604 "cell_type": "markdown",
604 "cell_type": "markdown",
605 "metadata": {},
605 "metadata": {},
606 "source": [
606 "source": [
607 "Set the date from Python"
607 "Set the date from Python"
608 ]
608 ]
609 },
609 },
610 {
610 {
611 "cell_type": "code",
611 "cell_type": "code",
612 "collapsed": false,
612 "collapsed": false,
613 "input": [
613 "input": [
614 "my_widget.value = \"1998-12-01\" # December 1st, 1998"
614 "my_widget.value = \"1998-12-01\" # December 1st, 1998"
615 ],
615 ],
616 "language": "python",
616 "language": "python",
617 "metadata": {},
617 "metadata": {},
618 "outputs": [],
618 "outputs": [],
619 "prompt_number": 13
619 "prompt_number": 13
620 },
620 },
621 {
621 {
622 "cell_type": "heading",
622 "cell_type": "heading",
623 "level": 1,
623 "level": 1,
624 "metadata": {},
624 "metadata": {},
625 "source": [
625 "source": [
626 "Section 3 - Extra credit"
626 "Section 3 - Extra credit"
627 ]
627 ]
628 },
628 },
629 {
629 {
630 "cell_type": "markdown",
630 "cell_type": "markdown",
631 "metadata": {},
631 "metadata": {},
632 "source": [
632 "source": [
633 "The 3rd party `dateutil` library is required to continue. https://pypi.python.org/pypi/python-dateutil"
633 "The 3rd party `dateutil` library is required to continue. https://pypi.python.org/pypi/python-dateutil"
634 ]
634 ]
635 },
635 },
636 {
636 {
637 "cell_type": "code",
637 "cell_type": "code",
638 "collapsed": false,
638 "collapsed": false,
639 "input": [
639 "input": [
640 "# Import the dateutil library to parse date strings.\n",
640 "# Import the dateutil library to parse date strings.\n",
641 "from dateutil import parser"
641 "from dateutil import parser"
642 ],
642 ],
643 "language": "python",
643 "language": "python",
644 "metadata": {},
644 "metadata": {},
645 "outputs": [],
645 "outputs": [],
646 "prompt_number": 14
646 "prompt_number": 14
647 },
647 },
648 {
648 {
649 "cell_type": "markdown",
649 "cell_type": "markdown",
650 "metadata": {},
650 "metadata": {},
651 "source": [
651 "source": [
652 "In the last section we created a fully working date picker widget.\n",
652 "In the last section we created a fully working date picker widget.\n",
653 "Now we will add custom validation and support for labels.\n",
653 "Now we will add custom validation and support for labels.\n",
654 "So far, only the ISO date format \"YYYY-MM-DD\" is supported.\n",
654 "So far, only the ISO date format \"YYYY-MM-DD\" is supported.\n",
655 "Now, we will add support for all of the date formats recognized by the Python dateutil library."
655 "Now, we will add support for all of the date formats recognized by the Python dateutil library."
656 ]
656 ]
657 },
657 },
658 {
658 {
659 "cell_type": "heading",
659 "cell_type": "heading",
660 "level": 2,
660 "level": 2,
661 "metadata": {},
661 "metadata": {},
662 "source": [
662 "source": [
663 "Python"
663 "Python"
664 ]
664 ]
665 },
665 },
666 {
666 {
667 "cell_type": "markdown",
667 "cell_type": "markdown",
668 "metadata": {},
668 "metadata": {},
669 "source": [
669 "source": [
670 "The standard property name used for widget labels is `description`.\n",
670 "The standard property name used for widget labels is `description`.\n",
671 "In the code block below, `description` has been added to the Python widget."
671 "In the code block below, `description` has been added to the Python widget."
672 ]
672 ]
673 },
673 },
674 {
674 {
675 "cell_type": "code",
675 "cell_type": "code",
676 "collapsed": false,
676 "collapsed": false,
677 "input": [
677 "input": [
678 "class DateWidget(widgets.DOMWidget):\n",
678 "class DateWidget(widgets.DOMWidget):\n",
679 " _view_name = Unicode('DatePickerView', sync=True)\n",
679 " _view_name = Unicode('DatePickerView', sync=True)\n",
680 " value = Unicode(sync=True)\n",
680 " value = Unicode(sync=True)\n",
681 " description = Unicode(sync=True)"
681 " description = Unicode(sync=True)"
682 ],
682 ],
683 "language": "python",
683 "language": "python",
684 "metadata": {},
684 "metadata": {},
685 "outputs": [],
685 "outputs": [],
686 "prompt_number": 15
686 "prompt_number": 15
687 },
687 },
688 {
688 {
689 "cell_type": "markdown",
689 "cell_type": "markdown",
690 "metadata": {},
690 "metadata": {},
691 "source": [
691 "source": [
692 "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.\n",
692 "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.\n",
693 "We can take advantage of this to perform validation and parsing of different date string formats.\n",
693 "We can take advantage of this to perform validation and parsing of different date string formats.\n",
694 "Below, a method that listens to value has been added to the DateWidget."
694 "Below, a method that listens to value has been added to the DateWidget."
695 ]
695 ]
696 },
696 },
697 {
697 {
698 "cell_type": "code",
698 "cell_type": "code",
699 "collapsed": false,
699 "collapsed": false,
700 "input": [
700 "input": [
701 "class DateWidget(widgets.DOMWidget):\n",
701 "class DateWidget(widgets.DOMWidget):\n",
702 " _view_name = Unicode('DatePickerView', sync=True)\n",
702 " _view_name = Unicode('DatePickerView', sync=True)\n",
703 " value = Unicode(sync=True)\n",
703 " value = Unicode(sync=True)\n",
704 " description = Unicode(sync=True)\n",
704 " description = Unicode(sync=True)\n",
705 "\n",
705 "\n",
706 " # This function automatically gets called by the traitlet machinery when\n",
706 " # This function automatically gets called by the traitlet machinery when\n",
707 " # value is modified because of this function's name.\n",
707 " # value is modified because of this function's name.\n",
708 " def _value_changed(self, name, old_value, new_value):\n",
708 " def _value_changed(self, name, old_value, new_value):\n",
709 " pass"
709 " pass"
710 ],
710 ],
711 "language": "python",
711 "language": "python",
712 "metadata": {},
712 "metadata": {},
713 "outputs": [],
713 "outputs": [],
714 "prompt_number": 16
714 "prompt_number": 16
715 },
715 },
716 {
716 {
717 "cell_type": "markdown",
717 "cell_type": "markdown",
718 "metadata": {},
718 "metadata": {},
719 "source": [
719 "source": [
720 "Now the function parses the date string,\n",
720 "Now the function parses the date string,\n",
721 "and only sets the value in the correct format."
721 "and only sets the value in the correct format."
722 ]
722 ]
723 },
723 },
724 {
724 {
725 "cell_type": "code",
725 "cell_type": "code",
726 "collapsed": false,
726 "collapsed": false,
727 "input": [
727 "input": [
728 "class DateWidget(widgets.DOMWidget):\n",
728 "class DateWidget(widgets.DOMWidget):\n",
729 " _view_name = Unicode('DatePickerView', sync=True)\n",
729 " _view_name = Unicode('DatePickerView', sync=True)\n",
730 " value = Unicode(sync=True)\n",
730 " value = Unicode(sync=True)\n",
731 " description = Unicode(sync=True)\n",
731 " description = Unicode(sync=True)\n",
732 " \n",
732 " \n",
733 " # This function automatically gets called by the traitlet machinery when\n",
733 " # This function automatically gets called by the traitlet machinery when\n",
734 " # value is modified because of this function's name.\n",
734 " # value is modified because of this function's name.\n",
735 " def _value_changed(self, name, old_value, new_value):\n",
735 " def _value_changed(self, name, old_value, new_value):\n",
736 " \n",
736 " \n",
737 " # Parse the date time value.\n",
737 " # Parse the date time value.\n",
738 " try:\n",
738 " try:\n",
739 " parsed_date = parser.parse(new_value)\n",
739 " parsed_date = parser.parse(new_value)\n",
740 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
740 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
741 " except:\n",
741 " except:\n",
742 " parsed_date_string = ''\n",
742 " parsed_date_string = ''\n",
743 " \n",
743 " \n",
744 " # Set the parsed date string if the current date string is different.\n",
744 " # Set the parsed date string if the current date string is different.\n",
745 " if self.value != parsed_date_string:\n",
745 " if self.value != parsed_date_string:\n",
746 " self.value = parsed_date_string"
746 " self.value = parsed_date_string"
747 ],
747 ],
748 "language": "python",
748 "language": "python",
749 "metadata": {},
749 "metadata": {},
750 "outputs": [],
750 "outputs": [],
751 "prompt_number": 17
751 "prompt_number": 17
752 },
752 },
753 {
753 {
754 "cell_type": "markdown",
754 "cell_type": "markdown",
755 "metadata": {},
755 "metadata": {},
756 "source": [
756 "source": [
757 "Finally, a `CallbackDispatcher` is added so the user can perform custom validation.\n",
757 "Finally, a `CallbackDispatcher` is added so the user can perform custom validation.\n",
758 "If any one of the callbacks registered with the dispatcher returns False,\n",
758 "If any one of the callbacks registered with the dispatcher returns False,\n",
759 "the new date is not set.\n",
759 "the new date is not set.\n",
760 "\n",
760 "\n",
761 "**Final Python code below:**"
761 "**Final Python code below:**"
762 ]
762 ]
763 },
763 },
764 {
764 {
765 "cell_type": "code",
765 "cell_type": "code",
766 "collapsed": false,
766 "collapsed": false,
767 "input": [
767 "input": [
768 "class DateWidget(widgets.DOMWidget):\n",
768 "class DateWidget(widgets.DOMWidget):\n",
769 " _view_name = Unicode('DatePickerView', sync=True)\n",
769 " _view_name = Unicode('DatePickerView', sync=True)\n",
770 " value = Unicode(sync=True)\n",
770 " value = Unicode(sync=True)\n",
771 " description = Unicode(sync=True)\n",
771 " description = Unicode(sync=True)\n",
772 " \n",
772 " \n",
773 " def __init__(self, **kwargs):\n",
773 " def __init__(self, **kwargs):\n",
774 " super(DateWidget, self).__init__(**kwargs)\n",
774 " super(DateWidget, self).__init__(**kwargs)\n",
775 " \n",
775 " \n",
776 " self.validate = widgets.CallbackDispatcher()\n",
776 " self.validate = widgets.CallbackDispatcher()\n",
777 " \n",
777 " \n",
778 " # This function automatically gets called by the traitlet machinery when\n",
778 " # This function automatically gets called by the traitlet machinery when\n",
779 " # value is modified because of this function's name.\n",
779 " # value is modified because of this function's name.\n",
780 " def _value_changed(self, name, old_value, new_value):\n",
780 " def _value_changed(self, name, old_value, new_value):\n",
781 " \n",
781 " \n",
782 " # Parse the date time value.\n",
782 " # Parse the date time value.\n",
783 " try:\n",
783 " try:\n",
784 " parsed_date = parser.parse(new_value)\n",
784 " parsed_date = parser.parse(new_value)\n",
785 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
785 " parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n",
786 " except:\n",
786 " except:\n",
787 " parsed_date_string = ''\n",
787 " parsed_date_string = ''\n",
788 " \n",
788 " \n",
789 " # Set the parsed date string if the current date string is different.\n",
789 " # Set the parsed date string if the current date string is different.\n",
790 " if old_value != new_value:\n",
790 " if old_value != new_value:\n",
791 " valid = self.validate(parsed_date)\n",
791 " valid = self.validate(parsed_date)\n",
792 " if valid in (None, True):\n",
792 " if valid in (None, True):\n",
793 " self.value = parsed_date_string\n",
793 " self.value = parsed_date_string\n",
794 " else:\n",
794 " else:\n",
795 " self.value = old_value\n",
795 " self.value = old_value\n",
796 " self.send_state() # The traitlet event won't fire since the value isn't changing.\n",
796 " self.send_state() # The traitlet event won't fire since the value isn't changing.\n",
797 " # We need to force the back-end to send the front-end the state\n",
797 " # We need to force the back-end to send the front-end the state\n",
798 " # to make sure that the date control date doesn't change."
798 " # to make sure that the date control date doesn't change."
799 ],
799 ],
800 "language": "python",
800 "language": "python",
801 "metadata": {},
801 "metadata": {},
802 "outputs": [],
802 "outputs": [],
803 "prompt_number": 18
803 "prompt_number": 18
804 },
804 },
805 {
805 {
806 "cell_type": "heading",
806 "cell_type": "heading",
807 "level": 2,
807 "level": 2,
808 "metadata": {},
808 "metadata": {},
809 "source": [
809 "source": [
810 "JavaScript"
810 "JavaScript"
811 ]
811 ]
812 },
812 },
813 {
813 {
814 "cell_type": "markdown",
814 "cell_type": "markdown",
815 "metadata": {},
815 "metadata": {},
816 "source": [
816 "source": [
817 "Using the Javascript code from the last section,\n",
817 "Using the Javascript code from the last section,\n",
818 "we add a label to the date time object.\n",
818 "we add a label to the date time object.\n",
819 "The label is a div with the `widget-hlabel` class applied to it.\n",
819 "The label is a div with the `widget-hlabel` class applied to it.\n",
820 "`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.\n",
820 "`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.\n",
821 "Similar to the `widget-hlabel` class is the `widget-hbox-single` class.\n",
821 "Similar to the `widget-hlabel` class is the `widget-hbox-single` class.\n",
822 "The `widget-hbox-single` class applies special styling to widget containers that store a single line horizontal widget.\n",
822 "The `widget-hbox-single` class applies special styling to widget containers that store a single line horizontal widget.\n",
823 "\n",
823 "\n",
824 "We hide the label if the description value is blank."
824 "We hide the label if the description value is blank."
825 ]
825 ]
826 },
826 },
827 {
827 {
828 "cell_type": "code",
828 "cell_type": "code",
829 "collapsed": false,
829 "collapsed": false,
830 "input": [
830 "input": [
831 "%%javascript\n",
831 "%%javascript\n",
832 "\n",
832 "\n",
833 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
833 "require([\"widgets/js/widget\"], function(WidgetManager){\n",
834 " \n",
834 " \n",
835 " // Define the DatePickerView\n",
835 " // Define the DatePickerView\n",
836 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
836 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
837 " render: function(){\n",
837 " render: function(){\n",
838 " this.$el.addClass('widget-hbox-single'); /* Apply this class to the widget container to make\n",
838 " this.$el.addClass('widget-hbox-single'); /* Apply this class to the widget container to make\n",
839 " it fit with the other built in widgets.*/\n",
839 " it fit with the other built in widgets.*/\n",
840 " // Create a label.\n",
840 " // Create a label.\n",
841 " this.$label = $('<div />')\n",
841 " this.$label = $('<div />')\n",
842 " .addClass('widget-hlabel')\n",
842 " .addClass('widget-hlabel')\n",
843 " .appendTo(this.$el)\n",
843 " .appendTo(this.$el)\n",
844 " .hide(); // Hide the label by default.\n",
844 " .hide(); // Hide the label by default.\n",
845 " \n",
845 " \n",
846 " // Create the date picker control.\n",
846 " // Create the date picker control.\n",
847 " this.$date = $('<input />')\n",
847 " this.$date = $('<input />')\n",
848 " .attr('type', 'date')\n",
848 " .attr('type', 'date')\n",
849 " .appendTo(this.$el);\n",
849 " .appendTo(this.$el);\n",
850 " },\n",
850 " },\n",
851 " \n",
851 " \n",
852 " update: function() {\n",
852 " update: function() {\n",
853 " \n",
853 " \n",
854 " // Set the value of the date control and then call base.\n",
854 " // Set the value of the date control and then call base.\n",
855 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
855 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
856 " \n",
856 " \n",
857 " // Hide or show the label depending on the existance of a description.\n",
857 " // Hide or show the label depending on the existance of a description.\n",
858 " var description = this.model.get('description');\n",
858 " var description = this.model.get('description');\n",
859 " if (description == undefined || description == '') {\n",
859 " if (description == undefined || description == '') {\n",
860 " this.$label.hide();\n",
860 " this.$label.hide();\n",
861 " } else {\n",
861 " } else {\n",
862 " this.$label.show();\n",
862 " this.$label.show();\n",
863 " this.$label.text(description);\n",
863 " this.$label.text(description);\n",
864 " }\n",
864 " }\n",
865 " \n",
865 " \n",
866 " return DatePickerView.__super__.update.apply(this);\n",
866 " return DatePickerView.__super__.update.apply(this);\n",
867 " },\n",
867 " },\n",
868 " \n",
868 " \n",
869 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
869 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
870 " events: {\"change\": \"handle_date_change\"},\n",
870 " events: {\"change\": \"handle_date_change\"},\n",
871 " \n",
871 " \n",
872 " // Callback for when the date is changed.\n",
872 " // Callback for when the date is changed.\n",
873 " handle_date_change: function(event) {\n",
873 " handle_date_change: function(event) {\n",
874 " this.model.set('value', this.$date.val());\n",
874 " this.model.set('value', this.$date.val());\n",
875 " this.touch();\n",
875 " this.touch();\n",
876 " },\n",
876 " },\n",
877 " });\n",
877 " });\n",
878 " \n",
878 " \n",
879 " // Register the DatePickerView with the widget manager.\n",
879 " // Register the DatePickerView with the widget manager.\n",
880 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
880 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
881 "});"
881 "});"
882 ],
882 ],
883 "language": "python",
883 "language": "python",
884 "metadata": {},
884 "metadata": {},
885 "outputs": [
885 "outputs": [
886 {
886 {
887 "javascript": [
887 "javascript": [
888 "\n",
888 "\n",
889 "require([\"notebook/js/widgets/widget\"], function(WidgetManager){\n",
889 "require([\"widgets/js/widget\"], function(WidgetManager){\n",
890 " \n",
890 " \n",
891 " // Define the DatePickerView\n",
891 " // Define the DatePickerView\n",
892 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
892 " var DatePickerView = IPython.DOMWidgetView.extend({\n",
893 " render: function(){\n",
893 " render: function(){\n",
894 " this.$el.addClass('widget-hbox-single'); /* Apply this class to the widget container to make\n",
894 " this.$el.addClass('widget-hbox-single'); /* Apply this class to the widget container to make\n",
895 " it fit with the other built in widgets.*/\n",
895 " it fit with the other built in widgets.*/\n",
896 " // Create a label.\n",
896 " // Create a label.\n",
897 " this.$label = $('<div />')\n",
897 " this.$label = $('<div />')\n",
898 " .addClass('widget-hlabel')\n",
898 " .addClass('widget-hlabel')\n",
899 " .appendTo(this.$el)\n",
899 " .appendTo(this.$el)\n",
900 " .hide(); // Hide the label by default.\n",
900 " .hide(); // Hide the label by default.\n",
901 " \n",
901 " \n",
902 " // Create the date picker control.\n",
902 " // Create the date picker control.\n",
903 " this.$date = $('<input />')\n",
903 " this.$date = $('<input />')\n",
904 " .attr('type', 'date')\n",
904 " .attr('type', 'date')\n",
905 " .appendTo(this.$el);\n",
905 " .appendTo(this.$el);\n",
906 " },\n",
906 " },\n",
907 " \n",
907 " \n",
908 " update: function() {\n",
908 " update: function() {\n",
909 " \n",
909 " \n",
910 " // Set the value of the date control and then call base.\n",
910 " // Set the value of the date control and then call base.\n",
911 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
911 " this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n",
912 " \n",
912 " \n",
913 " // Hide or show the label depending on the existance of a description.\n",
913 " // Hide or show the label depending on the existance of a description.\n",
914 " var description = this.model.get('description');\n",
914 " var description = this.model.get('description');\n",
915 " if (description == undefined || description == '') {\n",
915 " if (description == undefined || description == '') {\n",
916 " this.$label.hide();\n",
916 " this.$label.hide();\n",
917 " } else {\n",
917 " } else {\n",
918 " this.$label.show();\n",
918 " this.$label.show();\n",
919 " this.$label.text(description);\n",
919 " this.$label.text(description);\n",
920 " }\n",
920 " }\n",
921 " \n",
921 " \n",
922 " return DatePickerView.__super__.update.apply(this);\n",
922 " return DatePickerView.__super__.update.apply(this);\n",
923 " },\n",
923 " },\n",
924 " \n",
924 " \n",
925 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
925 " // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n",
926 " events: {\"change\": \"handle_date_change\"},\n",
926 " events: {\"change\": \"handle_date_change\"},\n",
927 " \n",
927 " \n",
928 " // Callback for when the date is changed.\n",
928 " // Callback for when the date is changed.\n",
929 " handle_date_change: function(event) {\n",
929 " handle_date_change: function(event) {\n",
930 " this.model.set('value', this.$date.val());\n",
930 " this.model.set('value', this.$date.val());\n",
931 " this.touch();\n",
931 " this.touch();\n",
932 " },\n",
932 " },\n",
933 " });\n",
933 " });\n",
934 " \n",
934 " \n",
935 " // Register the DatePickerView with the widget manager.\n",
935 " // Register the DatePickerView with the widget manager.\n",
936 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
936 " WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n",
937 "});"
937 "});"
938 ],
938 ],
939 "metadata": {},
939 "metadata": {},
940 "output_type": "display_data",
940 "output_type": "display_data",
941 "text": [
941 "text": [
942 "<IPython.core.display.Javascript at 0x1094eef90>"
942 "<IPython.core.display.Javascript at 0x1094eef90>"
943 ]
943 ]
944 }
944 }
945 ],
945 ],
946 "prompt_number": 19
946 "prompt_number": 19
947 },
947 },
948 {
948 {
949 "cell_type": "heading",
949 "cell_type": "heading",
950 "level": 2,
950 "level": 2,
951 "metadata": {},
951 "metadata": {},
952 "source": [
952 "source": [
953 "Test"
953 "Test"
954 ]
954 ]
955 },
955 },
956 {
956 {
957 "cell_type": "markdown",
957 "cell_type": "markdown",
958 "metadata": {},
958 "metadata": {},
959 "source": [
959 "source": [
960 "To test the drawing of the label we create the widget like normal but supply the additional description property a value."
960 "To test the drawing of the label we create the widget like normal but supply the additional description property a value."
961 ]
961 ]
962 },
962 },
963 {
963 {
964 "cell_type": "code",
964 "cell_type": "code",
965 "collapsed": false,
965 "collapsed": false,
966 "input": [
966 "input": [
967 "# Add some additional widgets for aesthetic purpose\n",
967 "# Add some additional widgets for aesthetic purpose\n",
968 "display(widgets.TextWidget(description=\"First:\"))\n",
968 "display(widgets.TextWidget(description=\"First:\"))\n",
969 "display(widgets.TextWidget(description=\"Last:\"))\n",
969 "display(widgets.TextWidget(description=\"Last:\"))\n",
970 "\n",
970 "\n",
971 "my_widget = DateWidget()\n",
971 "my_widget = DateWidget()\n",
972 "display(my_widget)\n",
972 "display(my_widget)\n",
973 "my_widget.description=\"DOB:\""
973 "my_widget.description=\"DOB:\""
974 ],
974 ],
975 "language": "python",
975 "language": "python",
976 "metadata": {},
976 "metadata": {},
977 "outputs": [],
977 "outputs": [],
978 "prompt_number": 20
978 "prompt_number": 20
979 },
979 },
980 {
980 {
981 "cell_type": "markdown",
981 "cell_type": "markdown",
982 "metadata": {},
982 "metadata": {},
983 "source": [
983 "source": [
984 "Now we will try to create a widget that only accepts dates in the year 2014. We render the widget without a description to verify that it can still render without a label."
984 "Now we will try to create a widget that only accepts dates in the year 2014. We render the widget without a description to verify that it can still render without a label."
985 ]
985 ]
986 },
986 },
987 {
987 {
988 "cell_type": "code",
988 "cell_type": "code",
989 "collapsed": false,
989 "collapsed": false,
990 "input": [
990 "input": [
991 "my_widget = DateWidget()\n",
991 "my_widget = DateWidget()\n",
992 "display(my_widget)\n",
992 "display(my_widget)\n",
993 "\n",
993 "\n",
994 "def require_2014(date):\n",
994 "def require_2014(date):\n",
995 " return not date is None and date.year == 2014\n",
995 " return not date is None and date.year == 2014\n",
996 "my_widget.validate.register_callback(require_2014)"
996 "my_widget.validate.register_callback(require_2014)"
997 ],
997 ],
998 "language": "python",
998 "language": "python",
999 "metadata": {},
999 "metadata": {},
1000 "outputs": [],
1000 "outputs": [],
1001 "prompt_number": 21
1001 "prompt_number": 21
1002 },
1002 },
1003 {
1003 {
1004 "cell_type": "code",
1004 "cell_type": "code",
1005 "collapsed": false,
1005 "collapsed": false,
1006 "input": [
1006 "input": [
1007 "# Try setting a valid date\n",
1007 "# Try setting a valid date\n",
1008 "my_widget.value = \"December 2, 2014\""
1008 "my_widget.value = \"December 2, 2014\""
1009 ],
1009 ],
1010 "language": "python",
1010 "language": "python",
1011 "metadata": {},
1011 "metadata": {},
1012 "outputs": [],
1012 "outputs": [],
1013 "prompt_number": 22
1013 "prompt_number": 22
1014 },
1014 },
1015 {
1015 {
1016 "cell_type": "code",
1016 "cell_type": "code",
1017 "collapsed": false,
1017 "collapsed": false,
1018 "input": [
1018 "input": [
1019 "# Try setting an invalid date\n",
1019 "# Try setting an invalid date\n",
1020 "my_widget.value = \"June 12, 1999\""
1020 "my_widget.value = \"June 12, 1999\""
1021 ],
1021 ],
1022 "language": "python",
1022 "language": "python",
1023 "metadata": {},
1023 "metadata": {},
1024 "outputs": [],
1024 "outputs": [],
1025 "prompt_number": 23
1025 "prompt_number": 23
1026 },
1026 },
1027 {
1027 {
1028 "cell_type": "code",
1028 "cell_type": "code",
1029 "collapsed": false,
1029 "collapsed": false,
1030 "input": [
1030 "input": [
1031 "my_widget.value"
1031 "my_widget.value"
1032 ],
1032 ],
1033 "language": "python",
1033 "language": "python",
1034 "metadata": {},
1034 "metadata": {},
1035 "outputs": [
1035 "outputs": [
1036 {
1036 {
1037 "metadata": {},
1037 "metadata": {},
1038 "output_type": "pyout",
1038 "output_type": "pyout",
1039 "prompt_number": 24,
1039 "prompt_number": 24,
1040 "text": [
1040 "text": [
1041 "u'2014-12-02'"
1041 "u'2014-12-02'"
1042 ]
1042 ]
1043 }
1043 }
1044 ],
1044 ],
1045 "prompt_number": 24
1045 "prompt_number": 24
1046 },
1046 },
1047 {
1047 {
1048 "cell_type": "markdown",
1048 "cell_type": "markdown",
1049 "metadata": {},
1049 "metadata": {},
1050 "source": [
1050 "source": [
1051 "This concludes Part 6 of the [series](index.ipynb)."
1051 "This concludes Part 6 of the [series](index.ipynb)."
1052 ]
1052 ]
1053 }
1053 }
1054 ],
1054 ],
1055 "metadata": {}
1055 "metadata": {}
1056 }
1056 }
1057 ]
1057 ]
1058 } No newline at end of file
1058 }
General Comments 0
You need to be logged in to leave comments. Login now