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%',