added basic form for Edit Pet

Still need to fix an issue for Joda Time to work together with AngularJS
This commit is contained in:
michaelisvy 2015-07-05 17:52:11 +08:00
parent 3d6685d0ae
commit e40a958a8e
15 changed files with 309 additions and 146 deletions

View file

@ -34,7 +34,6 @@ import org.hibernate.annotations.Type;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.springframework.beans.support.MutableSortDefinition; import org.springframework.beans.support.MutableSortDefinition;
import org.springframework.beans.support.PropertyComparator; import org.springframework.beans.support.PropertyComparator;
import org.springframework.format.annotation.DateTimeFormat;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
@ -51,7 +50,6 @@ public class Pet extends NamedEntity {
@Column(name = "birth_date") @Column(name = "birth_date")
@Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime") @Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
@DateTimeFormat(pattern = "yyyy/MM/dd")
private DateTime birthDate; private DateTime birthDate;
@ManyToOne @ManyToOne

View file

@ -18,7 +18,7 @@ package org.springframework.samples.petclinic.web;
import java.util.Collection; import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.HttpStatus;
import org.springframework.samples.petclinic.model.Owner; import org.springframework.samples.petclinic.model.Owner;
import org.springframework.samples.petclinic.service.ClinicService; import org.springframework.samples.petclinic.service.ClinicService;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
@ -28,6 +28,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
/** /**
@ -56,26 +57,27 @@ public class OwnerResource {
return this.clinicService.findOwnerById(ownerId); return this.clinicService.findOwnerById(ownerId);
} }
@RequestMapping(value = "/owner/{ownerId}", method = RequestMethod.PUT) /**
public Owner updateOwner(@PathVariable("ownerId") int ownerId, @RequestBody Owner ownerRequest) { * Create Owner
Owner ownerModel = retrieveOwner(ownerId); */
// This is done by hand for simplicity purpose. In a real life use-case we should consider using MapStruct. @RequestMapping(value = "/owner", method = RequestMethod.POST)
ownerModel.setFirstName(ownerRequest.getFirstName()); @ResponseStatus(HttpStatus.CREATED)
ownerModel.setLastName(ownerRequest.getLastName()); public void createOwner(@RequestBody Owner owner) {
ownerModel.setCity(ownerRequest.getCity()); this.clinicService.saveOwner(owner);
ownerModel.setAddress(ownerRequest.getAddress());
ownerModel.setTelephone(ownerRequest.getTelephone());
this.clinicService.saveOwner(ownerModel);
return ownerModel;
// TODO: need to handle failure // TODO: need to handle failure
} }
/**
* Read single Owner
*/
@RequestMapping(value = "/owner/{ownerId}", method = RequestMethod.GET) @RequestMapping(value = "/owner/{ownerId}", method = RequestMethod.GET)
public Owner findOwner(@PathVariable("ownerId") int ownerId) { public Owner findOwner(@PathVariable("ownerId") int ownerId) {
return retrieveOwner(ownerId); return retrieveOwner(ownerId);
} }
/**
* Read List of Owners
*/
@RequestMapping(value = "/owner/list", method = RequestMethod.GET) @RequestMapping(value = "/owner/list", method = RequestMethod.GET)
public Collection<Owner> findOwnerCollection(@RequestParam("lastName") String ownerLastName) { public Collection<Owner> findOwnerCollection(@RequestParam("lastName") String ownerLastName) {
@ -92,4 +94,23 @@ public class OwnerResource {
} }
} }
/**
* Update Owner
*/
@RequestMapping(value = "/owner/{ownerId}", method = RequestMethod.PUT)
public Owner updateOwner(@PathVariable("ownerId") int ownerId, @RequestBody Owner ownerRequest) {
Owner ownerModel = retrieveOwner(ownerId);
// This is done by hand for simplicity purpose. In a real life use-case we should consider using MapStruct.
ownerModel.setFirstName(ownerRequest.getFirstName());
ownerModel.setLastName(ownerRequest.getLastName());
ownerModel.setCity(ownerRequest.getCity());
ownerModel.setAddress(ownerRequest.getAddress());
ownerModel.setTelephone(ownerRequest.getTelephone());
this.clinicService.saveOwner(ownerModel);
return ownerModel;
// TODO: need to handle failure
}
} }

View file

@ -23,7 +23,6 @@ import org.springframework.samples.petclinic.model.Owner;
import org.springframework.samples.petclinic.model.Pet; import org.springframework.samples.petclinic.model.Pet;
import org.springframework.samples.petclinic.model.PetType; import org.springframework.samples.petclinic.model.PetType;
import org.springframework.samples.petclinic.service.ClinicService; import org.springframework.samples.petclinic.service.ClinicService;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.InitBinder;
@ -31,7 +30,7 @@ import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.support.SessionStatus; import org.springframework.web.bind.support.SessionStatus;
/** /**
@ -39,15 +38,14 @@ import org.springframework.web.bind.support.SessionStatus;
* @author Ken Krebs * @author Ken Krebs
* @author Arjen Poutsma * @author Arjen Poutsma
*/ */
@Controller @RestController
@SessionAttributes("pet") public class PetResource {
public class PetController {
private final ClinicService clinicService; private final ClinicService clinicService;
@Autowired @Autowired
public PetController(ClinicService clinicService) { public PetResource(ClinicService clinicService) {
this.clinicService = clinicService; this.clinicService = clinicService;
} }
@ -78,15 +76,14 @@ public class PetController {
} else { } else {
this.clinicService.savePet(pet); this.clinicService.savePet(pet);
status.setComplete(); status.setComplete();
return "redirect:/owners/{ownerId}"; return "redirect:/owner/{ownerId}";
} }
} }
@RequestMapping(value = "/owners/*/pets/{petId}/edit", method = RequestMethod.GET) @RequestMapping(value = "/owner/*/pet/{petId}", method = RequestMethod.GET)
public String initUpdateForm(@PathVariable("petId") int petId, Map<String, Object> model) { public Pet findPet(@PathVariable("petId") int petId) {
Pet pet = this.clinicService.findPetById(petId); Pet pet = this.clinicService.findPetById(petId);
model.put("pet", pet); return pet;
return "pets/createOrUpdatePetForm";
} }
@RequestMapping(value = "/owners/{ownerId}/pets/{petId}/edit", method = {RequestMethod.PUT, RequestMethod.POST}) @RequestMapping(value = "/owners/{ownerId}/pets/{petId}/edit", method = {RequestMethod.PUT, RequestMethod.POST})

View file

@ -1,64 +0,0 @@
<!DOCTYPE html>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="petclinic" tagdir="/WEB-INF/tags" %>
<html lang="en">
<jsp:include page="../fragments/staticFiles.jsp"/>
<body>
<script>
$(function () {
$("#birthDate").datepicker({ dateFormat: 'yy/mm/dd'});
});
</script>
<div class="container">
<jsp:include page="../fragments/bodyHeader.jsp"/>
<c:choose>
<c:when test="${pet['new']}">
<c:set var="method" value="post"/>
</c:when>
<c:otherwise>
<c:set var="method" value="put"/>
</c:otherwise>
</c:choose>
<h2>
<c:if test="${pet['new']}">New </c:if>
Pet
</h2>
<form:form modelAttribute="pet" method="${method}"
class="form-horizontal">
<div class="control-group" id="owner">
<label class="control-label">Owner </label>
<c:out value="${pet.owner.firstName} ${pet.owner.lastName}"/>
</div>
<petclinic:inputField label="Name" name="name"/>
<petclinic:inputField label="Birth Date" name="birthDate"/>
<div class="control-group">
<petclinic:selectField name="type" label="Type " names="${types}" size="5"/>
</div>
<div class="form-actions">
<c:choose>
<c:when test="${pet['new']}">
<button type="submit">Add Pet</button>
</c:when>
<c:otherwise>
<button type="submit">Update Pet</button>
</c:otherwise>
</c:choose>
</div>
</form:form>
<c:if test="${!pet['new']}">
</c:if>
<jsp:include page="../fragments/footer.jsp"/>
</div>
</body>
</html>

View file

@ -15,6 +15,7 @@
<script src="scripts/app/main/MainController.js"></script> <script src="scripts/app/main/MainController.js"></script>
<script src="scripts/app/vet/VetController.js"></script> <script src="scripts/app/vet/VetController.js"></script>
<script src="scripts/app/owner/OwnerController.js"></script> <script src="scripts/app/owner/OwnerController.js"></script>
<script src="scripts/app/pet/PetController.js"></script>
</head> </head>
<body class="container"> <body class="container">

View file

@ -29,8 +29,8 @@ petClinicApp.config(['$stateProvider', '$urlRouterProvider', function($stateProv
url: 'owner/search', url: 'owner/search',
views: { views: {
'content@': { 'content@': {
templateUrl: 'scripts/app/owner/ownerSearchForm.html', controller: 'ownerSearchController',
controller: 'ownerSearchController' templateUrl: 'scripts/app/owner/ownerSearchForm.html'
} }
} }
@ -39,8 +39,8 @@ petClinicApp.config(['$stateProvider', '$urlRouterProvider', function($stateProv
url: 'owner/list?lastName', url: 'owner/list?lastName',
views: { views: {
'content@': { 'content@': {
templateUrl: 'scripts/app/owner/ownerList.html', controller: 'ownerListController',
controller: 'ownerListController' templateUrl: 'scripts/app/owner/ownerList.html'
} }
} }
@ -49,8 +49,18 @@ petClinicApp.config(['$stateProvider', '$urlRouterProvider', function($stateProv
url: 'owner/:id', url: 'owner/:id',
views: { views: {
'content@': { 'content@': {
templateUrl: 'scripts/app/owner/ownerDetail.html', controller: 'ownerDetailController',
controller: 'ownerDetailController' templateUrl: 'scripts/app/owner/ownerDetail.html'
}
}
}).
state('app.ownercreate', {
url: 'owner',
views: {
'content@': {
controller: 'ownerFormController',
templateUrl: 'scripts/app/owner/ownerForm.html'
} }
} }
@ -59,8 +69,8 @@ petClinicApp.config(['$stateProvider', '$urlRouterProvider', function($stateProv
url: 'owner/:id/edit', url: 'owner/:id/edit',
views: { views: {
'content@': { 'content@': {
templateUrl: 'scripts/app/owner/ownerForm.html', controller: 'ownerFormController',
controller: 'ownerFormController' templateUrl: 'scripts/app/owner/ownerForm.html'
} }
} }
@ -69,8 +79,28 @@ petClinicApp.config(['$stateProvider', '$urlRouterProvider', function($stateProv
url: 'vets', url: 'vets',
views: { views: {
'content@': { 'content@': {
templateUrl: 'scripts/app/vet/vetList.html', controller: 'vetController',
controller: 'vetController' templateUrl: 'scripts/app/vet/vetList.html'
}
}
}).
state('app.petedit', {
url: 'owner/:ownerid/pet/:petid',
views: {
'content@': {
controller: 'petFormController',
templateUrl: 'scripts/app/pet/petForm.html'
}
}
}).
state('app.petcreate', {
url: 'owner/:ownerid/pet',
views: {
'content@': {
controller: 'petFormController',
templateUrl: 'scripts/app/pet/petForm.html'
} }
} }

View file

@ -1,5 +1,12 @@
'use strict'; 'use strict';
function loadOwner($scope, $resource, $stateParams) {
var ownerResource = $resource('/petclinic/owner/' + $stateParams.id);
$scope.owner = ownerResource.get();
}
/* /*
* Owner Search * Owner Search
*/ */
@ -36,16 +43,13 @@ angular.module('controllers').controller('ownerDetailController', ['$scope', '$r
loadOwner loadOwner
]); ]);
function loadOwner($scope, $resource, $stateParams) {
var ownerResource = $resource('/petclinic/owner/' + $stateParams.id);
$scope.owner = ownerResource.get();
}
/* /*
* Owner Edit Form * Form used to create and edit owners
*/ */
angular.module('controllers').controller('ownerFormController', ['$scope', '$resource', '$http', '$stateParams', '$state', angular.module('controllers').controller('ownerFormController', ['$scope', '$http', '$resource', '$stateParams', '$state',
function($scope, $resource, $http, $stateParams, $state) { function($scope, $http, $resource, $stateParams, $state) {
$scope.submitOwnerForm = {}; $scope.submitOwnerForm = {};
@ -60,12 +64,22 @@ function($scope, $resource, $http, $stateParams, $state) {
city: form.city, city: form.city,
telephone: form.telephone telephone: form.telephone
}; };
if ($state.current.name == 'app.owneredit') {
var restUrl = "/petclinic/owner/" + $stateParams.id; var restUrl = "/petclinic/owner/" + $stateParams.id;
$http.put(restUrl, data); $http.put(restUrl, data);
$state.go('app.ownerlist'); $state.go('app.ownerlist');
} }
else { // in case of owner creation
var restUrl = "/petclinic/owner";
$http.post(restUrl, data);
$state.go('app.ownerlist');
}
}
if ($state.current.name == 'app.owneredit') {
loadOwner($scope, $resource, $stateParams); loadOwner($scope, $resource, $stateParams);
}
}]); }]);

View file

@ -23,10 +23,8 @@
<td> <td>
<a class="btn btn-info" ui-sref="app.owneredit({id: owner.id})">Edit Owner</a></td> <a class="btn btn-info" ui-sref="app.owneredit({id: owner.id})">Edit Owner</a></td>
<td> <td>
<spring:url value="{ownerId}/pets/new.html" var="addUrl"> <a ui-sref="app.petcreate({ownerid: owner.id})" class="btn btn-success">Add New Pet</a>
<spring:param name="ownerId" value="${owner.id}"/> </td>
</spring:url>
<a href="${fn:escapeXml(addUrl)}" class="btn btn-success">Add New Pet</a></td>
</tr> </tr>
</table> </table>
@ -58,11 +56,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<spring:url value="/owners/{ownerId}/pets/{petId}/edit" var="petUrl"> <a ui-sref="app.petedit( {ownerid: owner.id, petid: pet.id})">Edit Pet</a>
<spring:param name="ownerId" value="${owner.id}"/>
<spring:param name="petId" value="${pet.id}"/>
</spring:url>
<a href="${fn:escapeXml(petUrl)}">Edit Pet</a>
</td> </td>
<td> <td>
<spring:url value="/owners/{ownerId}/pets/{petId}/visits/new" var="visitUrl"> <spring:url value="/owners/{ownerId}/pets/{petId}/visits/new" var="visitUrl">

View file

@ -12,4 +12,4 @@
</form> </form>
<br/> <br/>
<a href="/owners/new">Add Owners</a> <a ui-sref="app.ownercreate">Add Owners</a>

View file

@ -0,0 +1,64 @@
'use strict';
function loadPet($scope, $resource, $stateParams) {
var petResource = $resource('/petclinic/owner/' + $stateParams.ownerid +"/pet/" +$stateParams.petid);
$scope.pet = petResource.get();
}
/*
* Form used to create and edit pets
*/
angular.module('controllers').controller('petFormController', ['$scope', '$http', '$resource', '$stateParams', '$state',
function($scope, $http, $resource, $stateParams, $state) {
$scope.submitPetForm = {};
$scope.submitPetForm = function() {
var form = $scope.pet;
// Creating a Javascript object
var data = {
name: form.name,
birthDate: form.birthDate,
type: form.type
};
if ($state.current.name == 'app.petedit') {
var restUrl = "/petclinic/owner/" + $stateParams.ownerid +"/pet/" +$stateParams.petid;
$http.put(restUrl, data);
$state.go('app.ownerdetail');
}
else { // in case of pet creation
var restUrl = "/petclinic/owner/" + $stateParams.ownerid +"/pet";
$http.post(restUrl, data);
$state.go('app.ownerdetail');
}
}
if ($state.current.name == 'app.petedit') {
loadPet($scope, $resource, $stateParams);
}
}]);

View file

@ -0,0 +1,44 @@
<!DOCTYPE html>
<script>
$(function () {
$("#birthDate").datepicker({ dateFormat: 'yy/mm/dd'});
});
</script>
<div class="container">
<h2>Pet (work in progress) </h2>
<form class="form-horizontal" name="petForm" data-ng-controller="petFormController">
<fieldset>
<div class="control-group" id="owner">
<label class="control-label"> Owner </label>
{{pet.owner.firstName}} {{pet.owner.lastName}}
</div>
<div class="control-group" id="name">
<label class="control-label">Name </label>
<input ng-model="pet.name" name="name" required/>
<span ng-show="petForm.name.$error.required" class="help-inline"> Name is required.</span>
</div>
<div class="control-group" id="birthDate">
<label class="control-label">Birth date </label>
<input ng-model="pet.birthDate" value="{{pet.birthDate | date:'MM/dd/yyyy'}} name="birthDate" required/>
<span ng-show="petForm.name.$error.required" class="help-inline"> birth date is required.</span>
</div>
<div class="control-group">
<petclinic:selectField name="type" label="Type " names="${types}" size="5"/>
</div>
<div class="form-actions">
<c:choose>
<c:when test="${pet['new']}">
<button type="submit">Add Pet</button>
</c:when>
<c:otherwise>
<button type="submit">Update Pet</button>
</c:otherwise>
</c:choose>
</div>
</fieldset>
</form>
</div>

View file

@ -0,0 +1,28 @@
package org.springframework.samples.petclinic.web;
import org.junit.runner.RunWith;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring/business-config.xml", "classpath:spring/tools-config.xml", "classpath:spring/mvc-core-config.xml"})
@WebAppConfiguration
@ActiveProfiles("spring-data-jpa")
public abstract class AbstractWebResourceTests {
protected MockMvc mockMvc;
public void runMockSpringMVC(Object resource) {
this.mockMvc = MockMvcBuilders.standaloneSetup(resource).build();
}
public AbstractWebResourceTests() {
super();
}
}

View file

@ -0,0 +1,53 @@
package org.springframework.samples.petclinic.web;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.ResultActions;
/**
* Test class for the UserResource REST controller.
*
* @see UserResource
*/
public class PetResourceTests extends AbstractWebResourceTests {
@Autowired
private PetResource petResource;
@Before
public void setup() {
runMockSpringMVC(petResource);
}
/**
* Expected JSon result:
* {
"id":2,
"name":"Basil",
"birthDate":1344211200000,
"type":{
"id":6,
"name":"hamster",
"new":false
},
"visits":[],
"new":false
}
*/
@Test
public void shouldGetAPetInJSonFormat() throws Exception {
ResultActions actions = mockMvc.perform(get("/owner/2/pet/2.json").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
actions.andExpect(content().contentType("application/json;charset=UTF-8"))
.andExpect(jsonPath("$.id").value(2))
.andExpect(jsonPath("$.name").value("Basil"))
.andExpect(jsonPath("$.type.id").value(6));
}
}

View file

@ -7,44 +7,27 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
/** /**
* Test class for the UserResource REST controller. * Test class for the UserResource REST controller.
* *
* @see UserResource * @see UserResource
*/ */
@RunWith(SpringJUnit4ClassRunner.class) public class VetResourceTests extends AbstractWebResourceTests {
@ContextConfiguration({"classpath:spring/business-config.xml", "classpath:spring/tools-config.xml", "classpath:spring/mvc-core-config.xml"})
@WebAppConfiguration
@ActiveProfiles("spring-data-jpa")
public class VetControllerTests {
@Autowired @Autowired
private VetResource vetResource; private VetResource vetResource;
@Autowired
private WebApplicationContext ctx;
private MockMvc mockMvc;
@Before @Before
public void setup() { public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(vetResource).build(); runMockSpringMVC(vetResource);
} }
@Test @Test
public void testGetExistingUser() throws Exception { public void shouldGetAListOfVetsInJSonFormat() throws Exception {
ResultActions actions = mockMvc.perform(get("/vets.json").accept(MediaType.APPLICATION_JSON)) ResultActions actions = mockMvc.perform(get("/vets.json").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()); .andExpect(status().isOk());
actions.andExpect(content().contentType("application/json")) actions.andExpect(content().contentType("application/json"))