Custom file upload widget?

Is there a way to circumvent (isn’t reactive until file upload is done):

Like using javascript?

Any ideas why I am getting a bunch of errors? It works alone as a bokeh component, but the moment I call pn.extension(), it returns all the errors.

import panel as pn

from bokeh.core.properties import List, String, Dict, Int, Complex, Float
from bokeh.models import Button, LayoutDOM

import param

from panel.widgets.base import Widget

IMPL = """
import * as p from "core/properties"
import {LayoutDOM, LayoutDOMView} from "models/layouts/layout_dom"

export class FileInputView extends LayoutDOMView
initialize: (options) ->
super(options)
input = document.createElement("input")
input.type = "file"
input.onchange = () => @model.value = input.value
@el.appendChild(input)

export class FileInput extends LayoutDOM
default_view: FileInputView
type: "FileInput"
@define {
value: [ p.String ]
mime_type: [ p.String ]
filename = [ p.String ]
}
"""

class _BkFileInput(LayoutDOM):
    __implementation__ = IMPL

    value = String()
    
    mime_type = String()
    
    filename = String()


class FileInput(Widget):

    value = param.Parameter(default=None)

    _widget_type = _BkFileInput

    _rename = {'name': None, 'filename': None}
    
    def _process_param_change(self, msg):
        msg = super()._process_param_change(msg)
        if 'value' in msg:
            msg.pop('value')
        if 'mime_type' in msg:
            msg.pop('mime_type')
        return msg

    def _filter_properties(self, properties):
        properties = super()._filter_properties(properties)
        return properties + ['value', 'mime_type', 'filename']

    def _process_property_change(self, msg):
        msg = super()._process_property_change(msg)
        if 'value' in msg:
            if isinstance(msg['value'], string_types):
                msg['value'] = b64decode(msg['value'])
            else:
                msg['value'] = [b64decode(content) for content in msg['value']]
        return msg
    
pn.extension()

fi = FileInput()
button = pn.widgets.Button()
Compilation failed:

_BkFileInput.ts:6:1 - error TS1005: ',' expected.

6 initialize: (options) ->
  ~~~~~~~~~~
_BkFileInput.ts:6:11 - error TS1005: ',' expected.

6 initialize: (options) ->
            ~
_BkFileInput.ts:6:23 - error TS1005: ',' expected.

6 initialize: (options) ->
                        ~
_BkFileInput.ts:6:24 - error TS1109: Expression expected.

6 initialize: (options) ->
                         ~
_BkFileInput.ts:10:24 - error TS1005: '{' expected.

10 input.onchange = () => @model.value = input.value
                          ~
_BkFileInput.ts:10:36 - error TS1146: Declaration expected.

10 input.onchange = () => @model.value = input.value
                                      
_BkFileInput.ts:10:37 - error TS1128: Declaration or statement expected.

10 input.onchange = () => @model.value = input.value
                                       ~
_BkFileInput.ts:14:1 - error TS1005: ',' expected.

14 default_view: FileInputView
   ~~~~~~~~~~~~
_BkFileInput.ts:14:13 - error TS1005: ',' expected.

14 default_view: FileInputView
               ~
_BkFileInput.ts:15:1 - error TS1005: ',' expected.

15 type: "FileInput"
   ~~~~
_BkFileInput.ts:15:5 - error TS1005: ',' expected.

15 type: "FileInput"
       ~
_BkFileInput.ts:16:1 - error TS1005: ',' expected.

16 @define {
   ~
_BkFileInput.ts:16:8 - error TS1146: Declaration expected.

16 @define {
          
_BkFileInput.ts:21:1 - error TS1005: '}' expected.

This kind of works, although very clunky.

import base64
from io import BytesIO


import param
import panel as pn
from panel.widgets.base import Widget
from bokeh.core.properties import List, String, Dict, Int, Complex, Float
from bokeh.models import Button, LayoutDOM

pn.extension()

IMPL = """
import * as p from "core/properties"
import {LayoutDOM, LayoutDOMView} from "models/layouts/layout_dom"

export class FileInputView extends LayoutDOMView
initialize: (options) ->
super(options)
input = document.createElement("input")
input.type = "file"
input.onchange = () => @model.value = input.value
@el.appendChild(input)

export class FileInput extends LayoutDOM
default_view: FileInputView
type: "FileInput"
@define {
value: [ p.String ]
mime_type: [ p.String ]
filename = [ p.String ]
}
"""

class FileInput(LayoutDOM):
    __implementation__ = IMPL

    value = String()
    
    mime_type = String()
    
    filename = String()


fi = FileInput()
button = pn.widgets.Button(name="Status", disabled=True)


input_value = base64.b64decode(fi.value)

def test(attr, old, new):
    button.loading = True

fi.on_change("value", test)

pn.Column(fi, button)

pn.pane.JPG(BytesIO(base64.b64decode(fi.value)))

Okay, better solution is to use jscallback:

1 Like

An alternative is to build you own file upload widget using ReactiveHTML. I’m experimenting with one here paithon/image_input.py

The progress is there. I’m just not uploading big files. So its quick.

ImageInput

2 Likes