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
+
+```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/list-todos.jsp
+
+```
+<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
+
+
+
+
+Todo's for ${name}
+
+
+
+
+
+
+ Your todos are
+
+
+ Description |
+ Target Date |
+ Is it Done? |
+ |
+ |
+
+
+
+
+
+ ${todo.desc} |
+ |
+ ${todo.done} |
+ Update |
+ Delete |
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+---
+### src/main/webapp/WEB-INF/jsp/login.jsp
+
+```
+
+
+
+First Web Application
+
+
+
+ ${errorMessage}
+
+
+
+
+```
+---
+### src/main/webapp/WEB-INF/jsp/todo.jsp
+
+```
+<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
+
+
+
+First Web Application
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+---
+### src/main/webapp/WEB-INF/jsp/welcome.jsp
+
+```
+
+
+
+First Web Application
+
+
+
+ Welcome ${name}!! Click here to manage your todo's.
+
+
+
+```
+---
+### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java
+
+```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() {
+ }
+
+}
+```
+---
+### todo.txt
+
+```
+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
+```
+---
diff --git a/02.Spring-Boot-Web-Application/Step20.zip b/02.Spring-Boot-Web-Application/Step20.zip
new file mode 100644
index 0000000..15de171
Binary files /dev/null and b/02.Spring-Boot-Web-Application/Step20.zip differ
diff --git a/02.Spring-Boot-Web-Application/Step21.md b/02.Spring-Boot-Web-Application/Step21.md
new file mode 100644
index 0000000..47f82cc
--- /dev/null
+++ b/02.Spring-Boot-Web-Application/Step21.md
@@ -0,0 +1,702 @@
+## What we will do:
+- Add a navigation bar
+- Use JSP Fragments
+- Exercise : Align the login & welcome pages.
+- Exercise : Highlight the correct menu item.
+
+## Useful Snippets
+```
+
+```
+
+## Files List
+
+### pom.xml
+
+```xml
+
+
+ 4.0.0
+
+ com.in28minutes.springboot.web
+ spring-boot-first-web-application
+ 0.0.1-SNAPSHOT
+ jar
+
+ spring-boot-first-web-application
+ Demo project for Spring Boot
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.4.3.RELEASE
+
+
+
+
+ UTF-8
+ UTF-8
+ 1.8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ javax.servlet
+ jstl
+
+
+
+ org.webjars
+ bootstrap
+ 3.3.6
+
+
+
+ org.webjars
+ bootstrap-datepicker
+ 1.0.1
+
+
+
+ org.webjars
+ jquery
+ 1.9.1
+
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-jasper
+ provided
+
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ runtime
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+```
+---
+### src/main/java/com/in28minutes/springboot/web/controller/LoginController.java
+
+```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
+
+```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
+
+```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
+
+```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
+
+```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 todos = new ArrayList();
+ 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 retrieveTodos(String user) {
+ List filteredTodos = new ArrayList();
+ 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 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
+
+```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
+
+```
+
+
+
+
+
+