Tabbed forms with Spring MVC and Bootstrap 2

NEC Motorcycle Show (110)

In the previous part of this mini-tutorial, we didn’t actually do much. We really just split a large form into tabs and put it under Twitter Bootstrap’s styles. In this part I’ll show how to implement a cool feature that make your tabbed forms so much more user-friendly: how to return to the active tab on submit.

Return to the active tab on submit

With tabbed forms, especially those that show the submit button on each tab, users may be tempted to store their progress multiple times while using the form. This is very prudent on their part, but can cause a bit of a headache.
Say you have a tabbed-form for editing an employee detail (like the one we had in the previous part). When the user saves, you will want the system to validate, save, and return to the edit form. However, when you do that, bootstrap will make your first (leftmost) tab active. This is not always the desired behaviour.
To make the form more user-friendly, you and your users would probably like to have the form submitted, and stay on the same tab. Luckily – it is quite easy to do.
Building off the previous part, add the following in your jsp:

<ul id="tabMenu" class="nav nav-tabs">
  <li id="basic-details-tab"><a href="#basic-details" data-toggle="tab">Basic Details</a></li>
  <li id="job-details-tab"><a href="#job-details" data-toggle="tab">Job Details</a></li>
  <li id="payment-details-tab"><a href="#payment-details" data-toggle="tab">Payment Details</a></li>
  <li id="payrate-details-tab"><a href="#payrate-details" data-toggle="tab">Payrate Details</a></li>
  <li id="tax-details-tab"><a href="#tax-details" data-toggle="tab">Tax Details</a></li>
</ul>
.
.
<form:form modelAttribute="employee" cssClass="form-horizontal" method="POST">
  <div id="tabContent" class="tab-content">
    <input name="focus" type="hidden" />
    .
    .   
</form:form>
.
.
<script type="text/javascript">
  $(document).ready(function() {
    $('#tabMenu > li').click(function() {
      $('input[name=focus]').val($(this).attr('id'));
    });
  });
</script>
  • Lines 1-7 show the tabs, as per Twitter Bootstrap. Nothing special here.
  • In line 12, we add a hidden field to the form. It doesn’t specifically matter where this hidden form field resides, as long as it is inside your form. We are going to use this field to tell Spring which tab was open when the form was submitted.
  • In line 20 we bind a click() event to each of the tabs in the tabbed-form tab line. The event, when fired, populates the hidden form field with the id of the clicked tab.

Whenever the user clicks a tab, the hidden field is populated with the tab id. This is good news, because when the user submits the form, this id gets sent to the @controller alongside the rest of the form details:

//In the Employee Controller
//The employee controller
@RequestMapping(value = "/edit/{id}", method = RequestMethod.POST)
  public String update(@Valid Employee employee, BindingResult bindingResult,RedirectAttributes redir,Model model) {
     
    if (bindingResult.hasErrors()) {
      model.addAttribute("focus",request.getParameter("focus"));
      return  "/people/edit";
    }
    employee = employeeService.save(employee);
    redir.addFlashAttribute("focus",request.getParameter("focus"));
    return "redirect:/people/edit/" + employee.getId();
    
} 

In the controller we may have one of two outcomes: the form may validate and save, or – we may find validation errors.

The simple case is when there are validation errors. In this scenario, we return to the same screen, with the errors all packaged neatly inside the BindingResult object. Also, in this scenario, we have the Model injected for us, so it is a simple task of adding a focus attribute to the model, which contains the value of the focus request parameter (which is the content of the focus hidden input field on our form).

The more complex case is when there are no errors. In this case we save the employee to the database (using the employeeService) – and return to the edit page with a redirect. We do the redirect to ensure that everything is “clear”, any setup we perform in the edit’s GET request is done, and to refresh the employee data.
However, when we go through a redirect – we lose the model. This is not important in the case of the (non-existing) error messages, but it means we can’t store the focus attribute on the model, as it won’t make it to the GET request.

Luckily, we have the RedirectAttributes. In the Spring MVC context, the RedirectAttributes implementation functions like a flash container (a container for data between redirects in a web application) – which is exactly what we need. In line 11 – we add the focus to the RedirectAttributes object, and all is ready for the GET request:

@RequestMapping(value = "/edit/{id}", method = RequestMethod.GET)
  public String getEdit(@PathVariable("id") Long id, Model model) {
		
    Employee e = employeeService.findOne(id);
    model.addAttribute(e);
    return "/people/edit";
  }

What? you say – where is the RedirectAttrbiutes object?
Do not fear. It is there – but it is now called a Model. This is the way Spring works, which is neat – The GET method requires a model instance, which it will later pass to the view. The RedirectAttributes is in essence a model. So – if we got here via a redirect command that populated a RedirectAttributes – then the GET method will have this injected as the model. If not – then a new model will be constructed for us.
At the end of the call, the view is loaded, and if we arrived here via a redirect, then we should have the “focus” attribute available, in the model, for the view. All that is left, is to add a little piece of code in the view to handle this:

//inside $document.ready() of the jsp page
$('.nav-tabs li').removeClass('active');
$('.tab-pane').removeClass('active in');
$('#tabMenu').show();
var tab = '${focus}';
$('#'+tab).addClass('active');
$('#' + tab.slice(0,-4)).addClass('active in');
$('input[name=focus]').val(${empty focus ? 'basic-details-tab' : focus});

Pretty clear: in the lines 2-3 we remove the ‘active’ classes of all tabs. This isn’t strictly required, but is for good measure. Then in line 6 – we add the active class to the relevant tab (based on the id we received in focus model attribute).
In line 7 – we add the active class to the tab-pane (alternatively you could call click() on the relevant <li> element) – Note that we use a shortcut here with slice – perhaps you’ll want to do it differently.
And in line 8 – we just make sure that the hidden input field is initialized with the right value (or with a default).

So this sorts out the focus issue. In the next part – how to add error markers to individual tabs.
<

5 thoughts on “Tabbed forms with Spring MVC and Bootstrap 2

  1. Tiger Liu

    Nice! Thank you! Where I download the full sample source code? Can you support the url or email to me!

  2. info2scs

    Nice tutorial. I am working on a similar project. Appreciate if you can share the source code.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>