1
"""
2
Layout component to lay out objects in a set of tabs.
3
"""
4 7
import param
5

6 7
from bokeh.models import (
7
    Spacer as BkSpacer, Panel as BkPanel, Tabs as BkTabs
8
)
9

10 7
from ..viewable import Layoutable
11 7
from .base import NamedListPanel
12

13

14 7
class Tabs(NamedListPanel):
15
    """
16
    Panel of Viewables to be displayed in separate tabs.
17
    """
18

19 7
    closable = param.Boolean(default=False, doc="""
20
        Whether it should be possible to close tabs.""")
21

22 7
    dynamic = param.Boolean(default=False, doc="""
23
        Dynamically populate only the active tab.""")
24

25 7
    tabs_location = param.ObjectSelector(
26
        default='above', objects=['above', 'below', 'left', 'right'], doc="""
27
        The location of the tabs relative to the tab contents.""")
28

29 7
    height = param.Integer(default=None, bounds=(0, None))
30

31 7
    width = param.Integer(default=None, bounds=(0, None))
32

33 7
    _bokeh_model = BkTabs
34

35 7
    _source_transforms = {'dynamic': None, 'objects': None}
36

37 7
    _rename = {'name': None, 'objects': 'tabs', 'dynamic': None}
38

39 7
    _linked_props = ['active', 'tabs']
40

41 7
    _js_transforms = {'tabs': """
42
    var ids = [];
43
    for (var t of value) {{ ids.push(t.id) }};
44
    var value = ids;
45
    """}
46

47 7
    def __init__(self, *objects, **params):
48 7
        super(Tabs, self).__init__(*objects, **params)
49 7
        self.param.active.bounds = (0, len(self)-1)
50 7
        self.param.watch(self._update_active, ['dynamic', 'active'])
51

52 7
    def _init_properties(self):
53 7
        return {k: v for k, v in self.param.get_param_values()
54
                if v is not None and k != 'closable'}
55

56

57 7
    def _update_names(self, event):
58 7
        self.param.active.bounds = (0, len(event.new)-1)
59 7
        super(Tabs, self)._update_names(event)
60

61 7
    def _cleanup(self, root):
62 7
        super(Tabs, self)._cleanup(root)
63 7
        if root.ref['id'] in self._panels:
64 7
            del self._panels[root.ref['id']]
65

66
    #----------------------------------------------------------------
67
    # Callback API
68
    #----------------------------------------------------------------
69

70 7
    def _process_close(self, ref, attr, old, new):
71
        """
72
        Handle closed tabs.
73
        """
74 7
        model, _ = self._models.get(ref)
75 7
        if model:
76 7
            try:
77 7
                inds = [old.index(tab) for tab in new]
78 0
            except Exception:
79 0
                return old, None
80 7
            old = self.objects
81 7
            new = [old[i] for i in inds]
82 7
        return old, new
83

84 7
    def _comm_change(self, doc, ref, comm, attr, old, new):
85 7
        if attr in self._changing.get(ref, []):
86 7
            self._changing[ref].remove(attr)
87 7
            return
88 7
        if attr == 'tabs':
89 7
            old, new = self._process_close(ref, attr, old, new)
90 7
            if new is None:
91 0
                return
92 7
        super(Tabs, self)._comm_change(doc, ref, comm, attr, old, new)
93

94 7
    def _server_change(self, doc, ref, attr, old, new):
95 7
        if attr in self._changing.get(ref, []):
96 0
            self._changing[ref].remove(attr)
97 0
            return
98 7
        if attr == 'tabs':
99 7
            old, new = self._process_close(ref, attr, old, new)
100 7
            if new is None:
101 0
                return
102 7
        super(Tabs, self)._server_change(doc, ref, attr, old, new)
103

104 7
    def _update_active(self, *events):
105 7
        for event in events:
106 7
            if event.name == 'dynamic' or (self.dynamic and event.name == 'active'):
107 7
                self.param.trigger('objects')
108 7
                return
109

110
    #----------------------------------------------------------------
111
    # Model API
112
    #----------------------------------------------------------------
113

114 7
    def _update_model(self, events, msg, root, model, doc, comm=None):
115 7
        msg = dict(msg)
116 7
        if 'closable' in msg:
117 0
            closable = msg.pop('closable')
118 0
            for child in model.tabs:
119 0
                child.closable = closable
120 7
        super(Tabs, self)._update_model(events, msg, root, model, doc, comm)
121

122 7
    def _get_objects(self, model, old_objects, doc, root, comm=None):
123
        """
124
        Returns new child models for the layout while reusing unchanged
125
        models and cleaning up any dropped objects.
126
        """
127 7
        from ..pane.base import RerenderError, panel
128 7
        new_models = []
129 7
        if len(self._names) != len(self):
130 0
            raise ValueError('Tab names do not match objects, ensure '
131
                             'that the Tabs.objects are not modified '
132
                             'directly. Found %d names, expected %d.' %
133
                             (len(self._names), len(self)))
134 7
        for i, (name, pane) in enumerate(zip(self._names, self)):
135 7
            pane = panel(pane, name=name)
136 7
            self.objects[i] = pane
137

138 7
        for obj in old_objects:
139 7
            if obj not in self.objects:
140 7
                obj._cleanup(root)
141

142 7
        current_objects = list(self)
143 7
        panels = self._panels[root.ref['id']]
144 7
        for i, (name, pane) in enumerate(zip(self._names, self)):
145 7
            hidden = self.dynamic and i != self.active
146 7
            if (pane in old_objects and id(pane) in panels and
147
                ((hidden and isinstance(panels[id(pane)].child, BkSpacer)) or
148
                 (not hidden and not isinstance(panels[id(pane)].child, BkSpacer)))):
149 7
                panel = panels[id(pane)]
150 7
                new_models.append(panel)
151 7
                continue
152 7
            elif self.dynamic and i != self.active:
153 7
                child = BkSpacer(**{k: v for k, v in pane.param.get_param_values()
154
                                    if k in Layoutable.param})
155
            else:
156 7
                try:
157 7
                    child = pane._get_model(doc, root, model, comm)
158 0
                except RerenderError:
159 0
                    return self._get_objects(model, current_objects[:i], doc, root, comm)
160 7
            panel = panels[id(pane)] = BkPanel(
161
                title=name, name=pane.name, child=child, closable=self.closable
162
            )
163 7
            new_models.append(panel)
164 7
        return new_models

Read our documentation on viewing source code .

Loading