Repository: bokeh/bokeh

Issue:
File Open Dialog
Hello,

I would like to have a "File Open Dialog" to interactively specify local data files.
This was previously discussed in the Bokeh [Google Groups](https://groups.google.com/a/continuum.io/forum/#!topic/bokeh/Hd8_Jwyoptc).

Best regards


Discussion:
Just FYI. This might be a reasonable addition, but I can't speculate about when it might make it into the core library. In the mean time your option is to create custom extension: 

http://bokeh.pydata.org/en/latest/docs/user_guide/extensions.html
For anyone searching for help: in the meantime before this is added to bokeh, here's how I implemented sending a file from the user's computer to a Bokeh server:

- Using templates (Bokeh's, Flask's, whatever you're using), insert an HTML file choosing input: `    <input id="file-input" type="file">`
- Attach a javascript callback to a button that reads the selected file's contents
- If the file is complex like an excel document, you can base64 encode it.  If it's something like a text file or a csv, you might as well leave it as-is
- Insert it into a dedicated `ColumnDataSource`, treating it as a dictionary to transfer data from browser to server
- On the server side, implement an `on_change("data", callback)` for the data source
- Base64 decode the file if necessary
- If you just need the file contents as a string, you're done.  If you need it as a proper file (for instance, to use with `pandas.read_excel`), you can wrap it in a `StringIO` object to make it file-like

It's a bit complicated, but the upcoming `namespace` model for Bokeh would make it a bit easier.  Feel free to ping me for code samples.
Hello @kevinsa5 ,

could you provide code samples. Thank you
The following is my code

import pandas as pd  
from bokeh.layouts import row 
from bokeh.models import ColumnDataSource, CustomJS 
from bokeh.models.widgets import Button
from bokeh.io import curdoc 

source = ColumnDataSource(dict())

print(source.data.keys)

def callback1(attr,old,new):
    print('data got changed')

button = Button(label="Upload", button_type="success") 
button.callback = CustomJS(args=dict(source=source), 
                           code="""
                            fileSelector = document.createElement('input');
                            fileSelector.setAttribute('type', 'file');

                            selectDialogueLink = document.createElement('a');
                            selectDialogueLink.setAttribute('href', '');

                            selectDialogueLink.onclick = fileSelector.click();

                            document.body.appendChild(selectDialogueLink);

                            if ('files' in fileSelector) {
                                if (fileSelector.files.length == 0) {
                                    alert("Select file.");
                                } else {
                                    for (var i = 0; i < fileSelector.files.length; i++) {
                                        var file = fileSelector.files[i];
                                        var reader = new FileReader();
                                        source.data = reader.readAsArrayBuffer(file);
                                        if ('name' in file) {
                                            alert("name: " + file.name);
                                        }
                                        if ('size' in file) {
                                            alert("size: " + file.size);
                                        }
                                    }
                                }
                             } 
                           """) 
source.on_change('data',callback1) 

curdoc().add_root(row(button))
Hi @divyamounika, here is an example script that you can work from. Try uploading a simple excel file to see if it works for you.  I also added the filename, as you can see.  This code is largely untested -- you may want to add filesize limits, only allow certain file types, etc.  

```
# -*- coding: utf-8 -*-
"""
Created on Wed May 03 11:26:21 2017

@author: Kevin Anderson
"""

import pandas as pd
from bokeh.layouts import row
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.models.widgets import Button
from bokeh.io import curdoc

import StringIO
import base64

file_source = ColumnDataSource({'file_contents':[], 'file_name':[]})

def file_callback(attr,old,new):
    print 'filename:', file_source.data['file_name']
    raw_contents = file_source.data['file_contents'][0]
    # remove the prefix that JS adds  
    prefix, b64_contents = raw_contents.split(",", 1)
    file_contents = base64.b64decode(b64_contents)
    file_io = StringIO.StringIO(file_contents)
    df = pd.read_excel(file_io)
    print "file contents:"
    print df

file_source.on_change('data', file_callback)

button = Button(label="Upload", button_type="success")
button.callback = CustomJS(args=dict(file_source=file_source), code = """
function read_file(filename) {
    var reader = new FileReader();
    reader.onload = load_handler;
    reader.onerror = error_handler;
    // readAsDataURL represents the file's data as a base64 encoded string
    reader.readAsDataURL(filename);
}

function load_handler(event) {
    var b64string = event.target.result;
    file_source.data = {'file_contents' : [b64string], 'file_name':[input.files[0].name]};
    file_source.trigger("change");
}

function error_handler(evt) {
    if(evt.target.error.name == "NotReadableError") {
        alert("Can't read file!");
    }
}

var input = document.createElement('input');
input.setAttribute('type', 'file');
input.onchange = function(){
    if (window.FileReader) {
        read_file(input.files[0]);
    } else {
        alert('FileReader is not supported in this browser');
    }
}
input.click();
""")

curdoc().add_root(row(button))
```
Hello @kevinsa5 ,

Thank you for the code. I was able to upload .csv files data with a minor modification on my system.

modification -->` file_io = io.StringIO(bytes.decode(file_contents))
`
It worked like magic.
There is some content here that may be useful to this issue:

https://stackoverflow.com/questions/40794180/upload-a-csv-file-and-read-it-in-bokeh-web-app
@kevinsa5 and @divyamounika - could you throw some light on what ‘file_io’ contents would be? Because this works for csv file but for .xlsx throws TypeError.  Could you guide me here?
@joel-wilson `file_io` is a StringIO object, basically an in-memory file.  StringIO behaves slightly different between python 2 and 3, fyi.  You should narrow down what exactly the issue on your end is, since it probably isn't a problem with Bokeh based on your description.  Try making the smallest possible script that still has the issue, that should help you to figure it out.  
For those you would infuture reference this thread : Here is a solution to have a upload button to handle `.xlsx` file . This was ofr `python3`

    import pandas as pd
    import io
    import base64


    def file_callback_dt1(attr,old,new):
        print('filename:', file_source_dt1.data['file_name'])
        raw_contents = file_source_dt1.data['file_contents'][0]
        prefix, b64_contents = raw_contents.split(",", 1)
        file_contents = base64.b64decode(b64_contents)
        file_io = io.BytesIO(file_contents)
        excel_object = pd.ExcelFile(file_io, engine='xlrd')
        dt_1 = excel_object.parse(sheet_name = 'Sheet1', index_col = 0)
        # rest is upto you :)

I am creating a pie chart but the problem is that I want to load a tsv file from the file open dialog box when the link is clicked.how it can be possible to get the data file from dialog box and as the user opens it a pie chart is displayed.
Help please!
I need to get the filename and the folder where the file is stored for the "kevinsa5 commented on May 3, 2017"
@joaochenriques that snippet of code does not store the file -- it only brings the file contents into memory.  If you want to store the file somewhere on disk, you can try something like
```
with open("/path/to/output_file") as f:
    f.write(file_contents)
```
Thank you @kevinsa5. I am a new user of Bokeh, and I forgot that it is a client/server architecture!
Noting the SO example to create a custom extension it far out of date:

https://stackoverflow.com/a/42613897/3406693

Should either add a file dialog directly or create an updated example (and update that SO answer accordingly either way)
Note that [panel](https://github.com/pyviz/panel/) has an implementation of a [FileInput](https://github.com/pyviz/panel/blob/master/panel/models/fileinput.ts) widget, which could be moved into bokeh.
> Note that panel has an implementation of a FileInput widget, which could be moved into bokeh.

It's not my birthday but I wouldn't turn down this present
@philippjfr I am going to see about doing this now, so it can be in the next release. 

Resolving patch:
diff --git a/bokeh/models/widgets/inputs.py b/bokeh/models/widgets/inputs.py
--- a/bokeh/models/widgets/inputs.py
+++ b/bokeh/models/widgets/inputs.py
@@ -40,6 +40,7 @@
     'AutocompleteInput',
     'ColorPicker',
     'DatePicker',
+    'FileInput',
     'InputWidget',
     'MultiSelect',
     'PasswordInput',
@@ -80,6 +81,38 @@ def coerce_value(cls, val):
 # General API
 #-----------------------------------------------------------------------------
 
+class FileInput(Widget):
+    ''' Present a file-chooser dialog to users and return the contents of a
+    selected file.
+
+    '''
+
+    file = String(default="", readonly=True, help="""
+    A base64-encoded string of the contents of the selected file.
+    """)
+
+    accept = String(default="", help="""
+    Comma-separated list of standard HTML file input filters that restrict what
+    files the user can pick from. Values can be:
+
+    `<file extension>`:
+        Specific file extension(s) (e.g: .gif, .jpg, .png, .doc) are pickable
+
+    `audio/*`:
+        all sound files are pickable
+
+    `video/*`:
+        all video files are pickable
+
+    `image/*`:
+        all image files are pickable
+
+    `<media type>`:
+        A valid `IANA Media Type`_, with no parameters.
+
+    .. _IANA Media Type: https://www.iana.org/assignments/media-types/media-types.xhtml
+    """)
+
 
 class TextInput(InputWidget):
     ''' Single-line input widget.
diff --git a/sphinx/source/docs/user_guide/examples/interaction_colorpicker.py b/sphinx/source/docs/user_guide/examples/interaction_colorpicker.py
new file mode 100644
--- /dev/null
+++ b/sphinx/source/docs/user_guide/examples/interaction_colorpicker.py
@@ -0,0 +1,8 @@
+from bokeh.io import output_file, show
+from bokeh.models.widgets import ColorPicker
+
+output_file("color_picker.html")
+
+color_picker = ColorPicker(color="#ff4466", title="Choose color:", width=200)
+
+show(color_picker)
diff --git a/sphinx/source/docs/user_guide/examples/interaction_fileinput.py b/sphinx/source/docs/user_guide/examples/interaction_fileinput.py
new file mode 100644
--- /dev/null
+++ b/sphinx/source/docs/user_guide/examples/interaction_fileinput.py
@@ -0,0 +1,8 @@
+from bokeh.io import output_file, show
+from bokeh.models.widgets import FileInput
+
+output_file("file_input.html")
+
+file_input = FileInput()
+
+show(file_input)


Test patch:
diff --git a/tests/baselines/sphinx/source/docs/user_guide/examples/interaction_colorpicker b/tests/baselines/sphinx/source/docs/user_guide/examples/interaction_colorpicker
new file mode 100644
--- /dev/null
+++ b/tests/baselines/sphinx/source/docs/user_guide/examples/interaction_colorpicker
@@ -0,0 +1 @@
+ColorPicker bbox=[0, 0, 200, 50]
diff --git a/tests/baselines/sphinx/source/docs/user_guide/examples/interaction_fileinput b/tests/baselines/sphinx/source/docs/user_guide/examples/interaction_fileinput
new file mode 100644
--- /dev/null
+++ b/tests/baselines/sphinx/source/docs/user_guide/examples/interaction_fileinput
@@ -0,0 +1 @@
+FileInput bbox=[0, 0, 300, 21]