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)")

Read our documentation on viewing source code .

Loading