Date Picker Widget.ipynb
1045 lines
| 31.5 KiB
| text/plain
|
TextLexer
Jonathan Frederic
|
r14340 | { | |
Min RK
|
r18669 | "cells": [ | |
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"Before reading, make sure to review\n", | |||
"\n", | |||
"- [MVC prgramming](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)\n", | |||
"- [Backbone.js](https://www.codeschool.com/courses/anatomy-of-backbonejs)\n", | |||
"- [The widget IPEP](https://github.com/ipython/ipython/wiki/IPEP-23%3A-Backbone.js-Widgets)\n", | |||
"- [The original widget PR discussion](https://github.com/ipython/ipython/pull/4374)" | |||
Jonathan Frederic
|
r14340 | ] | |
Min RK
|
r18669 | }, | |
Jonathan Frederic
|
r14340 | { | |
Min RK
|
r18669 | "cell_type": "code", | |
"execution_count": 1, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [], | |||
"source": [ | |||
"from __future__ import print_function # For py 2.7 compat\n", | |||
"\n", | |||
"from IPython.html import widgets # Widget definitions\n", | |||
"from IPython.display import display # Used to display widgets in the notebook\n", | |||
"from IPython.utils.traitlets import Unicode # Used to declare attributes of our widget" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"# Abstract" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"This notebook implements a custom date picker widget,\n", | |||
"in order to demonstrate the widget creation process.\n", | |||
"\n", | |||
"To create a custom widget, both Python and JavaScript code is required." | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"# Section 1 - Basics" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"## Python" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"When starting a project like this, it is often easiest to make a simple base implementation,\n", | |||
"to verify that the underlying framework is working as expected.\n", | |||
"To start, we will create an empty widget and make sure that it can be rendered.\n", | |||
"The first step is to define the widget in Python." | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 2, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [], | |||
"source": [ | |||
"class DateWidget(widgets.DOMWidget):\n", | |||
" _view_name = Unicode('DatePickerView', sync=True)" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"Our widget inherits from `widgets.DOMWidget` since it is intended that it will be displayed in the notebook directly.\n", | |||
"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", | |||
"**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." | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"## JavaScript" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"In the IPython notebook [require.js](http://requirejs.org/) is used to load JavaScript dependencies.\n", | |||
"All IPython widget code depends on `widgets/js/widget.js`,\n", | |||
"where the base widget model and base view are defined.\n", | |||
"We use require.js to load this file:" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 3, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [ | |||
Jonathan Frederic
|
r14340 | { | |
Min RK
|
r18669 | "data": { | |
"application/javascript": [ | |||
"\n", | |||
"require([\"widgets/js/widget\"], function(WidgetManager){\n", | |||
"\n", | |||
"});" | |||
], | |||
"text/plain": [ | |||
"<IPython.core.display.Javascript at 0x109491690>" | |||
] | |||
}, | |||
Jonathan Frederic
|
r14340 | "metadata": {}, | |
Min RK
|
r18669 | "output_type": "display_data" | |
} | |||
], | |||
"source": [ | |||
"%%javascript\n", | |||
"\n", | |||
"require([\"widgets/js/widget\"], function(WidgetManager){\n", | |||
"\n", | |||
"});" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"Now we need to define a view that can be used to represent the model.\n", | |||
"To do this, the `IPython.DOMWidgetView` is extended.\n", | |||
"**A render function must be defined**.\n", | |||
"The render function is used to render a widget view instance to the DOM.\n", | |||
"For now, the render function renders a div that contains the text *Hello World!*\n", | |||
"Lastly, the view needs to be registered with the widget manager.\n", | |||
"\n", | |||
"**Final JavaScript code below:**" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 4, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [ | |||
Jonathan Frederic
|
r14340 | { | |
Min RK
|
r18669 | "data": { | |
"application/javascript": [ | |||
"\n", | |||
"require([\"widgets/js/widget\"], function(WidgetManager){\n", | |||
" \n", | |||
" // Define the DatePickerView\n", | |||
" var DatePickerView = IPython.DOMWidgetView.extend({\n", | |||
" render: function(){ this.$el.text('Hello World!'); },\n", | |||
" });\n", | |||
" \n", | |||
" // Register the DatePickerView with the widget manager.\n", | |||
" WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n", | |||
"});" | |||
], | |||
"text/plain": [ | |||
"<IPython.core.display.Javascript at 0x1094917d0>" | |||
] | |||
}, | |||
Jonathan Frederic
|
r14340 | "metadata": {}, | |
Min RK
|
r18669 | "output_type": "display_data" | |
} | |||
], | |||
"source": [ | |||
"%%javascript\n", | |||
"\n", | |||
"require([\"widgets/js/widget\"], function(WidgetManager){\n", | |||
" \n", | |||
" // Define the DatePickerView\n", | |||
" var DatePickerView = IPython.DOMWidgetView.extend({\n", | |||
" render: function(){ this.$el.text('Hello World!'); },\n", | |||
" });\n", | |||
" \n", | |||
" // Register the DatePickerView with the widget manager.\n", | |||
" WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n", | |||
"});" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"## Test" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"To test what we have so far, create the widget, just like you would the builtin widgets:" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 5, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [], | |||
"source": [ | |||
"DateWidget()" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"# Section 2 - Something useful" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"## Python" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"In the last section we created a simple widget that displayed *Hello World!*\n", | |||
"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", | |||
"The new attribute must be a traitlet, so the widget machinery can handle it.\n", | |||
"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", | |||
"Adding this to the code from the last section:" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 6, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [], | |||
"source": [ | |||
"class DateWidget(widgets.DOMWidget):\n", | |||
" _view_name = Unicode('DatePickerView', sync=True)\n", | |||
" value = Unicode(sync=True)" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"## JavaScript" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"In the JavaScript, there is no need to define counterparts to the traitlets.\n", | |||
"When the JavaScript model is created for the first time,\n", | |||
"it copies all of the traitlet `sync=True` attributes from the Python model.\n", | |||
"We need to replace *Hello World!* with an actual HTML date picker widget." | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 7, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [ | |||
Jonathan Frederic
|
r14340 | { | |
Min RK
|
r18669 | "data": { | |
"application/javascript": [ | |||
"\n", | |||
"require([\"widgets/js/widget\"], function(WidgetManager){\n", | |||
" \n", | |||
" // Define the DatePickerView\n", | |||
" var DatePickerView = IPython.DOMWidgetView.extend({\n", | |||
" render: function(){\n", | |||
" \n", | |||
" // Create the date picker control.\n", | |||
" this.$date = $('<input />')\n", | |||
" .attr('type', 'date')\n", | |||
" .appendTo(this.$el);\n", | |||
" },\n", | |||
" });\n", | |||
" \n", | |||
" // Register the DatePickerView with the widget manager.\n", | |||
" WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n", | |||
"});" | |||
], | |||
"text/plain": [ | |||
"<IPython.core.display.Javascript at 0x109491750>" | |||
] | |||
}, | |||
Jonathan Frederic
|
r14340 | "metadata": {}, | |
Min RK
|
r18669 | "output_type": "display_data" | |
} | |||
], | |||
"source": [ | |||
"%%javascript\n", | |||
"\n", | |||
"require([\"widgets/js/widget\"], function(WidgetManager){\n", | |||
" \n", | |||
" // Define the DatePickerView\n", | |||
" var DatePickerView = IPython.DOMWidgetView.extend({\n", | |||
" render: function(){\n", | |||
" \n", | |||
" // Create the date picker control.\n", | |||
" this.$date = $('<input />')\n", | |||
" .attr('type', 'date')\n", | |||
" .appendTo(this.$el);\n", | |||
" },\n", | |||
" });\n", | |||
" \n", | |||
" // Register the DatePickerView with the widget manager.\n", | |||
" WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n", | |||
"});" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"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." | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 8, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [ | |||
Jonathan Frederic
|
r14340 | { | |
Min RK
|
r18669 | "data": { | |
"application/javascript": [ | |||
"\n", | |||
"require([\"widgets/js/widget\"], function(WidgetManager){\n", | |||
" \n", | |||
" // Define the DatePickerView\n", | |||
" var DatePickerView = IPython.DOMWidgetView.extend({\n", | |||
" render: function(){\n", | |||
" \n", | |||
" // Create the date picker control.\n", | |||
" this.$date = $('<input />')\n", | |||
" .attr('type', 'date')\n", | |||
" .appendTo(this.$el);\n", | |||
" },\n", | |||
" \n", | |||
" update: function() {\n", | |||
" \n", | |||
" // Set the value of the date control and then call base.\n", | |||
" this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n", | |||
" return DatePickerView.__super__.update.apply(this);\n", | |||
" },\n", | |||
" });\n", | |||
" \n", | |||
" // Register the DatePickerView with the widget manager.\n", | |||
" WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n", | |||
"});" | |||
], | |||
"text/plain": [ | |||
"<IPython.core.display.Javascript at 0x109491750>" | |||
] | |||
}, | |||
Jonathan Frederic
|
r14340 | "metadata": {}, | |
Min RK
|
r18669 | "output_type": "display_data" | |
} | |||
], | |||
"source": [ | |||
"%%javascript\n", | |||
"\n", | |||
"require([\"widgets/js/widget\"], function(WidgetManager){\n", | |||
" \n", | |||
" // Define the DatePickerView\n", | |||
" var DatePickerView = IPython.DOMWidgetView.extend({\n", | |||
" render: function(){\n", | |||
" \n", | |||
" // Create the date picker control.\n", | |||
" this.$date = $('<input />')\n", | |||
" .attr('type', 'date')\n", | |||
" .appendTo(this.$el);\n", | |||
" },\n", | |||
" \n", | |||
" update: function() {\n", | |||
" \n", | |||
" // Set the value of the date control and then call base.\n", | |||
" this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n", | |||
" return DatePickerView.__super__.update.apply(this);\n", | |||
" },\n", | |||
" });\n", | |||
" \n", | |||
" // Register the DatePickerView with the widget manager.\n", | |||
" WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n", | |||
"});" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"To get the changed value from the frontend to publish itself to the backend,\n", | |||
"we need to listen to the change event triggered by the HTM date control and set the value in the model.\n", | |||
"After the date change event fires and the new value is set in the model,\n", | |||
"it is very important that we call `this.touch()` to let the widget machinery know which view changed the model.\n", | |||
"This is important because the widget machinery needs to know which cell to route the message callbacks to.\n", | |||
"\n", | |||
"**Final JavaScript code below:**" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 9, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [ | |||
Jonathan Frederic
|
r14340 | { | |
Min RK
|
r18669 | "data": { | |
"application/javascript": [ | |||
"\n", | |||
"\n", | |||
"require([\"widgets/js/widget\"], function(WidgetManager){\n", | |||
" \n", | |||
" // Define the DatePickerView\n", | |||
" var DatePickerView = IPython.DOMWidgetView.extend({\n", | |||
" render: function(){\n", | |||
" \n", | |||
" // Create the date picker control.\n", | |||
" this.$date = $('<input />')\n", | |||
" .attr('type', 'date')\n", | |||
" .appendTo(this.$el);\n", | |||
" },\n", | |||
" \n", | |||
" update: function() {\n", | |||
" \n", | |||
" // Set the value of the date control and then call base.\n", | |||
" this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n", | |||
" return DatePickerView.__super__.update.apply(this);\n", | |||
" },\n", | |||
" \n", | |||
" // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n", | |||
" events: {\"change\": \"handle_date_change\"},\n", | |||
" \n", | |||
" // Callback for when the date is changed.\n", | |||
" handle_date_change: function(event) {\n", | |||
" this.model.set('value', this.$date.val());\n", | |||
" this.touch();\n", | |||
" },\n", | |||
" });\n", | |||
" \n", | |||
" // Register the DatePickerView with the widget manager.\n", | |||
" WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n", | |||
"});" | |||
], | |||
"text/plain": [ | |||
"<IPython.core.display.Javascript at 0x109491b10>" | |||
] | |||
}, | |||
Jonathan Frederic
|
r14340 | "metadata": {}, | |
Min RK
|
r18669 | "output_type": "display_data" | |
} | |||
], | |||
"source": [ | |||
"%%javascript\n", | |||
"\n", | |||
"\n", | |||
"require([\"widgets/js/widget\"], function(WidgetManager){\n", | |||
" \n", | |||
" // Define the DatePickerView\n", | |||
" var DatePickerView = IPython.DOMWidgetView.extend({\n", | |||
" render: function(){\n", | |||
" \n", | |||
" // Create the date picker control.\n", | |||
" this.$date = $('<input />')\n", | |||
" .attr('type', 'date')\n", | |||
" .appendTo(this.$el);\n", | |||
" },\n", | |||
" \n", | |||
" update: function() {\n", | |||
" \n", | |||
" // Set the value of the date control and then call base.\n", | |||
" this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n", | |||
" return DatePickerView.__super__.update.apply(this);\n", | |||
" },\n", | |||
" \n", | |||
" // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n", | |||
" events: {\"change\": \"handle_date_change\"},\n", | |||
" \n", | |||
" // Callback for when the date is changed.\n", | |||
" handle_date_change: function(event) {\n", | |||
" this.model.set('value', this.$date.val());\n", | |||
" this.touch();\n", | |||
" },\n", | |||
" });\n", | |||
" \n", | |||
" // Register the DatePickerView with the widget manager.\n", | |||
" WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n", | |||
"});" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"## Test" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"To test, create the widget the same way that the other widgets are created." | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 10, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [], | |||
"source": [ | |||
"my_widget = DateWidget()\n", | |||
"display(my_widget)" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"Display the widget again to make sure that both views remain in sync." | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 11, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [], | |||
"source": [ | |||
"my_widget" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"Read the date from Python" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 12, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [ | |||
Jonathan Frederic
|
r14340 | { | |
Min RK
|
r18669 | "data": { | |
"text/plain": [ | |||
"u''" | |||
] | |||
}, | |||
"execution_count": 12, | |||
Jonathan Frederic
|
r14340 | "metadata": {}, | |
Min RK
|
r18669 | "output_type": "execute_result" | |
} | |||
], | |||
"source": [ | |||
"my_widget.value" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"Set the date from Python" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 13, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [], | |||
"source": [ | |||
"my_widget.value = \"1998-12-01\" # December 1st, 1998" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"# Section 3 - Extra credit" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"The 3rd party `dateutil` library is required to continue. https://pypi.python.org/pypi/python-dateutil" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 14, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [], | |||
"source": [ | |||
"# Import the dateutil library to parse date strings.\n", | |||
"from dateutil import parser" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"In the last section we created a fully working date picker widget.\n", | |||
"Now we will add custom validation and support for labels.\n", | |||
"So far, only the ISO date format \"YYYY-MM-DD\" is supported.\n", | |||
"Now, we will add support for all of the date formats recognized by the Python dateutil library." | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"## Python" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"The standard property name used for widget labels is `description`.\n", | |||
"In the code block below, `description` has been added to the Python widget." | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 15, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [], | |||
"source": [ | |||
"class DateWidget(widgets.DOMWidget):\n", | |||
" _view_name = Unicode('DatePickerView', sync=True)\n", | |||
" value = Unicode(sync=True)\n", | |||
" description = Unicode(sync=True)" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"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", | |||
"We can take advantage of this to perform validation and parsing of different date string formats.\n", | |||
"Below, a method that listens to value has been added to the DateWidget." | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 16, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [], | |||
"source": [ | |||
"class DateWidget(widgets.DOMWidget):\n", | |||
" _view_name = Unicode('DatePickerView', sync=True)\n", | |||
" value = Unicode(sync=True)\n", | |||
" description = Unicode(sync=True)\n", | |||
"\n", | |||
" # This function automatically gets called by the traitlet machinery when\n", | |||
" # value is modified because of this function's name.\n", | |||
" def _value_changed(self, name, old_value, new_value):\n", | |||
" pass" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"Now the function parses the date string,\n", | |||
"and only sets the value in the correct format." | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 17, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [], | |||
"source": [ | |||
"class DateWidget(widgets.DOMWidget):\n", | |||
" _view_name = Unicode('DatePickerView', sync=True)\n", | |||
" value = Unicode(sync=True)\n", | |||
" description = Unicode(sync=True)\n", | |||
" \n", | |||
" # This function automatically gets called by the traitlet machinery when\n", | |||
" # value is modified because of this function's name.\n", | |||
" def _value_changed(self, name, old_value, new_value):\n", | |||
" \n", | |||
" # Parse the date time value.\n", | |||
" try:\n", | |||
" parsed_date = parser.parse(new_value)\n", | |||
" parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n", | |||
" except:\n", | |||
" parsed_date_string = ''\n", | |||
" \n", | |||
" # Set the parsed date string if the current date string is different.\n", | |||
" if self.value != parsed_date_string:\n", | |||
" self.value = parsed_date_string" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"Finally, a `CallbackDispatcher` is added so the user can perform custom validation.\n", | |||
"If any one of the callbacks registered with the dispatcher returns False,\n", | |||
"the new date is not set.\n", | |||
"\n", | |||
"**Final Python code below:**" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 18, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [], | |||
"source": [ | |||
"class DateWidget(widgets.DOMWidget):\n", | |||
" _view_name = Unicode('DatePickerView', sync=True)\n", | |||
" value = Unicode(sync=True)\n", | |||
" description = Unicode(sync=True)\n", | |||
" \n", | |||
" def __init__(self, **kwargs):\n", | |||
" super(DateWidget, self).__init__(**kwargs)\n", | |||
" \n", | |||
" self.validate = widgets.CallbackDispatcher()\n", | |||
" \n", | |||
" # This function automatically gets called by the traitlet machinery when\n", | |||
" # value is modified because of this function's name.\n", | |||
" def _value_changed(self, name, old_value, new_value):\n", | |||
" \n", | |||
" # Parse the date time value.\n", | |||
" try:\n", | |||
" parsed_date = parser.parse(new_value)\n", | |||
" parsed_date_string = parsed_date.strftime(\"%Y-%m-%d\")\n", | |||
" except:\n", | |||
" parsed_date_string = ''\n", | |||
" \n", | |||
" # Set the parsed date string if the current date string is different.\n", | |||
" if old_value != new_value:\n", | |||
" valid = self.validate(parsed_date)\n", | |||
" if valid in (None, True):\n", | |||
" self.value = parsed_date_string\n", | |||
" else:\n", | |||
" self.value = old_value\n", | |||
" self.send_state() # The traitlet event won't fire since the value isn't changing.\n", | |||
" # We need to force the back-end to send the front-end the state\n", | |||
" # to make sure that the date control date doesn't change." | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"## JavaScript" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"Using the Javascript code from the last section,\n", | |||
"we add a label to the date time object.\n", | |||
"The label is a div with the `widget-hlabel` class applied to it.\n", | |||
"`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", | |||
"Similar to the `widget-hlabel` class is the `widget-hbox-single` class.\n", | |||
"The `widget-hbox-single` class applies special styling to widget containers that store a single line horizontal widget.\n", | |||
"\n", | |||
"We hide the label if the description value is blank." | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 19, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [ | |||
Jonathan Frederic
|
r14340 | { | |
Min RK
|
r18669 | "data": { | |
"application/javascript": [ | |||
"\n", | |||
"require([\"widgets/js/widget\"], function(WidgetManager){\n", | |||
" \n", | |||
" // Define the DatePickerView\n", | |||
" var DatePickerView = IPython.DOMWidgetView.extend({\n", | |||
" render: function(){\n", | |||
" this.$el.addClass('widget-hbox-single'); /* Apply this class to the widget container to make\n", | |||
" it fit with the other built in widgets.*/\n", | |||
" // Create a label.\n", | |||
" this.$label = $('<div />')\n", | |||
" .addClass('widget-hlabel')\n", | |||
" .appendTo(this.$el)\n", | |||
" .hide(); // Hide the label by default.\n", | |||
" \n", | |||
" // Create the date picker control.\n", | |||
" this.$date = $('<input />')\n", | |||
" .attr('type', 'date')\n", | |||
" .appendTo(this.$el);\n", | |||
" },\n", | |||
" \n", | |||
" update: function() {\n", | |||
" \n", | |||
" // Set the value of the date control and then call base.\n", | |||
" this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n", | |||
" \n", | |||
" // Hide or show the label depending on the existance of a description.\n", | |||
" var description = this.model.get('description');\n", | |||
" if (description == undefined || description == '') {\n", | |||
" this.$label.hide();\n", | |||
" } else {\n", | |||
" this.$label.show();\n", | |||
" this.$label.text(description);\n", | |||
" }\n", | |||
" \n", | |||
" return DatePickerView.__super__.update.apply(this);\n", | |||
" },\n", | |||
" \n", | |||
" // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n", | |||
" events: {\"change\": \"handle_date_change\"},\n", | |||
" \n", | |||
" // Callback for when the date is changed.\n", | |||
" handle_date_change: function(event) {\n", | |||
" this.model.set('value', this.$date.val());\n", | |||
" this.touch();\n", | |||
" },\n", | |||
" });\n", | |||
" \n", | |||
" // Register the DatePickerView with the widget manager.\n", | |||
" WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n", | |||
"});" | |||
], | |||
"text/plain": [ | |||
"<IPython.core.display.Javascript at 0x1094eef90>" | |||
] | |||
}, | |||
Jonathan Frederic
|
r14340 | "metadata": {}, | |
Min RK
|
r18669 | "output_type": "display_data" | |
} | |||
], | |||
"source": [ | |||
"%%javascript\n", | |||
"\n", | |||
"require([\"widgets/js/widget\"], function(WidgetManager){\n", | |||
" \n", | |||
" // Define the DatePickerView\n", | |||
" var DatePickerView = IPython.DOMWidgetView.extend({\n", | |||
" render: function(){\n", | |||
" this.$el.addClass('widget-hbox-single'); /* Apply this class to the widget container to make\n", | |||
" it fit with the other built in widgets.*/\n", | |||
" // Create a label.\n", | |||
" this.$label = $('<div />')\n", | |||
" .addClass('widget-hlabel')\n", | |||
" .appendTo(this.$el)\n", | |||
" .hide(); // Hide the label by default.\n", | |||
" \n", | |||
" // Create the date picker control.\n", | |||
" this.$date = $('<input />')\n", | |||
" .attr('type', 'date')\n", | |||
" .appendTo(this.$el);\n", | |||
" },\n", | |||
" \n", | |||
" update: function() {\n", | |||
" \n", | |||
" // Set the value of the date control and then call base.\n", | |||
" this.$date.val(this.model.get('value')); // ISO format \"YYYY-MM-DDTHH:mm:ss.sssZ\" is required\n", | |||
" \n", | |||
" // Hide or show the label depending on the existance of a description.\n", | |||
" var description = this.model.get('description');\n", | |||
" if (description == undefined || description == '') {\n", | |||
" this.$label.hide();\n", | |||
" } else {\n", | |||
" this.$label.show();\n", | |||
" this.$label.text(description);\n", | |||
" }\n", | |||
" \n", | |||
" return DatePickerView.__super__.update.apply(this);\n", | |||
" },\n", | |||
" \n", | |||
" // Tell Backbone to listen to the change event of input controls (which the HTML date picker is)\n", | |||
" events: {\"change\": \"handle_date_change\"},\n", | |||
" \n", | |||
" // Callback for when the date is changed.\n", | |||
" handle_date_change: function(event) {\n", | |||
" this.model.set('value', this.$date.val());\n", | |||
" this.touch();\n", | |||
" },\n", | |||
" });\n", | |||
" \n", | |||
" // Register the DatePickerView with the widget manager.\n", | |||
" WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n", | |||
"});" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"## Test" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"To test the drawing of the label we create the widget like normal but supply the additional description property a value." | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 20, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [], | |||
"source": [ | |||
"# Add some additional widgets for aesthetic purpose\n", | |||
"display(widgets.TextWidget(description=\"First:\"))\n", | |||
"display(widgets.TextWidget(description=\"Last:\"))\n", | |||
"\n", | |||
"my_widget = DateWidget()\n", | |||
"display(my_widget)\n", | |||
"my_widget.description=\"DOB:\"" | |||
] | |||
}, | |||
{ | |||
"cell_type": "markdown", | |||
"metadata": {}, | |||
"source": [ | |||
"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." | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 21, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [], | |||
"source": [ | |||
"my_widget = DateWidget()\n", | |||
"display(my_widget)\n", | |||
"\n", | |||
"def require_2014(date):\n", | |||
" return not date is None and date.year == 2014\n", | |||
"my_widget.validate.register_callback(require_2014)" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 22, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [], | |||
"source": [ | |||
"# Try setting a valid date\n", | |||
"my_widget.value = \"December 2, 2014\"" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 23, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [], | |||
"source": [ | |||
"# Try setting an invalid date\n", | |||
"my_widget.value = \"June 12, 1999\"" | |||
] | |||
}, | |||
{ | |||
"cell_type": "code", | |||
"execution_count": 24, | |||
"metadata": { | |||
"collapsed": false | |||
}, | |||
"outputs": [ | |||
Jason Grout
|
r14505 | { | |
Min RK
|
r18669 | "data": { | |
"text/plain": [ | |||
"u'2014-12-02'" | |||
] | |||
}, | |||
"execution_count": 24, | |||
Jason Grout
|
r14505 | "metadata": {}, | |
Min RK
|
r18669 | "output_type": "execute_result" | |
Jonathan Frederic
|
r14340 | } | |
], | |||
Min RK
|
r18669 | "source": [ | |
"my_widget.value" | |||
] | |||
Jonathan Frederic
|
r14340 | } | |
Min RK
|
r18669 | ], | |
"metadata": { | |||
"cell_tags": [ | |||
[ | |||
"<None>", | |||
null | |||
] | |||
] | |||
}, | |||
"nbformat": 4, | |||
"nbformat_minor": 0 | |||
Brian E. Granger
|
r16098 | } |