{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "# Widget Events"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Special events"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `Button` is not used to represent a data type.  Instead the button widget is used to handle mouse clicks.  The `on_click` method of the `Button` can be used to register function to be called when the button is clicked.  The doc string of the `on_click` can be seen below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "cc40e7bff4e14d7aaf74f6bc5f675d71",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Button(description='Click Me!', style=ButtonStyle())"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import ipywidgets as widgets\n",
    "from IPython.display import display\n",
    "\n",
    "button = widgets.Button(description=\"Click Me!\")\n",
    "display(button)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Register a callback to execute when the button is clicked.\n",
      "\n",
      "        The callback will be called with one argument, the clicked button\n",
      "        widget instance.\n",
      "\n",
      "        Parameters\n",
      "        ----------\n",
      "        remove: bool (optional)\n",
      "            Set to true to remove the callback from the list of callbacks.\n",
      "        \n"
     ]
    }
   ],
   "source": [
    "print(widgets.Button.on_click.__doc__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### Example"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since button clicks are stateless, they are transmitted from the front-end to the back-end using custom messages.  By using the `on_click` method, a button that prints a message when it has been clicked is shown below. To capture `print`s (or any other kind of output including errors) and ensure it is displayed, be sure to send it to an `Output` widget (or put the information you want to display into an `HTML` widget)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "cc40e7bff4e14d7aaf74f6bc5f675d71",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Button(description='Click Me!', style=ButtonStyle())"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "118a3b96e47f4b4f94ceb095636c3ef5",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# button = widgets.Button(description=\"Click Me!\")\n",
    "output = widgets.Output()\n",
    "\n",
    "display(button, output)\n",
    "@output.capture()\n",
    "def on_button_clicked(b):\n",
    "    print(\"Button clicked.\")\n",
    "\n",
    "button.on_click(on_button_clicked)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Traitlet events"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Widget properties are IPython traitlets and traitlets are eventful.  To handle  changes, the `observe` method of the widget can be used to register a callback.  The doc string for `observe` can be seen below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Setup a handler to be called when a trait changes.\n",
      "\n",
      "        This is used to setup dynamic notifications of trait changes.\n",
      "\n",
      "        Parameters\n",
      "        ----------\n",
      "        handler : callable\n",
      "            A callable that is called when a trait changes. Its\n",
      "            signature should be ``handler(change)``, where ``change`` is a\n",
      "            dictionary. The change dictionary at least holds a 'type' key.\n",
      "            * ``type``: the type of notification.\n",
      "            Other keys may be passed depending on the value of 'type'. In the\n",
      "            case where type is 'change', we also have the following keys:\n",
      "            * ``owner`` : the HasTraits instance\n",
      "            * ``old`` : the old value of the modified trait attribute\n",
      "            * ``new`` : the new value of the modified trait attribute\n",
      "            * ``name`` : the name of the modified trait attribute.\n",
      "        names : list, str, All\n",
      "            If names is All, the handler will apply to all traits.  If a list\n",
      "            of str, handler will apply to all names in the list.  If a\n",
      "            str, the handler will apply just to that name.\n",
      "        type : str, All (default: 'change')\n",
      "            The type of notification to filter by. If equal to All, then all\n",
      "            notifications are passed to the observe handler.\n",
      "        \n"
     ]
    }
   ],
   "source": [
    "print(widgets.Widget.observe.__doc__)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "f5f939f96b1443bfa4cf78180b1bee77",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "IntSlider(value=0)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "widgets.IntSlider()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Registering callbacks to trait changes in the kernel\n",
    "\n",
    "Since attributes of widgets on the Python side are traitlets, you can register handlers to the change events whenever the model gets updates from the front-end.\n",
    "\n",
    "The handler passed to observe will be called with one change argument. The change object holds at least a `type` key and a `name` key, corresponding respectively to the type of notification and the name of the attribute that triggered the notification."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### Callback signatures"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mentioned in the doc string, the callback registered must have the signature `handler(change)` where `change` is a dictionary holding the information about the change. \n",
    "\n",
    "Using this method, an example of how to output an `IntSlider`'s value as it is changed can be seen below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "8ac0f4f86fdb440685aef2828674ead6",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "IntSlider(value=0)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "7b2742ecc38949af8f0fd0afaf74bdac",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "int_range = widgets.IntSlider()\n",
    "output2 = widgets.Output()\n",
    "\n",
    "display(int_range, output2)\n",
    "\n",
    "def on_value_change(change):\n",
    "    output2.clear_output()\n",
    "    with output2:\n",
    "        print(change['new'])\n",
    "\n",
    "int_range.observe(on_value_change, names='value')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "c71d961de4b040218fbed6be5a605f8c",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Label(value='The values of range1 and range2 are synchronized')"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "1e50363a65e5409d936e3eb90a14bb05",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "IntSlider(value=1, description='Slider', max=5, min=-5)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "caption = widgets.Label(value='The values of range1 and range2 are synchronized')\n",
    "slider = widgets.IntSlider(min=-5, max=5, value=1, description='Slider')\n",
    "\n",
    "def handle_slider_change(change):\n",
    "    caption.value = 'The slider value is ' + (\n",
    "        'negative' if change.new < 0 else 'nonnegative'\n",
    "    )\n",
    "\n",
    "slider.observe(handle_slider_change, names='value')\n",
    "\n",
    "display(caption, slider)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Why `observe` instead of `link`?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using `link` is great if no transformation of the values is needed. `observe` is useful if some kind of calculation needs to be done with the values or if the values that are related have different types.\n",
    "\n",
    "The example below converts between Celsius and Farhenheit. As written, changing the temperature in Celcius will update the temperature in Farenheit, but not the other way around. You will add that as an exercise."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "f07c584778244df0a3b995b944fe63ec",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "FloatText(value=0.0, description='Temp $^\\\\circ$C')"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "fce68e14733e4a3382a539e9129fe945",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "FloatText(value=32.0, description='Temp $^\\\\circ$F')"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def C_to_F(temp):\n",
    "    return 1.8 * temp + 32\n",
    "\n",
    "def F_to_C(temp):\n",
    "    return (temp -32) / 1.8\n",
    "\n",
    "degree_C = widgets.FloatText(description='Temp $^\\circ$C', value=0)\n",
    "degree_F = widgets.FloatText(description='Temp $^\\circ$F', value=C_to_F(degree_C.value))\n",
    "\n",
    "def on_C_change(change):\n",
    "    degree_F.value = C_to_F(change['new'])\n",
    "    \n",
    "degree_C.observe(on_C_change, names='value')\n",
    "\n",
    "display(degree_C, degree_F)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Advanced Widget Linking"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In an earlier notebook you used `link` to link the value of one widget to another. \n",
    "\n",
    "There are a couple of other linking methods that offer more flexibility:\n",
    "\n",
    "+ `dlink` is a *directional* link; updates happen in one direction but not the other.\n",
    "+ `jslink` and `jsdlink` do the linking in the front end (i.e. in JavaScript without any communication to Python). "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Linking traitlets attributes in the kernel (ie. in Python)\n",
    "\n",
    "The first method is to use the `link` and `dlink`. This only works if we are interacting with a live kernel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "11b6970421704dd4a9fd21640605dec3",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Label(value='The values of slider1 and slider2 are synchronized')"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "fca5a7c000314bb58f434aa84aa6806e",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "IntSlider(value=0, description='Slider 1')"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "08a35127291b424d96798a59ceb007ef",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "IntSlider(value=0, description='Slider 2')"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "caption = widgets.Label(value='The values of slider1 and slider2 are synchronized')\n",
    "sliders1, slider2 = widgets.IntSlider(description='Slider 1'),\\\n",
    "                    widgets.IntSlider(description='Slider 2')\n",
    "\n",
    "display(caption, sliders1, slider2)\n",
    "\n",
    "l = widgets.link((sliders1, 'value'), (slider2, 'value'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "ae807ba500a04f5482d80e3e7a0c3045",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "HTML(value='Changes in source values are reflected in target1, but changes in target1 do not affect source')"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "2804522dfe1644c8abf8b52f03fff9d5",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "IntSlider(value=0, description='Source')"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "9cc84cfae3864ba09f0b907e7f2fac93",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "IntSlider(value=0, description='Target 1')"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "caption = widgets.HTML(value='Changes in source values are reflected in target1, but changes in target1 do not affect source')\n",
    "source, target1 = widgets.IntSlider(description='Source'),\\\n",
    "                  widgets.IntSlider(description='Target 1')\n",
    "\n",
    "display(caption, source, target1)\n",
    "\n",
    "dl = widgets.dlink((source, 'value'), (target1, 'value'))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Links can be broken by calling `unlink`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "l.unlink()\n",
    "dl.unlink()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Function `widgets.jslink` returns a `Link` widget. The link can be broken by calling the `unlink` method."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Linking widgets attributes from the client side"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can also directly link widget attributes in the browser using the link widgets, in either a unidirectional or a bidirectional fashion.\n",
    "\n",
    "Javascript links persist when embedding widgets in html web pages without a kernel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "f11c648c0ae74c6b9125eccbad64df4f",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Label(value='The values of range1 and range2 are synchronized')"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "cee3c8c7ab174beb88989f24cd686c29",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "IntSlider(value=0, description='Range 1')"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "e5a7e2a6b68e4d01a2111d1ea4bb5a89",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "IntSlider(value=0, description='Range 2')"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "caption = widgets.Label(value='The values of range1 and range2 are synchronized')\n",
    "range1, range2 = widgets.IntSlider(description='Range 1'),\\\n",
    "                 widgets.IntSlider(description='Range 2')\n",
    "\n",
    "display(caption, range1, range2)\n",
    "\n",
    "l = widgets.jslink((range1, 'value'), (range2, 'value'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "eae0cfd1bcf14f939e88bd3915ccef9c",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Label(value='Changes in source_range values are reflected in target_range1')"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "64c819b36240491e93080167546996a1",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "IntSlider(value=0, description='Source range')"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "1988145dca8346158cbe547435dfbeee",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "IntSlider(value=0, description='Target range 1')"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "caption = widgets.Label(value='Changes in source_range values are reflected in target_range1')\n",
    "source_range, target_range1 = widgets.IntSlider(description='Source range'),\\\n",
    "                              widgets.IntSlider(description='Target range 1')\n",
    "\n",
    "display(caption, source_range, target_range1)\n",
    "\n",
    "dl = widgets.jsdlink((source_range, 'value'), (target_range1, 'value'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The links can be broken by calling the `unlink` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "l.unlink()\n",
    "dl.unlink()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### The difference between linking in the kernel and linking in the client\n",
    "\n",
    "Linking in the kernel means linking via python. If two sliders are linked in the kernel, when one slider is changed the browser sends a message to the kernel (python in this case) updating the changed slider, the link widget in the kernel then propagates the change to the other slider object in the kernel, and then the other slider's kernel object sends a message to the browser to update the other slider's views in the browser. If the kernel is not running (as in a static web page), then the controls will not be linked.\n",
    "\n",
    "Linking using jslink (i.e., on the browser side) means contructing the link in Javascript. When one slider is changed, Javascript running in the browser changes the value of the other slider in the browser, without needing to communicate with the kernel at all. If the sliders are attached to kernel objects, each slider will update their kernel-side objects independently.\n",
    "\n",
    "To see the difference between the two, go to the [static version of this page in the ipywidgets documentation](http://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Events.html) and try out the sliders near the bottom. The ones linked in the kernel with `link` and `dlink` are no longer linked, but the ones linked in the browser with `jslink` and `jsdlink` are still linked."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Continuous vs delayed updates\n",
    "\n",
    "Some widgets offer a choice with their `continuous_update` attribute between continually updating values or only updating values when a user submits the value (for example, by pressing Enter or navigating away from the control). In the next example, we see the \"Delayed\" controls only transmit their value after the user finishes dragging the slider or submitting the textbox. The \"Continuous\" controls continually transmit their values as they are changed. Try typing a two-digit number into each of the text boxes, or dragging each of the sliders, to see the difference."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "b15f60f06eb7425290493f7755b44d7c",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "VBox(children=(IntSlider(value=0, continuous_update=False, description='Delayed'), IntText(value=0, descriptio…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "a = widgets.IntSlider(description=\"Delayed\", continuous_update=False)\n",
    "b = widgets.IntText(description=\"Delayed\", continuous_update=False)\n",
    "c = widgets.IntSlider(description=\"Continuous\", continuous_update=True)\n",
    "d = widgets.IntText(description=\"Continuous\", continuous_update=True)\n",
    "\n",
    "widgets.link((a, 'value'), (b, 'value'))\n",
    "widgets.link((a, 'value'), (c, 'value'))\n",
    "widgets.link((a, 'value'), (d, 'value'))\n",
    "widgets.VBox([a,b,c,d])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sliders, `Text`, and `Textarea` controls default to `continuous_update=True`. `IntText` and other text boxes for entering integer or float numbers default to `continuous_update=False` (since often you'll want to type an entire number before submitting the value by pressing enter or navigating out of the box)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "cell_tags": [
   [
    "<None>",
    null
   ]
  ],
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "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.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}