{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "When deploying a Panel app or dashboard as a Bokeh application, it is rendered into a default template that serves the JS and CSS resources as well as the actual Panel object being shown. However, it is often desirable to customize the layout of the deployed app, or even to embed multiple separate panels into an app. The ``Template`` component in Panel allows customizing this default template, including the ability to rendering multiple components in a single document easily.\n", "\n", "## What is a template?\n", "\n", "A template is defined using the [Jinja2](http://jinja.pocoo.org/docs/) templating language, which makes it straightforward to extend the default template in various ways or even replace it entirely. Before modifying the default template, let us take a look at it in its entirety:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```html\n", "\n", "\n", "{% block head %}\n", "\n", " {% block inner_head %}\n", " \n", " {% block title %}{{ title | e if title else \"Panel App\" }}{% endblock %}\n", " {% block preamble %}{% endblock %}\n", " {% block resources %}\n", " {% block css_resources %}\n", " {{ bokeh_css | indent(8) if bokeh_css }}\n", " {% endblock %}\n", " {% block js_resources %}\n", " {{ bokeh_js | indent(8) if bokeh_js }}\n", " {% endblock %}\n", " {% endblock %}\n", " {% block postamble %}{% endblock %}\n", " {% endblock %}\n", "\n", "{% endblock %}\n", "{% block body %}\n", "\n", " {% block inner_body %}\n", " {% block contents %}\n", " {% for doc in docs %}\n", " {{ embed(doc) if doc.elementid }}\n", " {% for root in doc.roots %}\n", " {{ embed(root) | indent(10) }}\n", " {% endfor %}\n", " {% endfor %}\n", " {% endblock %}\n", " {{ plot_script | indent(8) }}\n", " {% endblock %}\n", "\n", "{% endblock %}\n", "\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see the template defines a number of custom blocks, which can be overridden by extending this default template." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using custom templates" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import panel as pn\n", "import holoviews as hv\n", "\n", "pn.extension()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once we have loaded Panel we can start defining a custom template. As mentioned before, it is usually easiest to simply extend an existing template by overriding certain blocks. To begin with we start by using `{% extends base %}` to declare that we are merely extending an existing template rather than defining a whole new one; otherwise we would have to repeat the entire header sections of the full template to ensure all the appropriate resources are loaded.\n", "\n", "In this case we will extend the postamble block of the header to load some additional resources, and the contents block to redefine how the components will be laid out. Specifically, we will load bootstrap.css in the preamble allowing us to use the bootstrap grid system to lay out the output." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "template = \"\"\"\n", "{% extends base %}\n", "\n", "\n", "{% block postamble %}\n", "\n", "{% endblock %}\n", "\n", "\n", "{% block contents %}\n", "{{ app_title }}\n", "

This is a Panel app with a custom template allowing us to compose multiple Panel objects into a single HTML document.

\n", "
\n", "
\n", "
\n", "
\n", " {{ embed(roots.A) }}\n", "
\n", "
\n", " {{ embed(roots.B) }}\n", "
\n", "
\n", "
\n", "{% endblock %}\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you look closely we defined two different roots in the template using the `embed` macro. In order to be able to render the template we now have to first construct the `pn.Template` object with the template HTML and then populate the template with the two required roots, in this case `'A'` and `'B'` by using the `add_panel` method. If either of the roots is not defined the app is invalid and will fail to launch. The app will also fail to launch if any panels are added that are not referenced in the template.\n", "\n", "Additionally we have also declared a new `app_title` variable in the template, which we can populate by using the `add_variable` method:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tmpl = pn.Template(template)\n", "\n", "tmpl.add_variable('app_title', '

Custom Template App

')\n", "\n", "tmpl.add_panel('A', hv.Curve([1, 2, 3]))\n", "tmpl.add_panel('B', hv.Curve([1, 2, 3]))\n", "\n", "tmpl.servable();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Embedding a different CSS framework (like Bootstrap) in the notebook can have undesirable side-effects so a `Template` may also be given a separate `nb_template` that will be used when rendering inside the notebook:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "nb_template = \"\"\"\n", "{% extends base %}\n", "\n", "{% block contents %}\n", "{{ app_title }}\n", "

This is a Panel app with a custom template allowing us to compose multiple Panel objects into a single HTML document.

\n", "
\n", "
\n", "
\n", " {{ embed(roots.A) }}\n", "
\n", "
\n", " {{ embed(roots.B) }}\n", "
\n", "
\n", "{% endblock %}\n", "\"\"\"\n", "\n", "tmpl = pn.Template(template, nb_template=nb_template)\n", "\n", "tmpl.add_variable('app_title', '

Custom Template App

')\n", "\n", "tmpl.add_panel('A', hv.Curve([1, 2, 3]))\n", "tmpl.add_panel('B', hv.Curve([1, 2, 3]))\n", "\n", "tmpl.servable()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Loading template from file\n", "\n", "If the template is larger it is often cleaner to define it in a separate file. You can either read it in as a string, or use the official loading mechanism available for Jinja2 templates by defining a `Environment` along with a `loader`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from jinja2 import Environment, FileSystemLoader\n", "\n", "env = Environment(loader=FileSystemLoader('.'))\n", "jinja_template = env.get_template('sample_template.html')\n", "\n", "tmpl = pn.Template(jinja_template)\n", "\n", "tmpl.add_panel('A', hv.Curve([1, 2, 3]))\n", "tmpl.add_panel('B', hv.Curve([1, 2, 3]))" ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 4 }