Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    Upload image without SmartGWT Server->uploadItem.submitForm, invisible Frame, Servlet

    I don't use SmartGWT Server, our server is a JBoss where we have implemented REST Web Services.

    I will try to provide a complete tutorial in order to :
    - Choose one or several image files in the file chooser of an UploadItem
    - Display it in a com.smartgwt.client.widgets.Img object in my browser

    It seemed so easy to do such basic stuff but I was wrong.
    Due to security reasons, the browser does not give a way to get complete path to the file.
    Then your SmartGWT client code has no way to read the file on client workstation.

    The method uploadItem.getDisplayValue() gives you on Firefox only file name and on Chrome file name with "C:/fakepath/" as suffix.

    The only method (I'm writing this tutorial in order to get confirmation) to do such thing is to :
    - Perform a uploadItem.submitForm() on client
    - Implement a Servlet on server which will :
    * receive submit form datapackage
    * extract file(s) binary
    * call back client in order to give client file(s) binary
    - Client get called back and display file(s) binary in Img objects

    To make it a little more complicated, the uploadItem.submitForm will make your browser change page and go on Servlet's defined URL.
    You may not want this and want to stay on your current page waiting for being called back (me too).
    To do such thing, you have to define an invisible frame which will be the target of submitForm().
    The Servlet will provide Javascript code as return to this invisible Frame in order to get your client called back.
    That is why we will have a Javascript native method on our client.

    I build this solution from all those pages :
    http://www.mrsondao.com/TopicDetail.aspx?TopicId=2
    http://forums.smartclient.com/showthread.php?t=3102
    http://ruchi0711.blogspot.fr/2011/07...smart-gwt.html
    http://maryniuk.blogspot.fr/2009/09/...-smartgwt.html
    http://blog.karanfil.info/2011/03/sm...le-upload.html
    http://stackoverflow.com/questions/8...ynamic-form-wi
    http://forums.smartclient.com/showthread.php?t=5477

    Let's do it step by step.

    ---------------------
    -- INVISIBLE FRAME --
    ---------------------

    Code:
    // Hidden frame in order to perform upload image file
    private static NamedFrame uploadFileHiddenFrame = new NamedFrame("uploadFileHiddenFrame");
    ...
    uploadFileHiddenFrame.setWidth("1px");
    uploadFileHiddenFrame.setHeight("1px");
    uploadFileHiddenFrame.setVisible(false);
    mainlayout.addMember(uploadFileHiddenFrame);
    This invisible frame has to be added to your client.
    In my case, mainLayout is my main layout containing our whole MMI.

    -----------------
    -- SUBMIT FORM --
    -----------------

    submitForm method is performed on a DynamicForm

    Code:
    // DynamicForm in order to load one image
    // client-server servlet call and back
    final DynamicForm uploadForm = new DynamicForm();
    uploadForm.setEncoding(Encoding.MULTIPART);
    // Set servlet name
    uploadForm.setAction(GWT.getModuleBaseURL() + "uploadImageFile");
    // In order to do not perform full page reload
    // target is set to CATV hidden frame defined in HomeView
    uploadForm.setTarget("uploadFileHiddenFrame");
    
    // Upload item allowing to choose image file
    uploadItem = new UploadItem();
    uploadItem.addChangedHandler(new ChangedHandler() {
    
      @Override
      public void onChanged(ChangedEvent event) {
        // Call servlet threw hidden frame
        uploadForm.submitForm();
      }
    });
    uploadForm.setItems(uploadItem);
    In my case, I have created a class com.example.client.MyCanvas which extends CanvasItem.
    The DynamicForm is added to my canvas.

    Code:
    public MyCanvas(...){
      ...
      myCanvas.setCanvas(uploadForm);
      ...
    }
    ----------------------------------------------
    -- Javascript native function for call back --
    ----------------------------------------------

    After adding uploadForm in myCanvas, we can add call back mechanism

    Code:
    public MyCanvas(...){
      ...
      // Add native javascript mechanism in order to perform a call back
      addUploadImageCallback(this);
      ...
    }
    Method addUploadImageCallback in MyCanvas.

    Code:
    private native void addUploadImageCallback(com.example.client.MyCanvas load) /*-{
      $wnd.uploadCompleteCallback = function(imagePayload) {
        load.@com.example.client.MyCanvas::uploadCompleteCallback(Ljava/lang/String;)(imagePayload);
      };
    }-*/;
    With this, the invisible frame we have declared previously will be able to receive result from servlet and executed the method uploadCompleteCallback on myCanvas.

    Now we can declare method uploadCompleteCallback in MyCanvas.

    Code:
    private void uploadCompleteCallback(String imagePayload) {
      System.out.println(imagePayload);
    }
    -------------
    -- SERVLET --
    -------------

    In the file web.xml :

    Code:
    <!-- Upload Image File -->
    <servlet>
    	<servlet-name>UploadImageFile</servlet-name>
    	<servlet-class>com.example.server.UploadImageFile</servlet-class>
    </servlet>
    <servlet-mapping>
    	<servlet-name>UploadImageFile</servlet-name>
    	<url-pattern>/myapp/uploadImageFile</url-pattern>
    </servlet-mapping>
    myapp == GWT.getModuleBaseURL()

    Code:
    package com.example.server;
    
    import ...
    
    public class UploadImageFile extends HttpServlet {
      @Override
      protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        
        PrintWriter out = resp.getWriter();
        resp.setContentType("text/html");
        
        String message = "coucou";
        
        out.println("<html>");
        out.println("<head>");
        out.println("<script type =\"text/javascript\">");
        out.println("function foo() { ");
        out.println("window.top.uploadCompleteCallback('" + message + "');");
        out.println("}");
        out.println("</script>");
        out.println("</head>");
        out.println("<body onload=\"foo();\">");
        out.println("</body>");
        out.println("</html>");
        
        resp.flushBuffer();
      }
    }
    This generated Javascript code will be send back to the invisible frame declared previously.
    This invisible frame will execute such code and call uploadCompleteCallback method with message argument on myCanvas.

    -----------------------
    -- END FIRST VERSION --
    -----------------------

    At this point, it should be working and you should see on client console the message "coucou".

    ---------------------
    -- EXTRACT FILE(S) --
    ---------------------

    In servlet I use Apache Commons File Upload in order to extract file(s) easily, there is many others ways to do it.

    Code:
    <dependency>
      <groupId>commons-fileupload</groupId>
    	<artifactId>commons-fileupload</artifactId>
    	<version>1.2.1</version>
    </dependency>
    Code:
    public class UploadImageFile extends HttpServlet {
      private final DiskFileItemFactory factory = new DiskFileItemFactory();
      
      public UploadImageFile() {
        // set size threshold to 20 MB
        factory.setSizeThreshold(2000000);
      }
      
      @Override
      protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        
        final ServletFileUpload upload = new ServletFileUpload(factory);
        final List<FileItem> fileItems = upload.parseRequest(req);
        
        for (FileItem fileItem : fileItems) {
          String fileName = fileItem.getName();
          byte[] imageFileAsByteArray = fileItem.get();
        }
        
        PrintWriter out = resp.getWriter();
        resp.setContentType("text/html");
        
        String message = "coucou";
        
        out.println("<html>");
        out.println("<head>");
        out.println("<script type =\"text/javascript\">");
        out.println("function foo() { ");
        out.println("window.top.uploadCompleteCallback('" + message + "');");
        out.println("}");
        out.println("</script>");
        out.println("</head>");
        out.println("<body onload=\"foo();\">");
        out.println("</body>");
        out.println("</html>");
        
        resp.flushBuffer();
      }
    }
    -----------------------
    -- BINARY AS BASE 64 --
    -----------------------

    I choose to encode the binary from byte[] to Base64 String.
    I use Apache Commons Codec.

    Code:
    <dependency>
      <groupId>commons-codec</groupId>
      <artifactId>commons-codec</artifactId>
      <version>1.10</version>
    </dependency>
    Code:
    // Convert byte[] as Base64 String
    String imageFileAsBase64 = new String(Base64.encodeBase64(imageFileAsByteArray));
    Now you can return imageFileAsBase64 instead of "coucou"

    -----------------
    -- ADD HEADERS --
    -----------------

    In order to be display on client in Img object (or TileGrid for example), you must add headers to your Base64 String :
    Let's assume the image file uploaded is a jpg.

    Code:
    String mimeType = "image/jpeg";
    String headers = "data:" + mimeType + ";base64,";
    String imagePayload = headers + imageFileAsBase64;
    Others mimeType : http://www.sitepoint.com/web-foundat...complete-list/

    Now you can return imagePayload instead of imageFileAsBase64

    --------------------
    -- DISPLAY IN IMG --
    --------------------

    In uploadCompleteCallback method, you can display image in a Img object :

    Code:
    Img picture = new Img();
    picture.setSrc(imagePayload);
    // add picture to your MMI
    ---------------------
    -- THIS IS THE END --
    ---------------------

    I worry about my solution, i found it too complicated for something so basic (just display an image in client .....).
    I'm new on SmartGWT development and basically Front-End development.

    I take advice and critics. Is there some way much more easier to do that ?
    It hard for me to admit I need a Javascript native function to perform a call back, it must be another way.

    Thanks by advance.
    Last edited by david-hervas; 28 Apr 2015, 06:59. Reason: Add a link to complete list of mimeTypes

    #2
    This is a correct solution. It is a bit complicated, but this is the standard approach for doing a file upload without leaving the page.

    It's the same general approach that the SmartGWT server uses, except of course, the built-in feature handles validation errors on either the uploaded file or other fields in the same form, re-displaying the form with errors without losing the user's selected file, actually storing the file to SQL/Hibernate/JPA, being able to subsequently download the file or display it inline, and many other details - all as an automatic result of just calling saveData() with none of this complex code required.

    Comment


      #3
      Thanks for explanation.

      2 additional small things on the tutorial :

      1) I put on the tutorial the call uploadItem.submitForm() in the ChangedHandler of the UploadItem.

      You may experience some problems while loading twice same file, read this :
      http://forums.smartclient.com/showthread.php?t=32708

      I use uploadItem.clearValue(); in my case to make it reliable, you may want instead to add an "Upload" button and perform submitForm() within ClickHandler to make it reliable.

      2) To perform multiple load, you have to do :

      Code:
      uploadItem.setMultiple(true);
      In the servlet, in order to return multiple image payloads, I create one String containing payloads (imagePayload = header + Base64 String) separated by the '*' character (as '*' is not a Base64 used character).

      On client, i get each payload with a StringTokenizer.

      If somebody knows the way to modify methods addUploadImageCallback and uploadCompleteCallback and servlet code in order to return a kind of String[], please post it.
      Last edited by david-hervas; 28 Apr 2015, 01:06.

      Comment

      Working...
      X