1
|
|
"""
|
2
|
|
The input widgets generally allow entering arbitrary information into
|
3
|
|
a text field or similar.
|
4
|
|
"""
|
5
|
6
|
from __future__ import absolute_import, division, unicode_literals
|
6
|
|
|
7
|
6
|
import ast
|
8
|
6
|
import json
|
9
|
|
|
10
|
6
|
from base64 import b64decode
|
11
|
6
|
from datetime import datetime, date
|
12
|
6
|
from six import string_types
|
13
|
|
|
14
|
6
|
import param
|
15
|
|
|
16
|
6
|
from bokeh.models.widgets import (
|
17
|
|
CheckboxGroup as _BkCheckboxGroup, ColorPicker as _BkColorPicker,
|
18
|
|
DatePicker as _BkDatePicker, Div as _BkDiv, TextInput as _BkTextInput,
|
19
|
|
PasswordInput as _BkPasswordInput, Spinner as _BkSpinner,
|
20
|
|
FileInput as _BkFileInput, TextAreaInput as _BkTextAreaInput,
|
21
|
|
NumericInput as _BkNumericInput)
|
22
|
|
|
23
|
6
|
from ..layout import Column
|
24
|
6
|
from ..util import as_unicode
|
25
|
6
|
from .base import Widget, CompositeWidget
|
26
|
|
|
27
|
|
|
28
|
6
|
class TextInput(Widget):
|
29
|
|
|
30
|
6
|
value = param.String(default='', allow_None=True)
|
31
|
|
|
32
|
6
|
placeholder = param.String(default='')
|
33
|
|
|
34
|
6
|
_widget_type = _BkTextInput
|
35
|
|
|
36
|
|
|
37
|
6
|
class PasswordInput(Widget):
|
38
|
|
|
39
|
6
|
value = param.String(default='', allow_None=True)
|
40
|
|
|
41
|
6
|
placeholder = param.String(default='')
|
42
|
|
|
43
|
6
|
_widget_type = _BkPasswordInput
|
44
|
|
|
45
|
|
|
46
|
6
|
class TextAreaInput(Widget):
|
47
|
|
|
48
|
6
|
value = param.String(default='', allow_None=True)
|
49
|
|
|
50
|
6
|
placeholder = param.String(default='')
|
51
|
|
|
52
|
6
|
max_length = param.Integer(default=5000)
|
53
|
|
|
54
|
6
|
_widget_type = _BkTextAreaInput
|
55
|
|
|
56
|
|
|
57
|
6
|
class FileInput(Widget):
|
58
|
|
|
59
|
6
|
accept = param.String(default=None)
|
60
|
|
|
61
|
6
|
filename = param.ClassSelector(default=None, class_=(str, list),
|
62
|
|
is_instance=True)
|
63
|
|
|
64
|
6
|
mime_type = param.ClassSelector(default=None, class_=(str, list),
|
65
|
|
is_instance=True)
|
66
|
|
|
67
|
6
|
multiple = param.Boolean(default=False)
|
68
|
|
|
69
|
6
|
value = param.Parameter(default=None)
|
70
|
|
|
71
|
6
|
_widget_type = _BkFileInput
|
72
|
|
|
73
|
6
|
_source_transforms = {'value': "'data:' + source.mime_type + ';base64,' + value"}
|
74
|
|
|
75
|
6
|
_rename = {'name': None, 'filename': None}
|
76
|
|
|
77
|
6
|
def _process_param_change(self, msg):
|
78
|
6
|
msg = super(FileInput, self)._process_param_change(msg)
|
79
|
6
|
if 'value' in msg:
|
80
|
6
|
msg.pop('value')
|
81
|
6
|
if 'mime_type' in msg:
|
82
|
6
|
msg.pop('mime_type')
|
83
|
6
|
return msg
|
84
|
|
|
85
|
6
|
def _filter_properties(self, properties):
|
86
|
6
|
properties = super(FileInput, self)._filter_properties(properties)
|
87
|
6
|
return properties + ['value', 'mime_type', 'filename']
|
88
|
|
|
89
|
6
|
def _process_property_change(self, msg):
|
90
|
6
|
msg = super(FileInput, self)._process_property_change(msg)
|
91
|
6
|
if 'value' in msg:
|
92
|
6
|
if isinstance(msg['value'], string_types):
|
93
|
6
|
msg['value'] = b64decode(msg['value'])
|
94
|
|
else:
|
95
|
0
|
msg['value'] = [b64decode(content) for content in msg['value']]
|
96
|
6
|
return msg
|
97
|
|
|
98
|
6
|
def save(self, filename):
|
99
|
|
"""
|
100
|
|
Saves the uploaded FileInput data to a file or BytesIO object.
|
101
|
|
|
102
|
|
Arguments
|
103
|
|
---------
|
104
|
|
filename (str): File path or file-like object
|
105
|
|
"""
|
106
|
0
|
if isinstance(filename, string_types):
|
107
|
0
|
with open(filename, 'wb') as f:
|
108
|
0
|
f.write(self.value)
|
109
|
|
else:
|
110
|
0
|
filename.write(self.value)
|
111
|
|
|
112
|
|
|
113
|
6
|
class StaticText(Widget):
|
114
|
|
|
115
|
6
|
style = param.Dict(default=None, doc="""
|
116
|
|
Dictionary of CSS property:value pairs to apply to this Div.""")
|
117
|
|
|
118
|
6
|
value = param.Parameter(default=None)
|
119
|
|
|
120
|
6
|
_format = '<b>{title}</b>: {value}'
|
121
|
|
|
122
|
6
|
_rename = {'name': None, 'value': 'text'}
|
123
|
|
|
124
|
6
|
_target_transforms = {'value': 'target.text.split(": ")[0]+": "+value'}
|
125
|
|
|
126
|
6
|
_source_transforms = {'value': 'value.split(": ")[1]'}
|
127
|
|
|
128
|
6
|
_widget_type = _BkDiv
|
129
|
|
|
130
|
6
|
def _process_param_change(self, msg):
|
131
|
6
|
msg = super(StaticText, self)._process_property_change(msg)
|
132
|
6
|
if 'value' in msg:
|
133
|
6
|
text = as_unicode(msg.pop('value'))
|
134
|
6
|
partial = self._format.replace('{value}', '').format(title=self.name)
|
135
|
6
|
if self.name:
|
136
|
6
|
text = self._format.format(title=self.name, value=text.replace(partial, ''))
|
137
|
6
|
msg['text'] = text
|
138
|
6
|
return msg
|
139
|
|
|
140
|
|
|
141
|
6
|
class DatePicker(Widget):
|
142
|
|
|
143
|
6
|
value = param.CalendarDate(default=None)
|
144
|
|
|
145
|
6
|
start = param.CalendarDate(default=None)
|
146
|
|
|
147
|
6
|
end = param.CalendarDate(default=None)
|
148
|
|
|
149
|
6
|
disabled_dates = param.List(default=None, class_=(date, str))
|
150
|
|
|
151
|
6
|
enabled_dates = param.List(default=None, class_=(date, str))
|
152
|
|
|
153
|
6
|
_source_transforms = {}
|
154
|
|
|
155
|
6
|
_rename = {'start': 'min_date', 'end': 'max_date', 'name': 'title'}
|
156
|
|
|
157
|
6
|
_widget_type = _BkDatePicker
|
158
|
|
|
159
|
6
|
def _process_property_change(self, msg):
|
160
|
6
|
msg = super(DatePicker, self)._process_property_change(msg)
|
161
|
6
|
if 'value' in msg:
|
162
|
6
|
if isinstance(msg['value'], string_types):
|
163
|
6
|
msg['value'] = datetime.date(datetime.strptime(msg['value'], '%Y-%m-%d'))
|
164
|
6
|
return msg
|
165
|
|
|
166
|
|
|
167
|
6
|
class ColorPicker(Widget):
|
168
|
|
|
169
|
6
|
value = param.Color(default=None, doc="""
|
170
|
|
The selected color""")
|
171
|
|
|
172
|
6
|
_widget_type = _BkColorPicker
|
173
|
|
|
174
|
6
|
_rename = {'value': 'color', 'name': 'title'}
|
175
|
|
|
176
|
|
|
177
|
6
|
class _NumericInputBase(Widget):
|
178
|
|
|
179
|
6
|
value = param.Number(default=0, allow_None=True, doc="""
|
180
|
|
The initial value of the spinner.""")
|
181
|
|
|
182
|
6
|
placeholder = param.String(default='0', doc="""
|
183
|
|
Placeholder for empty input field.""")
|
184
|
|
|
185
|
6
|
format = param.String(default=None, allow_None=True, doc="""
|
186
|
|
Number formating : http://numbrojs.com/old-format.html .""")
|
187
|
|
|
188
|
6
|
_rename = {'name': 'title', 'start': 'low', 'end': 'high'}
|
189
|
|
|
190
|
6
|
_widget_type = _BkNumericInput
|
191
|
|
|
192
|
6
|
__abstract = True
|
193
|
|
|
194
|
|
|
195
|
6
|
class _IntInputBase(_NumericInputBase):
|
196
|
|
|
197
|
6
|
value = param.Integer(default=0, allow_None=True, doc="""
|
198
|
|
The initial value of the spinner.""")
|
199
|
|
|
200
|
6
|
start = param.Integer(default=None, allow_None=True, doc="""
|
201
|
|
Optional minimum allowable value.""")
|
202
|
|
|
203
|
6
|
end = param.Integer(default=None, allow_None=True, doc="""
|
204
|
|
Optional maximum allowable value.""")
|
205
|
|
|
206
|
6
|
mode = param.String(default='int', constant=True, doc="""
|
207
|
|
Define the type of number which can be enter in the input""")
|
208
|
|
|
209
|
6
|
__abstract = True
|
210
|
|
|
211
|
|
|
212
|
6
|
class _FloatInputBase(_NumericInputBase):
|
213
|
|
|
214
|
6
|
value = param.Number(default=0, allow_None=True, doc="""
|
215
|
|
The initial value of the spinner.""")
|
216
|
|
|
217
|
6
|
start = param.Number(default=None, allow_None=True, doc="""
|
218
|
|
Optional minimum allowable value.""")
|
219
|
|
|
220
|
6
|
end = param.Number(default=None, allow_None=True, doc="""
|
221
|
|
Optional maximum allowable value.""")
|
222
|
|
|
223
|
6
|
mode = param.String(default='float', constant=True, doc="""
|
224
|
|
Define the type of number which can be enter in the input""")
|
225
|
|
|
226
|
6
|
__abstract = True
|
227
|
|
|
228
|
|
|
229
|
6
|
class _SpinnerBase(_NumericInputBase):
|
230
|
|
|
231
|
6
|
page_step_multiplier = param.Integer(default=10, bounds=(0, None), doc="""
|
232
|
|
Defines the multiplication factor applied to step when the page up
|
233
|
|
and page down keys are pressed.""")
|
234
|
|
|
235
|
6
|
wheel_wait = param.Integer(default=100, doc="""
|
236
|
|
Defines the debounce time in ms before updating `value_throttled` when
|
237
|
|
the mouse wheel is used to change the input.""")
|
238
|
|
|
239
|
6
|
_widget_type = _BkSpinner
|
240
|
|
|
241
|
6
|
__abstract = True
|
242
|
|
|
243
|
6
|
def __init__(self, **params):
|
244
|
6
|
if params.get('value') is None:
|
245
|
6
|
value = params.get('start', self.value)
|
246
|
6
|
if value is not None:
|
247
|
6
|
params['value'] = value
|
248
|
6
|
if 'value' in params and 'value_throttled' in self.param:
|
249
|
6
|
params['value_throttled'] = params['value']
|
250
|
6
|
super(_SpinnerBase, self).__init__(**params)
|
251
|
|
|
252
|
|
|
253
|
6
|
class IntInput(_SpinnerBase, _IntInputBase):
|
254
|
|
|
255
|
6
|
step = param.Integer(default=1)
|
256
|
|
|
257
|
6
|
value_throttled = param.Integer(default=None, constant=True)
|
258
|
|
|
259
|
|
|
260
|
6
|
class FloatInput(_SpinnerBase, _FloatInputBase):
|
261
|
|
|
262
|
6
|
step = param.Number(default=0.1)
|
263
|
|
|
264
|
6
|
value_throttled = param.Number(default=None, constant=True)
|
265
|
|
|
266
|
|
|
267
|
6
|
class NumberInput(_SpinnerBase):
|
268
|
|
|
269
|
6
|
def __new__(self, **params):
|
270
|
6
|
param_list = ["value", "start", "stop", "step"]
|
271
|
6
|
if all(isinstance(params.get(p, 0), int) for p in param_list):
|
272
|
6
|
return IntInput(**params)
|
273
|
|
else:
|
274
|
0
|
return FloatInput(**params)
|
275
|
|
|
276
|
|
|
277
|
|
# Backward compatibility
|
278
|
6
|
Spinner = NumberInput
|
279
|
|
|
280
|
|
|
281
|
6
|
class LiteralInput(Widget):
|
282
|
|
"""
|
283
|
|
LiteralInput allows declaring Python literals using a text
|
284
|
|
input widget. Optionally a type may be declared.
|
285
|
|
"""
|
286
|
|
|
287
|
6
|
serializer = param.ObjectSelector(default='ast', objects=['ast', 'json'], doc="""
|
288
|
|
The serialization (and deserialization) method to use. 'ast'
|
289
|
|
uses ast.literal_eval and 'json' uses json.loads and json.dumps.
|
290
|
|
""")
|
291
|
|
|
292
|
6
|
type = param.ClassSelector(default=None, class_=(type, tuple),
|
293
|
|
is_instance=True)
|
294
|
|
|
295
|
6
|
value = param.Parameter(default=None)
|
296
|
|
|
297
|
6
|
_rename = {'name': 'title', 'type': None, 'serializer': None}
|
298
|
|
|
299
|
6
|
_source_transforms = {'value': """JSON.parse(value.replace(/'/g, '"'))"""}
|
300
|
|
|
301
|
6
|
_target_transforms = {'value': """JSON.stringify(value).replace(/,/g, ", ").replace(/:/g, ": ")"""}
|
302
|
|
|
303
|
6
|
_widget_type = _BkTextInput
|
304
|
|
|
305
|
6
|
def __init__(self, **params):
|
306
|
6
|
super(LiteralInput, self).__init__(**params)
|
307
|
6
|
self._state = ''
|
308
|
6
|
self._validate(None)
|
309
|
6
|
self._callbacks.append(self.param.watch(self._validate, 'value'))
|
310
|
|
|
311
|
6
|
def _validate(self, event):
|
312
|
6
|
if self.type is None: return
|
313
|
6
|
new = self.value
|
314
|
6
|
if not isinstance(new, self.type) and new is not None:
|
315
|
6
|
if event:
|
316
|
6
|
self.value = event.old
|
317
|
6
|
types = repr(self.type) if isinstance(self.type, tuple) else self.type.__name__
|
318
|
6
|
raise ValueError('LiteralInput expected %s type but value %s '
|
319
|
|
'is of type %s.' %
|
320
|
|
(types, new, type(new).__name__))
|
321
|
|
|
322
|
6
|
def _process_property_change(self, msg):
|
323
|
6
|
msg = super(LiteralInput, self)._process_property_change(msg)
|
324
|
6
|
new_state = ''
|
325
|
6
|
if 'value' in msg:
|
326
|
6
|
value = msg.pop('value')
|
327
|
6
|
try:
|
328
|
6
|
if self.serializer == 'json':
|
329
|
0
|
value = json.loads(value)
|
330
|
|
else:
|
331
|
6
|
value = ast.literal_eval(value)
|
332
|
6
|
except Exception:
|
333
|
6
|
new_state = ' (invalid)'
|
334
|
6
|
value = self.value
|
335
|
|
else:
|
336
|
6
|
if self.type and not isinstance(value, self.type):
|
337
|
6
|
vtypes = self.type if isinstance(self.type, tuple) else (self.type,)
|
338
|
6
|
typed_value = None
|
339
|
6
|
for vtype in vtypes:
|
340
|
6
|
try:
|
341
|
6
|
typed_value = vtype(value)
|
342
|
6
|
except Exception:
|
343
|
6
|
pass
|
344
|
|
else:
|
345
|
0
|
break
|
346
|
6
|
if typed_value is None and value is not None:
|
347
|
6
|
new_state = ' (wrong type)'
|
348
|
6
|
value = self.value
|
349
|
|
else:
|
350
|
0
|
value = typed_value
|
351
|
6
|
msg['value'] = value
|
352
|
6
|
msg['name'] = msg.get('title', self.name).replace(self._state, '') + new_state
|
353
|
6
|
self._state = new_state
|
354
|
6
|
self.param.trigger('name')
|
355
|
6
|
return msg
|
356
|
|
|
357
|
6
|
def _process_param_change(self, msg):
|
358
|
6
|
msg = super(LiteralInput, self)._process_param_change(msg)
|
359
|
6
|
if 'value' in msg:
|
360
|
6
|
value = msg['value']
|
361
|
6
|
if isinstance(value, string_types):
|
362
|
0
|
value = repr(value)
|
363
|
6
|
elif self.serializer == 'json':
|
364
|
0
|
value = json.dumps(value)
|
365
|
|
else:
|
366
|
6
|
value = '' if value is None else as_unicode(value)
|
367
|
6
|
msg['value'] = value
|
368
|
6
|
msg['title'] = self.name
|
369
|
6
|
return msg
|
370
|
|
|
371
|
|
|
372
|
6
|
class DatetimeInput(LiteralInput):
|
373
|
|
"""
|
374
|
|
DatetimeInput allows declaring Python literals using a text
|
375
|
|
input widget. Optionally a type may be declared.
|
376
|
|
"""
|
377
|
|
|
378
|
6
|
format = param.String(default='%Y-%m-%d %H:%M:%S', doc="""
|
379
|
|
Datetime format used for parsing and formatting the datetime.""")
|
380
|
|
|
381
|
6
|
value = param.Date(default=None)
|
382
|
|
|
383
|
6
|
start = param.Date(default=None)
|
384
|
|
|
385
|
6
|
end = param.Date(default=None)
|
386
|
|
|
387
|
6
|
type = datetime
|
388
|
|
|
389
|
6
|
_source_transforms = {'value': None, 'start': None, 'end': None}
|
390
|
|
|
391
|
6
|
_rename = {'format': None, 'type': None, 'name': 'title',
|
392
|
|
'start': None, 'end': None, 'serializer': None}
|
393
|
|
|
394
|
6
|
def __init__(self, **params):
|
395
|
6
|
super(DatetimeInput, self).__init__(**params)
|
396
|
6
|
self.param.watch(self._validate, 'value')
|
397
|
6
|
self._validate(None)
|
398
|
|
|
399
|
6
|
def _validate(self, event):
|
400
|
6
|
new = self.value
|
401
|
6
|
if new is not None and ((self.start is not None and self.start > new) or
|
402
|
|
(self.end is not None and self.end < new)):
|
403
|
6
|
value = datetime.strftime(new, self.format)
|
404
|
6
|
start = datetime.strftime(self.start, self.format)
|
405
|
6
|
end = datetime.strftime(self.end, self.format)
|
406
|
6
|
if event:
|
407
|
6
|
self.value = event.old
|
408
|
6
|
raise ValueError('DatetimeInput value must be between {start} and {end}, '
|
409
|
|
'supplied value is {value}'.format(start=start, end=end,
|
410
|
|
value=value))
|
411
|
|
|
412
|
6
|
def _process_property_change(self, msg):
|
413
|
6
|
msg = Widget._process_property_change(self, msg)
|
414
|
6
|
new_state = ''
|
415
|
6
|
if 'value' in msg:
|
416
|
6
|
value = msg.pop('value')
|
417
|
6
|
try:
|
418
|
6
|
value = datetime.strptime(value, self.format)
|
419
|
6
|
except Exception:
|
420
|
6
|
new_state = ' (invalid)'
|
421
|
6
|
value = self.value
|
422
|
|
else:
|
423
|
6
|
if value is not None and ((self.start is not None and self.start > value) or
|
424
|
|
(self.end is not None and self.end < value)):
|
425
|
6
|
new_state = ' (out of bounds)'
|
426
|
6
|
value = self.value
|
427
|
6
|
msg['value'] = value
|
428
|
6
|
msg['name'] = msg.get('title', self.name).replace(self._state, '') + new_state
|
429
|
6
|
self._state = new_state
|
430
|
6
|
return msg
|
431
|
|
|
432
|
6
|
def _process_param_change(self, msg):
|
433
|
6
|
msg = Widget._process_param_change(self, msg)
|
434
|
6
|
if 'value' in msg:
|
435
|
6
|
value = msg['value']
|
436
|
6
|
if value is None:
|
437
|
6
|
value = ''
|
438
|
|
else:
|
439
|
6
|
value = datetime.strftime(msg['value'], self.format)
|
440
|
6
|
msg['value'] = value
|
441
|
6
|
msg['title'] = self.name
|
442
|
6
|
return msg
|
443
|
|
|
444
|
|
|
445
|
6
|
class DatetimeRangeInput(CompositeWidget):
|
446
|
|
|
447
|
6
|
value = param.Tuple(default=(None, None), length=2)
|
448
|
|
|
449
|
6
|
start = param.Date(default=None)
|
450
|
|
|
451
|
6
|
end = param.Date(default=None)
|
452
|
|
|
453
|
6
|
_composite_type = Column
|
454
|
|
|
455
|
6
|
def __init__(self, **params):
|
456
|
6
|
self._text = StaticText(margin=(5, 0, 0, 0), style={'white-space': 'nowrap'})
|
457
|
6
|
self._start = DatetimeInput(sizing_mode='stretch_width', margin=(5, 0, 0, 0))
|
458
|
6
|
self._end = DatetimeInput(sizing_mode='stretch_width', margin=(5, 0, 0, 0))
|
459
|
6
|
if 'value' not in params:
|
460
|
0
|
params['value'] = (params['start'], params['end'])
|
461
|
6
|
super().__init__(**params)
|
462
|
6
|
self._msg = ''
|
463
|
6
|
self._composite.extend([self._text, self._start, self._end])
|
464
|
6
|
self._updating = False
|
465
|
6
|
self._update_widgets()
|
466
|
6
|
self._update_label()
|
467
|
|
|
468
|
6
|
@param.depends('name', '_start.name', '_end.name', watch=True)
|
469
|
1
|
def _update_label(self):
|
470
|
6
|
self._text.value = f'{self.name}{self._start.name}{self._end.name}{self._msg}'
|
471
|
|
|
472
|
6
|
@param.depends('_start.value', '_end.value', watch=True)
|
473
|
1
|
def _update(self):
|
474
|
6
|
if self._updating:
|
475
|
6
|
return
|
476
|
6
|
if (self._start.value is not None and
|
477
|
|
self._end.value is not None and
|
478
|
|
self._start.value > self._end.value):
|
479
|
6
|
self._msg = ' (start of range must be <= end)'
|
480
|
6
|
self._update_label()
|
481
|
6
|
return
|
482
|
6
|
elif self._msg:
|
483
|
6
|
self._msg = ''
|
484
|
6
|
self._update_label()
|
485
|
6
|
try:
|
486
|
6
|
self._updating = True
|
487
|
6
|
self.value = (self._start.value, self._end.value)
|
488
|
|
finally:
|
489
|
6
|
self._updating = False
|
490
|
|
|
491
|
6
|
@param.depends('value', 'start', 'end', 'name', watch=True)
|
492
|
1
|
def _update_widgets(self):
|
493
|
6
|
if self._updating:
|
494
|
6
|
return
|
495
|
6
|
try:
|
496
|
6
|
self._updating = True
|
497
|
6
|
self._start.param.set_param(value=self.value[0], start=self.start, end=self.end)
|
498
|
6
|
self._end.param.set_param(value=self.value[1], start=self.start, end=self.end)
|
499
|
|
finally:
|
500
|
6
|
self._updating = False
|
501
|
|
|
502
|
|
|
503
|
6
|
class Checkbox(Widget):
|
504
|
|
|
505
|
6
|
value = param.Boolean(default=False)
|
506
|
|
|
507
|
6
|
_supports_embed = True
|
508
|
|
|
509
|
6
|
_rename = {'value': 'active', 'name': 'labels'}
|
510
|
|
|
511
|
6
|
_source_transforms = {'value': "value.indexOf(0) >= 0", 'name': "value[0]"}
|
512
|
|
|
513
|
6
|
_target_transforms = {'value': "value ? [0] : []", 'name': "[value]"}
|
514
|
|
|
515
|
6
|
_widget_type = _BkCheckboxGroup
|
516
|
|
|
517
|
6
|
def _process_property_change(self, msg):
|
518
|
6
|
msg = super(Checkbox, self)._process_property_change(msg)
|
519
|
6
|
if 'value' in msg:
|
520
|
6
|
msg['value'] = 0 in msg.pop('value')
|
521
|
6
|
if 'name' in msg:
|
522
|
0
|
msg['name'] = [msg['name']]
|
523
|
6
|
return msg
|
524
|
|
|
525
|
6
|
def _process_param_change(self, msg):
|
526
|
6
|
msg = super(Checkbox, self)._process_param_change(msg)
|
527
|
6
|
if 'active' in msg:
|
528
|
6
|
msg['active'] = [0] if msg.pop('active', None) else []
|
529
|
6
|
if 'labels' in msg:
|
530
|
6
|
msg['labels'] = [msg.pop('labels')]
|
531
|
6
|
return msg
|
532
|
|
|
533
|
6
|
def _get_embed_state(self, root, values=None, max_opts=3):
|
534
|
6
|
return (self, self._models[root.ref['id']][0], [False, True],
|
535
|
|
lambda x: str(0 in x.active).lower(), 'active',
|
536
|
|
"String(cb_obj.active.indexOf(0) >= 0)")
|