Announcement Announcement Module
Collapse
No announcement yet.
upload N files through a form Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • upload N files through a form

    I would like to have a form that allows users to upload multiple files. I would like to make it work similar to gmail's file attachment, where when the page files loads, there is a link to attach a file. If the link is clicked, using javascript it puts an input of type file on the screen. Then there is a link to add another file. So this allows the user to dynamically build a list of any number of files to upload.

    So I can handle the javascript to build the form, but in my FormController, I would like to have a JavaBean command object that as a List<FileObject>, where file object is a JavaBean that has a String name property and a byte[] data property. Any ideas on how to create a controller to do this easily? I read through the reference documentation on file upload handling, googled and searched through this forum, didn't see any example, so I figured I would ask if any one else has done it yet. If not, I'll figure it out and post my solution here.

  • #2
    My first problem is that I don't know how to handle a list of N items, file upload or not. So far I have an HTML form like this:
    Code:
    <html>
      <body>
        <form method="POST" enctype="multipart/form-data">
          <input type="text" name="files&#91;0&#93;.name">
          <input type="file" name="files&#91;0&#93;.data">
          <br>
          <input type="submit">
        </form>
      </body>
    </html>
    And right now the only thing in my controller is:
    Code:
        protected void initBinder&#40;HttpServletRequest request, ServletRequestDataBinder binder&#41; throws Exception &#123;
            binder.registerCustomEditor&#40;byte&#91;&#93;.class, new ByteArrayMultipartFileEditor&#40;&#41;&#41;;
        &#125;
        
        protected ModelAndView onSubmit&#40;HttpServletRequest request, HttpServletResponse response, Object command, BindException errors&#41; throws Exception &#123;
            MultipleFileUploadForm form = &#40;MultipleFileUploadForm&#41;command;
            log.debug&#40;form.getFiles&#40;&#41;.size&#40;&#41;+" files uploaded"&#41;;
            for&#40;FileBean file &#58; form.getFiles&#40;&#41;&#41; &#123;
                log.debug&#40;file.getName&#40;&#41;+","+file.getBytes&#40;&#41;&#41;;
            &#125;
            return null;
        &#125;
    Here is MultipleFileUploadForm:
    Code:
        private List<FileBean> files = new ArrayList<FileBean>&#40;&#41;;
    
        public MultipleFileUploadForm&#40;&#41; &#123;
            files.add&#40;new FileBean&#40;&#41;&#41;;
        &#125;
        
        public List<FileBean> getFiles&#40;&#41; &#123;
            return files;
        &#125;
        public void setFiles&#40;List<FileBean> files&#41; &#123;
            this.files = files;
        &#125;
    FileBean is just a JavaBean with a String name property and a byte[] data property. Now, this works fine, but the only reason it works is because I am initializing the List<FileBean> property in MultipleFileUploadController to have one element. If I increase my form to contain two elements, like this:
    Code:
    <html>
      <body>
        <form method="POST" enctype="multipart/form-data">
          <input type="text" name="files&#91;0&#93;.name">
          <input type="file" name="files&#91;0&#93;.data">
          <br>
          <input type="text" name="files&#91;1&#93;.name">
          <input type="file" name="files&#91;1&#93;.data">
          <br>
          <input type="submit">
        </form>
      </body>
    </html>
    I get this exception:
    Code:
    org.springframework.beans.InvalidPropertyException&#58; Invalid property 'files&#91;1&#93;' of bean class &#91;MultipleFileUploadForm&#93;&#58; Index of out of bounds in property path 'files&#91;1&#93;'; nested exception is java.lang.IndexOutOfBoundsException&#58; Index&#58; 1, Size&#58; 1
    java.lang.IndexOutOfBoundsException&#58; Index&#58; 1, Size&#58; 1
            at java.util.ArrayList.RangeCheck&#40;ArrayList.java&#58;547&#41;
            at java.util.ArrayList.get&#40;ArrayList.java&#58;322&#41;
            at org.springframework.beans.BeanWrapperImpl.getPropertyValue&#40;BeanWrapperImpl.java&#58;549&#41;
            at org.springframework.beans.BeanWrapperImpl.getNestedBeanWrapper&#40;BeanWrapperImpl.java&#58;428&#41;
            at org.springframework.beans.BeanWrapperImpl.getBeanWrapperForPropertyPath&#40;BeanWrapperImpl.java&#58;406&#41;
            at org.springframework.beans.BeanWrapperImpl.setPropertyValue&#40;BeanWrapperImpl.java&#58;609&#41;
            at org.springframework.beans.BeanWrapperImpl.setPropertyValue&#40;BeanWrapperImpl.java&#58;758&#41;
            at org.springframework.beans.BeanWrapperImpl.setPropertyValues&#40;BeanWrapperImpl.java&#58;785&#41;
            at org.springframework.validation.DataBinder.bind&#40;DataBinder.java&#58;314&#41;
            at org.springframework.web.bind.ServletRequestDataBinder.bind&#40;ServletRequestDataBinder.java&#58;179&#41;
            at org.springframework.web.servlet.mvc.BaseCommandController.bindAndValidate&#40;BaseCommandController.java&#58;296&#41;
            at org.springframework.web.servlet.mvc.AbstractFormController.handleRequestInternal&#40;AbstractFormController.java&#58;236&#41;
            at org.springframework.web.servlet.mvc.AbstractController.handleRequest&#40;AbstractController.java&#58;128&#41;
            at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle&#40;SimpleControllerHandlerAdapter.java&#58;44&#41;
    So, my question is, what is the best way to initialize the List so that it can grow to have any number of elements, depending on how many elements the dynamic HTML for has? Can I do this in the Binder somehow?

    Comment


    • #3
      Ok, here's my solution. First I created a helper method that gets all of the files from the request and loads then into a List of FileBean:
      Code:
      public abstract class MultipartUtil &#123;
      
          public static List<FileBean> getFiles&#40;HttpServletRequest request&#41; throws Exception &#123;
              MultipartHttpServletRequest req =
                  &#40;MultipartHttpServletRequest&#41;request;
              MultipartFile file = null;
              FileBean bean = null;
              List<FileBean> files = new ArrayList<FileBean>&#40;&#41;;
              for&#40;Object o&#58; req.getFileMap&#40;&#41;.values&#40;&#41;&#41; &#123;
                  if&#40;o != null&#41; &#123;
                      file = &#40;MultipartFile&#41;o;
                      bean = new FileBean&#40;&#41;;
                      bean.setName&#40;file.getOriginalFilename&#40;&#41;&#41;;
                      bean.setData&#40;file.getBytes&#40;&#41;&#41;;
                      files.add&#40;bean&#41;;
                  &#125;
              &#125;
              return files;
          &#125;
          
      &#125;
      Now the controller is just this simple:
      Code:
          protected ModelAndView onSubmit&#40;HttpServletRequest request, HttpServletResponse response, Object command, BindException errors&#41; throws Exception &#123;
              for&#40;FileBean file&#58; MultipartUtil.getFiles&#40;request&#41;&#41; &#123;
                  log.debug&#40;file.getName&#40;&#41;+" "+file.getBytes&#40;&#41;&#41;;
              &#125;
              return null;
          &#125;
      No need for any PropertyEditors. And here is the HTML with the javascript:
      Code:
      <html>
        <head>
          <script language="JavaScript">
          var rowCount = 0;
          function addFile&#40;&#41; &#123;
            var table = document.getElementById&#40;'attachments'&#41;;
            var tbody = table.getElementsByTagName&#40;"tbody"&#41;&#91;0&#93;;
            
            var rows = tbody.getElementsByTagName&#40;"TR"&#41;;
            var row = document.createElement&#40;"TR"&#41;;
            var cell = document.createElement&#40;"TD"&#41;;
            var input = document.createElement&#40;"INPUT"&#41;; 
            input.setAttribute&#40;"type","file"&#41;;
            input.setAttribute&#40;"name","file&#91;"+rowCount+"&#93;"&#41;;
            cell.appendChild&#40;input&#41;;
            
            var removeSpan = document.createElement&#40;"SPAN"&#41;;
            removeSpan.innerHTML = '<a href="javascript&#58;removeFile&#40;'+rowCount+'&#41;>remove</a>';
            cell.appendChild&#40;removeSpan&#41;;
            
            row.setAttribute&#40;"id","file-"+rowCount&#41;;
            row.appendChild&#40;cell&#41;;
            tbody.appendChild&#40;row&#41;;
            rowCount++;
          &#125;
          
          function removeFile&#40;i&#41; &#123;
            var tbody = document.getElementById&#40;'attachments'&#41;.getElementsByTagName&#40;"tbody"&#41;&#91;0&#93;;
            tbody.removeChild&#40;document.getElementById&#40;"file-"+i&#41;&#41;;
          &#125;
          </script>
        </head>
        <body>
          <form method="POST" enctype="multipart/form-data">
            <table id="attachments" border="0"><tr><td></td></tr></table>
            <a href="javascript&#58;addFile&#40;&#41;">Attach a File</a><br/>
            <br/>
            <input type="submit"><br/>
          </form>
        </body>
      </html>

      Comment

      Working...
      X