2

I wrote custom component, which is simple form. After submitting the form, I want to validate the form and in case of error, I want to display them. I tried to make a method with POST request mapping in my class MyFormComponentController extends AbstractAcceleratorCMSComponentController<MyFormComponentModel> but it gets me an error saying that method is not allowed. So I wrote a controller and added action to my form component

@Controller
@RequestMapping(value = "/action")
public class MyFormPageController extends AbstractPageController {

    @RequestMapping(method = RequestMethod.POST)
    public String doMyPageController(@Valid final MyForm myForm, final BindingResult bindingResult, final Model model)
            return ???; //..in case of error?

But when there is an error in my form and I wan't to return is back to the view, only component is displayed. I tried to pass there the value I got from getView() method in my component controller, but it didn't help.

EDIT: I also tried to make form submit with ajax call, like:

$(document).on("submit",'#myForm', function(e){
            e.preventDefault();
            var postData = $("#myForm").serialize();
            var url= $(".js-my-action").data("url");
            $.ajax({
                url: url,
                data: JSON.stringify(postData),
                type: "POST",
                success: function (response){
                }
            });
        })

and the method which I want to call is

@RequestMapping(value = "/action", method = RequestMethod.POST)
    public String doMyPageController(@RequestBody @Valid final MyForm catalogOrderForm, final Model model) throws IOException, JaloBusinessException, CMSItemNotFoundException {

Here I tried to change @RequestBody to @ModelAttribute with a name of the form, but I only get Acces denied (CSRFToken) error:

ERROR [hybrisHTTP24] [SikoAcceleratorAccessDeniedHandler] Access denied happend - org.springframework.security.web.csrf.InvalidCsrfTokenException: Invalid CSRF Token '66d045fd-c9ae-4210-ae97-f2a0e739bcbf"' was found on the request parameter 'CSRFToken' or header 'CSRFToken'.

The data which I'm passing in ajax call:

"firstName=Test&lastName=Test&street=Test&city=Test&postalCode=38801&email=test%40test.com&_inspiration=on&_kitchen=on&_door=on&_gdpr=on&CSRFToken=66d045fd-c9ae-4210-ae97-f2a0e739bcbf"

Then I tried to pass data in ajax call like:

data: {myForm: postData}

but it also didn't work.

Stepan Tuhacek
  • 219
  • 5
  • 18
  • Since you have created the component to render form, I hope you want to use that component on multiple pages. In that case, you can't return any hardcode view, because form can be submitted from any page(view). What I would suggest handling form submission through the ajax call. – HybrisHelp Sep 26 '18 at 08:51
  • Tell us your requirement, what exactly you are trying to achieve. There could be an alternate solution. – HybrisHelp Sep 26 '18 at 08:53
  • Well, maybe you're right that I should use ajax call for this. I want to achieve this - there's classic page with inserted component and that's my form. After submitting form I'll do validation and then if there's something wrong, I want to display exactly same page with errors at that form. – Stepan Tuhacek Sep 26 '18 at 08:59

4 Answers4

1

Using forms in a CMS Component is a tricky issue. The solution might depend on your use case. Here are a few solutions I've seen so far:

Use component only on specific URLs/Pages Consider the cart page. There are lots of forms. They are only supposed for use on the cart page (/cart). You could also define a content page to view in case of failure/success. The search page would be an example for that. When you use the search component, you are always redirected to search page / search empty page when you enter a search term.

Use referer information The login component uses a bit more intelligent approach. When you login from a specific page (e.g. product page), the login controller checks the referer URL and redirects to the page you visited before.

Parameter in form for URL Some solutions add an additional string parameter to the form to store the current URL. I've also seen solutions where a parameter is stored in the session using SessionService. This solution however has the drawback, that only one form can be used throughout one session.

Configure per CMS Component where it is used and where it redirects to success/failure It's also possible to define a specific content page/URL to show after success/failure. In this case you could have multiple instances of this component which all redirect to a specific page on success/failure.

Use AJAX Another solution is to submit the form using AJAX and handle failure/success actions in JavaScript.

There might be other and more valid implementations. Please share your thoughts with me.

Johannes von Zmuda
  • 1,814
  • 8
  • 14
0

You can use ResponseEntity as return type.

@RequestMapping(method = RequestMethod.POST)
    public ResponseEntity<string> doMyPageController...
        return new ResponseEntity<"Error message" , HttpStatus.BAD_REQUEST>

You need process this response in jsp file. Accelerator has some tags for processing these responses, you can check template storefront.

mkysoft
  • 5,392
  • 1
  • 21
  • 30
  • I tried this approach to show errors at the form, but it's not showing it. I used `bindingResult.rejectValue("firstName", "error.msg")` but the ajax call just jumps into "error" branch and nothing happens at the form. – Stepan Tuhacek Sep 27 '18 at 08:34
0

Since you're using Spring, the answer is mind bendingly easy:

On your controller class put @Valid annotation

This will cause a 400 error to be returned from your controller along with a JSON payload spelling out the exact cause of error for every error encountered.

See Spring 3 type conversion and validation

Christian Bongiorno
  • 5,150
  • 3
  • 38
  • 76
  • No it's not. I don't have problem with validation itself, it's working great. What I need is the correct return value in the post method to return back to the whole page with my inserted hybris component. For the note, I already have that annotation there, I'll edit it in the question. – Stepan Tuhacek Sep 26 '18 at 08:05
0

Component on different pages - Since you have created the component to render the form, I hope you want to use that component on multiple pages. In that case, you can't return any hardcode view, because form can be submitted from any page(view). What I would suggest handling form submission through the ajax call.

Component on Single pages - If you know this component will be used on the single page. Then you can simply use normal page submission and return that page view from your controller. Here you don't require to use AJAX.


Suggestion:

Instead of creating a custom component just for your form, you can create an instance of JspIncludeComponent and point to your JSP/VIEW.

Like

$contentCatalog=yourContentCatalog
$contentCV=catalogVersion(catalog(id[default=$contentCatalog]),version[default='Staged'])

INSERT_UPDATE JspIncludeComponent ; $contentCV[unique=true] ; uid[unique=true]    ; name                  ; page                                          
                                  ;                         ; formComponent       ; Form Component        ; /WEB-INF/views/desktop/pages/form/newsForm.jsp 

EDIT:

Acces denied (CSRFToken) error - Hybris Commerce provides a CSRF protection mechanism, that is enabled by default. CSRFHandlerInterceptor validates each POST request for CSRF token. To bypass CSRF check for any request, you can add URL pattern to the csrfAllowedUrlPatterns list.

To solve your issue, Simply use spring form, which renders CSRFToken in the hidden field. Now you can submit the whole form using form.serialize().

JSP:

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>

    <form:form id="myForm" action="${myFormActionURL}" method="POST" modelAttribute="myForm">
        <input type="text" placeholder="<spring:theme code="myform.firstName"/>" name="fname" required>
         ...
    </form:form>

AJAX:

var form = $("#myForm");
    $.ajax({
        url: url,
        data: form.serialize(),
        type: 'POST',
        success: function (data) {

        },
        error: function (data) {

        }
    });

Controller:

@SuppressWarnings("boxing")
@ResponseBody
@RequestMapping(value = "/myForm", method = RequestMethod.POST)
public String saveMyForm(@Valid @ModelAttribute("myForm") final MyForm myForm, final Model model)
{
        return "succcess";
}
HybrisHelp
  • 5,518
  • 2
  • 27
  • 65
  • But when it's inserted to JspIncludeComponent, how can it be connected with formComponentController to fill that form? Should the be also an instance? `INSERT_UPDATE formComponent; $contentCV[unique = true]; uid[unique = true]...` – Stepan Tuhacek Sep 26 '18 at 10:21
  • well, JspIncludeComponent is used to render the view(more on static data). If you want to fill your form with some default value from the server side, this won't help. – HybrisHelp Sep 26 '18 at 10:33
  • I also tried ajax call as you've suggested, but it's not working, see the edited question please. – Stepan Tuhacek Sep 26 '18 at 15:32
  • You have to pass CSRF token in POST request. Refer my updated answer. – HybrisHelp Sep 27 '18 at 05:39
  • Well I already am passing that token in the POST request, see the updated question. – Stepan Tuhacek Sep 27 '18 at 06:22
  • Ok so problem was in this, I shouldn't be using that: `data: JSON.stringify(postData)` – Stepan Tuhacek Sep 27 '18 at 06:26
  • Is it also possible to show the errors at the form with this approach? I added form:errors, rejected some values, but it only jumps into error branch and nothing happens. – Stepan Tuhacek Sep 27 '18 at 08:41
  • you have to do it through JS, refer [this example](https://www.boraji.com/spring-4-mvc-jquery-ajax-form-submit-example) – HybrisHelp Sep 27 '18 at 08:54