-->

and reducing su

2019-05-06 16:31发布

问题:

i have a huge jsf-page with an underlying ViewScopeBean that contains a very large form (containing nearly 100 inputs) with many ajaxified input fields. for every input field, i only submit the current value and render nothing. so just a sample field in that form:

<h:selectBooleanCheckbox id="includeForeignCurrencies" 
                         value="#{RatingManagerBean.formData.foreignCurrencies}"
                         action="#{RatingManagerBean.calculate}">

    <f:ajax render="@none" execute="includeForeignCurrencies"/> 

</h:selectBooleanCheckbox>

after the ajax post i inspect e.g. the developer tools in firebug and recognize that the submitted size of the post data is up to 2 kb. then i select the row, choose "copy post data" and pasted it into an editor:

every field of that form is submitted although i am only interested in the current changed field:

form-search=form-search
countryCode=AT
ratingType={"type":"COUNTRY"}
averageRating
minAmount
maxAmount
averageAmount
company
location
staffResponsible
staffResponsible2
requestDate
reminderDate
acutalCurrency
compareCurrencies   

 //70 other empty form-ids....

includeForeignCurrencies=on

javax.faces.ViewState=1825148886808299106:-354534052529224349
javax.faces.source=includeForeignCurrencies
javax.faces.partial.event=click
javax.faces.partial.execute=includeForeignCurrencies
javax.faces.behavior.event=valueChange
javax.faces.partial.ajax=true

is there a way to reduce the posted data like:

includeForeignCurrencies=on

javax.faces.ViewState=1825148886808299106:-354534052529224349
javax.faces.source=includeForeignCurrencies
javax.faces.partial.event=click
javax.faces.partial.execute=includeForeignCurrencies
javax.faces.behavior.event=valueChange
javax.faces.partial.ajax=true

or even just

includeForeignCurrencies=on

thanks in advance!

Workaround:

as a possible workaround i came up with disabling all inputs that did not trigger the post and re-enable them in ajax complete. post data size is now 200 B instead of 2000 B.

as i am not sure if this may cause any other issues, this is an update of the question, not an answer.

<h:selectBooleanCheckbox id="includeForeignCurrencies" 
                     value="#{RatingManagerBean.formData.foreignCurrencies}"
                     action="#{RatingManagerBean.calculate}"
                     onclick="prepareAjaxPost(this)">

<f:ajax render="@none" execute="includeForeignCurrencies" onevent="ajaxPostComplete"/> 

</h:selectBooleanCheckbox>

javascript/jQuery:

function prepareAjaxPost(source){   

   //keep already disabled inputs disabled
   $("#form-search :input:not(:disabled)").filter(function() {      

        return !this.id.match(/javax.faces.ViewState/); //all inputs ignoring jsf- viewstate ones

    }).each(function(){
          var input = $(this);
          var others = input.not(source); 
          others.attr("disabled", true);
          others.addClass("blocked"); //some style that has no visual effect for users

    });
}

function ajaxPostComplete(data) {
    switch (data.status) {      
        case "success": {

           $("#form-search :input.blocked").each(function(){
              $(this).attr("disabled",false).removeClass("blocked");     
           });

           break;
        }
}

回答1:

Start by reading the 'edit 2:' at the end

I know your question is about JSF 2.3, but out of curiosity, I wanted to see if it would be possible at all in plain JSF. And the good thing is that it seems possible for at least Mojarra 2.2.8 (did not have any other environments at hand quickly) by overriding a jsf.js function. And... if you want it to be 'conditional', so not on every input/button/..., also add a passthrough attribute.

I checked/debugged the sourcecode of the Mojarra jsf.js file and noticed it is only possible to strip/filter out fields after the full querystring is created instead of the PrimeFaces way where they prevent fields to be added 'upfront'. The function where this can be done is the getViewState and by overriding this, calling the original one and adding some logic before returning the result, it all seems to work. The downside is that it might filter out to much (see the comment at the end)

The js to add to your code (make sure it loaded is after jsf.js)

partialSubmit.js

var orgViewState = jsf.getViewState;
jsf.getViewState = function(form) {

    var myViewState = orgViewState(form);

    var eventSource = this.ajax.request.arguments[0];
    // Read partialSubmit from the input (Mojarra puts attributes without camelcasing in the html, so pt:partialSubmit in the xhtml becomes partialsubmit in the html 
    var partialSubmit = eventSource.attributes['partialsubmit'];

    //If not on the input, check the form
    if (partialSubmit === undefined) {
        partialSubmit = form.attributes['partialsubmit'];
    } 

    if (partialSubmit != undefined && partialSubmit.value === "true") {
        var params = myViewState.split("&");
        var kvpairs = [];
        var ids = [];
        // Create list of id's based on the 'execute' list
        var execute = this.ajax.request.arguments[2].execute;
        // TODO check for other replacements (@parent, @form)?
        execute = execute.replace('@this', eventSource.id);
        execute = execute.replace('@none', "");

        var ids = execute.split(" ");
        for(j=0;j<ids.length;j++) {
            var id = ids[j];
            for (i=0;i<params.length; i++) {
                var kv = params[i].split("=");
                if (kv[0] === kv[1] || //to pass on the form id value keypair. Not sure this is needed
                    kv[0] === "javax.faces.ViewState" || // check if other 'relevant' fields need to be passed on (viewid, conversationid etc... How to detect these?
                    encodeURIComponent(id) === kv[0]) {
                    kvpairs.push("&"+params[i]);
                }
            }
        }
        return kvpairs.join("");
    } else {
        return myViewState;
    }
}

And example of the usage is provided below. In this example the first and last input fields have pt:partialSubmt="true" added to the inputs (could not get it to work when added to the ajax tag). There are also two ajax enabled h:commandButtons, one with and one without the partial submit. Using the browser developer tool you can see the differences when using them

partialSubmitDemo.xhtml

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:p="http://primefaces.org/ui"
    xmlns:pt="http://xmlns.jcp.org/jsf/passthrough">

    <h:head />

    <h:body>
        <!-- adding pt:partialSubmit="true" to the form makes it work for every input/button in the form -->
        <h:form>

            <h:panelGrid columns="4" border="1" id="panel">
                <h:outputText value="label" />
                <h:outputText value="input" />
                <h:outputText value="single ajax" />
                <h:outputText value="all ajax" />

                <h:outputLabel value="inp0" />
                <h:inputText value="#{sessionScope['inp0']}" id="inp0" pt:partialsubmit="true">
                    <f:ajax render="aout0" execute="@this" />
                </h:inputText>
                <h:outputText value="#{sessionScope['inp0']}" id="aout0" />
                <h:outputText value="#{sessionScope['inp0']}" id="out0" />

                <h:outputLabel value="inp1" />
                <h:inputText value="#{sessionScope['inp1']}" id="inp1">
                    <f:ajax render="aout1" execute="@this" />
                </h:inputText>
                <h:outputText value="#{sessionScope['inp1']}" id="aout1" />
                <h:outputText value="#{sessionScope['inp1']}" id="out1" />

                <h:outputLabel value="inp2" />
                <h:inputText value="#{sessionScope['inp2']}" id="inp2">
                    <f:ajax render="aout2" execute="@this" />
                </h:inputText>
                <h:outputText value="#{sessionScope['inp2']}" id="aout2" />
                <h:outputText value="#{sessionScope['inp2']}" id="out2" />

                <h:outputLabel value="inp3" />
                <h:inputText value="#{sessionScope['inp3']}" id="inp3">
                    <f:ajax render="aout3" execute="@this" />
                </h:inputText>
                <h:outputText value="#{sessionScope['inp3']}" id="aout3" />
                <h:outputText value="#{sessionScope['inp3']}" id="out3" />

                <h:outputLabel value="inp4" />
                <h:inputText value="#{sessionScope['inp4']}" id="inp4">
                    <f:ajax render="aout4" execute="@this" />
                </h:inputText>
                <h:outputText value="#{sessionScope['inp4']}" id="aout4" />
                <h:outputText value="#{sessionScope['inp4']}" id="out4" />

                <h:outputLabel value="inp5" />
                <h:inputText value="#{sessionScope['inp5']}" id="inp5">
                    <f:ajax render="aout5" execute="@this" />
                </h:inputText>
                <h:outputText value="#{sessionScope['inp5']}" id="aout5" />
                <h:outputText value="#{sessionScope['inp5']}" id="out5" />

                <h:outputLabel value="inp6" />
                <h:inputText value="#{sessionScope['inp6']}" id="inp6">
                    <f:ajax render="aout6" execute="@this" />
                </h:inputText>
                <h:outputText value="#{sessionScope['inp6']}" id="aout6" />
                <h:outputText value="#{sessionScope['inp6']}" id="out6" />

                <h:outputLabel value="inp7" />
                <h:inputText value="#{sessionScope['inp7']}" id="inp7">
                    <f:ajax render="aout7" execute="@this" />
                </h:inputText>
                <h:outputText value="#{sessionScope['inp7']}" id="aout7" />
                <h:outputText value="#{sessionScope['inp7']}" id="out7" />

                <h:outputLabel value="inp8" />
                <h:inputText value="#{sessionScope['inp8']}" id="inp8">
                    <f:ajax render="aout8" execute="@this" />
                </h:inputText>
                <h:outputText value="#{sessionScope['inp8']}" id="aout8" />
                <h:outputText value="#{sessionScope['inp8']}" id="out8" />

                <h:outputLabel value="inp9" />
                <h:inputText value="#{sessionScope['inp9']}" id="inp9"
                    pt:partialsubmit="true">
                    <f:ajax render="aout9" execute="@this" />
                </h:inputText>
                <h:outputText value="#{sessionScope['inp9']}" id="aout9" />
                <h:outputText value="#{sessionScope['inp9']}" id="out9" />

            </h:panelGrid>

            <h:commandButton value="Update all ajax partial submit" pt:partialsubmit="true">
                <f:ajax render="out0 out1 out2 out3 out4 out5 out6 out7 out8 out9" execute="@this" />
            </h:commandButton>

            <h:commandButton value="Update all ajax full submit">
                <f:ajax render="out0 out1 out2 out3 out4 out5 out6 out7 out8 out9" execute="@this" />
            </h:commandButton>
            <h:commandButton value="Clear all" pt:partialsubmit="true">
                <f:setPropertyActionListener value="" target="#{sessionScope['inp0']}" />
                <f:setPropertyActionListener value="" target="#{sessionScope['inp1']}" />
                <f:setPropertyActionListener value="" target="#{sessionScope['inp2']}" />
                <f:setPropertyActionListener value="" target="#{sessionScope['inp3']}" />
                <f:setPropertyActionListener value="" target="#{sessionScope['inp4']}" />
                <f:setPropertyActionListener value="" target="#{sessionScope['inp5']}" />
                <f:setPropertyActionListener value="" target="#{sessionScope['inp6']}" />
                <f:setPropertyActionListener value="" target="#{sessionScope['inp7']}" />
                <f:setPropertyActionListener value="" target="#{sessionScope['inp8']}" />
                <f:setPropertyActionListener value="" target="#{sessionScope['inp9']}" />
                <f:ajax render="panel" execute="@this" />
            </h:commandButton>

        </h:form>

        <script src="partialSubmit.js" type="text/javascript" />

    </h:body>
</html>

You can see some comments in the javascript code that it needs optimization but it is a good working start/beginning.

Edit:

  • Added the option to add pt:partialSubmit="true" to the <h:form> So using it form wide
  • Investigated the passing on of existing request params by using the OmniFaces <o:form includeRequestParams="true"> and they are not stripped due to them not being in the 'viewState' key-value pairs of the form fields.

Edit 2:

The great OmniFaces project will have this in the upcoming 3.0 release. A commit has been made by @BalusC that closed issue 394 that I created there as a result of this question. WITHIN 5 DAYS Using the

<o:form> 

will default to a partialSubmit



标签: jsf jsf-2.3