Add a navigation bar
Use JSP Fragments
Exercise : Align the login & welcome pages.
Exercise : Highlight the correct menu item.
<nav role="navigation" class="navbar navbar-default">
<div class="">
<a href="http://www.in28minutes.com" class="navbar-brand">in28Minutes</a>
</div>
<div class="navbar-collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="/login">Home</a></li>
<li><a href="/list-todos">Todos</a></li>
</ul>
</div>
</nav>
<?xml version =" 1.0" encoding =" UTF-8" ?>
<project xmlns =" http://maven.apache.org/POM/4.0.0" xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=" http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" >
<modelVersion >4.0.0</modelVersion >
<groupId >com.in28minutes.springboot.web</groupId >
<artifactId >spring-boot-first-web-application</artifactId >
<version >0.0.1-SNAPSHOT</version >
<packaging >jar</packaging >
<name >spring-boot-first-web-application</name >
<description >Demo project for Spring Boot</description >
<parent >
<groupId >org.springframework.boot</groupId >
<artifactId >spring-boot-starter-parent</artifactId >
<version >1.4.3.RELEASE</version >
<relativePath /> <!-- lookup parent from repository -->
</parent >
<properties >
<project .build.sourceEncoding>UTF-8</project .build.sourceEncoding>
<project .reporting.outputEncoding>UTF-8</project .reporting.outputEncoding>
<java .version>1.8</java .version>
</properties >
<dependencies >
<dependency >
<groupId >org.springframework.boot</groupId >
<artifactId >spring-boot-starter-web</artifactId >
</dependency >
<dependency >
<groupId >javax.servlet</groupId >
<artifactId >jstl</artifactId >
</dependency >
<dependency >
<groupId >org.webjars</groupId >
<artifactId >bootstrap</artifactId >
<version >3.3.6</version >
</dependency >
<dependency >
<groupId >org.webjars</groupId >
<artifactId >bootstrap-datepicker</artifactId >
<version >1.0.1</version >
</dependency >
<dependency >
<groupId >org.webjars</groupId >
<artifactId >jquery</artifactId >
<version >1.9.1</version >
</dependency >
<dependency >
<groupId >org.apache.tomcat.embed</groupId >
<artifactId >tomcat-embed-jasper</artifactId >
<scope >provided</scope >
</dependency >
<dependency >
<groupId >org.springframework.boot</groupId >
<artifactId >spring-boot-devtools</artifactId >
<scope >runtime</scope >
</dependency >
<dependency >
<groupId >org.springframework.boot</groupId >
<artifactId >spring-boot-starter-test</artifactId >
<scope >test</scope >
</dependency >
</dependencies >
<build >
<plugins >
<plugin >
<groupId >org.springframework.boot</groupId >
<artifactId >spring-boot-maven-plugin</artifactId >
</plugin >
</plugins >
</build >
</project >
src/main/java/com/in28minutes/springboot/web/controller/LoginController.java
package com .in28minutes .springboot .web .controller ;
import org .springframework .beans .factory .annotation .Autowired ;
import org .springframework .stereotype .Controller ;
import org .springframework .ui .ModelMap ;
import org .springframework .web .bind .annotation .RequestMapping ;
import org .springframework .web .bind .annotation .RequestMethod ;
import org .springframework .web .bind .annotation .RequestParam ;
import org .springframework .web .bind .annotation .SessionAttributes ;
import com .in28minutes .springboot .web .service .LoginService ;
@ Controller
@ SessionAttributes ("name" )
public class LoginController {
@ Autowired
LoginService service ;
@ RequestMapping (value ="/login" , method = RequestMethod .GET )
public String showLoginPage (ModelMap model ){
return "login" ;
}
@ RequestMapping (value ="/login" , method = RequestMethod .POST )
public String showWelcomePage (ModelMap model , @ RequestParam String name , @ RequestParam String password ){
boolean isValidUser = service .validateUser (name , password );
if (!isValidUser ) {
model .put ("errorMessage" , "Invalid Credentials" );
return "login" ;
}
model .put ("name" , name );
model .put ("password" , password );
return "welcome" ;
}
}
src/main/java/com/in28minutes/springboot/web/controller/TodoController.java
package com .in28minutes .springboot .web .controller ;
import java .text .SimpleDateFormat ;
import java .util .Date ;
import javax .validation .Valid ;
import org .springframework .beans .factory .annotation .Autowired ;
import org .springframework .beans .propertyeditors .CustomDateEditor ;
import org .springframework .stereotype .Controller ;
import org .springframework .ui .ModelMap ;
import org .springframework .validation .BindingResult ;
import org .springframework .web .bind .WebDataBinder ;
import org .springframework .web .bind .annotation .InitBinder ;
import org .springframework .web .bind .annotation .RequestMapping ;
import org .springframework .web .bind .annotation .RequestMethod ;
import org .springframework .web .bind .annotation .RequestParam ;
import org .springframework .web .bind .annotation .SessionAttributes ;
import com .in28minutes .springboot .web .model .Todo ;
import com .in28minutes .springboot .web .service .LoginService ;
import com .in28minutes .springboot .web .service .TodoService ;
@ Controller
@ SessionAttributes ("name" )
public class TodoController {
@ Autowired
TodoService service ;
@ InitBinder
public void initBinder (WebDataBinder binder ) {
// Date - dd/MM/yyyy
SimpleDateFormat dateFormat = new SimpleDateFormat ("dd/MM/yyyy" );
binder .registerCustomEditor (Date .class , new CustomDateEditor (
dateFormat , false ));
}
@ RequestMapping (value = "/list-todos" , method = RequestMethod .GET )
public String showTodos (ModelMap model ) {
String name = (String ) model .get ("name" );
model .put ("todos" , service .retrieveTodos (name ));
return "list-todos" ;
}
@ RequestMapping (value = "/add-todo" , method = RequestMethod .GET )
public String showAddTodoPage (ModelMap model ) {
model .addAttribute ("todo" , new Todo (0 , (String ) model .get ("name" ),
"Default Desc" , new Date (), false ));
return "todo" ;
}
@ RequestMapping (value = "/delete-todo" , method = RequestMethod .GET )
public String deleteTodo (@ RequestParam int id ) {
service .deleteTodo (id );
return "redirect:/list-todos" ;
}
@ RequestMapping (value = "/update-todo" , method = RequestMethod .GET )
public String showUpdateTodoPage (@ RequestParam int id , ModelMap model ) {
Todo todo = service .retrieveTodo (id );
model .put ("todo" , todo );
return "todo" ;
}
@ RequestMapping (value = "/update-todo" , method = RequestMethod .POST )
public String updateTodo (ModelMap model , @ Valid Todo todo ,
BindingResult result ) {
if (result .hasErrors ()) {
return "todo" ;
}
todo .setUser ((String ) model .get ("name" ));
service .updateTodo (todo );
return "redirect:/list-todos" ;
}
@ RequestMapping (value = "/add-todo" , method = RequestMethod .POST )
public String addTodo (ModelMap model , @ Valid Todo todo , BindingResult result ) {
if (result .hasErrors ()) {
return "todo" ;
}
service .addTodo ((String ) model .get ("name" ), todo .getDesc (), todo .getTargetDate (),
false );
return "redirect:/list-todos" ;
}
}
src/main/java/com/in28minutes/springboot/web/model/Todo.java
package com .in28minutes .springboot .web .model ;
import java .util .Date ;
import javax .validation .constraints .Size ;
public class Todo {
private int id ;
private String user ;
@ Size (min =10 , message ="Enter at least 10 Characters..." )
private String desc ;
private Date targetDate ;
private boolean isDone ;
public Todo () {
super ();
}
public Todo (int id , String user , String desc , Date targetDate ,
boolean isDone ) {
super ();
this .id = id ;
this .user = user ;
this .desc = desc ;
this .targetDate = targetDate ;
this .isDone = isDone ;
}
public int getId () {
return id ;
}
public void setId (int id ) {
this .id = id ;
}
public String getUser () {
return user ;
}
public void setUser (String user ) {
this .user = user ;
}
public String getDesc () {
return desc ;
}
public void setDesc (String desc ) {
this .desc = desc ;
}
public Date getTargetDate () {
return targetDate ;
}
public void setTargetDate (Date targetDate ) {
this .targetDate = targetDate ;
}
public boolean isDone () {
return isDone ;
}
public void setDone (boolean isDone ) {
this .isDone = isDone ;
}
@ Override
public int hashCode () {
final int prime = 31 ;
int result = 1 ;
result = prime * result + id ;
return result ;
}
@ Override
public boolean equals (Object obj ) {
if (this == obj ) {
return true ;
}
if (obj == null ) {
return false ;
}
if (getClass () != obj .getClass ()) {
return false ;
}
Todo other = (Todo ) obj ;
if (id != other .id ) {
return false ;
}
return true ;
}
@ Override
public String toString () {
return String .format (
"Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]" , id ,
user , desc , targetDate , isDone );
}
}
src/main/java/com/in28minutes/springboot/web/service/LoginService.java
package com .in28minutes .springboot .web .service ;
import org .springframework .stereotype .Component ;
import org .springframework .stereotype .Service ;
@ Service
public class LoginService {
public boolean validateUser (String userid , String password ) {
// in28minutes, dummy
return userid .equalsIgnoreCase ("in28minutes" )
&& password .equalsIgnoreCase ("dummy" );
}
}
src/main/java/com/in28minutes/springboot/web/service/TodoService.java
package com .in28minutes .springboot .web .service ;
import java .util .ArrayList ;
import java .util .Date ;
import java .util .Iterator ;
import java .util .List ;
import org .springframework .stereotype .Service ;
import com .in28minutes .springboot .web .model .Todo ;
@ Service
public class TodoService {
private static List <Todo > todos = new ArrayList <Todo >();
private static int todoCount = 3 ;
static {
todos .add (new Todo (1 , "in28Minutes" , "Learn Spring MVC" , new Date (),
false ));
todos .add (new Todo (2 , "in28Minutes" , "Learn Struts" , new Date (), false ));
todos .add (new Todo (3 , "in28Minutes" , "Learn Hibernate" , new Date (),
false ));
}
public List <Todo > retrieveTodos (String user ) {
List <Todo > filteredTodos = new ArrayList <Todo >();
for (Todo todo : todos ) {
if (todo .getUser ().equalsIgnoreCase (user )) {
filteredTodos .add (todo );
}
}
return filteredTodos ;
}
public Todo retrieveTodo (int id ) {
for (Todo todo : todos ) {
if (todo .getId ()==id ) {
return todo ;
}
}
return null ;
}
public void updateTodo (Todo todo ){
todos .remove (todo );
todos .add (todo );
}
public void addTodo (String name , String desc , Date targetDate ,
boolean isDone ) {
todos .add (new Todo (++todoCount , name , desc , targetDate , isDone ));
}
public void deleteTodo (int id ) {
Iterator <Todo > iterator = todos .iterator ();
while (iterator .hasNext ()) {
Todo todo = iterator .next ();
if (todo .getId () == id ) {
iterator .remove ();
}
}
}
}
src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java
package com .in28minutes .springboot .web ;
import org .springframework .boot .SpringApplication ;
import org .springframework .boot .autoconfigure .SpringBootApplication ;
import org .springframework .context .annotation .ComponentScan ;
@ SpringBootApplication
@ ComponentScan ("com.in28minutes.springboot.web" )
public class SpringBootFirstWebApplication {
public static void main (String [] args ) {
SpringApplication .run (SpringBootFirstWebApplication .class , args );
}
}
src/main/resources/application.properties
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
logging.level.org.springframework.web=INFO
src/main/webapp/WEB-INF/jsp/common/footer.jspf
<script src="webjars/jquery/1.9.1/jquery.min.js"></script>
<script src="webjars/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script
src="webjars/bootstrap-datepicker/1.0.1/js/bootstrap-datepicker.js"></script>
<script>
$('#targetDate').datepicker({
format : 'dd/mm/yyyy'
});
</script>
</body>
</html>
src/main/webapp/WEB-INF/jsp/common/header.jspf
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<html>
<head>
<title>First Web Application</title>
<link href="webjars/bootstrap/3.3.6/css/bootstrap.min.css"
rel="stylesheet">
</head>
<body>
src/main/webapp/WEB-INF/jsp/common/navigation.jspf
<nav role="navigation" class="navbar navbar-default">
<div class="">
<a href="http://www.in28minutes.com" class="navbar-brand">in28Minutes</a>
</div>
<div class="navbar-collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="/login">Home</a></li>
<li><a href="/list-todos">Todos</a></li>
</ul>
</div>
</nav>
src/main/webapp/WEB-INF/jsp/list-todos.jsp
<%@ include file="common/header.jspf" %>
<%@ include file="common/navigation.jspf" %>
<div class="container">
<table class="table table-striped">
<caption>Your todos are</caption>
<thead>
<tr>
<th>Description</th>
<th>Target Date</th>
<th>Is it Done?</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<c:forEach items="${todos}" var="todo">
<tr>
<td>${todo.desc}</td>
<td><fmt:formatDate value="${todo.targetDate}" pattern="dd/MM/yyyy"/></td>
<td>${todo.done}</td>
<td><a type="button" class="btn btn-success"
href="/update-todo?id=${todo.id}">Update</a></td>
<td><a type="button" class="btn btn-warning"
href="/delete-todo?id=${todo.id}">Delete</a></td>
</tr>
</c:forEach>
</tbody>
</table>
<div>
<a class="button" href="/add-todo">Add a Todo</a>
</div>
</div>
<%@ include file="common/footer.jspf" %>
src/main/webapp/WEB-INF/jsp/login.jsp
<%@ include file="common/header.jspf" %>
<%@ include file="common/navigation.jspf" %>
<div class="container">
<font color="red">${errorMessage}</font>
<form method="post">
Name : <input type="text" name="name" />
Password : <input type="password" name="password" />
<input type="submit" />
</form>
</div>
<%@ include file="common/footer.jspf" %>
src/main/webapp/WEB-INF/jsp/todo.jsp
<%@ include file="common/header.jspf" %>
<%@ include file="common/navigation.jspf" %>
<div class="container">
<form:form method="post" commandName="todo">
<form:hidden path="id" />
<fieldset class="form-group">
<form:label path="desc">Description</form:label>
<form:input path="desc" type="text" class="form-control"
required="required" />
<form:errors path="desc" cssClass="text-warning" />
</fieldset>
<fieldset class="form-group">
<form:label path="targetDate">Target Date</form:label>
<form:input path="targetDate" type="text" class="form-control"
required="required" />
<form:errors path="targetDate" cssClass="text-warning" />
</fieldset>
<button type="submit" class="btn btn-success">Add</button>
</form:form>
</div>
<%@ include file="common/footer.jspf" %>
src/main/webapp/WEB-INF/jsp/welcome.jsp
<%@ include file="common/header.jspf"%>
<%@ include file="common/navigation.jspf"%>
<div class="container">
Welcome ${name}!! <a href="/list-todos">Click here</a> to manage your
todo's.
</div>
<%@ include file="common/footer.jspf"%>
src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java
package com .in28minutes .springboot .web ;
import org .junit .Test ;
import org .junit .runner .RunWith ;
import org .springframework .boot .test .context .SpringBootTest ;
import org .springframework .test .context .junit4 .SpringRunner ;
@ RunWith (SpringRunner .class )
@ SpringBootTest
public class SpringBootFirstWebApplicationTests {
@ Test
public void contextLoads () {
}
}
Implementing Server Side Validation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Command Bean or Form Backing Bean
Add Validation
Use Validation on Controller
Display Errors in View
Command Bean
~~~~~~~~~~~~
Controller
View - Spring Form Tags
LoginController -> adds name to model
welcome.jsp -> shows ${name}
TodoController -> redirects to list-todos.jsp
${name} is empty
Component, Service, Repository, Controller
Autowired
ComponentScan
Field dummyService in com.in28minutes.springboot.web.controller.LoginController
required a bean of type 'com.in28minutes.dummy.DummyService'
that could not be found.
Spring Boot Starter Parent
Spring Boot Starter Web
@SpringBootApplication
Auto Configuration
Dispatcher Servlet
/login => "login"
"login" => src/main/webapp/WEB-INF/jsp/login.jsp
Search for a view named "login"
/login => LoginController