Repository: mockup
Branch: refs/heads/master
Date: 2014-12-12T11:51:12-05:00
Author: Allen Thomerson (athomerson) <allen.thomerson@gmail.com>
Commit: https://github.com/plone/mockup/commit/f1eb937607d046bcd49a861f2f19b5b5128f9841

Modified the upload pattern to be used as a widget for NamedFiles

Files changed:
M mockup/patterns/upload/less/pattern.upload.less
M mockup/patterns/upload/pattern.js
M mockup/tests/pattern-upload-test.js

diff --git a/mockup/patterns/upload/less/pattern.upload.less b/mockup/patterns/upload/less/pattern.upload.less
index 7184337..0e2ff71 100644
--- a/mockup/patterns/upload/less/pattern.upload.less
+++ b/mockup/patterns/upload/less/pattern.upload.less
@@ -64,3 +64,37 @@
     }
     .dz-max-files-reached {}
 }
+.uploaded {
+    margin: 0 0 0 0px;
+    width: 100%;
+    display: table;
+    table-layout: fixed;
+}
+
+.uploaded {
+    -moz-box-shadow: 0 0 16px #ccc;
+}
+
+.existfileupload {
+    display: table-row;
+}
+
+.newfileupload {
+    display: table-row;
+}
+
+.filename {
+    display: table-cell;
+    padding-right: 1em;
+}
+
+.filesize {
+    display: table-cell;
+    padding-right: 1em;
+}
+
+.remove-item {
+    text-decoration: none;
+    cursor: pointer;
+    display: table-cell;
+}
diff --git a/mockup/patterns/upload/pattern.js b/mockup/patterns/upload/pattern.js
index 634afe5..facdcf2 100644
--- a/mockup/patterns/upload/pattern.js
+++ b/mockup/patterns/upload/pattern.js
@@ -18,6 +18,8 @@
  *    previewsContainer(selector): JavaScript selector for file preview in div element. (.upload-previews)
  *    container(selector): JavaScript selector for where to put upload stuff into in case of form. If not provided it will be place before the first submit button. ('')
  *    relatedItems(object): Related items pattern options. Will only use only if relativePath is used to use correct upload destination ({ attributes: ["UID", "Title", "Description", "getURL", "Type", "path", "ModificationDate"], batchSize: 20, basePath: "/", vocabularyUrl: null, width: 500, maximumSelectionSize: 1, placeholder: "Search for item on site..." })
+ *    isWidget(boolean): true or false for determining if this is a widget for Named Files
+ *    existing(object): Existing files pattern option. List of files already uploaded. Format: {[{name: 'filename', size: 12345, tmpname: 'filename', url: 'downloadurl'}, {name: 'otherfile', size: 23456, tmpname: 'otherfile', url: 'downloadurl'}]}
  *
  * Documentation:
  *
@@ -58,6 +60,7 @@ define([
 
   i18n.loadCatalog('widgets');
   var _t = i18n.MessageFactory('widgets');
+  var files = [];
 
   var UploadPattern = Base.extend({
     name: 'upload',
@@ -90,7 +93,9 @@ define([
         width: 500,
         maximumSelectionSize: 1,
         placeholder: _t('Search for item on site...')
-      }
+      },
+      isWidget: false,
+      existing: []
     },
 
     //placeholder: 'Search for item on site...'
@@ -102,6 +107,10 @@ define([
       self.currentPath = self.options.currentPath;
       self.numFiles = 0;
       self.currentFile = 0;
+ 
+      var $hidden = $('<input type="hidden" value="">');
+      $hidden.attr('name', self.options.paramName + 'uploaded');
+      self.$el.append($hidden);
 
       template = _.template(template, {_t: _t});
       self.$el.addClass(self.options.className);
@@ -113,6 +122,16 @@ define([
         self.$el.find('h2.title').hide();
       }
 
+      if (self.options.maxFiles === 1) {
+        self.$el.find('div.dz-message').find('p').text(_t('Drop file here...'));
+      }
+
+      if (self.options.isWidget) {
+        self.$el.find('p.help').hide();
+        self.$el.find('div.path').hide();
+        self.$el.find('.upload-all').hide();
+      }
+
       if (!self.options.ajaxUpload) {
         // no ajax upload, drop the fallback
         $('.fallback', this.$el).remove();
@@ -165,6 +184,9 @@ define([
 
       self.dropzone.on('addedfile', function(file) {
         self.showControls();
+        if ((files.length + 1) > self.options.maxFiles) {
+            self.dropzone.emit('maxfilesexceeded', file);
+        }
       });
 
       self.dropzone.on('removedfile', function() {
@@ -182,8 +204,48 @@ define([
       }
 
       self.dropzone.on('complete', function(file) {
-        if (self.dropzone.files.length < 1) {
+       if (self.dropzone.files.length < 1) {
           self.hideControls();
+       }
+        if (self.options.isWidget) {
+           self.dropzone.removeFile(file);
+        }
+      });
+
+      self.dropzone.on('success', function(file, response) {
+        var jsonResp = JSON.parse(response);
+        var $uploaded = self.$el.find('.uploaded');
+        var pushFile = {};
+        pushFile.name = jsonResp.name;
+        pushFile.tmpname = jsonResp.tmpname;
+        pushFile.size = jsonResp.size;
+        pushFile.url = jsonResp.url;
+        var $newfile = $('<div>');
+        $newfile.addClass('newfileupload');
+        var $url = $('<a>');
+        $url.attr('href', jsonResp.url);
+        $newfile.append($url);
+        var $filename = $('<span>');
+        $filename.addClass('filename');
+        $filename.text(jsonResp.name);
+        $url.append($filename);
+        var $filesize = $('<span>');
+        $filesize.append('  size: '+ self.formatBytes(jsonResp.size) );
+        $filesize.addClass('filesize');
+        $url.after($filesize);
+        var $remove = $('<button type="button" class="btn btn-danger btn-xs remove-item"> <span class="glyphicon glyphicon-remove"></span> </button>');
+        $remove.on( "click", function() {
+                  files = $.grep(files, function(value) {
+                                 return value !== pushFile;
+                   });
+                   $hidden.val(JSON.stringify(files));
+                   $newfile.remove();
+                   });
+        $filesize.after($remove);
+        files.push(pushFile);
+        $hidden.val(JSON.stringify(files));
+        if (self.options.isWidget) {
+            $uploaded.append($newfile);
         }
       });
 
@@ -194,6 +256,17 @@ define([
         self.$progress.attr('aria-valuenow', pct).css('width', pct + '%');
       });
 
+      self.dropzone.on('maxfilesexceeded', function(file) {
+       self.dropzone.removeFile(file);
+       var $uploaded = self.$el.find('.uploaded');
+       var $errmsg =$('<div>');
+       $errmsg.addClass('has-error');
+       $errmsg.text(_t('Maximum number of files reached'));
+       $uploaded.append($errmsg);
+       $errmsg.fadeIn('slow');
+       $errmsg.delay(5000).fadeOut();
+      });
+
       $('.upload-all', self.$el).click(function (e) {
         e.preventDefault();
         e.stopPropagation();
@@ -203,6 +276,7 @@ define([
           }
         });
       });
+    self.uploadExisting();
     },
 
     showControls: function() {
@@ -279,6 +353,8 @@ define([
       delete options.autoCleanResults;
       delete options.fileaddedClassName;
       delete options.useTus;
+      delete options.isWidget;
+      delete options.existing;
 
       if (self.options.previewsContainer) {
         /*
@@ -293,6 +369,9 @@ define([
 
       // XXX: do we need to allow this?
       options.autoProcessQueue = false;
+      if (self.options.isWidget) {
+          options.autoProcessQueue = true;
+      }
       // options.addRemoveLinks = true;  // we show them in the template
       options.previewTemplate = PreviewTemplate;
 
@@ -411,6 +490,48 @@ define([
         self.options.url = self.dropzone.options.url = self.getUrl();
       });
       return ri;
+    },
+
+    uploadExisting: function () {
+      // Load existing files:
+      var self = this;
+      var existing = self.options.existing;
+      var $hidden = this.$el.find('input[name="'+self.options.paramName+'uploaded"]');
+      var $container = this.$el.find('.upload-area');
+      var $uploaded = $('<div class="uploaded">');
+      $container.after($uploaded);
+      existing.forEach( function (file) {
+          var pushFile = {};
+          pushFile.name = file.name;
+          pushFile.tmpname = file.tmpname;
+          pushFile.size = file.size;
+          pushFile.url = file.url;
+          var $existing = $('<div>');
+          $existing.addClass('existfileupload');
+          var $url = $('<a>');
+          $url.attr('href', file.url);
+          $existing.append($url);
+          var $filename = $('<span>');
+          $filename.addClass('filename');
+          $filename.text(file.name);
+          $url.append($filename);
+          var $filesize = $('<span>');
+          $filesize.append('  size: '+ self.formatBytes(file.size) );
+          $filesize.addClass('filesize');
+          $url.after($filesize);
+          var $remove = $('<button type="button" class="btn btn-danger btn-xs remove-item"> <span class="glyphicon glyphicon-remove"></span> </button>');
+          $remove.on( "click", function() {
+                    files = $.grep(files, function(value) {
+                                   return value !== pushFile;
+                     });
+                     $hidden.val(JSON.stringify(files));
+                     $existing.remove();
+                     });
+          $filesize.after($remove);
+          files.push(pushFile);
+          $hidden.val(JSON.stringify(files));
+          $uploaded.append($existing);
+      });
     }
 
   });
diff --git a/mockup/tests/pattern-upload-test.js b/mockup/tests/pattern-upload-test.js
index 7c55df2..e3eb5aa 100644
--- a/mockup/tests/pattern-upload-test.js
+++ b/mockup/tests/pattern-upload-test.js
@@ -77,4 +77,106 @@ define([
     });
 
   });
+
+/* ==========================
+   TEST: Widget
+  ========================== */
+
+  describe('Widget', function () {
+
+    describe('NoExisting', function () {
+      beforeEach(function() {
+        this.$el = $('' +
+          '<div>' +
+          '  <div class="pat-upload"' +
+          '    data-pat-upload="{&quot;url&quot;: &quot;/upload&quot;, &quot;isWidget&quot;: true, &quot;maxFiles&quot;: 3, ' +
+          '&quot;showTitle&quot;: false, &quot;paramName&quot;: &quot;dummy&quot;, &quot;autoCleanResults&quot;: false}">' +
+          '  </div>' +
+          '</div>');
+      });
+      afterEach(function() {
+        this.$el.remove();
+      });
+
+      it('new elements', function() {
+        // initialize pattern
+        registry.scan(this.$el);
+        expect(this.$el.find('p.help').is(':visible')).to.equal(false);
+        expect(this.$el.find('div.path').is(':visible')).to.equal(false);
+        expect(this.$el.find('.upload-all').is(':visible')).to.equal(false);
+        var uploaded = this.$el.find('div.uploaded');
+        expect(uploaded.length).to.equal(1);
+        expect(this.$el.find('input[name="dummyuploaded"]').val()).to.equal('');
+
+      });
+
+    });
+
+    describe('SingleFile', function () {
+      beforeEach(function() {
+        this.$el = $('' +
+          '<div>' +
+          '  <div class="pat-upload"' +
+          '    data-pat-upload="{&quot;url&quot;: &quot;/upload&quot;, &quot;isWidget&quot;: true, &quot;maxFiles&quot;: 1, ' +
+          '&quot;showTitle&quot;: false, &quot;paramName&quot;: &quot;dummy&quot;, &quot;autoCleanResults&quot;: false}">' +
+          '  </div>' +
+          '</div>');
+      });
+      afterEach(function() {
+        this.$el.remove();
+      });
+
+      it('single file', function() {
+        // initialize pattern
+        registry.scan(this.$el);
+        var textmsg = this.$el.find('div.dz-message').find('p');
+        expect(textmsg.text()).to.equal('Drop file here...');
+
+      });
+
+    });
+
+    describe('Existing', function () {
+      beforeEach(function() {
+        this.$el = $('' +
+          '<div>' +
+          '  <div class="pat-upload"' +
+          '    data-pat-upload="{&quot;url&quot;: &quot;/upload&quot;, &quot;isWidget&quot;: true, &quot;maxFiles&quot;: 3, ' +
+          '&quot;showTitle&quot;: false, &quot;paramName&quot;: &quot;dummy&quot;, &quot;autoCleanResults&quot;: false,' +
+          ' &quot;existing&quot;: [{&quot;url&quot;: &quot;/downloadexisting&quot;,' +
+          ' &quot;size&quot;: 2292263,' +
+          ' &quot;name&quot;: &quot;testfile.txt&quot;,' +
+          ' &quot;title&quot;: &quot;testfile.txt&quot;}]}">' +
+          '  </div>' +
+          '</div>');
+      });
+      afterEach(function() {
+        this.$el.remove();
+      });
+
+      it('new elements', function() {
+        // initialize pattern
+        registry.scan(this.$el);
+        expect(this.$el.find('.uploaded').length).to.not.equal(0);
+        var uploaded = this.$el.find('.uploaded');
+        // 'Div' element to hold existing file info is added
+        var existing = uploaded.children();
+        expect(existing.hasClass('existfileupload')).to.equal(true);
+        // 'a' element with proper 'href' is added
+        expect(existing.find('a').attr('href')).to.equal('/downloadexisting');
+        // Filename is added
+        expect(existing.find('span.filename').html()).to.equal('testfile.txt');
+        var hidden = this.$el.find('input[name="dummyuploaded"]');
+        expect(hidden.val()).to.equal('[{"name":"testfile.txt","size":2292263,"url":"/downloadexisting"}]');
+//        console.log(hidden);
+        // Now delete the file
+        existing.find('button.remove-item').trigger('click');
+        // Input hidden element is updated
+        expect(hidden.val()).to.equal('[]');
+
+      });
+
+    });
+
+  });
 });


Repository: mockup
Branch: refs/heads/master
Date: 2014-12-12T12:48:37-05:00
Author: Allen Thomerson (athomerson) <allen.thomerson@gmail.com>
Commit: https://github.com/plone/mockup/commit/0be2e8cff540d8c9892f39716aae7507b02470a4

Fixed css to display progress bars and proper alignment of file previews.

Files changed:
M mockup/patterns/upload/less/pattern.upload.less

diff --git a/mockup/patterns/upload/less/pattern.upload.less b/mockup/patterns/upload/less/pattern.upload.less
index 0e2ff71..158090e 100644
--- a/mockup/patterns/upload/less/pattern.upload.less
+++ b/mockup/patterns/upload/less/pattern.upload.less
@@ -27,7 +27,7 @@
         }
         .previews{
             margin-top: 2em;
-            .item{ padding: 5px 0; }
+            .item{ padding: 5px 0; clear: both; }
             .title{ padding-left: 0; }
             .info img {
                 width: 32px;
@@ -62,6 +62,12 @@
     .upload-previews .dz-preview .dz-details .dz-size{
         font-size: 11px;
     }
+    .dz-upload {
+        background-color: red;
+        height: 10px;
+        display: block;
+        width: 0%;
+    }
     .dz-max-files-reached {}
 }
 .uploaded {


Repository: mockup
Branch: refs/heads/master
Date: 2014-12-15T00:20:11-06:00
Author: Nathan Van Gheem (vangheem) <vangheem@gmail.com>
Commit: https://github.com/plone/mockup/commit/174ad686caae14627b25f005b83249b7e2a659fe

Merge pull request #447 from athomerson/master

Modified the upload pattern to be used as a widget for NamedFiles

Files changed:
M mockup/patterns/upload/less/pattern.upload.less
M mockup/patterns/upload/pattern.js
M mockup/tests/pattern-upload-test.js

diff --git a/mockup/patterns/upload/less/pattern.upload.less b/mockup/patterns/upload/less/pattern.upload.less
index 7184337..158090e 100644
--- a/mockup/patterns/upload/less/pattern.upload.less
+++ b/mockup/patterns/upload/less/pattern.upload.less
@@ -27,7 +27,7 @@
         }
         .previews{
             margin-top: 2em;
-            .item{ padding: 5px 0; }
+            .item{ padding: 5px 0; clear: both; }
             .title{ padding-left: 0; }
             .info img {
                 width: 32px;
@@ -62,5 +62,45 @@
     .upload-previews .dz-preview .dz-details .dz-size{
         font-size: 11px;
     }
+    .dz-upload {
+        background-color: red;
+        height: 10px;
+        display: block;
+        width: 0%;
+    }
     .dz-max-files-reached {}
 }
+.uploaded {
+    margin: 0 0 0 0px;
+    width: 100%;
+    display: table;
+    table-layout: fixed;
+}
+
+.uploaded {
+    -moz-box-shadow: 0 0 16px #ccc;
+}
+
+.existfileupload {
+    display: table-row;
+}
+
+.newfileupload {
+    display: table-row;
+}
+
+.filename {
+    display: table-cell;
+    padding-right: 1em;
+}
+
+.filesize {
+    display: table-cell;
+    padding-right: 1em;
+}
+
+.remove-item {
+    text-decoration: none;
+    cursor: pointer;
+    display: table-cell;
+}
diff --git a/mockup/patterns/upload/pattern.js b/mockup/patterns/upload/pattern.js
index 634afe5..facdcf2 100644
--- a/mockup/patterns/upload/pattern.js
+++ b/mockup/patterns/upload/pattern.js
@@ -18,6 +18,8 @@
  *    previewsContainer(selector): JavaScript selector for file preview in div element. (.upload-previews)
  *    container(selector): JavaScript selector for where to put upload stuff into in case of form. If not provided it will be place before the first submit button. ('')
  *    relatedItems(object): Related items pattern options. Will only use only if relativePath is used to use correct upload destination ({ attributes: ["UID", "Title", "Description", "getURL", "Type", "path", "ModificationDate"], batchSize: 20, basePath: "/", vocabularyUrl: null, width: 500, maximumSelectionSize: 1, placeholder: "Search for item on site..." })
+ *    isWidget(boolean): true or false for determining if this is a widget for Named Files
+ *    existing(object): Existing files pattern option. List of files already uploaded. Format: {[{name: 'filename', size: 12345, tmpname: 'filename', url: 'downloadurl'}, {name: 'otherfile', size: 23456, tmpname: 'otherfile', url: 'downloadurl'}]}
  *
  * Documentation:
  *
@@ -58,6 +60,7 @@ define([
 
   i18n.loadCatalog('widgets');
   var _t = i18n.MessageFactory('widgets');
+  var files = [];
 
   var UploadPattern = Base.extend({
     name: 'upload',
@@ -90,7 +93,9 @@ define([
         width: 500,
         maximumSelectionSize: 1,
         placeholder: _t('Search for item on site...')
-      }
+      },
+      isWidget: false,
+      existing: []
     },
 
     //placeholder: 'Search for item on site...'
@@ -102,6 +107,10 @@ define([
       self.currentPath = self.options.currentPath;
       self.numFiles = 0;
       self.currentFile = 0;
+ 
+      var $hidden = $('<input type="hidden" value="">');
+      $hidden.attr('name', self.options.paramName + 'uploaded');
+      self.$el.append($hidden);
 
       template = _.template(template, {_t: _t});
       self.$el.addClass(self.options.className);
@@ -113,6 +122,16 @@ define([
         self.$el.find('h2.title').hide();
       }
 
+      if (self.options.maxFiles === 1) {
+        self.$el.find('div.dz-message').find('p').text(_t('Drop file here...'));
+      }
+
+      if (self.options.isWidget) {
+        self.$el.find('p.help').hide();
+        self.$el.find('div.path').hide();
+        self.$el.find('.upload-all').hide();
+      }
+
       if (!self.options.ajaxUpload) {
         // no ajax upload, drop the fallback
         $('.fallback', this.$el).remove();
@@ -165,6 +184,9 @@ define([
 
       self.dropzone.on('addedfile', function(file) {
         self.showControls();
+        if ((files.length + 1) > self.options.maxFiles) {
+            self.dropzone.emit('maxfilesexceeded', file);
+        }
       });
 
       self.dropzone.on('removedfile', function() {
@@ -182,8 +204,48 @@ define([
       }
 
       self.dropzone.on('complete', function(file) {
-        if (self.dropzone.files.length < 1) {
+       if (self.dropzone.files.length < 1) {
           self.hideControls();
+       }
+        if (self.options.isWidget) {
+           self.dropzone.removeFile(file);
+        }
+      });
+
+      self.dropzone.on('success', function(file, response) {
+        var jsonResp = JSON.parse(response);
+        var $uploaded = self.$el.find('.uploaded');
+        var pushFile = {};
+        pushFile.name = jsonResp.name;
+        pushFile.tmpname = jsonResp.tmpname;
+        pushFile.size = jsonResp.size;
+        pushFile.url = jsonResp.url;
+        var $newfile = $('<div>');
+        $newfile.addClass('newfileupload');
+        var $url = $('<a>');
+        $url.attr('href', jsonResp.url);
+        $newfile.append($url);
+        var $filename = $('<span>');
+        $filename.addClass('filename');
+        $filename.text(jsonResp.name);
+        $url.append($filename);
+        var $filesize = $('<span>');
+        $filesize.append('  size: '+ self.formatBytes(jsonResp.size) );
+        $filesize.addClass('filesize');
+        $url.after($filesize);
+        var $remove = $('<button type="button" class="btn btn-danger btn-xs remove-item"> <span class="glyphicon glyphicon-remove"></span> </button>');
+        $remove.on( "click", function() {
+                  files = $.grep(files, function(value) {
+                                 return value !== pushFile;
+                   });
+                   $hidden.val(JSON.stringify(files));
+                   $newfile.remove();
+                   });
+        $filesize.after($remove);
+        files.push(pushFile);
+        $hidden.val(JSON.stringify(files));
+        if (self.options.isWidget) {
+            $uploaded.append($newfile);
         }
       });
 
@@ -194,6 +256,17 @@ define([
         self.$progress.attr('aria-valuenow', pct).css('width', pct + '%');
       });
 
+      self.dropzone.on('maxfilesexceeded', function(file) {
+       self.dropzone.removeFile(file);
+       var $uploaded = self.$el.find('.uploaded');
+       var $errmsg =$('<div>');
+       $errmsg.addClass('has-error');
+       $errmsg.text(_t('Maximum number of files reached'));
+       $uploaded.append($errmsg);
+       $errmsg.fadeIn('slow');
+       $errmsg.delay(5000).fadeOut();
+      });
+
       $('.upload-all', self.$el).click(function (e) {
         e.preventDefault();
         e.stopPropagation();
@@ -203,6 +276,7 @@ define([
           }
         });
       });
+    self.uploadExisting();
     },
 
     showControls: function() {
@@ -279,6 +353,8 @@ define([
       delete options.autoCleanResults;
       delete options.fileaddedClassName;
       delete options.useTus;
+      delete options.isWidget;
+      delete options.existing;
 
       if (self.options.previewsContainer) {
         /*
@@ -293,6 +369,9 @@ define([
 
       // XXX: do we need to allow this?
       options.autoProcessQueue = false;
+      if (self.options.isWidget) {
+          options.autoProcessQueue = true;
+      }
       // options.addRemoveLinks = true;  // we show them in the template
       options.previewTemplate = PreviewTemplate;
 
@@ -411,6 +490,48 @@ define([
         self.options.url = self.dropzone.options.url = self.getUrl();
       });
       return ri;
+    },
+
+    uploadExisting: function () {
+      // Load existing files:
+      var self = this;
+      var existing = self.options.existing;
+      var $hidden = this.$el.find('input[name="'+self.options.paramName+'uploaded"]');
+      var $container = this.$el.find('.upload-area');
+      var $uploaded = $('<div class="uploaded">');
+      $container.after($uploaded);
+      existing.forEach( function (file) {
+          var pushFile = {};
+          pushFile.name = file.name;
+          pushFile.tmpname = file.tmpname;
+          pushFile.size = file.size;
+          pushFile.url = file.url;
+          var $existing = $('<div>');
+          $existing.addClass('existfileupload');
+          var $url = $('<a>');
+          $url.attr('href', file.url);
+          $existing.append($url);
+          var $filename = $('<span>');
+          $filename.addClass('filename');
+          $filename.text(file.name);
+          $url.append($filename);
+          var $filesize = $('<span>');
+          $filesize.append('  size: '+ self.formatBytes(file.size) );
+          $filesize.addClass('filesize');
+          $url.after($filesize);
+          var $remove = $('<button type="button" class="btn btn-danger btn-xs remove-item"> <span class="glyphicon glyphicon-remove"></span> </button>');
+          $remove.on( "click", function() {
+                    files = $.grep(files, function(value) {
+                                   return value !== pushFile;
+                     });
+                     $hidden.val(JSON.stringify(files));
+                     $existing.remove();
+                     });
+          $filesize.after($remove);
+          files.push(pushFile);
+          $hidden.val(JSON.stringify(files));
+          $uploaded.append($existing);
+      });
     }
 
   });
diff --git a/mockup/tests/pattern-upload-test.js b/mockup/tests/pattern-upload-test.js
index 7c55df2..e3eb5aa 100644
--- a/mockup/tests/pattern-upload-test.js
+++ b/mockup/tests/pattern-upload-test.js
@@ -77,4 +77,106 @@ define([
     });
 
   });
+
+/* ==========================
+   TEST: Widget
+  ========================== */
+
+  describe('Widget', function () {
+
+    describe('NoExisting', function () {
+      beforeEach(function() {
+        this.$el = $('' +
+          '<div>' +
+          '  <div class="pat-upload"' +
+          '    data-pat-upload="{&quot;url&quot;: &quot;/upload&quot;, &quot;isWidget&quot;: true, &quot;maxFiles&quot;: 3, ' +
+          '&quot;showTitle&quot;: false, &quot;paramName&quot;: &quot;dummy&quot;, &quot;autoCleanResults&quot;: false}">' +
+          '  </div>' +
+          '</div>');
+      });
+      afterEach(function() {
+        this.$el.remove();
+      });
+
+      it('new elements', function() {
+        // initialize pattern
+        registry.scan(this.$el);
+        expect(this.$el.find('p.help').is(':visible')).to.equal(false);
+        expect(this.$el.find('div.path').is(':visible')).to.equal(false);
+        expect(this.$el.find('.upload-all').is(':visible')).to.equal(false);
+        var uploaded = this.$el.find('div.uploaded');
+        expect(uploaded.length).to.equal(1);
+        expect(this.$el.find('input[name="dummyuploaded"]').val()).to.equal('');
+
+      });
+
+    });
+
+    describe('SingleFile', function () {
+      beforeEach(function() {
+        this.$el = $('' +
+          '<div>' +
+          '  <div class="pat-upload"' +
+          '    data-pat-upload="{&quot;url&quot;: &quot;/upload&quot;, &quot;isWidget&quot;: true, &quot;maxFiles&quot;: 1, ' +
+          '&quot;showTitle&quot;: false, &quot;paramName&quot;: &quot;dummy&quot;, &quot;autoCleanResults&quot;: false}">' +
+          '  </div>' +
+          '</div>');
+      });
+      afterEach(function() {
+        this.$el.remove();
+      });
+
+      it('single file', function() {
+        // initialize pattern
+        registry.scan(this.$el);
+        var textmsg = this.$el.find('div.dz-message').find('p');
+        expect(textmsg.text()).to.equal('Drop file here...');
+
+      });
+
+    });
+
+    describe('Existing', function () {
+      beforeEach(function() {
+        this.$el = $('' +
+          '<div>' +
+          '  <div class="pat-upload"' +
+          '    data-pat-upload="{&quot;url&quot;: &quot;/upload&quot;, &quot;isWidget&quot;: true, &quot;maxFiles&quot;: 3, ' +
+          '&quot;showTitle&quot;: false, &quot;paramName&quot;: &quot;dummy&quot;, &quot;autoCleanResults&quot;: false,' +
+          ' &quot;existing&quot;: [{&quot;url&quot;: &quot;/downloadexisting&quot;,' +
+          ' &quot;size&quot;: 2292263,' +
+          ' &quot;name&quot;: &quot;testfile.txt&quot;,' +
+          ' &quot;title&quot;: &quot;testfile.txt&quot;}]}">' +
+          '  </div>' +
+          '</div>');
+      });
+      afterEach(function() {
+        this.$el.remove();
+      });
+
+      it('new elements', function() {
+        // initialize pattern
+        registry.scan(this.$el);
+        expect(this.$el.find('.uploaded').length).to.not.equal(0);
+        var uploaded = this.$el.find('.uploaded');
+        // 'Div' element to hold existing file info is added
+        var existing = uploaded.children();
+        expect(existing.hasClass('existfileupload')).to.equal(true);
+        // 'a' element with proper 'href' is added
+        expect(existing.find('a').attr('href')).to.equal('/downloadexisting');
+        // Filename is added
+        expect(existing.find('span.filename').html()).to.equal('testfile.txt');
+        var hidden = this.$el.find('input[name="dummyuploaded"]');
+        expect(hidden.val()).to.equal('[{"name":"testfile.txt","size":2292263,"url":"/downloadexisting"}]');
+//        console.log(hidden);
+        // Now delete the file
+        existing.find('button.remove-item').trigger('click');
+        // Input hidden element is updated
+        expect(hidden.val()).to.equal('[]');
+
+      });
+
+    });
+
+  });
 });