{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import base64\n",
    "from __future__ import print_function # py 2.7 compat.\n",
    "from IPython.html import widgets # Widget definitions.\n",
    "from IPython.utils.traitlets import Unicode # Traitlet needed to add synced attributes to the widget."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is a custom widget that allows the user to upload file data to the notebook server.  The file data is sent via a statefull `value` attribute of the widget.  The widget has an upload failed event that fires in the front-end and is echoed to the back-end using a custom msg."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "class FileWidget(widgets.DOMWidget):\n",
    "    _view_name = Unicode('FilePickerView', sync=True)\n",
    "    value = Unicode(sync=True)\n",
    "    filename = Unicode(sync=True)\n",
    "    \n",
    "    def __init__(self, **kwargs):\n",
    "        \"\"\"Constructor\"\"\"\n",
    "        widgets.DOMWidget.__init__(self, **kwargs) # Call the base.\n",
    "        \n",
    "        # Allow the user to register error callbacks with the following signatures:\n",
    "        #    callback()\n",
    "        #    callback(sender)\n",
    "        self.errors = widgets.CallbackDispatcher(accepted_nargs=[0, 1])\n",
    "        \n",
    "        # Listen for custom msgs\n",
    "        self.on_msg(self._handle_custom_msg)\n",
    "\n",
    "    def _handle_custom_msg(self, content):\n",
    "        \"\"\"Handle a msg from the front-end.\n",
    "\n",
    "        Parameters\n",
    "        ----------\n",
    "        content: dict\n",
    "            Content of the msg.\"\"\"\n",
    "        if 'event' in content and content['event'] == 'error':\n",
    "            self.errors()\n",
    "            self.errors(self)\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "%%javascript\n",
    "\n",
    "require([\"widgets/js/widget\", \"widgets/js/manager\"], function(widget, manager){\n",
    "\n",
    "    var FilePickerView = widget.DOMWidgetView.extend({\n",
    "        render: function(){\n",
    "            // Render the view.\n",
    "            this.setElement($('<input />')\n",
    "                .attr('type', 'file'));\n",
    "        },\n",
    "        \n",
    "        events: {\n",
    "            // List of events and their handlers.\n",
    "            'change': 'handle_file_change',\n",
    "        },\n",
    "       \n",
    "        handle_file_change: function(evt) { \n",
    "            // Handle when the user has changed the file.\n",
    "            \n",
    "            // Retrieve the first (and only!) File from the FileList object\n",
    "            var file = evt.target.files[0];\n",
    "            if (file) {\n",
    "\n",
    "                // Read the file's textual content and set value to those contents.\n",
    "                var that = this;\n",
    "                var file_reader = new FileReader();\n",
    "                file_reader.onload = function(e) {\n",
    "                    that.model.set('value', e.target.result);\n",
    "                    that.touch();\n",
    "                }\n",
    "                file_reader.readAsText(file);\n",
    "            } else {\n",
    "\n",
    "                // The file couldn't be opened.  Send an error msg to the\n",
    "                // back-end.\n",
    "                this.send({ 'event': 'error' });\n",
    "            }\n",
    "\n",
    "            // Set the filename of the file.\n",
    "            this.model.set('filename', file.name);\n",
    "            this.touch();\n",
    "        },\n",
    "    });\n",
    "        \n",
    "    // Register the DatePickerView with the widget manager.\n",
    "    manager.WidgetManager.register_widget_view('FilePickerView', FilePickerView);\n",
    "});"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following shows how the file widget can be used."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "file_widget = FileWidget()\n",
    "\n",
    "# Register an event to echo the filename when it has been changed.\n",
    "def file_loading():\n",
    "    print(\"Loading %s\" % file_widget.filename)\n",
    "file_widget.on_trait_change(file_loading, 'filename')\n",
    "\n",
    "# Register an event to echo the filename and contents when a file\n",
    "# has been uploaded.\n",
    "def file_loaded():\n",
    "    print(\"Loaded, file contents: %s\" % file_widget.value)\n",
    "file_widget.on_trait_change(file_loaded, 'value')\n",
    "\n",
    "# Register an event to print an error message when a file could not\n",
    "# be opened.  Since the error messages are not handled through\n",
    "# traitlets but instead handled through custom msgs, the registration\n",
    "# of the handler is different than the two examples above.  Instead\n",
    "# the API provided by the CallbackDispatcher must be used.\n",
    "def file_failed():\n",
    "    print(\"Could not load file contents of %s\" % file_widget.filename)\n",
    "file_widget.errors.register_callback(file_failed)\n",
    "\n",
    "file_widget"
   ]
  }
 ],
 "metadata": {
  "cell_tags": [
   [
    "<None>",
    null
   ]
  ],
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.4.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
