Date Picker Widget.ipynb
838 lines
| 24.2 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", | ||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "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", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "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", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "metadata": { | ||
"collapsed": false | ||||
}, | ||||
Thomas Kluyver
|
r20097 | "outputs": [], | ||
Min RK
|
r18669 | "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", | ||||
Thomas Kluyver
|
r20097 | "Lastly, the view needs to be registered with the widget manager, for which we need to load another module.\n", | ||
Min RK
|
r18669 | "\n", | ||
"**Final JavaScript code below:**" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "code", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "metadata": { | ||
"collapsed": false | ||||
}, | ||||
Thomas Kluyver
|
r20097 | "outputs": [], | ||
Min RK
|
r18669 | "source": [ | ||
"%%javascript\n", | ||||
"\n", | ||||
Thomas Kluyver
|
r20097 | "require([\"widgets/js/widget\", \"widgets/js/manager\"], function(widget, manager){\n", | ||
Min RK
|
r18669 | " \n", | ||
" // Define the DatePickerView\n", | ||||
Thomas Kluyver
|
r20097 | " var DatePickerView = widget.DOMWidgetView.extend({\n", | ||
Min RK
|
r18669 | " render: function(){ this.$el.text('Hello World!'); },\n", | ||
" });\n", | ||||
" \n", | ||||
" // Register the DatePickerView with the widget manager.\n", | ||||
Thomas Kluyver
|
r20097 | " manager.WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n", | ||
Min RK
|
r18669 | "});" | ||
] | ||||
}, | ||||
{ | ||||
"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", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "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", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "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", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "metadata": { | ||
"collapsed": false | ||||
}, | ||||
Thomas Kluyver
|
r20097 | "outputs": [], | ||
Min RK
|
r18669 | "source": [ | ||
"%%javascript\n", | ||||
"\n", | ||||
Thomas Kluyver
|
r20097 | "require([\"widgets/js/widget\", \"widgets/js/manager\"], function(widget, manager){\n", | ||
Min RK
|
r18669 | " \n", | ||
" // Define the DatePickerView\n", | ||||
Thomas Kluyver
|
r20097 | " var DatePickerView = widget.DOMWidgetView.extend({\n", | ||
Min RK
|
r18669 | " 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", | ||||
Thomas Kluyver
|
r20097 | " manager.WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n", | ||
Min RK
|
r18669 | "});" | ||
] | ||||
}, | ||||
{ | ||||
"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", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "metadata": { | ||
"collapsed": false | ||||
}, | ||||
Thomas Kluyver
|
r20097 | "outputs": [], | ||
Min RK
|
r18669 | "source": [ | ||
"%%javascript\n", | ||||
"\n", | ||||
Thomas Kluyver
|
r20097 | "require([\"widgets/js/widget\", \"widgets/js/manager\"], function(widget, manager){\n", | ||
Min RK
|
r18669 | " \n", | ||
" // Define the DatePickerView\n", | ||||
Thomas Kluyver
|
r20097 | " var DatePickerView = widget.DOMWidgetView.extend({\n", | ||
Min RK
|
r18669 | " 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", | ||||
Thomas Kluyver
|
r20097 | " manager.WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n", | ||
Min RK
|
r18669 | "});" | ||
] | ||||
}, | ||||
{ | ||||
"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", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "metadata": { | ||
"collapsed": false | ||||
}, | ||||
Thomas Kluyver
|
r20097 | "outputs": [], | ||
Min RK
|
r18669 | "source": [ | ||
"%%javascript\n", | ||||
"\n", | ||||
"\n", | ||||
Thomas Kluyver
|
r20097 | "require([\"widgets/js/widget\", \"widgets/js/manager\"], function(widget, manager){\n", | ||
Min RK
|
r18669 | " \n", | ||
" // Define the DatePickerView\n", | ||||
Thomas Kluyver
|
r20097 | " var DatePickerView = widget.DOMWidgetView.extend({\n", | ||
Min RK
|
r18669 | " 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", | ||||
Thomas Kluyver
|
r20097 | " manager.WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n", | ||
Min RK
|
r18669 | "});" | ||
] | ||||
}, | ||||
{ | ||||
"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", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "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", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "metadata": { | ||
"collapsed": false | ||||
}, | ||||
"outputs": [], | ||||
"source": [ | ||||
"my_widget" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"Read the date from Python" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "code", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "metadata": { | ||
"collapsed": false | ||||
}, | ||||
Thomas Kluyver
|
r20097 | "outputs": [], | ||
Min RK
|
r18669 | "source": [ | ||
"my_widget.value" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "markdown", | ||||
"metadata": {}, | ||||
"source": [ | ||||
"Set the date from Python" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "code", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "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", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "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", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "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", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "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", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "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", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "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", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "metadata": { | ||
"collapsed": false | ||||
}, | ||||
Thomas Kluyver
|
r20097 | "outputs": [], | ||
Min RK
|
r18669 | "source": [ | ||
"%%javascript\n", | ||||
"\n", | ||||
Thomas Kluyver
|
r20097 | "require([\"widgets/js/widget\", \"widgets/js/manager\"], function(widget, manager){\n", | ||
Min RK
|
r18669 | " \n", | ||
" // Define the DatePickerView\n", | ||||
Thomas Kluyver
|
r20097 | " var DatePickerView = widget.DOMWidgetView.extend({\n", | ||
Min RK
|
r18669 | " 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", | ||||
Thomas Kluyver
|
r20097 | " manager.WidgetManager.register_widget_view('DatePickerView', DatePickerView);\n", | ||
Min RK
|
r18669 | "});" | ||
] | ||||
}, | ||||
{ | ||||
"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", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "metadata": { | ||
"collapsed": false | ||||
}, | ||||
"outputs": [], | ||||
"source": [ | ||||
"# Add some additional widgets for aesthetic purpose\n", | ||||
Thomas Kluyver
|
r20097 | "display(widgets.Text(description=\"First:\"))\n", | ||
"display(widgets.Text(description=\"Last:\"))\n", | ||||
Min RK
|
r18669 | "\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", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "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", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "metadata": { | ||
"collapsed": false | ||||
}, | ||||
"outputs": [], | ||||
"source": [ | ||||
"# Try setting a valid date\n", | ||||
"my_widget.value = \"December 2, 2014\"" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "code", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "metadata": { | ||
"collapsed": false | ||||
}, | ||||
"outputs": [], | ||||
"source": [ | ||||
"# Try setting an invalid date\n", | ||||
"my_widget.value = \"June 12, 1999\"" | ||||
] | ||||
}, | ||||
{ | ||||
"cell_type": "code", | ||||
Thomas Kluyver
|
r20097 | "execution_count": null, | ||
Min RK
|
r18669 | "metadata": { | ||
"collapsed": false | ||||
}, | ||||
Thomas Kluyver
|
r20097 | "outputs": [], | ||
Min RK
|
r18669 | "source": [ | ||
"my_widget.value" | ||||
] | ||||
Thomas Kluyver
|
r20097 | }, | ||
{ | ||||
"cell_type": "code", | ||||
"execution_count": null, | ||||
"metadata": { | ||||
"collapsed": true | ||||
}, | ||||
"outputs": [], | ||||
"source": [] | ||||
Jonathan Frederic
|
r14340 | } | ||
Min RK
|
r18669 | ], | ||
"metadata": { | ||||
"cell_tags": [ | ||||
[ | ||||
"<None>", | ||||
null | ||||
] | ||||
Thomas Kluyver
|
r20097 | ], | ||
"kernelspec": { | ||||
"display_name": "Python 3", | ||||
Min RK
|
r20278 | "language": "python", | ||
Thomas Kluyver
|
r20097 | "name": "python3" | ||
}, | ||||
"language_info": { | ||||
"codemirror_mode": { | ||||
"name": "ipython", | ||||
"version": 3 | ||||
}, | ||||
"file_extension": ".py", | ||||
"mimetype": "text/x-python", | ||||
"name": "python", | ||||
"nbconvert_exporter": "python", | ||||
"pygments_lexer": "ipython3", | ||||
"version": "3.4.2" | ||||
} | ||||
Min RK
|
r18669 | }, | ||
"nbformat": 4, | ||||
"nbformat_minor": 0 | ||||
Thomas Kluyver
|
r20097 | } | ||