Jan 16, 2014

Writing a simple application with Dojo and Spring MVC

In this tutorial we will develop a simple Web application with Spring MVC as J2EE framework and Dojo as javascript framework. We will use same Northwind Database as discussed in previous article. Following will be functionality of application.

User will be able to search for employees based on employee id, first name or last name. Once list is displayed user can select single record and update the same. Once the record is updated flow will go back to search screen.

From learning point of view we will learn following in Dojo
Modules like dojo/dom, dojo/query, dojo/on, dojo/domReady also we will see how define and require functions of dojo differ in nature. So lets start

First we will configure two config files in our Web.xml (deployment descriptor file)
root-context.xml: This file will have all information that will be shared across all servlet contexts (or modules or say throughout application)
mvcapp-servlet-context: This file will have information about beans for specific module.

So following are our files

root-context.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans    
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.2.xsd">
       
      
       <context:annotation-config />
      
       <bean id="dataSource"
              class="org.springframework.jdbc.datasource.DriverManagerDataSource">
              <property name="driverClassName" value="com.mysql.jdbc.Driver" />
              <property name="url" value="jdbc:mysql://localhost:3306/northwind" />
              <property name="username" value="root" />
              <property name="password" value="" />
       </bean>
</beans>

Here we have defined common beans that we will need for all modules like datasource which will have information about the DB that we want to connect to.

mvcapp-servlet-context.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans    
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.2.xsd">

       <context:component-scan base-package="com.northwindsh">
              <context:exclude-filter type="annotation"
                     expression="com.northwindsh.form.Contact" />
       </context:component-scan>
       <context:annotation-config />

       <bean
              class="org.springframework.web.servlet.view.InternalResourceViewResolver">
              <property name="prefix">
                     <value>/WEB-INF/pages/</value>
              </property>
              <property name="suffix">
                     <value>.jsp</value>
              </property>
       </bean>

       <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
              <property name="dataSource">
                     <ref bean="dataSource" />
              </property>

              <property name="hibernateProperties">
                     <props>
                           <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                           <prop key="hibernate.show_sql">true</prop>
                     </props>
              </property>

              <property name="annotatedClasses">
                     <list>
                           <value>com.northwindsh.bean.Customer</value>
                     </list>
              </property>

       </bean>
</beans>

In this configuration file we have information about,

sessionFactory (this too can be moved to module specific configuration file but for sack of brevity I am keeping it here) which will have information about hibernate configuration and classes that will be annoted by @Entity annotation and mapped to one or more tables by @Table annotation.
viewResolver: What suffix and prefix will be used to derive view object.
Package name that we need to scan for annotated classes for auto wiring beans.

Now in our web.xml we will provide entry for these two files and there will be a tweak for js and css files that interested reader can notice (there should be a cleaner way of doing the same but for now I am keeping it simple)

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app
       xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                     http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
       id="WebApp_ID" version="3.0">

       <display-name>Northwind Spring</display-name>
       <welcome-file-list>
              <welcome-file>index.html</welcome-file>
              <welcome-file>index.jsp</welcome-file>
       </welcome-file-list>
       <servlet>
              <servlet-name>mvcapp</servlet-name>
              <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
              <init-param>
                     <param-name>contextConfigLocation</param-name>
                     <param-value>/WEB-INF/mvcapp-servlet-context.xml</param-value>
              </init-param>
              <load-on-startup>1</load-on-startup>
       </servlet>

       <servlet-mapping>
              <servlet-name>mvcapp</servlet-name>
              <url-pattern>/</url-pattern>
       </servlet-mapping>
      
       <servlet-mapping>
              <servlet-name>default</servlet-name>
              <url-pattern>/dojo/*</url-pattern>
       </servlet-mapping>
      
       <servlet-mapping>
              <servlet-name>default</servlet-name>
              <url-pattern>/nwdojo/*</url-pattern>
       </servlet-mapping>

       <context-param>
              <param-name>contextConfigLocation</param-name>
              <param-value>/WEB-INF/root-context.xml</param-value>
       </context-param>

       <listener>
              <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
       </listener>
</web-app>

So first we will create our VO class 

package com.northwindsh.form;

public class Contact {
       private String firstname;
    private String lastname;
    private String email;
    private String telephone;
       public String getFirstname() {
              return firstname;
       }
       public void setFirstname(String firstname) {
              this.firstname = firstname;
       }
       public String getLastname() {
              return lastname;
       }
       public void setLastname(String lastname) {
              this.lastname = lastname;
       }
       public String getEmail() {
              return email;
       }
       public void setEmail(String email) {
              this.email = email;
       }
       public String getTelephone() {
              return telephone;
       }
       public void setTelephone(String telephone) {
              this.telephone = telephone;
       }
}


Now we write our first controller that will route us to search page. So following will be our controller with single method that will simply move us to search page.

package com.northwindsh.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;

import com.northwindsh.form.Contact;

@Controller
@SessionAttributes
@RequestMapping(value = "/customer")
public class CustomerController {

       @RequestMapping("/search")
       public ModelAndView search() {
              return new ModelAndView("select-customer", "command", new Contact());
       }
}

And we will create a jsp file in WEB-INF/pages/select-customer.jsp as follows

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
       <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
       <title>Customer</title>
       <script type="text/javascript" src="../dojo/dojo.js" data-dojo-config="async: true"></script>
</head>
<body>
       <form id='custSearchFrm'>
       <table>
              <tr>
                     <td>Customer Id</td>
                     <td><input id='custId' name='custId'></td>
              </tr>
              <tr>
                     <td>Company Name</td>
                     <td><input id='compName' name='compName'></td>
              </tr>
              <tr>
                     <td>Customer Last Name</td>
                     <td><input id='contactName' name='contactName'></td>
              </tr>
       </table>
       <br/>
       <input type="button" id='custSearchBtn' value='Search Customers'>
       </form>
       <div id="searchResults">
       Results will come here
       </div>
       <script type="text/javascript">
              require(["nwdojo/selectcustomer"],function(selCustObj){
              });
       </script>
</body>
</html>

Ensure that you have downloaded dojo library and it is present inside your WebContent folder in eclipse. Also notice that we are using custom module called selectcustomer by using dojo function require. When we pass module name to this function dojo will check for selectcustomer.js in folder nwdojo of root of web application. So we will create following file WebContent/nwdojo/selectcustomer.js

define(["dojo/request/xhr","dojo/dom","dojo/on","dojo/domReady!"],
       function(xhr,dom,on){
             
              function updateCustomer(){
                     dom.byId("custForm").submit();
              }
             
              function callAjaxListing(){
                     alert("Will send ajax call");
                     xhr("customer/list",{method:"post",data:{'custId':'1'},preventCache:true}).then(
                           function(data){
                                  dom.byId("searchResults").innerHTML=data;
                                  on(dom.byId("custEditBtn"),"click",updateCustomer);
                           },
                           function(err){
                                  alert("error"+err);
                           },
                           function(evt){
                           }
                     );
              }
              on(dom.byId("custSearchBtn"),"click",callAjaxListing);
       }
);

In this file note following
We are passing following modules as an element of first argument array of define function. With respect to each module (except last part “dojo/domReady!”) we have respective object (in exact sequence) as function variable which is second argument of function define. i.e. xhr for “dojo/request/xhr”, dom for “dojo/dom” and on for ” dojo/on”.

We are creating two functions
Ø  updateCustomer() which will use module dom to get the html form object identified by Id custForm and submit it.
Ø  callAjaxListing() which will use xhr module to get data from server (in turn we will finally get data from DB) by making AJAX call. On successful retrieval this data will be pasted in div identified by id searchResults and also onClick event of button identified by id custEditBtn will be attached to javascript function updateCustomer. Also note that we have attached this function to button on successful return of AJAX response

Be aware that we are yet to write server side component of both these actions i.e. listing data and submitting form. So when you hit url  http://localhost:8080/NorthWindSpringHibernate/customer/search following is what you will see



Now we will add functions (and appropriate mapping) in controller class to get the listing, show the selected record(s) and update selected customer. So our controller class will have following functions.

/**
 * Will show list of customers from table 'customers'
 * @param cust
 * @param uiModel
 * @return
 */
@RequestMapping("list")
public String listCustomers(@ModelAttribute Customer cust, Model uiModel) {
       System.out.println("Listing customers:" + cust);
       List<Customer> custList = custSvc.getCustomerList();
       uiModel.addAttribute("list", custList);
       return "customerlist";
}

/**
 * Will show selected customers
 * @param selectedIdList
 * @param uiModel
 * @return
 */
@RequestMapping("showselected")
public String showSelectedCustomers(
              @RequestParam(value = "selectedCustId") List<String> selectedIdList,
              Model uiModel) {
       System.out.println("Listing selected:" + selectedIdList);
       Customer cust = custSvc.getCustomerDetails(selectedIdList.get(0));
       uiModel.addAttribute("cust", cust);
       return "customerdetails";
}

/**
 * Will update customer record with value coming from frontend in argument 'model'
 * @param model
 * @param uiModel
 * @return
 */
@RequestMapping("updateCust")
public String updateCustomer(@ModelAttribute Customer model, Model uiModel) {
       System.out.println("Customer will be updated" + model);
       custSvc.updateCustomer(model);
       List<Customer> custList = custSvc.getCustomerList();
       uiModel.addAttribute("list", custList);
       return "customerlist";
}

Interested user can implement Service class and DAO class to list, display and update selected functionalities. Also we will create one jsp for listing search results and one for displaying details screen.

customerlist.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
       <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
       <script src='<c:url value="/dojo/dojo.js"/>' data-dojo-config="async: true"></script>
       <title>Customer List</title>
</head>
<body>
       <form action="showselected" method="post" id="custForm">
       <table>
              <thead>
                     <tr>
                           <th></th>
                           <th>Customer ID</th>
                           <th>Company Name</th>
                           <th>Contact Name</th>
                     </tr>
              </thead>
              <tbody>
                     <c:forEach items="${list}" var="cust" varStatus="loop">
                           <tr  id="record_${loop.index % 2}">
                                  <td><input type="checkbox" value="${cust.custId}" name='selectedCustId' id='selectedCustId'></td>
                                  <td><a href='show/${cust.custId}'>${cust.custId}</a></td>
                                  <td style="color:red">${cust.compName}</td>
                                  <td>${cust.contactName}</td>
                           </tr>
                     </c:forEach>
                     <tr>
                           <td>
                                  <input value="Edit" id="custEditBtn" type="button">
                           </td>
                           <td>
                                  <input value="Delete" id="custDelBtn" type="button">
                           </td>
                     </tr>
              </tbody>
       </table>
       </form>
       <script type="text/javascript">
    require([
        "nwdojo/customerlist"
    ], function(counterObj){
    });
       </script>
</body>
</html>

And

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
       pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

<script src='<c:url value="/dojo/dojo.js"/>'
       data-dojo-config="async: true"></script>
<title>Customer Details</title>
</head>
<body>
       <form action="updateCust" method="post" id='updateCust'>
       <table>
              <tr>
                     <td>Customer Id</td>
                     <td><input value='${cust.custId}' readonly="readonly" name='custId'></td>
              </tr>
              <tr>
                     <td>Company Name</td>
                     <td><input value='${cust.compName}' name='compName'></td>
              </tr>
              <tr>
                     <td>Contact Name</td>
                     <td><input value='${cust.contactName}' name='contactName'></td>
              </tr>
              <tr>
                     <td colspan='2'><input type="button" id='custUpdateBtn'
                           value="Update"></td>
              </tr>
       </table>
       </form>
       <script type="text/javascript">
    require([
        "nwdojo/customerdetails"
    ], function(counterObj){
    });
       </script>
</body>
</html>


Here again we will have new js file that will represent out customerdetails module.

define(["dojo/dom","dojo/on","dojo/domReady!"],
       function(dom,on){
              function updateCustomer(){
                     dom.byId("updateCust").submit();
              }
              on(dom.byId("custUpdateBtn"),"click",updateCustomer);
       }
);


When we click on Search button we will get list of customers as follows (note that here we have restricted size to 10)



When you will select one record and click on Edit button it will take us to next screen to edit the data.




We can update this value and next time one screen we can see updated value in Search Results


Listing after update.



So are done with small application. Interested reader can apply following

Ensure only one record is selected for edit…etc