ICEfaces
  1. ICEfaces
  2. ICE-9526

Move ICEmobile XHR2 FormData submit to ICEfaces core for ace:fileEntry

    Details

    • Type: New Feature New Feature
    • Status: Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 3.3
    • Fix Version/s: 4.0.BETA, 4.0
    • Component/s: ACE-Components, Framework
    • Labels:
      None
    • Environment:
      ICEfaces, ICEmobile, core
    • Assignee Priority:
      P2

      Description

      ICE-9516 requires augmenting the JSF submit process to allow for XHR2 send(FormData) for submitting a multipart form along with input type=file. ICEmobile already does something similar, so if we could move this support into ICEfaces core, then it could be available for both ICEmobile and ACE fileEntry, and eventually become the basis for a patch to JSF for all upload components to be able to use it.

        Activity

        Hide
        Ted Goddard added a comment -
        
        function html5handleResponse(context, data) {
            if (null == context.sourceid) {
                //was not a jsf upload
                return;
            }
        
            var jsfResponse = {};
            var parser = new DOMParser();
            var xmlDoc = parser.parseFromString(data, "text/xml");
        
            jsfResponse.responseXML = xmlDoc;
            jsf.ajax.response(jsfResponse, context);
        
        }
        
        function html5submitFunction(element, event, options) {
            var source = event ? event.target : element;
            source = source ? source : element;
            var form = element;
            while ((null != form) && ("form" != form.tagName.toLowerCase())) {
                form = form.parentNode;
            }
            var formData = new FormData(form);
            var formId = form.id;
            var sourceId = element ? element.id : event.target.id;
        
            if (options.execute) {
                var executeArray = options.execute.split(' ');
                if (executeArray.indexOf("@none") < 0) {
                    if (executeArray.indexOf("@all") < 0) {
                        options.execute = options.execute.replace("@this", element.id);
                        options.execute = options.execute.replace("@form", form.id);
                        if (executeArray.indexOf(element.name) < 0) {
                            options.execute = element.name + " " + options.execute;
                        }
                    } else {
                        options.execute = "@all";
                    }
                }
            } else {
        //        options.execute = element.name + " " + element.id;
                //ICEfaces default render @all
                options.execute = "@all";
            }
        
            if (options.render) {
                var renderArray = options.render.split(' ');
                if (renderArray.indexOf("@none") < 0) {
                    if (renderArray.indexOf("@all") < 0) {
                        options.render = options.render.replace("@this", element.id);
                        options.render = options.render.replace("@form", form.id);
                    } else {
                        options.render = "@all";
                    }
                }
            } else {
                //ICEfaces default execute @all
                options.execute = "@all";
            }
        
            formData.append("javax.faces.source", sourceId);
            formData.append(source.name, source.value);
            formData.append("javax.faces.partial.execute", options.execute);
            formData.append("javax.faces.partial.render", options.render);
            formData.append("javax.faces.partial.ajax", "true");
        
            if (event) {
                formData.append("javax.faces.partial.event", event.type);
            }
        
            if (options) {
                for (var p in options) {
                    if ("function" != typeof(options[p])) {
                        formData.append(p, options[p]);
                    }
                }
            }
        
            var context = {
                source: source,
                sourceid: sourceId,
                formid: formId,
                element: element,
                //'begin' event is not triggered since we do not invoke jsf.ajax.request -- onBeforeSubmit relies on this event
                onevent: options.onevent,
                onerror: options.onerror || function (param) {
                    alert("JSF error " + param.source + " " + param.description);
                }
            };
        
            var xhr = new XMLHttpRequest();
            xhr.open("POST", form.getAttribute("action"));
            xhr.setRequestHeader("Faces-Request", "partial/ajax");
            xhr.onreadystatechange = function () {
                if ((4 == xhr.readyState) && (200 == xhr.status)) {
                    html5handleResponse(context, xhr.responseText);
                }
            };
            xhr.send(formData);
        }
        
        Show
        Ted Goddard added a comment - function html5handleResponse(context, data) { if ( null == context.sourceid) { //was not a jsf upload return ; } var jsfResponse = {}; var parser = new DOMParser(); var xmlDoc = parser.parseFromString(data, "text/xml" ); jsfResponse.responseXML = xmlDoc; jsf.ajax.response(jsfResponse, context); } function html5submitFunction(element, event, options) { var source = event ? event.target : element; source = source ? source : element; var form = element; while (( null != form) && ( "form" != form.tagName.toLowerCase())) { form = form.parentNode; } var formData = new FormData(form); var formId = form.id; var sourceId = element ? element.id : event.target.id; if (options.execute) { var executeArray = options.execute.split(' '); if (executeArray.indexOf( "@none" ) < 0) { if (executeArray.indexOf( "@all" ) < 0) { options.execute = options.execute.replace( "@ this " , element.id); options.execute = options.execute.replace( "@form" , form.id); if (executeArray.indexOf(element.name) < 0) { options.execute = element.name + " " + options.execute; } } else { options.execute = "@all" ; } } } else { // options.execute = element.name + " " + element.id; //ICEfaces default render @all options.execute = "@all" ; } if (options.render) { var renderArray = options.render.split(' '); if (renderArray.indexOf( "@none" ) < 0) { if (renderArray.indexOf( "@all" ) < 0) { options.render = options.render.replace( "@ this " , element.id); options.render = options.render.replace( "@form" , form.id); } else { options.render = "@all" ; } } } else { //ICEfaces default execute @all options.execute = "@all" ; } formData.append( "javax.faces.source" , sourceId); formData.append(source.name, source.value); formData.append( "javax.faces.partial.execute" , options.execute); formData.append( "javax.faces.partial.render" , options.render); formData.append( "javax.faces.partial.ajax" , " true " ); if (event) { formData.append( "javax.faces.partial.event" , event.type); } if (options) { for ( var p in options) { if ( "function" != typeof(options[p])) { formData.append(p, options[p]); } } } var context = { source: source, sourceid: sourceId, formid: formId, element: element, //'begin' event is not triggered since we do not invoke jsf.ajax.request -- onBeforeSubmit relies on this event onevent: options.onevent, onerror: options.onerror || function (param) { alert( "JSF error " + param.source + " " + param.description); } }; var xhr = new XMLHttpRequest(); xhr.open( "POST" , form.getAttribute( "action" )); xhr.setRequestHeader( "Faces-Request" , "partial/ajax" ); xhr.onreadystatechange = function () { if ((4 == xhr.readyState) && (200 == xhr.status)) { html5handleResponse(context, xhr.responseText); } }; xhr.send(formData); }
        Hide
        Ted Goddard added a comment -

        When the above functions are included and configured via

        ice.submitFunction = html5submitFunction

        ICEfaces will use multipart upload for form submission.

        Show
        Ted Goddard added a comment - When the above functions are included and configured via ice.submitFunction = html5submitFunction ICEfaces will use multipart upload for form submission.
        Hide
        Ted Goddard added a comment -

        Mircea noted that this may omit certain JSF event callbacks such as onsuccess, onbeforesubmit, afterupdate. So a better approach would be to have mojarra integrate a patch based on this. This could be integrated as a mojarra-specific feature rather than a JSF feature – a mode in mojarra would allow FormData upload.

        The above code should be tested so that we know which events are not supported by the approach used by ICEmobile.

        Show
        Ted Goddard added a comment - Mircea noted that this may omit certain JSF event callbacks such as onsuccess, onbeforesubmit, afterupdate. So a better approach would be to have mojarra integrate a patch based on this. This could be integrated as a mojarra-specific feature rather than a JSF feature – a mode in mojarra would allow FormData upload. The above code should be tested so that we know which events are not supported by the approach used by ICEmobile.
        Hide
        Mark Collette added a comment -

        The Mojarra XHR1 code wires in all the appropriate callbacks, so we'd just need to do the same wiring for the XHR2 code as well.

        Show
        Mark Collette added a comment - The Mojarra XHR1 code wires in all the appropriate callbacks, so we'd just need to do the same wiring for the XHR2 code as well.
        Hide
        Mircea Toma added a comment - - edited

        There are a few things to consider before implementing a parallel implementation for jsf.ajax.request function:

        • As noted before, we will have to replicate the 'begin', 'success' and 'complete' JSF bridge events so that all our (ICEfaces) callbacks will work as expected.
        • The JSF bridge has a request queue which cannot be accessed, so we'll need to decide if we want to re-implement that too. Most probably the queue will not make sense for file uploads.
        • Implementing and alternative function jsf.ajax.request(element, event, options) will limit the type of upload that can be done through it. Normal file uploads that use file input elements will work just fine but in HTML5 files content can be uploaded when they are dropped on to a container element. To access these files a different kind of API might be required (see http://www.html5rocks.com/en/tutorials/dnd/basics/#toc-dnd-files ). We might subversively pass a filled in FormData object through the options parameter. A better solution might be to look at the captured/triggering event and try to see if there any files to be uploaded (see Event.dataTransfer.files or Event.dataTransfer.getData() for ondrop and Event.target.files for onchange).
        • For client side upload progress we can fire up custom events to all jsf.ajax.addOnEvent callbacks, hopefully without triggering any interference from the callbacks that do not understand these new events.
        Show
        Mircea Toma added a comment - - edited There are a few things to consider before implementing a parallel implementation for jsf.ajax.request function: As noted before, we will have to replicate the 'begin', 'success' and 'complete' JSF bridge events so that all our (ICEfaces) callbacks will work as expected. The JSF bridge has a request queue which cannot be accessed, so we'll need to decide if we want to re-implement that too. Most probably the queue will not make sense for file uploads. Implementing and alternative function jsf.ajax.request(element, event, options) will limit the type of upload that can be done through it. Normal file uploads that use file input elements will work just fine but in HTML5 files content can be uploaded when they are dropped on to a container element. To access these files a different kind of API might be required (see http://www.html5rocks.com/en/tutorials/dnd/basics/#toc-dnd-files ). We might subversively pass a filled in FormData object through the options parameter. A better solution might be to look at the captured/triggering event and try to see if there any files to be uploaded (see Event.dataTransfer.files or Event.dataTransfer.getData() for ondrop and Event.target.files for onchange ). For client side upload progress we can fire up custom events to all jsf.ajax.addOnEvent callbacks, hopefully without triggering any interference from the callbacks that do not understand these new events.
        Hide
        Mircea Toma added a comment -

        Added patches for Mojarra's current trunk and 2.2.2 tag. These patches are adding XHR2 file upload functionality.

        Show
        Mircea Toma added a comment - Added patches for Mojarra's current trunk and 2.2.2 tag. These patches are adding XHR2 file upload functionality.
        Hide
        Mircea Toma added a comment - - edited

        Created test application in icefaces/samples/core/test/xhr2-upload that can be used to test the new functionality added to Mojarra. The first test page uses the h:inputFile component to execute the upload, it can be observed how the upload now goes naturally through XHR2. The second page tests the drag and drop HTML5 functionality, where multiple files can be dropped into a target region and they all are uploaded automatically. For testing purposes (and simplicity) this test page uses only the server side of the h:inputFile component.

        Show
        Mircea Toma added a comment - - edited Created test application in icefaces/samples/core/test/xhr2-upload that can be used to test the new functionality added to Mojarra. The first test page uses the h:inputFile component to execute the upload, it can be observed how the upload now goes naturally through XHR2. The second page tests the drag and drop HTML5 functionality, where multiple files can be dropped into a target region and they all are uploaded automatically. For testing purposes (and simplicity) this test page uses only the server side of the h:inputFile component.
        Hide
        Ted Goddard added a comment -
        Show
        Ted Goddard added a comment - Mojarra tracker: https://java.net/jira/browse/JAVASERVERFACES-2975
        Hide
        Mircea Toma added a comment -

        Provided fix to Mojarra that was tested successfully in house.

        Show
        Mircea Toma added a comment - Provided fix to Mojarra that was tested successfully in house.

          People

          • Assignee:
            Mircea Toma
            Reporter:
            Mark Collette
          • Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: