From 32f2bbcee7905a5bcb0a3a509d6c125179514c2a Mon Sep 17 00:00:00 2001
From: Jeff Dairiki <dairiki@dairiki.org>
Date: Thu, 26 Sep 2013 12:38:09 -0700
Subject: [PATCH 1/9] file_upload.pt: add .form-control-static for correct
 vetical margins

---
 deform/templates/file_upload.pt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/deform/templates/file_upload.pt b/deform/templates/file_upload.pt
index 7c911507..a34c4fd9 100644
--- a/deform/templates/file_upload.pt
+++ b/deform/templates/file_upload.pt
@@ -1,4 +1,4 @@
-<div class="deform-file-upload"
+<div class="deform-file-upload form-control-static"
      tal:define="oid oid|field.oid;
                  css_class css_class|field.widget.css_class;
                  style style|field.widget.style">

From 12cd015be75d467f14287ec2c2ae6ca6a542d510 Mon Sep 17 00:00:00 2001
From: Jeff Dairiki <dairiki@dairiki.org>
Date: Thu, 26 Sep 2013 20:15:34 -0700
Subject: [PATCH 2/9] A better crack at a nicer looking FileUploadWidget.

Very clever ideas blantantly ripped-off from
http://www.abeautifulsite.net/blog/2013/08/whipping-file-inputs-into-shape-with-bootstrap-3/

Issues:

I've not tested this with many browsers.

I'm not sure where to put the id="${oid}".  (Is it really even necessary?)
Putting on the file input element causes deform.js to focus it (if it's
the first field on the page) which, I guess because it is not visible,
results in unwanted scrolling.
---
 deform/static/css/file_upload.css    | 39 ++++++++++++++++++++++++++++
 deform/static/scripts/file_upload.js | 11 ++++++++
 deform/templates/file_upload.pt      | 37 +++++++++++++-------------
 deform/widget.py                     |  8 ++++++
 4 files changed, 77 insertions(+), 18 deletions(-)
 create mode 100644 deform/static/css/file_upload.css
 create mode 100644 deform/static/scripts/file_upload.js

diff --git a/deform/static/css/file_upload.css b/deform/static/css/file_upload.css
new file mode 100644
index 00000000..014b0897
--- /dev/null
+++ b/deform/static/css/file_upload.css
@@ -0,0 +1,39 @@
+/* Ripped off from
+*  http://www.abeautifulsite.net/blog/2013/08/whipping-file-inputs-into-shape-with-bootstrap-3/
+*/
+.btn-file {
+    position: relative;
+    overflow: hidden;
+}
+.btn-file {
+    background-color: #f2f2f2;
+}
+.btn-file:hover {
+    background-color: #ededed;
+}
+.btn-file input[type=file] {
+    position: absolute;
+    top: 0;
+    right: 0;
+    min-width: 100%;
+    min-height: 100%;
+    font-size: 999px;
+    text-align: right;
+    filter: alpha(opacity=0);
+    opacity: 0;
+    background: red;
+    cursor: inherit;
+    display: block;
+}
+.btn-file .upload-change,
+.upload-have-file .upload-select
+{
+    display: none;
+}
+.upload-have-file .upload-change
+{
+    display: inline;
+}
+.upload-filename[readonly] {
+    background-color: #fdfdfd;
+}
diff --git a/deform/static/scripts/file_upload.js b/deform/static/scripts/file_upload.js
new file mode 100644
index 00000000..1813dacc
--- /dev/null
+++ b/deform/static/scripts/file_upload.js
@@ -0,0 +1,11 @@
+/* Ripped off from
+*  http://www.abeautifulsite.net/blog/2013/08/whipping-file-inputs-into-shape-with-bootstrap-3/
+*/
+
+$(document).on('change', '.btn-file :file', function() {
+    var $file_input = $(this);
+    var filename = $file_input.val().replace(/\\/g, '/').replace(/.*\//, '');
+    var $group = $file_input.parents('.input-group')
+    $group.find(':text').val(filename);
+    $group.find('.btn-file').addClass('upload-have-file');
+});
diff --git a/deform/templates/file_upload.pt b/deform/templates/file_upload.pt
index a34c4fd9..883f81ab 100644
--- a/deform/templates/file_upload.pt
+++ b/deform/templates/file_upload.pt
@@ -1,24 +1,25 @@
-<div class="deform-file-upload form-control-static"
+<div class="deform-file-upload"
      tal:define="oid oid|field.oid;
                  css_class css_class|field.widget.css_class;
-                 style style|field.widget.style">
-
+                 style style|field.widget.style;
+                 uid cstruct.get('uid');
+                 have_file uid and ' upload-have-file' or '';"
+     tal:omit-tag="">
   ${field.start_mapping()}
-
-  <div class="deform-replaces" tal:condition="cstruct.get('uid')">
-
-    <input type="hidden" name="uid" value="${cstruct['uid']}"
-           id="${oid}-uid"/>
-    <span tal:content="cstruct.get('filename')"
-          id="${oid}-filename"/>
-
+  <div class="input-group">
+    <span class="input-group-btn">
+      <span class="btn btn-default btn-file${have_file}">
+        <span class="upload-select">Select file…</span>
+        <span class="upload-change">Change file…</span>
+        <input type="file" name="upload"/>
+      </span>
+    </span>
+    <input type="text" readonly=""
+           tal:attributes="
+           class string: form-control upload-filename ${css_class or ''};
+           value cstruct.get('filename');
+           style style"/>
   </div>
-
-  <input type="file" name="upload" 
-         tal:attributes="class css_class;
-                         style style;"
-         id="${oid}"/>
-
+  <input type="hidden" name="uid" value="${uid}" tal:condition="uid"/>
   ${field.end_mapping()}
-
 </div>
diff --git a/deform/widget.py b/deform/widget.py
index f5cc54aa..e415a20e 100644
--- a/deform/widget.py
+++ b/deform/widget.py
@@ -1617,6 +1617,8 @@ class FileUploadWidget(Widget):
     template = 'file_upload'
     readonly_template = 'readonly/file_upload'
 
+    requirements = (('fileupload', None),)
+
     _pstruct_schema = SchemaNode(
         Mapping(),
         SchemaNode(_FieldStorage(), name='upload', missing=None),
@@ -2045,6 +2047,12 @@ def __call__(self, requirements):
               'css':'deform:static/select2/select2.css',
             },
         },
+    'fileupload': {
+        None: {
+            'js': 'deform:static/scripts/file_upload.js',
+            'css': 'deform:static/css/file_upload.css',
+            },
+        },
     }
 
 default_resource_registry = ResourceRegistry()

From 506e7c477a2e69abaf615ee45d43ef4792d3f36f Mon Sep 17 00:00:00 2001
From: Jeff Dairiki <dairiki@dairiki.org>
Date: Fri, 27 Sep 2013 07:51:08 -0700
Subject: [PATCH 3/9] Allow setting the accept attribute on file input controls

---
 deform/templates/file_upload.pt | 3 ++-
 deform/widget.py                | 3 +++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/deform/templates/file_upload.pt b/deform/templates/file_upload.pt
index 883f81ab..4bfe8332 100644
--- a/deform/templates/file_upload.pt
+++ b/deform/templates/file_upload.pt
@@ -11,7 +11,8 @@
       <span class="btn btn-default btn-file${have_file}">
         <span class="upload-select">Select file…</span>
         <span class="upload-change">Change file…</span>
-        <input type="file" name="upload"/>
+        <input type="file" name="${oid}"
+               tal:attributes="accept accept|field.widget.accept"/>
       </span>
     </span>
     <input type="text" readonly=""
diff --git a/deform/widget.py b/deform/widget.py
index e415a20e..45876a8c 100644
--- a/deform/widget.py
+++ b/deform/widget.py
@@ -1613,9 +1613,12 @@ class FileUploadWidget(Widget):
         The template name used to render the widget in read-only mode.
         Default: ``readonly/file_upload``.
 
+    accept
+        The ``accept`` attribute of the input field (default ``None``).
     """
     template = 'file_upload'
     readonly_template = 'readonly/file_upload'
+    accept = None
 
     requirements = (('fileupload', None),)
 

From 22fe7c36a039bf64095554bb62982d8c95d05df8 Mon Sep 17 00:00:00 2001
From: Jeff Dairiki <dairiki@dairiki.org>
Date: Sat, 5 Oct 2013 08:13:51 -0700
Subject: [PATCH 4/9] dammit.  unbreak the upload widget again

---
 deform/templates/file_upload.pt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/deform/templates/file_upload.pt b/deform/templates/file_upload.pt
index 4bfe8332..37fbd788 100644
--- a/deform/templates/file_upload.pt
+++ b/deform/templates/file_upload.pt
@@ -11,7 +11,7 @@
       <span class="btn btn-default btn-file${have_file}">
         <span class="upload-select">Select file…</span>
         <span class="upload-change">Change file…</span>
-        <input type="file" name="${oid}"
+        <input type="file" name="upload"
                tal:attributes="accept accept|field.widget.accept"/>
       </span>
     </span>

From fee200adb1447cadf8e829769cde860a3d5d5ab5 Mon Sep 17 00:00:00 2001
From: Jeff Dairiki <dairiki@dairiki.org>
Date: Sun, 6 Oct 2013 10:54:56 -0700
Subject: [PATCH 5/9] Rejigger file upload js as a jquery plugin.

Move what's left of the custom CSS into form.css.
---
 deform/static/css/file_upload.css    |  39 ----------
 deform/static/css/form.css           |  11 +++
 deform/static/scripts/file_upload.js | 107 +++++++++++++++++++++++++--
 deform/templates/file_upload.pt      |  40 ++++------
 deform/widget.py                     |   1 -
 5 files changed, 126 insertions(+), 72 deletions(-)
 delete mode 100644 deform/static/css/file_upload.css

diff --git a/deform/static/css/file_upload.css b/deform/static/css/file_upload.css
deleted file mode 100644
index 014b0897..00000000
--- a/deform/static/css/file_upload.css
+++ /dev/null
@@ -1,39 +0,0 @@
-/* Ripped off from
-*  http://www.abeautifulsite.net/blog/2013/08/whipping-file-inputs-into-shape-with-bootstrap-3/
-*/
-.btn-file {
-    position: relative;
-    overflow: hidden;
-}
-.btn-file {
-    background-color: #f2f2f2;
-}
-.btn-file:hover {
-    background-color: #ededed;
-}
-.btn-file input[type=file] {
-    position: absolute;
-    top: 0;
-    right: 0;
-    min-width: 100%;
-    min-height: 100%;
-    font-size: 999px;
-    text-align: right;
-    filter: alpha(opacity=0);
-    opacity: 0;
-    background: red;
-    cursor: inherit;
-    display: block;
-}
-.btn-file .upload-change,
-.upload-have-file .upload-select
-{
-    display: none;
-}
-.upload-have-file .upload-change
-{
-    display: inline;
-}
-.upload-filename[readonly] {
-    background-color: #fdfdfd;
-}
diff --git a/deform/static/css/form.css b/deform/static/css/form.css
index d3a614df..ee90cf09 100644
--- a/deform/static/css/form.css
+++ b/deform/static/css/form.css
@@ -29,3 +29,14 @@ form .required:after {
 form .deform-readonly-text {
     font-style: italic;
 }
+
+/* styles for file_upload.js, file_upload.pt */
+.btn-file {
+    background-color: #f2f2f2;
+}
+.btn-file:hover {
+    background-color: #ededed;
+}
+.upload-filename[readonly] {
+    background-color: #fdfdfd;
+}
diff --git a/deform/static/scripts/file_upload.js b/deform/static/scripts/file_upload.js
index 1813dacc..f9151718 100644
--- a/deform/static/scripts/file_upload.js
+++ b/deform/static/scripts/file_upload.js
@@ -1,11 +1,102 @@
-/* Ripped off from
+/**
+* Nicer looking file inputs for bootstrap 3
+* By Jeff Dairiki <dairiki@dairiki.org> based on ideas from
 *  http://www.abeautifulsite.net/blog/2013/08/whipping-file-inputs-into-shape-with-bootstrap-3/
 */
+(function ($) {
 
-$(document).on('change', '.btn-file :file', function() {
-    var $file_input = $(this);
-    var filename = $file_input.val().replace(/\\/g, '/').replace(/.*\//, '');
-    var $group = $file_input.parents('.input-group')
-    $group.find(':text').val(filename);
-    $group.find('.btn-file').addClass('upload-have-file');
-});
+    var Upload = function (element, options) {
+        this.$element = $(element);
+        this.options = $.extend({}, Upload.DEFAULTS,
+                                this.$element.data(),
+                                options);
+
+        this.orig_style = this.$element.attr('style');
+        this.$input_group = $(this.options.template)
+            .replaceAll(this.$element)
+            .attr('style', this.orig_style);
+        this.$input_group.find('.input-group-btn')
+            .append(this.$element);
+        this.$element
+            .on('change.deform.upload', $.proxy(this, 'update'))
+            .css(this.options.element_style);
+        this.update();
+    };
+
+    Upload.DEFAULTS = {
+        filename: null,
+        selectfile: 'Select file…',
+        changefile: 'Change file…',
+
+        template: '<div class="input-group">'
+            + '<span class="input-group-btn"'
+            + ' style="position: relative; overflow: hidden;">'
+            + '<span class="btn btn-default btn-file"></span>'
+            + '</span>'
+            + '<input type="text" readonly=""'
+            + ' class="form-control upload-filename"/>'
+            + '</div>',
+
+        element_style: {
+            position: 'absolute',
+            bottom: '0',
+            right: '0',
+            minWidth: '100%',
+            minHeight: '100%',
+            fontSize: '999px',
+            textAlign: 'right',
+            filter: 'alpha(opacity=0)',
+            opacity: '0',
+            background: 'red',
+            cursor: 'inherit',
+            display: 'block'
+        }
+    };
+
+    Upload.prototype.update = function () {
+        var selected_filename = this.$element.val().replace(/.*[\\\/]/, ''),
+            options = this.options,
+            filename = selected_filename || options.filename;
+        this.$input_group.find(':text')
+            .val(filename);
+        this.$input_group.find('.btn-file')
+            .text(filename ? options.changefile : options.selectfile);
+    };
+
+    Upload.prototype.destroy = function () {
+        this.$element
+            .off('.deform.upload')
+            .attr('style', this.orig_style || null)
+            .replaceAll(this.$input_group)
+            .removeData('deform.upload');
+    };
+
+
+    ////////////////////////////////////////////////////////////////
+    // plugin definition
+
+    var old = $.fn.upload;
+
+    $.fn.upload = function (option) {
+        return this.each(function () {
+            var $this = $(this),
+                data = $this.data('deform.upload');
+            if (!data) {
+                var options = typeof option == 'object' && option;
+                data = new Upload(this, options);
+                $this.data('deform.upload', data);
+            }
+            if (typeof option == 'string') {
+                data[option]();
+            }
+        });
+    };
+
+    $.fn.upload.Constructor = Upload;
+
+    $.fn.upload.noConflict = function () {
+        $.fn.upload = old;
+        return this;
+    };
+
+})(window.jQuery);
diff --git a/deform/templates/file_upload.pt b/deform/templates/file_upload.pt
index 37fbd788..455339d3 100644
--- a/deform/templates/file_upload.pt
+++ b/deform/templates/file_upload.pt
@@ -1,26 +1,18 @@
-<div class="deform-file-upload"
-     tal:define="oid oid|field.oid;
-                 css_class css_class|field.widget.css_class;
-                 style style|field.widget.style;
-                 uid cstruct.get('uid');
-                 have_file uid and ' upload-have-file' or '';"
-     tal:omit-tag="">
+<tal:block tal:define="oid oid|field.oid;
+                       css_class css_class|field.widget.css_class;
+                       style style|field.widget.style;">
   ${field.start_mapping()}
-  <div class="input-group">
-    <span class="input-group-btn">
-      <span class="btn btn-default btn-file${have_file}">
-        <span class="upload-select">Select file…</span>
-        <span class="upload-change">Change file…</span>
-        <input type="file" name="upload"
-               tal:attributes="accept accept|field.widget.accept"/>
-      </span>
-    </span>
-    <input type="text" readonly=""
-           tal:attributes="
-           class string: form-control upload-filename ${css_class or ''};
-           value cstruct.get('filename');
-           style style"/>
-  </div>
-  <input type="hidden" name="uid" value="${uid}" tal:condition="uid"/>
+  <input type="file" name="upload" id="${oid}"
+         tal:attributes="style style;
+                         accept accept|field.widget.accept;
+                         data-filename cstruct.get('filename');"/>
+  <input tal:define="uid cstruct.get('uid')"
+         tal:condition="uid"
+         type="hidden" name="uid" value="${uid}"/>
   ${field.end_mapping()}
-</div>
+  <script type="text/javascript">
+    deform.addCallback('${oid}', function (oid) {
+      $('#' + oid).upload();
+    });
+  </script>
+</tal:block>
diff --git a/deform/widget.py b/deform/widget.py
index 45876a8c..418ac1dc 100644
--- a/deform/widget.py
+++ b/deform/widget.py
@@ -2053,7 +2053,6 @@ def __call__(self, requirements):
     'fileupload': {
         None: {
             'js': 'deform:static/scripts/file_upload.js',
-            'css': 'deform:static/css/file_upload.css',
             },
         },
     }

From 01bb6ea3a4338b6e6e274fdf562f7e06b0a4bd47 Mon Sep 17 00:00:00 2001
From: Jeff Dairiki <dairiki@dairiki.org>
Date: Sun, 6 Oct 2013 11:01:38 -0700
Subject: [PATCH 6/9] Make the entire widget clickable

---
 deform/static/scripts/file_upload.js | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/deform/static/scripts/file_upload.js b/deform/static/scripts/file_upload.js
index f9151718..62806d0e 100644
--- a/deform/static/scripts/file_upload.js
+++ b/deform/static/scripts/file_upload.js
@@ -14,8 +14,8 @@
         this.orig_style = this.$element.attr('style');
         this.$input_group = $(this.options.template)
             .replaceAll(this.$element)
-            .attr('style', this.orig_style);
-        this.$input_group.find('.input-group-btn')
+            .attr('style', this.orig_style)
+            .css({position: 'relative', overflow: 'hidden'})
             .append(this.$element);
         this.$element
             .on('change.deform.upload', $.proxy(this, 'update'))
@@ -29,8 +29,7 @@
         changefile: 'Change file…',
 
         template: '<div class="input-group">'
-            + '<span class="input-group-btn"'
-            + ' style="position: relative; overflow: hidden;">'
+            + '<span class="input-group-btn">'
             + '<span class="btn btn-default btn-file"></span>'
             + '</span>'
             + '<input type="text" readonly=""'

From 75c9a453199a7e810f6763229902b4a1d57f16d1 Mon Sep 17 00:00:00 2001
From: Jeff Dairiki <dairiki@dairiki.org>
Date: Sun, 6 Oct 2013 14:13:24 -0700
Subject: [PATCH 7/9] Reorder inputs to restore radiused border on the right
 side of the input-group

---
 deform/static/scripts/file_upload.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/deform/static/scripts/file_upload.js b/deform/static/scripts/file_upload.js
index 62806d0e..56a8ee56 100644
--- a/deform/static/scripts/file_upload.js
+++ b/deform/static/scripts/file_upload.js
@@ -15,8 +15,8 @@
         this.$input_group = $(this.options.template)
             .replaceAll(this.$element)
             .attr('style', this.orig_style)
-            .css({position: 'relative', overflow: 'hidden'})
-            .append(this.$element);
+            .css({position: 'relative', overflow: 'hidden'});
+        this.$input_group.children('input').before(this.$element);
         this.$element
             .on('change.deform.upload', $.proxy(this, 'update'))
             .css(this.options.element_style);

From 790e165588ae0b42e13582f741209a609fb781e9 Mon Sep 17 00:00:00 2001
From: Jeff Dairiki <dairiki@dairiki.org>
Date: Sun, 6 Oct 2013 14:25:25 -0700
Subject: [PATCH 8/9] Add extra wrapper to fix overflow: hidden in firefox

The .input-group has display: table.  This appears to cause Firefox to
ignore the overflow: hidden.  This results in the file select dialog being
triggered by clicks anywhere in a large region above and/or to the left
of the file widget.
---
 deform/static/scripts/file_upload.js | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/deform/static/scripts/file_upload.js b/deform/static/scripts/file_upload.js
index 56a8ee56..6ffbbec7 100644
--- a/deform/static/scripts/file_upload.js
+++ b/deform/static/scripts/file_upload.js
@@ -16,7 +16,7 @@
             .replaceAll(this.$element)
             .attr('style', this.orig_style)
             .css({position: 'relative', overflow: 'hidden'});
-        this.$input_group.children('input').before(this.$element);
+        this.$input_group.find(':text').before(this.$element);
         this.$element
             .on('change.deform.upload', $.proxy(this, 'update'))
             .css(this.options.element_style);
@@ -28,12 +28,14 @@
         selectfile: 'Select file…',
         changefile: 'Change file…',
 
-        template: '<div class="input-group">'
+        template: '<div>'
+            + '<div class="input-group">'
             + '<span class="input-group-btn">'
             + '<span class="btn btn-default btn-file"></span>'
             + '</span>'
             + '<input type="text" readonly=""'
             + ' class="form-control upload-filename"/>'
+            + '</div>'
             + '</div>',
 
         element_style: {

From 22e519466a6f70f9d26d5cf961b82c709a4f76ca Mon Sep 17 00:00:00 2001
From: Jeff Dairiki <dairiki@dairiki.org>
Date: Sun, 6 Oct 2013 15:41:25 -0700
Subject: [PATCH 9/9] Adjust positioning of invisible file input for FF 3.5

---
 deform/static/scripts/file_upload.js | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/deform/static/scripts/file_upload.js b/deform/static/scripts/file_upload.js
index 6ffbbec7..81adc9d6 100644
--- a/deform/static/scripts/file_upload.js
+++ b/deform/static/scripts/file_upload.js
@@ -40,7 +40,13 @@
 
         element_style: {
             position: 'absolute',
-            bottom: '0',
+            /* Older FF (3.5) seems to put a margin on the bottom of
+            *  the file input (the margin is proportional to
+            *  font-size, so in this case it's significant.)  Shift
+            *  bottom a bit to allow for some slop.
+            */
+            //bottom: '0',
+            bottom: '-40px',
             right: '0',
             minWidth: '100%',
             minHeight: '100%',