1

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.

Roman C
  • 49,761
  • 33
  • 66
  • 176
WesternGun
  • 11,303
  • 6
  • 88
  • 157

3 Answers3

0

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.

Andrea Ligios
  • 49,480
  • 26
  • 114
  • 243
  • Thanks, so if I understand well, you mean I have set `uncheckedValue=true` somewhere. But I don't know how to set this value, and as far as I remembered, interceptors are kept intact. Please tell me how to find this in my project, because a full search returns nothing. – WesternGun Jan 28 '16 at 14:33
  • Then probably it's not that, but to clear doubts, please update your question by adding your interceptor stack. Also specify if the checkboxes arrives all true in both actions or only in the validation one – Andrea Ligios Jan 28 '16 at 14:45
  • thanks @Andrea Ligios. I am not using interceptors, at least I find no interceptors explicitly set in any `.xml` files. – WesternGun Feb 01 '16 at 10:14
0

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

Community
  • 1
  • 1
Roman C
  • 49,761
  • 33
  • 66
  • 176
  • Useful info on `$http()` service! I somehow figured out something else Roman. See my own answer....sorry for your time spent. – WesternGun Feb 01 '16 at 10:19
0

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.

WesternGun
  • 11,303
  • 6
  • 88
  • 157