redesign owner details page

This commit is contained in:
Dapeng 2016-09-20 14:59:02 +08:00
parent 3510dbfc45
commit 15129c32c3
18 changed files with 162 additions and 499 deletions

View file

@ -15,6 +15,7 @@
*/
package org.springframework.samples.petclinic.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.format.annotation.DateTimeFormat;
@ -42,7 +43,7 @@ public class Visit extends BaseEntity {
*/
@Column(name = "visit_date")
@Temporal(TemporalType.TIMESTAMP)
@DateTimeFormat(pattern = "yyyy/MM/dd")
@JsonFormat(pattern = "yyyy-MM-dd")
private Date date;
/**

View file

@ -1,99 +0,0 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.web;
import java.util.Map;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.samples.petclinic.model.Pet;
import org.springframework.samples.petclinic.model.Visit;
import org.springframework.samples.petclinic.service.ClinicService;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* @author Juergen Hoeller
* @author Ken Krebs
* @author Arjen Poutsma
* @author Michael Isvy
*/
@Controller
public class VisitController {
private final ClinicService clinicService;
@Autowired
public VisitController(ClinicService clinicService) {
this.clinicService = clinicService;
}
@InitBinder
public void setAllowedFields(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("id");
}
/**
* Called before each and every @RequestMapping annotated method.
* 2 goals:
* - Make sure we always have fresh data
* - Since we do not use the session scope, make sure that Pet object always has an id
* (Even though id is not part of the form fields)
* @param petId
* @return Pet
*/
@ModelAttribute("visit")
public Visit loadPetWithVisit(@PathVariable("petId") int petId) {
Pet pet = this.clinicService.findPetById(petId);
Visit visit = new Visit();
pet.addVisit(visit);
return visit;
}
// Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is called
@GetMapping("/owners/*/pets/{petId}/visits/new")
public String initNewVisitForm(@PathVariable("petId") int petId, Map<String, Object> model) {
return "pets/createOrUpdateVisitForm";
}
// Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is called
@PostMapping("/owners/{ownerId}/pets/{petId}/visits/new")
public String processNewVisitForm(@Valid Visit visit, BindingResult result) {
if (result.hasErrors()) {
return "pets/createOrUpdateVisitForm";
} else {
this.clinicService.saveVisit(visit);
return "redirect:/owners/{ownerId}";
}
}
@GetMapping("/owners/*/pets/{petId}/visits")
public String showVisits(@PathVariable int petId, Map<String, Object> model) {
model.put("visits", this.clinicService.findPetById(petId).getVisits());
return "visitList";
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.samples.petclinic.model.Visit;
import org.springframework.samples.petclinic.service.ClinicService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
/**
* @author Juergen Hoeller
* @author Ken Krebs
* @author Arjen Poutsma
* @author Michael Isvy
*/
@RestController
public class VisitResource {
private final ClinicService clinicService;
@Autowired
public VisitResource(ClinicService clinicService) {
this.clinicService = clinicService;
}
@PostMapping("/owners/{ownerId}/pets/{petId}/visits")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void create(
@Valid @RequestBody Visit visit,
@PathVariable("petId") int petId) {
clinicService.findPetById(petId).addVisit(visit);
clinicService.saveVisit(visit);
}
@GetMapping("/owners/{ownerId}/pets/{petId}/visits")
public Object visits(@PathVariable("petId") int petId) {
return clinicService.findPetById(petId).getVisits();
}
}

View file

@ -12,13 +12,13 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular-route.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular-resource.min.js"></script>
<script src="scripts/app/app.js"></script>
<script src="scripts/app/owner-list/owner-list.component.js"></script>
<script src="scripts/app/owner-details/owner-details.component.js"></script>
<script src="scripts/app/owner-form/owner-form.component.js"></script>
<script src="scripts/app/pet-form/pet-form.component.js"></script>
<script src="scripts/app/visits/visits.component.js"></script>
<script src="scripts/app/vet-list/vet-list.component.js"></script>
</head>

View file

@ -2,7 +2,7 @@
/* App Module */
var petClinicApp = angular.module('petClinicApp', [
'ngRoute', 'layoutNav', 'layoutFooter', 'layoutWelcome',
'ownerList', 'ownerDetails', 'ownerForm','petForm', 'vetList']);
'ownerList', 'ownerDetails', 'ownerForm', 'petForm', 'visits', 'vetList']);
petClinicApp.config(['$locationProvider', '$routeProvider', function ($locationProvider, $routeProvider) {
@ -22,26 +22,18 @@ petClinicApp.config(['$locationProvider', '$routeProvider', function ($locationP
template: '<pet-form></pet-form>'
}).when('/owners/:ownerId/pets/:petId', {
template: '<pet-form></pet-form>'
}).when('/owners/:ownerId/pets/:petId/visits', {
template: '<visits></visits>'
}).when('/vets', {
template: '<vet-list></vet-list>'
}).otherwise('/welcome');
}]);
angular.module('layoutWelcome', []);
angular.module("layoutWelcome").component("layoutWelcome", {
templateUrl: "scripts/app/fragments/welcome.html"
});
angular.module('layoutNav', []);
angular.module("layoutNav").component("layoutNav", {
templateUrl: "scripts/app/fragments/nav.html"
});
angular.module('layoutFooter', []);
angular.module("layoutFooter").component("layoutFooter", {
templateUrl: "scripts/app/fragments/footer.html"
['welcome', 'nav', 'footer'].forEach(function(c) {
var mod = 'layout' + c.toUpperCase().substring(0, 1) + c.substring(1);
angular.module(mod, []);
angular.module(mod).component(mod, {
templateUrl: "scripts/app/fragments/" + c + ".html"
});
});

View file

@ -19,7 +19,7 @@
<a href="javascript:void(0)" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Owners <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#!/owners">All</a></li>
<li><a href="#!/new-owner">New</a></li>
<li><a href="#!/new-owner">Register</a></li>
</ul>
</li>

View file

@ -1 +1,3 @@
<p>Welcome to Petclinic </p>
<h1>Welcome to Petclinic</h1>
<img src="resources/images/pets.png" alt="pets logo" />

View file

@ -29,41 +29,22 @@
<h2>Pets and Visits</h2>
<table class="table" style="width:600px;">
<tr ng-repeat="pet in $ctrl.owner.pets">
<td valign="top" style="width: 120px;">
<dl class="dl-horizontal">
<dt>Name</dt>
<dd>{{pet.name}}</dd>
<dt>Birth Date</dt>
<dd>{{pet.birthDate | date:'MM/dd/yyyy'}}</dd>
<dt>Type</dt>
<dd>{{pet.type.name}}</dd>
</dl>
</td>
<td valign="top">
<table class="table-condensed">
<thead>
<tr>
<th>Visit Date</th>
<th>Description</th>
</tr>
</thead>
<tr ng-repeat="visit in pet.visits">
<td>{{visit.date | date:'MM/dd/yyyy'}}</td>
<td>{{visit.description}}</td>
</tr>
<tr>
<td>
<a href="#!/owners/{{$ctrl.owner.id}}/pets/{{pet.id}}">Edit Pet</a>
</td>
<td>
<a href="...">Add Visit</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<div ng-repeat="pet in $ctrl.owner.pets">
<h3>{{pet.name}}</h3>
<p>
{{pet.birthDate | date:'yyyy MMM dd'}} - {{pet.type.name}}
</p>
<h4>Visits</h4>
<p style="margin-left: 2em;" ng-repeat="visit in pet.visits">
<span style="font-style: italic; margin-right: 1em;">{{visit.date | date:'yyyy MMM dd'}}</span>
{{visit.description}}
</p>
<p style="margin-left: 2em;" ng-if="pet.visits.length == 0">
No visit yet
</p>
<hr/>
</div>

View file

@ -1,110 +0,0 @@
'use strict';
function loadOwner($scope, $resource, $stateParams) {
var ownerResource = $resource('/petclinic/owner/' + $stateParams.id);
$scope.owner = ownerResource.get();
}
/*
* Owner Search
*/
angular.module('controllers').controller('ownerSearchController', ['$scope', '$state',
function($scope, $state) {
$scope.ownerSearchForm = {};
// form always needs to be initialised
// otherwise we can't read $scope.ownerSearchForm.lastName
$scope.submitOwnerSearchForm = function() {
var lastNameValue;
$state.go('app.ownerlist', {lastName: $scope.ownerSearchForm.lastName});
}}]);
/*
* Owners List
*/
angular.module('controllers').controller('ownerListController', ['$scope', '$resource', '$stateParams',
function($scope, $resource, $stateParams) {
var destUrl = '/petclinic/owner/list?lastName=';
if(angular.isDefined($stateParams.lastName)) {
destUrl += $stateParams.lastName;
}
var ownerResource = $resource(destUrl);
$scope.ownerList = ownerResource.query();
}]);
/*
* Owners detail (used for both Editable and non-editable pages)
*/
angular.module('controllers').controller('ownerDetailController', ['$scope', '$resource', '$stateParams',
loadOwner
]);
/*
* Form used to create and edit owners
*/
angular.module('controllers').controller('ownerFormController', ['$scope', '$http', '$resource', '$stateParams', '$state',
function($scope, $http, $resource, $stateParams, $state) {
$scope.submitOwnerForm = {};
$scope.submitOwnerForm = function() {
var form = $scope.owner;
// Creating a Javascript object
var data = {
firstName: form.firstName,
lastName: form.lastName,
address: form.address,
city: form.city,
telephone: form.telephone
};
var request;
if ($state.current.name == 'app.owneredit') {
var restUrl = "/petclinic/owner/" + $stateParams.id;
request = $http.put(restUrl, data);
} else { // in case of owner creation
var restUrl = "/petclinic/owner";
request = $http.post(restUrl, data);
}
request.then(function () {
$state.go('app.ownerlist');
}, function (response) {
var error = response.data;
alert(error.error + "\r\n" + error.errors.map(function (e) {
return e.field + ": " + e.defaultMessage;
}).join("\r\n"));
});
}
if ($state.current.name == 'app.owneredit') {
loadOwner($scope, $resource, $stateParams);
}
}]);

View file

@ -1,75 +0,0 @@
<div class="container">
<h2>Owner Information</h2>
<table class="table table-striped" style="width:600px;">
<tr>
<th>Name</th>
<td><b>{{owner.firstName}} {{owner.lastName}}</b></td>
</tr>
<tr>
<th>Address</th>
<td>{{owner.address}}</td>
</tr>
<tr>
<th>City</th>
<td>{{owner.city}}</td>
</tr>
<tr>
<th>Telephone</th>
<td>{{owner.telephone}}</td>
</tr>
<tr>
<td>
<a class="btn btn-info" ui-sref="app.owneredit({id: owner.id})">Edit Owner</a></td>
<td>
<a ui-sref="app.petcreate({ownerid: owner.id})" class="btn btn-success">Add New Pet</a>
</td>
</tr>
</table>
<h2>Pets and Visits</h2>
<table class="table" style="width:600px;">
<tr ng-repeat="pet in owner.pets">
<td valign="top" style="width: 120px;">
<dl class="dl-horizontal">
<dt>Name</dt>
<dd>{{pet.name}}</dd>
<dt>Birth Date</dt>
<dd>{{pet.birthDate | date:'MM/dd/yyyy'}}</dd>
<dt>Type</dt>
<dd>{{pet.type.name}}</dd>
</dl>
</td>
<td valign="top">
<table class="table-condensed">
<thead>
<tr>
<th>Visit Date</th>
<th>Description</th>
</tr>
</thead>
<tr ng-repeat="visit in pet.visits">
<td>{{visit.date | date:'MM/dd/yyyy'}}</td>
<td>{{visit.description}}</td>
</tr>
<tr>
<td>
<a ui-sref="app.petedit( {ownerid: owner.id, petid: pet.id})">Edit Pet</a>
</td>
<td>
<spring:url value="/owners/{ownerId}/pets/{petId}/visits/new" var="visitUrl">
<spring:param name="ownerId" value="${owner.id}"/>
<spring:param name="petId" value="${pet.id}"/>
</spring:url>
<a href="${fn:escapeXml(visitUrl)}">Add Visit</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>

View file

@ -1,45 +0,0 @@
<div class="container">
<h2>Owner</h2>
<form class="form-horizontal" name="ownerForm" data-ng-controller="ownerFormController">
<div class="form-group">
<label class="col-sm-2 control-label">First name</label>
<div class="col-sm-4">
<input class="form-control" ng-model="owner.firstName" name="firstName" required/>
<span ng-show="ownerForm.firstName.$error.required" class="help-inline">First name is required.</span>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Last name</label>
<div class="col-sm-4">
<input class="form-control" ng-model="owner.lastName" name="lastName" required/>
<span ng-show="ownerForm.lastName.$error.required" class="help-inline">Last name is required.</span>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Address</label>
<div class="col-sm-4">
<input class="form-control" ng-model="owner.address" name="address" required/>
<span ng-show="ownerForm.address.$error.required" class="help-inline">Address is required.</span>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">City</label>
<div class="col-sm-4">
<input class="form-control" ng-model="owner.city" name="city" required/>
<span ng-show="ownerForm.city.$error.required" class="help-inline">City is required.</span>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Telephone</label>
<div class="col-sm-4">
<input class="form-control" ng-model="owner.telephone" name="telephone" required/>
<span ng-show="ownerForm.telephone.$error.required" class="help-inline">Telephone is required.</span>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-4">
<button class="btn btn-primary" type="submit" ng-click="submitOwnerForm()">Submit</button>
</div>
</div>
</form>
</div>

View file

@ -1,9 +0,0 @@
<h2>Owners</h2>
<form class="form-inline" ng-controller="ownerSearchController">
<div class="control-group">
<label class="control-label">Last name</label>
<input class="form-control" type="text" ng-model="ownerSearchForm.lastName" size="30" maxlength="80"/>
<button class="btn btn-info" type="submit" ng-click="submitOwnerSearchForm()">Find Owners</button> |
<a class="btn btn-info" ui-sref="app.ownercreate">Add New Owner</a>
</div>
</form>

View file

@ -16,9 +16,10 @@ angular.module("petForm").component("petForm", {
var petId = $routeParams.petId || 0;
if (petId) {
if (petId) { // edit
$http.get("owner/" + ownerId + "/pet/" + petId).then(function(resp) {
self.pet = resp.data;
self.pet.birthDate = new Date(self.pet.birthDate);
self.petTypeId = "" + self.pet.type.id;
});
} else {
@ -26,6 +27,7 @@ angular.module("petForm").component("petForm", {
self.pet = {
owner: resp.data.firstName + " " + resp.data.lastName
};
self.petTypeId = "1";
})
}
@ -54,6 +56,7 @@ angular.module("petForm").component("petForm", {
$location.url("owners/" + ownerId);
}, function (response) {
var error = response.data;
error.errors = error.errors || [];
alert(error.error + "\r\n" + error.errors.map(function (e) {
return e.field + ": " + e.defaultMessage;
}).join("\r\n"));

View file

@ -1,6 +1,6 @@
<h2>Pet</h2>
<form class="form-horizontal">
<form class="form-horizontal" ng-submit="$ctrl.submit()">
<div class="form-group">
<label class="col-sm-2 control-label">Owner</label>
<div class="col-sm-6">
@ -19,7 +19,7 @@
<div class="form-group">
<label class="col-sm-2 control-label">Birth date</label>
<div class="col-sm-6">
<input class="form-control" ng-model="$ctrl.pet.birthDate" value="{{pet.birthDate}}" required type="text"/>
<input class="form-control" ng-model="$ctrl.pet.birthDate" required type="date"/>
<span ng-show="petForm.name.$error.required" class="help-inline"> birth date is required.</span>
</div>
</div>
@ -35,7 +35,7 @@
<div class="form-group">
<div class="col-sm-6 col-sm-offset-2">
<button class="btn btn-primary" type="submit" ng-click="$ctrl.submit()">
<button class="btn btn-primary" type="submit">
Submit
</button>
</div>

View file

@ -1,62 +0,0 @@
'use strict';
function loadPet($scope, $resource, $stateParams) {
var petResource = $resource('/petclinic/owner/' + $stateParams.ownerid +"/pet/" + $stateParams.petid);
$scope.pet = petResource.get();
$scope.types = $resource("/petclinic/petTypes").query();
}
/*
* 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

@ -1,38 +0,0 @@
<div class="container">
<h2>Pet</h2>
<form class="form-horizontal" name="petForm" data-ng-controller="petFormController">
<div class="form-group">
<label class="col-sm-2 control-label">Owner</label>
<div class="col-sm-6">
<p class="form-control-static">{{pet.owner}}</p>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Name </label>
<div class="col-sm-6">
<input class="form-control col-sm-4" ng-model="pet.name" name="name" required type="text"/>
<span ng-show="petForm.name.$error.required" class="help-inline"> Name is required.</span>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Birth date </label>
<div class="col-sm-6">
<input class="form-control" ng-model="pet.birthDate" value="{{pet.birthDate}}" required type="text"/>
<span ng-show="petForm.name.$error.required" class="help-inline"> birth date is required.</span>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Type </label>
<div class="col-sm-6">
<select class="form-control" ng-model="pet.type.id">
<option ng-repeat="x in types" value="{{x.id}}">{{x.name}}</option>
</select>
</div>
</div>
</form>
</div>

View file

@ -0,0 +1,34 @@
'use strict';
angular.module('visits', [
'ngRoute'
]);
angular.module("visits").component("visits", {
templateUrl: "/petclinic/scripts/app/visits/visits.template.html",
controller: ["$http", '$routeParams', '$location', '$filter', function ($http, $routeParams, $location, $filter) {
var self = this;
var petId = $routeParams.petId || 0;
var url = "owners/" + ($routeParams.ownerId || 0) + "/pets/" + petId + "/visits";
self.date = new Date();
self.desc = "";
$http.get(url).then(function(resp) {
self.visits = resp.data;
});
self.submit = function() {
var data = {
date : $filter('date')(self.date, "yyyy-MM-dd"),
description : self.desc
};
console.log(data);
$http.post(url, data).then(function() {
$location.url("owners/" + $routeParams.ownerId);
}, function() {
});
};
}]
});

View file

@ -0,0 +1,27 @@
<h2>Visits</h2>
<form ng-submit="$ctrl.submit()">
<div class="form-group">
<label>Date</label>
<input type="date" class="form-control" ng-model='$ctrl.date'/>
</div>
<div class="form-group">
<label>Description</label>
<textarea class="form-control" ng-model="$ctrl.desc" style="resize:vertical;" required></textarea>
</div>
<div class="form-group">
<button class="btn btn-primary" type="submit">Add New Visit</button>
</div>
</form>
<h3>Previous Visits</h3>
<table class="table table-striped">
<tr ng-repeat="v in $ctrl.visits">
<td class="col-sm-2">{{v.date}}</td>
<td>{{v.description}}</td>
</tr>
</table>