s:checkbox fieldValue always true in validation be

2019-06-08 11:06发布

问题:

I am using Struts 2 <s:checkbox /> in my form processing, along with AngularJS and jQuery.
Before submitting, I need validation and until now we do this in the project:

When we press Submit button, this function is called:

$scope.processForm('myForm', '<s:url value="/form/validate.action" />', 
                             '<s:url value="/form/save.action" />');

, where processForm(formId, validateUrl, submitUrl) is a function defined by us:

$scope.processForm = function(form, validateUrl, submitUrl) {
    window.scroll(0,0);
    ProccessFormService.processStandarForm(form, validateUrl, submitUrl);
};

And furthermore, we have processStandarForm() defined in a global service:

angular.module('ourApp').controller('myFormCtrl', 
                                    function($scope, $modal, ProccessFormService) {
...
}

In service:

(function() {   
    angular.module('ourApp').factory('ProccessFormService', ['$http', function($http) {

    processStandarForm: function(form, validateUrl, submitUrl) {        
        this.processForm(form, validateUrl, submitUrl);
    },

    processForm: function(form, validateUrl, submitUrl, success) {

        if ((typeof form) == 'string') {
            form = document.getElementById(form);
        }

        var data = this.form2Object(form);
        var ctrl = this;

        if (data) {
            if (validateUrl) {
                $http({
                    method  : 'POST',
                    url     : validateUrl,
                    data    : $.param(data),  // pass in data as strings
                    headers : { 'Content-Type': 'application/x-www-form-urlencoded' }  
         // set the headers so angular passing info as form data (not request payload)
                }).success(function() {
                    if (!success) {
                        form.action = submitUrl;
                        form.submit();
                    } else {
                        ctrl.submitAjaxForm(submitUrl, data, success)
                    }
                });
            } else if (submitUrl) {
                if (!success) {
                    form.action = submitUrl;
                    form.submit();
                } else {
                    this.submitAjaxForm(submitUrl, data, success)
                }
            }
        }       
    },
}

Basically, we are submitting twice the form, firstly for validation, then for submitting.

What I don't understand, is that if I debug in action class, in the function of validate(), the boolean value of <s:checkbox /> is always true, but in submit() function, boolean values are submitted correctly, according to they are checked/not checked. Checkboxs are like this:

<div class="col-sm-12 form-checkbox">
     <s:checkbox name = "myForm.married" 
             ng-model = "checkboxModel" 
                value = "<s:property value='%{myForm.married}'/>"
            ng-change = "submitCheckbox();" 
              ng-init = "checkboxModel= %{myForm.married}" 
                theme = "simple"  
          ng-disabled = "anotherFunction()" />
</div>

I understand that, the value submitted is fieldValue="xxx" in <s:checkbox />, and by default is true. So I did this to change the fieldValue of every checkbox before the page is loaded. Although all the script are executed, nothing changed. I still get all true in validation.

$(document).ready(function(){
    $( "input:checkbox" ).each(function(){
        var checkbox = $(this);
        var checked = checkbox.prop("checked");//sera false/true
        if (checked == true){
            checkbox.prop("fieldValue", "true");
        } else {
            checkbox.prop("fieldValue", "false");
        }
    });
});

So, how can I get right boolean values not only in submitting, but also in validation?? Is the Angular service wrongly written ? I really doubt that but I am not able to figure out the question.

回答1:

Suspected that data is not serialized properly that used with angular $http().

If you want to emulate $.param() that used in jQuery you should use built-in serializer $httpParamSerializerJQLike.

Alternative $http params serializer that follows jQuery's param() method logic. The serializer will also sort the params alphabetically.

To use it for serializing $http request parameters, set it as the paramSerializer property:

$http({
  url: myUrl,
  method: 'GET',
  params: myParams,
  paramSerializer: '$httpParamSerializerJQLike'
});

It is also possible to set it as the default paramSerializer in the $httpProvider.

Additionally, you can inject the serializer and use it explicitly, for example to serialize form data for submission:

.controller(function($http, $httpParamSerializerJQLike) {
  //...

  $http({
    url: myUrl,
    method: 'POST',
    data: $httpParamSerializerJQLike(myData),
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    }
  });

});

Another ways to convert from $.param to angular you can find in Convert $.param in angularjs



回答2:

You've probably removed (or messed up: uncheckedValue=true) something useful from your Interceptor Stack, like the Checkbox Interceptor:

org.apache.struts2.interceptor.CheckboxInterceptor is in the defaultStack. It checks each form parameter submitted to the action and if it finds one with a prefix of _checkbox it inserts a value for a parameter whose name is derived from the suffix to _checkbox if it does not exist. The default value inserted is false but this can be changed by setting the uncheckedValue parameter on the interceptor.

This means that a checkbox can be accompanied by a hidden input with the same name but a prefix of _checkbox so that if the checkbox is not checked on the form the action will still receive a value rather than the default HTML action of not providing a value for unchecked checkboxes.



回答3:

I have figured out the problem myself. It lies in here:

var data = this.form2Object(form);

Noted that form2Object(form) is another function, I dug more into this and found this function is serializing the form, converting it to object manually and is not processing <input type="checkbox" /> correctly.

form2Object: function(form) {

    var data = null;
    if ((typeof form) == 'string') {
        form = document.getElementById(form);
    }
    if (form) {
        data = new Object();
        for (var i = 0; i < form.elements.length; i++) {
            if ( (form.elements[i].tagName.toUpperCase() == 'INPUT') || form.elements[i].tagName.toUpperCase() == 'TEXTAREA') {
//here it considers `<input type="checkbox" />` as normal `<input />`, and will submit its `value=xxx` in validation, and it will always be `true` due to `<s:checkbox />`. 
//But it will be correct in real submission because before that, we don't go through here.
                data[form.elements[i].name] = form.elements[i].value;
            } else if (form.elements[i].tagName.toUpperCase() == 'SELECT') {
                data[form.elements[i].name] = form.elements[i].value;
            }
        }
    }
    return data;
}

So I have changed it to this:

form2Object: function(form) {

    var data = null;

    if ((typeof form) == 'string') {
        form = document.getElementById(form);
    }
    if (form) {
        data = new Object();
        for (var i = 0; i < form.elements.length; i++) {
            if ( (form.elements[i].tagName.toUpperCase() == 'INPUT' &&
                form.elements[i].type.toUpperCase() != 'CHECKBOX')
                    || 
                form.elements[i].tagName.toUpperCase() == 'TEXTAREA') {
                data[form.elements[i].name] = form.elements[i].value;
            } else if (form.elements[i].tagName.toUpperCase() == 'SELECT') {
                data[form.elements[i].name] = form.elements[i].value;
            } else if (form.elements[i].tagName.toUpperCase() == 'INPUT' && 
                    form.elements[i].type.toUpperCase() == 'CHECKBOX') {
                var checkbox = $(form.elements[i]);
                if (checkbox.prop("checked") == true){
                    data[form.elements[i].name] = true;
                } else if (checkbox.prop("checked") == false){
                    data[form.elements[i].name] = false;
                }
            }
        }
    }
    return data;
}

But, reading @Roman C's answer, I realized that maybe it's better to serialize the form with build-in functions. So I accepted his answer.