1
"""
2
Templates allow multiple Panel objects to be embedded into custom HTML
3
documents.
4
"""
5 7
from __future__ import absolute_import, division, unicode_literals
6

7 7
import os
8 7
import sys
9 7
import uuid
10

11 7
from collections import OrderedDict
12 7
from functools import partial
13 7
from urllib.parse import urljoin
14

15 7
import param
16

17 7
from bokeh.document.document import Document as _Document
18 7
from bokeh.io import curdoc as _curdoc
19 7
from bokeh.settings import settings as _settings
20 7
from jinja2.environment import Template as _Template
21 7
from six import string_types
22 7
from pyviz_comms import JupyterCommManager as _JupyterCommManager
23

24 7
from ..config import config, panel_extension
25 7
from ..io.model import add_to_doc
26 7
from ..io.notebook import render_template
27 7
from ..io.resources import CDN_DIST, LOCAL_DIST
28 7
from ..io.save import save
29 7
from ..io.state import state
30 7
from ..layout import Column, ListLike, GridSpec
31 7
from ..models.comm_manager import CommManager
32 7
from ..pane import panel as _panel, HTML, Str, HoloViews
33 7
from ..pane.image import ImageBase
34 7
from ..util import url_path
35

36 7
from ..viewable import ServableMixin, Viewable
37 7
from ..widgets import Button
38 7
from ..widgets.indicators import BooleanIndicator, LoadingSpinner
39 7
from .theme import DefaultTheme, Theme
40

41 7
_server_info = (
42
    '<b>Running server:</b> <a target="_blank" href="https://localhost:{port}">'
43
    'https://localhost:{port}</a>'
44
)
45

46 7
FAVICON_URL = "/static/extensions/panel/icons/favicon.ico"
47

48

49 7
class BaseTemplate(param.Parameterized, ServableMixin):
50

51 7
    location = param.Boolean(default=False, doc="""
52
        Whether to add a Location component to this Template.
53
        Note if this is set to true, the Jinja2 template must
54
        either insert all available roots or explicitly embed
55
        the location root with : {{ embed(roots.location) }}.""")
56

57
    # Dictionary of property overrides by bokeh Model type
58 7
    _modifiers = {}
59

60 7
    __abstract = True
61

62 7
    def __init__(self, template=None, items=None, nb_template=None, **params):
63 7
        super(BaseTemplate, self).__init__(**params)
64 7
        if isinstance(template, string_types):
65 7
            self._code = template
66 7
            template = _Template(template)
67
        else:
68 0
            self._code = None
69 7
        self.template = template
70 7
        if isinstance(nb_template, string_types):
71 0
            nb_template = _Template(nb_template)
72 7
        self.nb_template = nb_template or template
73 7
        self._render_items = OrderedDict()
74 7
        self._render_variables = {}
75 7
        self._server = None
76 7
        self._layout = self._build_layout()
77

78 7
    def _build_layout(self):
79 7
        str_repr = Str(repr(self))
80 7
        server_info = HTML('')
81 7
        button = Button(name='Launch server')
82 7
        def launch(event):
83 0
            if self._server:
84 0
                button.name = 'Launch server'
85 0
                server_info.object = ''
86 0
                self._server.stop()
87 0
                self._server = None
88
            else:
89 0
                button.name = 'Stop server'
90 0
                self._server = self._get_server(start=True, show=True)
91 0
                server_info.object = _server_info.format(port=self._server.port)
92 7
        button.param.watch(launch, 'clicks')
93 7
        return Column(str_repr, server_info, button)
94

95 7
    def __repr__(self):
96 7
        cls = type(self).__name__
97 7
        spacer = '\n    '
98 7
        objs = ['[%s] %s' % (name, obj[0].__repr__(1))
99
                for name, obj in self._render_items.items()
100
                if not name.startswith('_')]
101 7
        template = '{cls}{spacer}{objs}'
102 7
        return template.format(
103
            cls=cls, objs=('%s' % spacer).join(objs), spacer=spacer)
104

105 7
    @classmethod
106 2
    def _apply_hooks(cls, viewable, root):
107 0
        ref = root.ref['id']
108 0
        for o in viewable.select():
109 0
            cls._apply_modifiers(o, ref)
110

111 7
    @classmethod
112 2
    def _apply_modifiers(cls, viewable, mref):
113 0
        if mref not in viewable._models:
114 0
            return
115 0
        model, _ = viewable._models[mref]
116 0
        modifiers = cls._modifiers.get(type(viewable), {})
117 0
        child_modifiers = modifiers.get('children', {})
118 0
        if child_modifiers:
119 0
            for child in viewable:
120 0
                child_params = {
121
                    k: v for k, v in child_modifiers.items()
122
                    if getattr(child, k) == child.param[k].default
123
                }
124 0
                child.param.set_param(**child_params)
125 0
                child_props = child._process_param_change(child_params)
126 0
                child._models[mref][0].update(**child_props)
127 0
        params = {
128
            k: v for k, v in modifiers.items() if k != 'children' and
129
            getattr(viewable, k) == viewable.param[k].default
130
        }
131 0
        viewable.param.set_param(**params)
132 0
        props = viewable._process_param_change(params)
133 0
        model.update(**props)
134

135 7
    def _apply_root(self, name, viewable, tags):
136 7
        pass
137

138 7
    def _init_doc(self, doc=None, comm=None, title=None, notebook=False, location=True):
139 7
        doc = doc or _curdoc()
140 7
        title = title or 'Panel Application'
141 7
        if location and self.location:
142 0
            loc = self._add_location(doc, location)
143 0
            doc.on_session_destroyed(loc._server_destroy)
144 7
        doc.title = title
145 7
        col = Column()
146 7
        preprocess_root = col.get_root(doc, comm)
147 7
        ref = preprocess_root.ref['id']
148 7
        for name, (obj, tags) in self._render_items.items():
149 7
            if self._apply_hooks not in obj._hooks:
150 7
                obj._hooks.append(self._apply_hooks)
151 7
            model = obj.get_root(doc, comm, preprocess=False)
152 7
            mref = model.ref['id']
153 7
            doc.on_session_destroyed(obj._server_destroy)
154 7
            for sub in obj.select(Viewable):
155 7
                submodel = sub._models.get(mref)
156 7
                if submodel is None:
157 0
                    continue
158 7
                sub._models[ref] = submodel
159 7
                if isinstance(sub, HoloViews) and mref in sub._plots:
160 7
                    sub._plots[ref] = sub._plots.get(mref)
161 7
            col.objects.append(obj)
162 7
            obj._documents[doc] = model
163 7
            model.name = name
164 7
            model.tags = tags
165 7
            self._apply_root(name, model, tags)
166 7
            add_to_doc(model, doc, hold=bool(comm))
167

168 7
        state._fake_roots.append(ref)
169 7
        state._views[ref] = (col, preprocess_root, doc, comm)
170

171 7
        col._preprocess(preprocess_root)
172 7
        col._documents[doc] = preprocess_root
173 7
        doc.on_session_destroyed(col._server_destroy)
174

175 7
        if notebook:
176 7
            doc.template = self.nb_template
177
        else:
178 0
            doc.template = self.template
179 7
        doc._template_variables.update(self._render_variables)
180 7
        return doc
181

182 7
    def _repr_mimebundle_(self, include=None, exclude=None):
183 0
        loaded = panel_extension._loaded
184 0
        if not loaded and 'holoviews' in sys.modules:
185 0
            import holoviews as hv
186 0
            loaded = hv.extension._loaded
187 0
        if not loaded:
188 0
            param.main.param.warning(
189
                'Displaying Panel objects in the notebook requires '
190
                'the panel extension to be loaded. Ensure you run '
191
                'pn.extension() before displaying objects in the '
192
                'notebook.'
193
            )
194 0
            return None
195

196 0
        try:
197 0
            assert get_ipython().kernel is not None # noqa
198 0
            state._comm_manager = _JupyterCommManager
199 0
        except Exception:
200 0
            pass
201

202 0
        from IPython.display import display
203

204 0
        doc = _Document()
205 0
        comm = state._comm_manager.get_server_comm()
206 0
        self._init_doc(doc, comm, notebook=True)
207 0
        ref = doc.roots[0].ref['id']
208 0
        manager = CommManager(
209
            comm_id=comm.id, plot_id=ref, name='comm_manager'
210
        )
211 0
        client_comm = state._comm_manager.get_client_comm(
212
            on_msg=partial(self._on_msg, ref, manager),
213
            on_error=partial(self._on_error, ref),
214
            on_stdout=partial(self._on_stdout, ref)
215
        )
216 0
        manager.client_comm_id = client_comm.id
217 0
        doc.add_root(manager)
218

219 0
        if config.console_output != 'disable':
220 0
            handle = display(display_id=uuid.uuid4().hex)
221 0
            state._handles[ref] = (handle, [])
222

223 0
        return render_template(doc, comm, manager)
224

225
    #----------------------------------------------------------------
226
    # Public API
227
    #----------------------------------------------------------------
228

229 7
    def save(self, filename, title=None, resources=None, embed=False,
230
             max_states=1000, max_opts=3, embed_json=False,
231
             json_prefix='', save_path='./', load_path=None):
232
        """
233
        Saves Panel objects to file.
234

235
        Arguments
236
        ---------
237
        filename: string or file-like object
238
           Filename to save the plot to
239
        title: string
240
           Optional title for the plot
241
        resources: bokeh resources
242
           One of the valid bokeh.resources (e.g. CDN or INLINE)
243
        embed: bool
244
           Whether the state space should be embedded in the saved file.
245
        max_states: int
246
           The maximum number of states to embed
247
        max_opts: int
248
           The maximum number of states for a single widget
249
        embed_json: boolean (default=True)
250
           Whether to export the data to json files
251
        json_prefix: str (default='')
252
           Prefix for the auto-generated json directory
253
        save_path: str (default='./')
254
           The path to save json files to
255
        load_path: str (default=None)
256
           The path or URL the json files will be loaded from.
257
        """
258 0
        if embed:
259 0
            raise ValueError("Embedding is not yet supported on Template.")
260

261 0
        return save(self, filename, title, resources, self.template,
262
                    self._render_variables, embed, max_states, max_opts,
263
                    embed_json, json_prefix, save_path, load_path)
264

265 7
    def server_doc(self, doc=None, title=None, location=True):
266
        """
267
        Returns a servable bokeh Document with the panel attached
268

269
        Arguments
270
        ---------
271
        doc : bokeh.Document (optional)
272
          The Bokeh Document to attach the panel to as a root,
273
          defaults to bokeh.io.curdoc()
274
        title : str
275
          A string title to give the Document
276
        location : boolean or panel.io.location.Location
277
          Whether to create a Location component to observe and
278
          set the URL location.
279

280
        Returns
281
        -------
282
        doc : bokeh.Document
283
          The Bokeh document the panel was attached to
284
        """
285 0
        return self._init_doc(doc, title=title, location=location)
286

287 7
    def select(self, selector=None):
288
        """
289
        Iterates over the Template and any potential children in the
290
        applying the Selector.
291

292
        Arguments
293
        ---------
294
        selector: type or callable or None
295
          The selector allows selecting a subset of Viewables by
296
          declaring a type or callable function to filter by.
297

298
        Returns
299
        -------
300
        viewables: list(Viewable)
301
        """
302 0
        objects = []
303 0
        for obj, _ in self._render_items.values():
304 0
            objects += obj.select(selector)
305 0
        return objects
306

307

308

309 7
class BasicTemplate(BaseTemplate):
310
    """
311
    BasicTemplate provides a baseclass for templates with a basic
312
    organization including a header, sidebar and main area. Unlike the
313
    more generic Template class these default templates make it easy
314
    for a user to generate an application with a polished look and
315
    feel without having to write any Jinja2 template themselves.
316
    """
317

318 7
    busy_indicator = param.ClassSelector(default=LoadingSpinner(width=20, height=20),
319
                                         class_=BooleanIndicator, constant=True, doc="""
320
        Visual indicator of application busy state.""")
321

322 7
    header = param.ClassSelector(class_=ListLike, constant=True, doc="""
323
        A list-like container which populates the header bar.""")
324

325 7
    main = param.ClassSelector(class_=ListLike, constant=True, doc="""
326
        A list-like container which populates the main area.""")
327

328 7
    main_max_width = param.String(default="", doc="""
329
        The maximum width of the main area. For example '800px' or '80%'.
330
        If the string is '' (default) no max width is set.""")
331

332 7
    sidebar = param.ClassSelector(class_=ListLike, constant=True, doc="""
333
        A list-like container which populates the sidebar.""")
334

335 7
    modal = param.ClassSelector(class_=ListLike, constant=True, doc="""
336
        A list-like container which populates the modal""")
337

338 7
    logo = param.String(constant=True, doc="""
339
        URI of logo to add to the header (if local file, logo is
340
        base64 encoded as URI).""")
341

342 7
    favicon = param.String(default=FAVICON_URL, constant=True, doc="""
343
        URI of favicon to add to the document head (if local file, favicon is
344
        base64 encoded as URI).""")
345

346 7
    title = param.String(default="Panel Application", doc="""
347
        A title to show in the header. Also added to the document head
348
        meta settings and as the browser tab title.""")
349

350 7
    site = param.String(default="", doc="""
351
        The name of the site. Will be shown in the header and link to the
352
        root of the site. Default is '', i.e. not shown.""")
353

354 7
    meta_description = param.String(doc="""
355
        A meta description to add to the document head for search
356
        engine optimization. For example 'P.A. Nelson'.""")
357

358 7
    meta_keywords = param.String(doc="""
359
        Meta keywords to add to the document head for search engine
360
        optimization.""")
361

362 7
    meta_author = param.String(doc="""
363
        A meta author to add to the the document head for search
364
        engine optimization. For example 'P.A. Nelson'.""")
365

366 7
    meta_refresh = param.String(doc="""
367
        A meta refresh rate to add to the document head. For example
368
        '30' will instruct the browser to refresh every 30
369
        seconds. Default is '', i.e. no automatic refresh.""")
370

371 7
    meta_viewport = param.String(doc="""
372
        A meta viewport to add to the header.""")
373

374 7
    base_url = param.String(doc="""
375
        Specifies the base URL for all relative URLs in a
376
        page. Default is '', i.e. not the domain.""")
377

378 7
    base_target = param.ObjectSelector(default="_self",
379
        objects=["_blank", "_self", "_parent", "_top"], doc="""
380
        Specifies the base Target for all relative URLs in a page.""")
381

382 7
    header_background = param.String(doc="""
383
        Optional header background color override.""")
384

385 7
    header_color = param.String(doc="""
386
        Optional header text color override.""")
387

388 7
    theme = param.ClassSelector(class_=Theme, default=DefaultTheme,
389
                                constant=True, is_instance=False, instantiate=False)
390

391 7
    location = param.Boolean(default=True, readonly=True)
392

393 7
    _css = None
394

395 7
    _template = None
396

397 7
    _modifiers = {}
398

399 7
    _resources = {'css': {}, 'js': {}}
400

401 7
    __abstract = True
402

403 7
    def __init__(self, **params):
404 7
        template = self._template.read_text()
405 7
        if 'header' not in params:
406 7
            params['header'] = ListLike()
407
        else:
408 7
            params['header'] = self._get_params(params['header'], self.param.header.class_)
409 7
        if 'main' not in params:
410 7
            params['main'] = ListLike()
411
        else:
412 7
            params['main'] = self._get_params(params['main'], self.param.main.class_)
413 7
        if 'sidebar' not in params:
414 7
            params['sidebar'] = ListLike()
415
        else:
416 7
            params['sidebar'] = self._get_params(params['sidebar'], self.param.sidebar.class_)
417 7
        if 'modal' not in params:
418 7
            params['modal'] = ListLike()
419
        else:
420 0
            params['modal'] = self._get_params(params['modal'], self.param.modal.class_)
421 7
        super(BasicTemplate, self).__init__(template=template, **params)
422 7
        if self.busy_indicator:
423 7
            state.sync_busy(self.busy_indicator)
424 7
        self._js_area = HTML(margin=0, width=0, height=0)
425 7
        self._render_items['js_area'] = (self._js_area, [])
426 7
        self._update_vars()
427 7
        self._update_busy()
428 7
        self.main.param.watch(self._update_render_items, ['objects'])
429 7
        self.modal.param.watch(self._update_render_items, ['objects'])
430 7
        self.sidebar.param.watch(self._update_render_items, ['objects'])
431 7
        self.header.param.watch(self._update_render_items, ['objects'])
432 7
        self.main.param.trigger('objects')
433 7
        self.sidebar.param.trigger('objects')
434 7
        self.header.param.trigger('objects')
435 7
        self.modal.param.trigger('objects')
436 7
        self.param.watch(self._update_vars, ['title', 'site', 'header_background',
437
                                             'header_color', 'main_max_width'])
438

439 7
    def _init_doc(self, doc=None, comm=None, title=None, notebook=False, location=True):
440 0
        title = title or self.title
441 0
        doc = super(BasicTemplate, self)._init_doc(doc, comm, title, notebook, location)
442 0
        if self.theme:
443 0
            theme = self.theme.find_theme(type(self))
444 0
            if theme and theme.bokeh_theme:
445 0
                doc.theme = theme.bokeh_theme
446 0
        return doc
447

448 7
    def _template_resources(self):
449 7
        name = type(self).__name__.lower()
450 7
        resources = _settings.resources(default="server")
451 7
        base_url = state.base_url[1:] if state.base_url.startswith('/') else state.base_url        
452 7
        if resources == 'server':
453 7
            dist_path = urljoin(base_url, LOCAL_DIST)
454
        else:
455 0
            dist_path = CDN_DIST
456

457
        # External resources
458 7
        css_files = dict(self._resources['css'])
459 7
        for cssname, css in css_files.items():
460 7
            css_path = url_path(css)
461 7
            css_files[cssname] = dist_path + f'bundled/{name}/{css_path}'
462 7
        js_files = dict(self._resources['js'])
463 7
        for jsname, js in js_files.items():
464 7
            js_path = url_path(js)
465 7
            js_files[jsname] = dist_path + f'bundled/{name}/{js_path}'
466

467
        # CSS files
468 7
        base_css = os.path.basename(self._css)
469 7
        css_files['base'] = dist_path + f'bundled/{name}/{base_css}'
470 7
        if self.theme:
471 7
            theme = self.theme.find_theme(type(self))
472 7
            if theme and theme.css:
473 7
                basename = os.path.basename(theme.css)
474 7
                css_files['theme'] = dist_path + f'bundled/{name}/{basename}'
475 7
        return {'css': css_files, 'js': js_files}
476

477 7
    def _update_vars(self, *args):
478 7
        self._render_variables['app_title'] = self.title
479 7
        self._render_variables['meta_name'] = self.title
480 7
        self._render_variables['site_title'] = self.site
481 7
        self._render_variables['meta_description'] = self.meta_description
482 7
        self._render_variables['meta_keywords'] = self.meta_keywords
483 7
        self._render_variables['meta_author'] = self.meta_author
484 7
        self._render_variables['meta_refresh'] = self.meta_refresh
485 7
        self._render_variables['meta_viewport'] = self.meta_viewport
486 7
        self._render_variables['base_url'] = self.base_url
487 7
        self._render_variables['base_target'] = self.base_target
488 7
        if os.path.isfile(self.logo):
489 0
            img = _panel(self.logo)
490 0
            if not isinstance(img, ImageBase):
491 0
                raise ValueError("Could not determine file type of logo: {self.logo}.")
492 0
            logo = img._b64()
493
        else:
494 7
            logo = self.logo
495 7
        if os.path.isfile(self.favicon):
496 0
            img = _panel(self.favicon)
497 0
            if not isinstance(img, ImageBase):
498 0
                raise ValueError("Could not determine file type of favicon: {self.favicon}.")
499 0
            favicon = img._b64()
500
        else:
501 7
            if _settings.resources(default='server') == 'cdn' and self.favicon == FAVICON_URL:
502 0
                favicon = CDN_DIST+"icons/favicon.ico"
503
            else:
504 7
                favicon = self.favicon
505 7
        self._render_variables['template_resources'] = self._template_resources()
506 7
        self._render_variables['app_logo'] = logo
507 7
        self._render_variables['app_favicon'] = favicon
508 7
        self._render_variables['app_favicon_type'] = self._get_favicon_type(self.favicon)
509 7
        self._render_variables['header_background'] = self.header_background
510 7
        self._render_variables['header_color'] = self.header_color
511 7
        self._render_variables['main_max_width'] = self.main_max_width
512

513 7
    def _update_busy(self):
514 7
        if self.busy_indicator:
515 7
            self._render_items['busy_indicator'] = (self.busy_indicator, [])
516 0
        elif 'busy_indicator' in self._render_items:
517 0
            del self._render_items['busy_indicator']
518 7
        self._render_variables['busy'] = self.busy_indicator is not None
519

520 7
    def _update_render_items(self, event):
521 7
        if event.obj is self and event.name == 'busy_indicator':
522 0
            return self._update_busy()
523 7
        if event.obj is self.main:
524 7
            tag = 'main'
525 7
        elif event.obj is self.sidebar:
526 7
            tag = 'nav'
527 7
        elif event.obj is self.header:
528 7
            tag = 'header'
529 7
        elif event.obj is self.modal:
530 7
            tag = 'modal'
531

532 7
        old = event.old if isinstance(event.old, list) else list(event.old.values())
533 7
        for obj in old:
534 7
            ref = str(id(obj))
535 7
            if ref in self._render_items:
536 7
                del self._render_items[ref]
537

538 7
        new = event.new if isinstance(event.new, list) else event.new.values()
539 7
        for o in new:
540 7
            if o not in old:
541 7
                for hvpane in o.select(HoloViews):
542 7
                    if self.theme.bokeh_theme:
543 0
                        hvpane.theme = self.theme.bokeh_theme
544

545 7
        labels = {}
546 7
        for obj in new:
547 7
            ref = str(id(obj))
548 7
            if obj.name.startswith(type(obj).__name__):
549 7
                labels[ref] = 'Content'
550
            else:
551 7
                labels[ref] = obj.name
552 7
            self._render_items[ref] = (obj, [tag])
553 7
        tags = [tags for _, tags in self._render_items.values()]
554 7
        self._render_variables['nav'] = any('nav' in ts for ts in tags)
555 7
        self._render_variables['header'] = any('header' in ts for ts in tags)
556 7
        self._render_variables['root_labels'] = labels
557

558 7
    def open_modal(self):
559
        """
560
        Opens the modal area
561
        """
562 0
        self._js_area.object = """
563
        <script>
564
          var modal = document.getElementById("pn-Modal");
565
          modal.style.display = "block";
566
        </script>
567
        """
568 0
        self._js_area.object = ""
569

570 7
    def close_modal(self):
571
        """
572
        Closes the modal area
573
        """
574 0
        self._js_area.object = """
575
        <script>
576
          var modal = document.getElementById("pn-Modal");
577
          modal.style.display = "none";
578
        </script>
579
        """
580 0
        self._js_area.object = ""
581

582 7
    @staticmethod
583 2
    def _get_favicon_type(favicon):
584 7
        if not favicon:
585 0
            return ""
586 7
        elif favicon.endswith(".png"):
587 0
            return "image/png"
588 7
        elif favicon.endswith("jpg"):
589 0
            return "image/jpg"
590 7
        elif favicon.endswith("gif"):
591 0
            return "image/gif"
592 7
        elif favicon.endswith("svg"):
593 0
            return "image/svg"
594 7
        elif favicon.endswith("ico"):
595 7
            return "image/x-icon"
596
        else:
597 0
            raise ValueError("favicon type not supported.")
598

599 7
    @staticmethod
600 2
    def _get_params(value, class_):
601 7
        if isinstance(value, class_):
602 7
            return value
603 7
        if isinstance(value, tuple):
604 0
            value = [*value]
605 7
        elif not isinstance(value, list):
606 7
            value = [value]
607

608
        # Important to fx. convert @param.depends functions
609 7
        value = [_panel(item) for item in value]
610

611 7
        if class_ is ListLike:
612 7
            return ListLike(objects=value)
613 7
        if class_ is GridSpec:
614 7
            grid = GridSpec(ncols=12, mode='override')
615 7
            for index, item in enumerate(value):
616 7
                grid[index, :]=item
617 7
            return grid
618

619 0
        return value
620

621

622 7
class Template(BaseTemplate):
623
    """
624
    A Template is a high-level component to render multiple Panel
625
    objects into a single HTML document defined through a Jinja2
626
    template. The Template object is given a Jinja2 template and then
627
    allows populating this template by adding Panel objects, which are
628
    given unique names. These unique names may then be referenced in
629
    the template to insert the rendered Panel object at a specific
630
    location. For instance, given a Jinja2 template that defines roots
631
    A and B like this:
632

633
        <div> {{ embed(roots.A) }} </div>
634
        <div> {{ embed(roots.B) }} </div>
635

636
    We can then populate the template by adding panel 'A' and 'B' to
637
    the Template object:
638

639
        template.add_panel('A', pn.panel('A'))
640
        template.add_panel('B', pn.panel('B'))
641

642
    Once a template has been fully populated it can be rendered using
643
    the same API as other Panel objects. Note that all roots that have
644
    been declared using the {{ embed(roots.A) }} syntax in the Jinja2
645
    template must be defined when rendered.
646

647
    Since embedding complex CSS frameworks inside a notebook can have
648
    undesirable side-effects and a notebook does not afford the same
649
    amount of screen space a Template may given separate template
650
    and nb_template objects. This allows for different layouts when
651
    served as a standalone server and when used in the notebook.
652
    """
653

654 7
    def __init__(self, template=None, nb_template=None, items=None, **params):
655 7
        super(Template, self).__init__(template=template, nb_template=nb_template, items=items, **params)
656 7
        items = {} if items is None else items
657 7
        for name, item in items.items():
658 0
            self.add_panel(name, item)
659

660
    #----------------------------------------------------------------
661
    # Public API
662
    #----------------------------------------------------------------
663

664 7
    def add_panel(self, name, panel, tags=[]):
665
        """
666
        Add panels to the Template, which may then be referenced by
667
        the given name using the jinja2 embed macro.
668

669
        Arguments
670
        ---------
671
        name : str
672
          The name to refer to the panel by in the template
673
        panel : panel.Viewable
674
          A Panel component to embed in the template.
675
        """
676 7
        if name in self._render_items:
677 0
            raise ValueError('The name %s has already been used for '
678
                             'another panel. Ensure each panel '
679
                             'has a unique name by which it can be '
680
                             'referenced in the template.' % name)
681 7
        self._render_items[name] = (_panel(panel), tags)
682 7
        self._layout[0].object = repr(self)
683

684 7
    def add_variable(self, name, value):
685
        """
686
        Add parameters to the template, which may then be referenced
687
        by the given name in the Jinja2 template.
688

689
        Arguments
690
        ---------
691
        name : str
692
          The name to refer to the panel by in the template
693
        value : object
694
          Any valid Jinja2 variable type.
695
        """
696 0
        if name in self._render_variables:
697 0
            raise ValueError('The name %s has already been used for '
698
                             'another variable. Ensure each variable '
699
                             'has a unique name by which it can be '
700
                             'referenced in the template.' % name)
701 0
        self._render_variables[name] = value

Read our documentation on viewing source code .

Loading