{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import param\n",
    "import panel as pn\n",
    "\n",
    "js_files = {\n",
    "    'mdc': 'https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js'\n",
    "}\n",
    "    \n",
    "css_files = [\n",
    "    'https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css'\n",
    "]\n",
    "\n",
    "pn.extension(js_files=js_files, css_files=css_files)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    " When building custom applications and dashboards it is frequently useful to extend Panel with custom components, which are specific to a particular application. Panel provides multiple mechanisms to extend and compose diffferent components or even add entirely new components. In this user guide we will go over these approaches and compare the benefits and drawbacks.\n",
    "\n",
    "## Viewer components\n",
    "\n",
    "The simplest way to extend Panel is to implement so called `Viewer` components. These components simply wrap other Panel object and make it possible to compose them as a unit just like any native Panel component. The core mechanism that makes this possible is the implementation of a ``__panel__`` method on the class, which Panel will call when displaying the component.\n",
    "\n",
    "Below we will declare a composite `EditableRange` component made up of two `FloatInput` widgets. The class creates the widgets and then sets up callbacks to sync the parameters on the underlying widgets with the parameters on the `Viewer` component and then implements the ``__panel__`` method, which returns the Panel layout to be rendered when displaying the component:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from panel.viewable import Viewer\n",
    "\n",
    "class EditableRange(Viewer):\n",
    "    \n",
    "    value = param.Range(doc=\"A numeric range.\")\n",
    "    \n",
    "    width = param.Integer(default=300)\n",
    "    \n",
    "    def __init__(self, **params):\n",
    "        self._start_input = pn.widgets.FloatInput()\n",
    "        self._end_input = pn.widgets.FloatInput(align='end')\n",
    "        super().__init__(**params)\n",
    "        self._layout = pn.Row(self._start_input, self._end_input)\n",
    "        self._sync_widgets()\n",
    "    \n",
    "    def __panel__(self):\n",
    "        return self._layout\n",
    "    \n",
    "    @param.depends('value', 'width', watch=True)\n",
    "    def _sync_widgets(self):\n",
    "        self._start_input.name = self.name\n",
    "        self._start_input.value = self.value[0]\n",
    "        self._end_input.value = self.value[1]\n",
    "        self._start_input.width = self.width//2\n",
    "        self._end_input.width = self.width//2\n",
    "        \n",
    "    @param.depends('_start_input.value', '_end_input.value', watch=True)\n",
    "    def _sync_params(self):\n",
    "        self.value = (self._start_input.value, self._end_input.value)\n",
    "    \n",
    "range_widget = EditableRange(name='Range', value=(0, 10))\n",
    "\n",
    "pn.Column(\n",
    "    '## This is a custom widget',\n",
    "    range_widget\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Implementing a component by subclassing the `Viewer` baseclass gives the component a number of useful affordances:\n",
    "    \n",
    "* It renders itself in a notebook (like all other Panel components)\n",
    "* It can be placed in a Panel layout component (such as a `Row` or `Column`)\n",
    "* It has `show` and `servable` methods\n",
    "\n",
    "This approach is very helpful when we want to wrap multiple existing Panel components into a easily reusable unit."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ReactiveHTML components\n",
    "\n",
    "The `ReactiveHTML` provides bi-directional syncing of arbitrary HTML attributes and DOM properties with parameters on the subclass. This kind of component must declare a HTML template written using Javascript template variables (`${}`) and optionally Jinja2 syntax:\n",
    "\n",
    "- `_template`: The HTML template to render declaring how to link parameters on the class to HTML attributes.\n",
    "\n",
    "Additionally the component may declare some additional attributes providing further functionality \n",
    "\n",
    "- `_child_config` (optional): Optional mapping that controls how children are rendered.\n",
    "- `_dom_events` (optional): Optional mapping of named nodes to DOM events to add event listeners to.\n",
    "- `_scripts` (optional): Optional mapping of Javascript to execute on specific parameter changes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### HTML templates\n",
    "\n",
    "A ReactiveHTML component is declared by providing an HTML template on the `_template` attribute on the class. Parameters are synced by inserting them as template variables of the form `${parameter}`, e.g.:\n",
    "\n",
    "```html\n",
    "    _template = '<div id=\"custom_id\" class=\"${div_class}\" onclick=\"${some_method}\">${children}</div>'\n",
    "```\n",
    "\n",
    "will interpolate the `div_class` parameter on the `class` attribute of the HTML element.\n",
    "\n",
    "In addition to providing attributes we can also provide children to an HTML tag. Any child parameter will be treated as other Panel components to render into the containing HTML. This makes it possible to use `ReactiveHTML` to lay out other components. Lastly the `${}` syntax may also be used to invoke Python methods and JS scripts. Note that you must declare an `id` on components which have linked parameters, methods or children.\n",
    "\n",
    "The HTML templates also support [Jinja2](https://jinja.palletsprojects.com/en/2.11.x/) syntax to template parameter variables and child objects. The Jinja2 templating engine is automatically given a few context variables:\n",
    "\n",
    "- `param`: The param namespace object allows templating parameter names, labels, docstrings and other attributes.\n",
    "- `__doc__`: The class docstring\n",
    "\n",
    "The difference between Jinja2 literal templating and the JS templating syntax is important to note. While literal values are inserted during the initial rendering step they are not dynamically linked."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Children\n",
    "\n",
    "In order to template other parameters as child objects there are a few options. By default all parameters referenced using `${child}` syntax are treated as if they were Panel components, e.g.:\n",
    "\n",
    "```html\n",
    "<div id=\"custom_id\">${parameter}</div>\n",
    "```\n",
    "\n",
    "will render the contents of the `parameter` as a Panel object. If you want to render it as a literal string instead you can use the regular Jinja templating syntax instead, i.e. `{{ parameter }}` or you can use the `_child_config` to declare you want to treat `parameter` as a literal:\n",
    "\n",
    "```python\n",
    "_child_config = {'parameter': 'literal'}\n",
    "```\n",
    "\n",
    "If the parameter is a list each item in the list will be inserted in sequence unless declared otherwise. However if you want to wrap each child in some custom HTML you will have to use Jinja2 loop syntax: \n",
    "\n",
    "```html\n",
    "<select>\n",
    "  {% for obj in parameter %}\n",
    "  <option id=\"option\">${obj}</option>\n",
    "  {% endfor %}\n",
    "</select>\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### DOM Events\n",
    "\n",
    "In certain cases it is necessary to explicitly declare event listeners on the DOM node to ensure that changes in their properties are synced when an event is fired. To make this possible the HTML element in question must be given an `id`, e.g.:\n",
    "\n",
    "```html\n",
    "    _template = '<input id=\"custom_id\"></input>'\n",
    "```\n",
    "\n",
    "Now we can use this name to declare set of `_dom_events` to subscribe to. The following will subscribe to change DOM events on the input element:\n",
    "\n",
    "```python\n",
    "    _dom_events = {'input': ['change']}\n",
    "```\n",
    "\n",
    "Once subscribed the class may also define a method following the `_{node-id}_{event}` naming convention which will fire when the DOM event triggers, e.g. we could define a `__custom_id_change` method. Any such callback will be given a `DOMEvent` object as the first and only argument. The `DOMEvent` contains information about the event on the `.data` attribute and declares the type of event on the `.type` attribute."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Scripts\n",
    "    \n",
    "In addition to declaring callbacks in Python it is also possible to declare Javascript callbacks on the `_scripts` attribute of the `ReactiveHTML` class. There are a few ways to trigger such callbacks.\n",
    "\n",
    "All scripts have a number of objects available in their namespace that allow accessing (and setting) the parameter values, store state, update the layout and access any named DOM nodes declared as part of the template. Specifically the following objects are declared in each callbacks namespace:\n",
    "\n",
    "* `self`: A namespace model which provides access to all scripts on the class, e.g. `self.value()` will call the 'value' script defined above.\n",
    "* `data`:   The data model holds the current values of the synced parameters, e.g. `data.value` will reflect the current value of the input node.\n",
    "* `model`:  The `ReactiveHTML` model which declares the layout parameters (e.g. `width` and `height`) and the component definition.\n",
    "* `state`:  An empty state dictionary which scripts can use to store state for the lifetime of the view.\n",
    "* `view`: Bokeh View class responsible for rendering the component. This provides access to method like `view.resize_layout()` to signal to Bokeh that  it should recompute the layout of the element. \n",
    "* `<node>`: All named DOM nodes in the HTML template, e.g. the `input` node in the example above.\n",
    "* `event`: If the script is invoked via an [inline callback](#Inline-callbacks) the corresponding event will be in the namespace\n",
    "\n",
    "#### Parameter callbacks\n",
    "\n",
    "If the key in the `_scripts` dictionary matches one of the parameters declared on the class the callback will automatically fire whenever the synced parameter value changes. As an example let's say we have a class which declares a `value` parameter, linked to the `value` attribute of an `<input>` HTML tag:\n",
    "\n",
    "```python\n",
    "    value = param.String()\n",
    "\n",
    "    _template = '<input id=\"custom_id\" value=\"${value}\"></input>'\n",
    "```\n",
    "\n",
    "We can now declare a `'value'` key in the `_scripts` dictionary, which will fire whenever the `value` is updated:\n",
    "\n",
    "```python\n",
    "   _scripts = {\n",
    "     'value': 'console.log(self, input, data.value)'\n",
    "   }\n",
    "```\n",
    "\n",
    "#### Lifecycle callbacks\n",
    "\n",
    "In addition to parameter callbacks there are a few reserved keys in the `_scripts` which are fired during rendering of the component:\n",
    "\n",
    "- `'render'`: This callback is invoked during initial rendering of the component.\n",
    "- `'after_layout'`: This callback is invoked after the component has been fully rendered and the layout is fully computed.\n",
    "- `'remove'`: This callback is invoked when the component is removed.\n",
    "\n",
    "#### Explicit calls\n",
    "\n",
    "It is also possible to explicitly invoke one script from the namespace of another script using the `self` object, e.g. we might define a `get_data` method that returns the data in a particular format:\n",
    "\n",
    "```python\n",
    "    _scripts = {\n",
    "      'get_data': 'return [data.x, data.y]'\n",
    "      'render'  : 'console.log(self.get_data())'\n",
    "    }\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Inline callbacks\n",
    "    \n",
    "Instead of declaring explicit DOM events Python callbacks can also be declared inline, e.g.:\n",
    "\n",
    "```html\n",
    "    _template = '<input id=\"custom_id\" onchange=\"${_input_change}\"></input>'\n",
    "```\n",
    "\n",
    "will look for an `_input_change` method on the `ReactiveHTML` component and call it when the event is fired.\n",
    "\n",
    "Additionally we can invoke the Javascript code declared in the `_scripts` dictionary by name using the `script` function, e.g.:\n",
    "\n",
    "```html\n",
    "    <input id=\"custom_id\" onchange=\"${script('some_script')}\"></input>\n",
    "```\n",
    "\n",
    "will invoke the following script if it is defined on the class:\n",
    "\n",
    "```python\n",
    "    _scripts = {\n",
    "        'some_script': 'console.log(self.state.event)'\n",
    "   }\n",
    "```\n",
    "\n",
    "Note that the event that triggered the callback will be made available in the namespace of the callback"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Examples\n",
    "\n",
    "#### Callbacks\n",
    "\n",
    "To see all of this in action we declare a `Slideshow` component which subscribes to `click` events on an `<img>` element and advances the image `index` on each click:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from panel.reactive import ReactiveHTML\n",
    "\n",
    "class Slideshow(ReactiveHTML):\n",
    "    \n",
    "    index = param.Integer(default=0)\n",
    "    \n",
    "    _template = '<img id=\"slideshow\" src=\"https://picsum.photos/800/300?image=${index}\" onclick=\"${_img_click}\"></img>'\n",
    "\n",
    "    def _img_click(self, event):\n",
    "        self.index += 1\n",
    "        \n",
    "Slideshow(width=800, height=300)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we can see this approach lets us quickly build custom HTML components with complex interactivity. However if we do not need any complex computations in Python we can also construct a pure JS equivalent:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class JSSlideshow(ReactiveHTML):\n",
    "    \n",
    "    index = param.Integer(default=0)\n",
    "    \n",
    "    _template = \"\"\"<img id=\"slideshow\" src=\"https://picsum.photos/800/300?image=${index}\" onclick=\"${script('click')}\"></img>\"\"\"\n",
    "\n",
    "    _scripts = {'click': 'data.index += 1'}\n",
    "    \n",
    "JSSlideshow(width=800, height=300)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Child templates\n",
    "\n",
    "If we want to provide a template for the children of an HTML node we have to use Jinja2 syntax to loop over the parameter. The component will automatically assign each `<option>` tag a unique id and insert the loop variable `option` into each of the tags:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Select(ReactiveHTML):\n",
    "\n",
    "    options = param.List(doc=\"Options to choose from.\")\n",
    "    \n",
    "    value = param.String(doc=\"Current selected option\")\n",
    "    \n",
    "    _template = \"\"\"\n",
    "    <select id=\"select\" value=\"${value}\" style=\"width: ${model.width}px\">\n",
    "      {% for option in options %}\n",
    "      <option id=\"option\">${option}</option>\n",
    "      {% endfor %}\n",
    "    </select>\n",
    "    \"\"\"\n",
    "    \n",
    "    _dom_events = {'select': ['change']}\n",
    "    \n",
    "select = Select(options=['A', 'B', 'C'])\n",
    "select"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The loop body can declare any number of HTML tags to add for each child object, e.g. to add labels or icons, however the child object (like the `${option}`) must always be wrapped by an HTML element (e.g. `<option>`) which must declare an `id`. Depending on your use case you can wrap each child in any HTML element you require, allowing complex nested components to be declared."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Pure Javascript events\n",
    "\n",
    "Next we will build a more complex example using pure Javascript events to draw on a canvas with configurable line width, color and the ability to clear and save the resulting drawing."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Canvas(ReactiveHTML):\n",
    "    \n",
    "    color = param.Color(default='#000000')\n",
    "    \n",
    "    line_width = param.Number(default=1, bounds=(0.1, 10))\n",
    "    \n",
    "    uri = param.String()\n",
    "\n",
    "    _template = \"\"\"\n",
    "    <canvas \n",
    "      id=\"canvas\"\n",
    "      style=\"border: 1px solid;\"\n",
    "      width=\"${model.width}\"\n",
    "      height=\"${model.height}\"\n",
    "      onmousedown=\"${script('start')}\"\n",
    "      onmousemove=\"${script('draw')}\"\n",
    "      onmouseup=\"${script('end')}\"\n",
    "    >\n",
    "    </canvas>\n",
    "    <button id=\"clear\" onclick='${script(\"clear\")}'>Clear</button>\n",
    "    <button id=\"save\" onclick='${script(\"save\")}'>Save</button>\n",
    "    \"\"\"\n",
    "    \n",
    "    _scripts = {\n",
    "        'render': \"\"\"\n",
    "          state.ctx = canvas.getContext(\"2d\")\n",
    "        \"\"\",\n",
    "        'start': \"\"\"\n",
    "          state.start = event\n",
    "          state.ctx.beginPath()\n",
    "          state.ctx.moveTo(state.start.offsetX, state.start.offsetY)\n",
    "        \"\"\",\n",
    "        'draw': \"\"\"\n",
    "          if (state.start == null)\n",
    "            return\n",
    "          state.ctx.lineTo(event.offsetX, event.offsetY)\n",
    "          state.ctx.stroke()\n",
    "        \"\"\",\n",
    "        'end': \"\"\"\n",
    "          delete state.start\n",
    "        \"\"\",\n",
    "        'clear': \"\"\"\n",
    "          state.ctx.clearRect(0, 0, canvas.width, canvas.height);\n",
    "        \"\"\",\n",
    "        'save': \"\"\"\n",
    "          data.uri = canvas.toDataURL();\n",
    "        \"\"\",\n",
    "        'line_width': \"\"\"\n",
    "          state.ctx.lineWidth = data.line_width;\n",
    "        \"\"\",\n",
    "        'color': \"\"\"\n",
    "          state.ctx.strokeStyle = data.color;\n",
    "        \"\"\"\n",
    "    }\n",
    "\n",
    "canvas = Canvas(width=400, height=400)\n",
    "\n",
    "# We create a separate HTML element which syncs with the uri parameter of the Canvas\n",
    "png_view = pn.pane.HTML()\n",
    "canvas.jslink(png_view, code={'uri': \"target.text = `<img src='${source.uri}'></img>`\"})\n",
    "\n",
    "pn.Column(\n",
    "    '# Drag on canvas to draw\\n To export the drawing to a png click save.',\n",
    "    pn.Row(\n",
    "        canvas.controls(['color', 'line_width']),\n",
    "        canvas,\n",
    "        png_view\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This example leverages all three ways a script is invoked:\n",
    "\n",
    "1. `'render'` is called on initialization\n",
    "2. `'start'`, `'draw'` and `'end'` are explicitly invoked using the `${script(...)}` syntax in inline callbacks\n",
    "3. `'line_width'` and `'color'` are invoked when the parameters change (i.e. when a widget is updated)\n",
    "\n",
    "It also makes extensive use of the available objects in the namespace:\n",
    "\n",
    "- `'render'`: Uses the `state` object to easily access the canvas rendering context in subsequent callbacks and accesses the `canvas` DOM node by name.\n",
    "- `'start'`, `'draw'`:  Use the `event` object provided by the `onmousedown` and `onmousemove` inline callbacks\n",
    "- `'save'`, `'line_width'`, `'color'`: Use the `data` object to get and set the current state of the parameter values"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## External dependencies\n",
    "\n",
    "Often the components you build will have dependencies on some external Javascript or CSS files. To make this possible `ReactiveHTML` components may declare `__javascript__`, `__javascript_modules__` and `__css__` attributes, specifying the external dependencies to load. Note that in a notebook as long as the component is imported before the call to `pn.extension` all its dependencies will be loaded automatically. If you want to require users to load the components as an extension explicitly via a `pn.extension` call you can declare an `_extension_name`.\n",
    "\n",
    "Below we will create a Material UI text field and declare the Javascript and CSS components to load:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MaterialTextField(ReactiveHTML):\n",
    "    \n",
    "    value = param.String(default='')\n",
    "    \n",
    "    _template = \"\"\"\n",
    "    <label id=\"text-field\" class=\"mdc-text-field mdc-text-field--filled\">\n",
    "      <span class=\"mdc-text-field__ripple\"></span>\n",
    "      <span class=\"mdc-floating-label\">Label</span>\n",
    "      <input id=\"text-input\" type=\"text\" class=\"mdc-text-field__input\" aria-labelledby=\"my-label\" value=\"${value}\"></input>\n",
    "      <span class=\"mdc-line-ripple\"></span>\n",
    "    </label>\n",
    "    \"\"\"\n",
    "    \n",
    "    _dom_events = {'text-input': ['change']}\n",
    "\n",
    "    # By declaring an _extension_name the component should be loaded explicitly with pn.extension('material-components')\n",
    "    _extension_name = 'material-components'\n",
    "\n",
    "    _scripts = {\n",
    "        'render': \"mdc.textField.MDCTextField.attachTo(text_field);\"\n",
    "    }\n",
    "    \n",
    "    __javascript__ = [\n",
    "        'https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js'\n",
    "    ]\n",
    "    \n",
    "    __css__ = [\n",
    "        'https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css'\n",
    "    ]\n",
    "    \n",
    "text_field = MaterialTextField()\n",
    "\n",
    "text_field"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In a notebook dependencies for this component will not be loaded unless the user explicitly loads them with a `pn.extension('material-components')`. In a server context you will also have to explicitly load this extension unless the component is rendered on initial page load, i.e. if the component is only added to the page in a callback you will also have to explicitly run `pn.extension('material-components')`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Building custom bokeh models\n",
    "\n",
    "The last approach to extending Panel with new components is to write custom Bokeh models. This involves writing, compiling and distributing custom Javascript and therefore requires considerably more effort than the other approaches. Detailed documentation on writing such components will be coming to the developer guide in the future."
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
