Sfoglia il codice sorgente

Fix #401: Various enhancements to preview file thumbnails and error validations

Kartik Visweswaran 9 anni fa
parent
commit
6011319c2e
6 ha cambiato i file con 107 aggiunte e 34 eliminazioni
  1. 1 0
      CHANGE.md
  2. 35 0
      README.md
  3. 4 8
      css/fileinput.css
  4. 0 0
      css/fileinput.min.css
  5. 67 26
      js/fileinput.js
  6. 0 0
      js/fileinput.min.js

+ 1 - 0
CHANGE.md

@@ -20,6 +20,7 @@ Change Log: `bootstrap-fileinput`
 13. (enh #391): Thumbnail styling enhancements for flash, html, and object types.
 14. (enh #393): Minor enhancements to abort events before upload.
 15. (bug #398): Validate `data.errorkeys` more correctly.
+16. (enh #401): Various enhancements to preview file thumbnails and error validations.
 
 ## version 4.2.5
 

+ 35 - 0
README.md

@@ -1105,6 +1105,13 @@ Files other than `image` or `text` will be displayed as a thumbnail with the fil
 ### zoomIndicator
 _string_ the icon for zooming the file content in a new modal dialog.  This is currently applicable only for text file previews. Defaults to `<i class="glyphicon glyphicon-zoom-in"></i>`.  The following variables will be replaced:
 
+### errorCloseButton
+_string_ the markup for the button to close the error container block. Note this button indicator must include the CSS class `kv-error-close`. If not set, this defaults to:
+
+```html
+<span class="close kv-error-close">&times;</span>
+```
+
 ### elErrorContainer
 _string_ the identifier for the container element displaying the error (e.g. `'#id'`). If not set, will default to the container with CSS class `kv-fileinput-error` inside the preview container (identified by `elPreviewContainer`). The `msgErrorClass` will be automatically appended to this container before displaying the error.
 
@@ -1318,6 +1325,34 @@ $('#input-id').on('fileunlock', function(event, filestack, extraData) {
 });
 ```
 
+#### filepreremove
+This event is triggered before removal of each thumbnail file in ajax upload mode. This is different than `filepredelete` in the sense, that this is NOT applicable for thumbnails set via `initialPreview`. The additional parameters available with this event are:
+
+- `previewId`: the identifier of the preview thumbnail container.
+- `index`: the zero-based index of the file in the preview container.
+
+>Note: You can call `event.preventDefault()` or `return false;` to abort the removal process.
+
+```js
+$('#input-id').on('filepreremove', function(event, id, index) {
+    if (!window.confirm('Do you wish to remove?')) {
+        event.preventDefault();
+    }
+});
+```
+
+#### fileremoved
+This event is triggered after removal of each thumbnail file in ajax upload mode. This is different than `filedeleted` in the sense, that this is NOT applicable for thumbnails set via `initialPreview`. The additional parameters available with this event are:
+
+- `previewId`: the identifier of the preview thumbnail container.
+- `index`: the zero-based index of the file in the preview container.
+
+```js
+$('#input-id').on('fileremoved', function(event, id, index) {
+    console.log('fileremoved id = ' + id);
+});
+```
+
 #### filepredelete
 This event is triggered before deletion of each thumbnail file in the `initialPreview` content set. Additional parameters available are: 
 

+ 4 - 8
css/fileinput.css

@@ -64,7 +64,7 @@
     color: #a94442;
     text-align: center;
     border-radius: 5px;
-    padding: 5px;
+    padding: 5px 10px 5px 5px;
 }
 
 .file-error-message pre, .file-error-message ul {
@@ -98,7 +98,7 @@
     vertical-align: middle;
 }
 
-.file-preview-frame:hover {
+.file-preview-frame:not(.file-preview-error):hover {
     box-shadow: 3px 3px 5px 0 #333;
 }
 
@@ -127,7 +127,7 @@
     opacity: 0.8;
 }
 
-.file-preview-other:hover {
+.file-preview-frame:not(.file-preview-error) .file-preview-other:hover {
     opacity: 1;
 }
 
@@ -190,7 +190,7 @@
     margin: 5px auto 10px auto;
 }
 
-.file-footer-caption:hover {
+.file-preview-frame:not(.file-preview-error) .file-footer-caption:hover {
     color: #000;
 }
 
@@ -218,8 +218,4 @@
 .file-uploading {
     background: url('../img/loading-sm.gif') no-repeat center bottom 10px;
     opacity: 0.6;
-}
-
-.file-input .btn[disabled], .file-input .btn.disabled, .file-input .btn[disabled] *, .file-input .btn.disabled * {
-    cursor: not-allowed;
 }

File diff suppressed because it is too large
+ 0 - 0
css/fileinput.min.css


+ 67 - 26
js/fileinput.js

@@ -218,7 +218,7 @@
             uploadClass: 'btn btn-xs btn-default',
             uploadTitle: 'Upload file',
             indicatorNew: '<i class="glyphicon glyphicon-hand-down text-warning"></i>',
-            indicatorSuccess: '<i class="glyphicon glyphicon-ok-sign file-icon-lg text-success"></i>',
+            indicatorSuccess: '<i class="glyphicon glyphicon-ok-sign text-success"></i>',
             indicatorError: '<i class="glyphicon glyphicon-exclamation-sign text-danger"></i>',
             indicatorLoading: '<i class="glyphicon glyphicon-hand-up text-muted"></i>',
             indicatorNewTitle: 'Not uploaded yet',
@@ -430,9 +430,9 @@
             return Math.round(new Date().getTime() + (Math.random() * 100));
         },
         htmlEncode = function (str) {
-            return str.replace(/\</g, '&lt;')
-                .replace(/\</g, '&gt;')
-                .replace(/\&/g, '&amp;')
+            return str.replace(/\&/g, '&amp;')
+                .replace(/\</g, '&lt;')
+                .replace(/\>/g, '&gt;')
                 .replace(/\"/g, '&quot;')
                 .replace(/\'/g, '&apos;');
         },
@@ -549,6 +549,9 @@
             t = self.getLayoutTemplate('progress');
             self.progressTemplate = t.replace('{class}', self.progressClass);
             self.progressCompleteTemplate = t.replace('{class}', self.progressCompleteClass);
+            if (self.$element.attr('disabled')) {
+                self.disable();
+            }
         },
         parseError: function (jqXHR, errorThrown, fileName) {
             var self = this, errMsg = $.trim(errorThrown + ''),
@@ -571,6 +574,9 @@
             } else {
                 self.$element.trigger(e);
             }
+            if (e.isDefaultPrevented()) {
+                return false;
+            }
             if (!e.result) {
                 return e.result;
             }
@@ -711,7 +717,7 @@
             var self = this, label = self.minFileCount > 1 ? self.filePlural : self.fileSingle,
                 msg = self.msgFilesTooLess.replace('{n}', self.minFileCount).replace('{files}', label),
                 $error = self.$errorContainer;
-            $error.html(msg);
+            self.addError(msg);
             self.isError = true;
             self.updateFileDetails(0);
             $error.fadeIn(800);
@@ -880,27 +886,39 @@
         initFileActions: function () {
             var self = this;
             self.$preview.find('.kv-file-remove').each(function () {
-                var $el = $(this), $frame = $el.closest('.file-preview-frame'),
-                    ind = $frame.attr('data-fileindex'), n, cap;
-                handler($el, 'click', function () {
-                    if (!self.validateMinCount()) {
+                var $el = $(this), $frame = $el.closest('.file-preview-frame'), hasError,
+                    id = $frame.attr('id'), ind = $frame.attr('data-fileindex'), n, cap, status;
+                handler($el, 'click', function (event) {
+                    status = self.raise('filepreremove', [id, ind]);
+                    if (status === false || !self.validateMinCount()) {
                         return false;
                     }
+                    hasError = $frame.hasClass('file-preview-error');
                     self.cleanMemory($frame);
                     $frame.fadeOut('slow', function () {
                         self.filestack[ind] = undefined;
                         self.clearObjects($frame);
                         $frame.remove();
-                        var filestack = self.getFileStack(true), len = filestack.length,
-                            chk = previewCache.count(self.id);
+                        if (id && hasError) {
+                            self.$errorContainer.find('li[data-file-id="' + id + '"]').fadeOut('fast', function() {
+                               $(this).remove();
+                               if (!self.$errorContainer.find('li').length &&
+                                   !self.$preview.find('.file-preview-frame.file-preview-error').length) {
+                                   self.resetErrors();
+                               }
+                            });
+                        }
+                        var filestack = self.getFileStack(true), len = filestack.length, chk = previewCache.count(self.id),
+                            hasThumb = self.showPreview && self.$preview.find('.file-preview-frame').length;
                         self.clearFileInput();
-                        if (len === 0 && chk === 0) {
+                        if (len === 0 && chk === 0 && !hasThumb) {
                             self.reset();
                         } else {
                             n = chk + len;
                             cap = n > 1 ? self.getMsgSelected(n) : (filestack[0] ? filestack[0].name : '');
                             self.setCaption(cap);
                         }
+                        self.raise('fileremoved', [id, ind]);
                     });
                 });
             });
@@ -909,7 +927,9 @@
                 handler($el, 'click', function () {
                     var $frame = $el.closest('.file-preview-frame'),
                         ind = $frame.attr('data-fileindex');
-                    self.uploadSingle(ind, self.filestack, false);
+                    if (!$frame.hasClass('file-preview-error')) {
+                        self.uploadSingle(ind, self.filestack, false);
+                    }
                 });
             });
         },
@@ -972,6 +992,10 @@
                 $indicator = $thumb.find('.file-upload-indicator'),
                 config = self.fileActionSettings;
             $thumb.removeClass('file-preview-success file-preview-error file-preview-loading');
+            if (status === 'Error') {
+                $thumb.find('.kv-file-upload').attr('disabled', true);
+                addCss($thumb, 'btn disabled');
+            }
             $indicator.html(config[icon]);
             $indicator.attr('title', config[msg]);
             $thumb.addClass(css);
@@ -1006,7 +1030,8 @@
         initPreviewDeletes: function () {
             var self = this, deleteExtraData = self.deleteExtraData || {},
                 resetProgress = function () {
-                    if (self.$preview.find('.kv-file-remove').length === 0) {
+                    var hasFiles = self.isUploadable ? previewCache.count(self.id) : self.$element.get(0).files.length;
+                    if (self.$preview.find('.kv-file-remove').length === 0 && !hasFiles) {
                         self.reset();
                         self.initialCaption = '';
                     }
@@ -1510,7 +1535,7 @@
             };
             fnSuccess = function (data, textStatus, jqXHR) {
                 var outData = self.getOutData(jqXHR, data), $thumbs = self.getThumbs(), key = 0;
-                    keys = isEmpty(data) || isEmpty(data.errorkeys) ? [] : data.errorkeys;
+                keys = isEmpty(data) || isEmpty(data.errorkeys) ? [] : data.errorkeys;
                 if (isEmpty(data) || isEmpty(data.error)) {
                     self.raise('filebatchuploadsuccess', [outData]);
                     setAllUploaded();
@@ -1633,6 +1658,15 @@
         showFileIcon: function () {
             this.$captionContainer.find('.kv-caption-icon').show();
         },
+        addError: function (msg) {
+            var self = this, $error = self.$errorContainer;
+            if (msg && $error.length) {
+                $error.html(self.errorCloseButton + msg);
+                $error.find('.kv-error-close').off('click').on('click', function () {
+                    $error.fadeOut('slow');
+                });
+            }
+        },
         resetErrors: function (fade) {
             var self = this, $error = self.$errorContainer;
             self.isError = false;
@@ -1649,17 +1683,18 @@
             if (!folders) {
                 return;
             }
-            $error.html(self.msgFoldersNotAllowed.replace(/\{n\}/g, folders));
+            self.addError(self.msgFoldersNotAllowed.replace(/\{n\}/g, folders));
             $error.fadeIn(800);
             addCss(self.$container, 'has-error');
             self.raise('filefoldererror', [folders]);
         },
         showUploadError: function (msg, params, event) {
-            var self = this, $error = self.$errorContainer, ev = event || 'fileuploaderror';
+            var self = this, $error = self.$errorContainer, ev = event || 'fileuploaderror',
+                e = params && params.id ? '<li data-file-id="' + params.id + '">' + msg + '</li>' : '<li>' + msg + '</li>';
             if ($error.find('ul').length === 0) {
-                $error.html('<ul><li>' + msg + '</li></ul>');
+                self.addError('<ul>' + e + '</ul>');
             } else {
-                $error.find('ul').append('<li>' + msg + '</li>');
+                $error.find('ul').append(e);
             }
             $error.fadeIn(800);
             self.raise(ev, [params]);
@@ -1671,7 +1706,7 @@
             var self = this, $error = self.$errorContainer, ev = event || 'fileerror';
             params = params || {};
             params.reader = self.reader;
-            $error.html(msg);
+            self.addError(msg);
             $error.fadeIn(800);
             self.raise(ev, [params]);
             if (!self.isUploadable) {
@@ -1777,7 +1812,7 @@
                         .replace(/\{footer\}/g, footer).replace(/\{data\}/g, data);
                 }
                 self.$preview.append("\n" + content);
-                self.validateImage(i, previewId);
+                self.validateImage(i, previewId, caption);
             } else {
                 self.previewDefault(file, previewId);
             }
@@ -1796,15 +1831,20 @@
             var self = this, $el = self.$element, $preview = self.$preview, reader = self.reader,
                 $container = self.$previewContainer, $status = self.$previewStatus, msgLoading = self.msgLoading,
                 msgProgress = self.msgProgress, previewInitId = self.previewInitId, numFiles = files.length,
-                settings = self.fileTypeSettings, ctr = self.filestack.length,
+                settings = self.fileTypeSettings, ctr = self.filestack.length, readFile,
                 throwError = function (msg, file, previewId, index) {
                     var p1 = $.extend(self.getOutData({}, {}, files), {id: previewId, index: index}),
                         p2 = {id: previewId, index: index, file: file, files: files};
                     self.previewDefault(file, previewId, true);
+                    if (self.isUploadable) {
+                        self.filestack.push(undefined);
+                    }
+                    setTimeout(readFile(index + 1), 100);
+                    self.initFileActions();
                     return self.isUploadable ? self.showUploadError(msg, p1) : self.showError(msg, p2);
                 };
 
-            function readFile(i) {
+            readFile = function (i) {
                 if (isEmpty($el.attr('multiple'))) {
                     numFiles = 1;
                 }
@@ -2039,10 +2079,10 @@
             }
             self.showFolderError(folders);
         },
-        validateImage: function (i, previewId) {
+        validateImage: function (i, previewId, fname) {
             var self = this, $preview = self.$preview, params, w1, w2,
-                $thumb = $preview.find("#" + previewId), fname = 'Untitled',
-                $img = $thumb.find('img');
+                $thumb = $preview.find("#" + previewId), $img = $thumb.find('img');
+            fname = fname || 'Untitled';
             if (!$img.length) {
                 return;
             }
@@ -2278,6 +2318,7 @@
         elPreviewImage: null,
         elPreviewStatus: null,
         elErrorContainer: null,
+        errorCloseButton: '<span class="close kv-error-close">&times;</span>',
         slugCallback: null,
         dropZoneEnabled: true,
         dropZoneTitleClass: 'file-drop-zone-title',

File diff suppressed because it is too large
+ 0 - 0
js/fileinput.min.js


Some files were not shown because too many files changed in this diff