javascript
带有Angular JS的Java EE 7 – CRUD,REST,验证–第2部分
這是Angular JS承諾的Java EE 7的后續(xù)版本–第1部分 。 花了比我預(yù)期更長的時間(找到時間來準(zhǔn)備代碼和博客文章),但是終于到了!
應(yīng)用程序
第1部分中的原始應(yīng)用程序只是帶有分頁的簡單列表,以及提供列表數(shù)據(jù)的REST服務(wù)。
在本文中,我們將添加CRUD(創(chuàng)建,讀取,更新,刪除)功能,綁定REST服務(wù)以在服務(wù)器端執(zhí)行這些操作并驗證數(shù)據(jù)。
設(shè)置
該設(shè)置與第1部分中的設(shè)置相同,但是這里是供參考的列表:
- Java EE 7
- 角JS
- ng-grid
- UI引導(dǎo)程序
- 野蠅
編碼
后端– Java EE 7
后端不需要很多更改。 由于我們希望能夠創(chuàng)建,讀取,更新和刪除,因此我們需要在REST服務(wù)中添加適當(dāng)?shù)姆椒▉韴?zhí)行以下操作:
人員資源
package com.cortez.samples.javaee7angular.rest;import com.cortez.samples.javaee7angular.data.Person; import com.cortez.samples.javaee7angular.pagination.PaginatedListWrapper;import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import javax.ws.rs.*; import javax.ws.rs.core.Application; import javax.ws.rs.core.MediaType; import java.util.List;@Stateless @ApplicationPath("/resources") @Path("persons") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public class PersonResource extends Application {@PersistenceContextprivate EntityManager entityManager;private Integer countPersons() {Query query = entityManager.createQuery("SELECT COUNT(p.id) FROM Person p");return ((Long) query.getSingleResult()).intValue();}@SuppressWarnings("unchecked")private List<Person> findPersons(int startPosition, int maxResults, String sortFields, String sortDirections) {Query query = entityManager.createQuery("SELECT p FROM Person p ORDER BY " + sortFields + " " + sortDirections);query.setFirstResult(startPosition);query.setMaxResults(maxResults);return query.getResultList();}private PaginatedListWrapper<Person> findPersons(PaginatedListWrapper<Person> wrapper) {wrapper.setTotalResults(countPersons());int start = (wrapper.getCurrentPage() - 1) * wrapper.getPageSize();wrapper.setList(findPersons(start,wrapper.getPageSize(),wrapper.getSortFields(),wrapper.getSortDirections()));return wrapper;}@GETpublic PaginatedListWrapper<Person> listPersons(@DefaultValue("1")@QueryParam("page")Integer page,@DefaultValue("id")@QueryParam("sortFields")String sortFields,@DefaultValue("asc")@QueryParam("sortDirections")String sortDirections) {PaginatedListWrapper<Person> paginatedListWrapper = new PaginatedListWrapper<>();paginatedListWrapper.setCurrentPage(page);paginatedListWrapper.setSortFields(sortFields);paginatedListWrapper.setSortDirections(sortDirections);paginatedListWrapper.setPageSize(10);return findPersons(paginatedListWrapper);}@GET@Path("{id}")public Person getPerson( @PathParam("id") Long id) {return entityManager.find(Person.class, id);}@POSTpublic Person savePerson(Person person) {if (person.getId() == null) {Person personToSave = new Person();personToSave.setName(person.getName());personToSave.setDescription(person.getDescription());personToSave.setImageUrl(person.getImageUrl());entityManager.persist(person);} else {Person personToUpdate = getPerson(person.getId());personToUpdate.setName(person.getName());personToUpdate.setDescription(person.getDescription());personToUpdate.setImageUrl(person.getImageUrl());person = entityManager.merge(personToUpdate);}return person;}@DELETE@Path("{id}")public void deletePerson(@PathParam("id") Long id) {entityManager.remove(getPerson(id));} }該代碼與普通的Java POJO完全相同,但是使用Java EE批注來增強(qiáng)行為。 @ApplicationPath("/resources")和@Path("persons")將在URL yourdomain/resources/persons ( yourdomain將是運(yùn)行應(yīng)用程序的主機(jī)@Path("persons")公開REST服務(wù)。 @Consumes(MediaType.APPLICATION_JSON)和@Produces(MediaType.APPLICATION_JSON)接受REST請求和響應(yīng)并將其格式化為JSON。
對于REST操作:
| @GET / GET | listPersons | http:// yourdomain / resources / persons | 返回10個人的分頁列表。 |
| @GET / GET | getPerson | http:// yourdomain / resources / persons / {id} | 通過其ID返回一個Person實體。 |
| @POST / POST | savePerson | http:// yourdomain / resources / persons | 創(chuàng)建或更新人員。 |
| @DELETE / DELETE | deletePerson | http:// yourdomain / resources / persons / {id} | 通過其ID刪除一個Person實體。 |
每個操作調(diào)用的url非常相似。 提交請求時,HTTP方法本身定義了區(qū)分需要調(diào)用哪個操作的魔術(shù)。 檢查HTTP方法定義 。
對于getPerson和deletePerson請注意,我們添加了注釋@Path("{id}") ,該注釋定義了調(diào)用服務(wù)的可選路徑。 由于我們需要知道要獲取或刪除的對象,因此需要以某種方式指示該id 。 這是在要調(diào)用的服務(wù)網(wǎng)址中完成的,因此,如果要刪除ID為1的Person,我們將使用HTTP方法DELETE調(diào)用http://yourdomain/resources/persons/1 。
后端內(nèi)容就是這樣。 僅30行代碼添加到了舊的REST服務(wù)。 我還向Person對象添加了一個新屬性,以保留指向圖像的鏈接,目的是顯示此人的化身。
UI – Angular JS
對于UI部分,我決定將其分為3個部分:網(wǎng)格,表單和反饋消息部分,每個部分都有自己的Angular控制器。 網(wǎng)格與第1部分中的網(wǎng)格基本相同,但確實需要對新內(nèi)容進(jìn)行一些調(diào)整:
網(wǎng)格HTML
<!-- Specify a Angular controller script that binds Javascript variables to the grid.--> <div class="grid" ng-controller="personsListController"><div><h3>List Persons</h3></div><!-- Binds the grid component to be displayed. --><div class="gridStyle" ng-grid="gridOptions"></div><!-- Bind the pagination component to be displayed. --><pagination direction-links="true" boundary-links="true"total-items="persons.totalResults" items-per-page="persons.pageSize"ng-model="persons.currentPage" ng-change="refreshGrid()"></pagination> </div>這里沒什么特別的。 與第1部分幾乎相同。
網(wǎng)格角控制器
app.controller('personsListController', function ($scope, $rootScope, personService) {// Initialize required information: sorting, the first page to show and the grid options.$scope.sortInfo = {fields: ['id'], directions: ['asc']};$scope.persons = {currentPage: 1};$scope.gridOptions = {data: 'persons.list',useExternalSorting: true,sortInfo: $scope.sortInfo,columnDefs: [{ field: 'id', displayName: 'Id' },{ field: 'name', displayName: 'Name' },{ field: 'description', displayName: 'Description' },{ field: '', width: 30, cellTemplate: '<span class="glyphicon glyphicon-remove remove" ng-click="deleteRow(row)"></span>' }],multiSelect: false,selectedItems: [],// Broadcasts an event when a row is selected, to signal the form that it needs to load the row data.afterSelectionChange: function (rowItem) {if (rowItem.selected) {$rootScope.$broadcast('personSelected', $scope.gridOptions.selectedItems[0].id);}}};// Refresh the grid, calling the appropriate rest method.$scope.refreshGrid = function () {var listPersonsArgs = {page: $scope.persons.currentPage,sortFields: $scope.sortInfo.fields[0],sortDirections: $scope.sortInfo.directions[0]};personService.get(listPersonsArgs, function (data) {$scope.persons = data;})};// Broadcast an event when an element in the grid is deleted. No real deletion is perfomed at this point.$scope.deleteRow = function (row) {$rootScope.$broadcast('deletePerson', row.entity.id);};// Watch the sortInfo variable. If changes are detected than we need to refresh the grid.// This also works for the first page access, since we assign the initial sorting in the initialize section.$scope.$watch('sortInfo.fields[0]', function () {$scope.refreshGrid();}, true);// Do something when the grid is sorted.// The grid throws the ngGridEventSorted that gets picked up here and assigns the sortInfo to the scope.// This will allow to watch the sortInfo in the scope for changed and refresh the grid.$scope.$on('ngGridEventSorted', function (event, sortInfo) {$scope.sortInfo = sortInfo;});// Picks the event broadcasted when a person is saved or deleted to refresh the grid elements with the most// updated information.$scope.$on('refreshGrid', function () {$scope.refreshGrid();});// Picks the event broadcasted when the form is cleared to also clear the grid selection.$scope.$on('clear', function () {$scope.gridOptions.selectAll(false);}); });還需要一些其他屬性來配置網(wǎng)格的行為。 重要的data: 'persons.list'是data: 'persons.list' ,它將網(wǎng)格數(shù)據(jù)綁定到Angular模型值$scope.persons , columnDefs允許我們根據(jù)需要對網(wǎng)格進(jìn)行建模。 由于我想添加一個選項來刪除每一行,因此我需要添加一個新單元格,當(dāng)您單擊十字圖標(biāo)時,該單元格將調(diào)用函數(shù)deleteRow 。 需要afterSelectionChanges函數(shù)來與網(wǎng)格中選定的人更新表單數(shù)據(jù)。 您可以在此處檢查其他網(wǎng)格選項。
其余代碼是不言自明的,其中也有一些注釋。 關(guān)于$rootScope.$broadcast特別說明:用于將事件調(diào)度到所有其他控制器。 這是控制器之間進(jìn)行通信的一種方式,因為網(wǎng)格,表單和反饋消息具有單獨的控制器。 如果所有內(nèi)容都只在一個控制器中,則不需要這樣做,只需一個簡單的函數(shù)調(diào)用就足夠了。 如果我們要保留多個控制器,另一種可能的解決方案是使用Angular服務(wù)。 所使用的方法看起來更加簡潔,因為它可以將應(yīng)用程序問題分開,并且不需要您實現(xiàn)其他Angular服務(wù),但是如果需要的話,調(diào)試起來可能會有些困難。
表格HTML
<div class="form" ng-controller="personsFormController"><!-- Verify person, if there is no id present, that we are Adding a Person --><div ng-if="person.id == null"><h3>Add Person</h3></div><!-- Otherwise it's an Edit --><div ng-if="person.id != null"><h3>Edit Person</h3></div><div><!-- Specify the function to be called on submit and disable HTML5 validation, since we're using Angular validation--><form name="personForm" ng-submit="updatePerson()" novalidate><!-- Display an error if the input is invalid and is dirty (only when someone changes the value) --><div class="form-group" ng-class="{'has-error' : personForm.name.$invalid && personForm.name.$dirty}"><label for="name">Name:</label><!-- Display a check when the field is valid and was modified --><span ng-class="{'glyphicon glyphicon-ok' : personForm.name.$valid && personForm.name.$dirty}"></span><input id="name" name="name" type="text" class="form-control" maxlength="50"ng-model="person.name"required ng-minlength="2" ng-maxlength="50"/><!-- Validation messages to be displayed on required, minlength and maxlength --><p class="help-block" ng-show="personForm.name.$error.required">Add Name.</p><p class="help-block" ng-show="personForm.name.$error.minlength">Name must be at least 2 characters long.</p><p class="help-block" ng-show="personForm.name.$error.maxlength">Name cannot be longer than 50 characters.</p></div><!-- Display an error if the input is invalid and is dirty (only when someone changes the value) --><div class="form-group" ng-class="{'has-error' : personForm.description.$invalid && personForm.description.$dirty}"><label for="description">Description:</label><!-- Display a check when the field is valid and was modified --><span ng-class="{'glyphicon glyphicon-ok' : personForm.description.$valid && personForm.description.$dirty}"></span><input id="description" name="description" type="text" class="form-control" maxlength="100"ng-model="person.description"required ng-minlength="5" ng-maxlength="100"/><!-- Validation messages to be displayed on required, minlength and maxlength --><p class="help-block" ng-show="personForm.description.$error.required">Add Description.</p><p class="help-block" ng-show="personForm.description.$error.minlength">Description must be at least 5 characters long.</p><p class="help-block" ng-show="personForm.description.$error.maxlength">Description cannot be longer than 100 characters.</p></div><!-- Display an error if the input is invalid and is dirty (only when someone changes the value) --><div class="form-group" ng-class="{'has-error' : personForm.imageUrl.$invalid && personForm.imageUrl.$dirty}"><label for="imageUrl">Image URL:</label><!-- Display a check when the field is valid and was modified --><span ng-class="{'glyphicon glyphicon-ok' : personForm.imageUrl.$valid && personForm.imageUrl.$dirty}"></span><input id="imageUrl" name="imageUrl" type="url" class="form-control" maxlength="500"ng-model="person.imageUrl"required/><!-- Validation messages to be displayed on required and invalid. Type 'url' makes checks to a proper url format. --><p class="help-block" ng-show="personForm.imageUrl.$error.required">Add Image URL.</p><p class="help-block" ng-show="personForm.imageUrl.$invalid && personForm.imageUrl.$dirty">Invalid Image URL.</p></div><div class="avatar" ng-if="person.imageUrl"><img ng-src="{{person.imageUrl}}" width="400" height="250"/></div><!-- Form buttons. The 'Save' button is only enabled when the form is valid. --><div class="buttons"><button type="button" class="btn btn-primary" ng-click="clearForm()">Clear</button><button type="submit" class="btn btn-primary" ng-disabled="personForm.$invalid">Save</button></div></form></div> </div>外觀如下:
許多代碼用于驗證目的,但讓我們更詳細(xì)地研究一下:每個input元素將其值綁定到person.something 。 這允許在HTML和JavaScript控制器之間的數(shù)據(jù)進(jìn)行建模,所以我們可以寫$scope.person.name在我們的控制器到達(dá)填好了表單輸入與名稱值, name 。 要訪問HTML表單中的數(shù)據(jù),我們使用表單名稱personForm加上輸入字段的名稱。
HTML5在輸入字段中有自己的一組驗證,但是我們想使用Angular驗證。 在這種情況下,我們需要通過在form元素上使用novalidate來禁用表單驗證。 現(xiàn)在,要使用Angular驗證,我們可以在input元素中使用一些Angular指令。 對于這種非常基本的形式,我們僅使用required , ng-minlength和ng-maxlength ,但您可以使用其他形式。 只需查看文檔即可 。
Angular根據(jù)輸入驗證狀態(tài)分配CSS類。 想一想,這些是可能的值:
| valid | ng有效 | 該字段有效時。 |
| invalid | ng無效 | 當(dāng)該字段無效時。 |
| pristine | ng-原始 | 當(dāng)領(lǐng)域從未被觸及過。 |
| dirty | ng-dirty | 更改字段時。 |
這些CSS類為空。 您需要創(chuàng)建它們,并在隨附CSS表單中為應(yīng)用程序分配樣式。 相反,我們將使用Bootstrap中非常好的樣式。 為了使它們起作用,需要將一些其他類應(yīng)用于這些元素。 包含輸入的div元素需要CSS類form-group ,而input元素需要CSS類form-control 。
要顯示無效的輸入字段,我們將ng-class="{'has-error' : personForm.name.$invalid && personForm.name.$dirty}"到包含的輸入div中。 此代碼評估personForm中的名稱是否無效以及名稱是否骯臟。 條件驗證后,輸入將顯示為無效。
最后,對于表單驗證消息,我們需要為每個輸入和要執(zhí)行的驗證類型驗證$error指令。 只需將ng-show="personForm.name.$error.minlength"到帶有消息HTML顯示元素,以警告用戶名稱輸入字段太短。
表單角度控制器
// Create a controller with name personsFormController to bind to the form section. app.controller('personsFormController', function ($scope, $rootScope, personService) {// Clears the form. Either by clicking the 'Clear' button in the form, or when a successfull save is performed.$scope.clearForm = function () {$scope.person = null;// For some reason, I was unable to clear field values with type 'url' if the value is invalid.// This is a workaroud. Needs proper investigation.document.getElementById('imageUrl').value = null;// Resets the form validation state.$scope.personForm.$setPristine();// Broadcast the event to also clear the grid selection.$rootScope.$broadcast('clear');};// Calls the rest method to save a person.$scope.updatePerson = function () {personService.save($scope.person).$promise.then(function () {// Broadcast the event to refresh the grid.$rootScope.$broadcast('refreshGrid');// Broadcast the event to display a save message.$rootScope.$broadcast('personSaved');$scope.clearForm();},function () {// Broadcast the event for a server error.$rootScope.$broadcast('error');});};// Picks up the event broadcasted when the person is selected from the grid and perform the person load by calling// the appropiate rest service.$scope.$on('personSelected', function (event, id) {$scope.person = personService.get({id: id});});// Picks us the event broadcasted when the person is deleted from the grid and perform the actual person delete by// calling the appropiate rest service.$scope.$on('deletePerson', function (event, id) {personService.delete({id: id}).$promise.then(function () {// Broadcast the event to refresh the grid.$rootScope.$broadcast('refreshGrid');// Broadcast the event to display a delete message.$rootScope.$broadcast('personDeleted');$scope.clearForm();},function () {// Broadcast the event for a server error.$rootScope.$broadcast('error');});}); });對于表單控制器,我們需要兩個功能來執(zhí)行與按鈕“清除”和“保存”按鈕相關(guān)的操作,這兩個功能是不言自明的。 快速說明:出于某種原因,Angular不會清除處于無效狀態(tài)的輸入字段。 我確實發(fā)現(xiàn)一些人抱怨同樣的問題,但我需要對此進(jìn)行進(jìn)一步調(diào)查。 也許這是我做錯了。
使用已實現(xiàn)相應(yīng)HTTP方法的$resource對象中的save和delete調(diào)用REST服務(wù)。 檢查文檔 。 您可以通過以下工廠獲得$resource :
REST服務(wù)
// Service that provides persons operations app.factory('personService', function ($resource) {return $resource('resources/persons/:id'); });控制器代碼的其余部分具有拾取由網(wǎng)格創(chuàng)建的事件的功能,以在表單中加載人員數(shù)據(jù)并刪除人員。 該控制器還會創(chuàng)建一些事件。 如果我們添加或刪除人員,則需要更新網(wǎng)格,以便生成一個事件,要求更新網(wǎng)格。
反饋消息HTML
<!-- Specify a Angular controller script that binds Javascript variables to the feedback messages.--> <div class="message" ng-controller="alertMessagesController"><alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)">{{alert.msg}}</alert> </div>這只是應(yīng)用程序的頂部,用于根據(jù)保存,刪除或服務(wù)器錯誤顯示成功或錯誤消息。
反饋消息角度控制器
// Create a controller with name alertMessagesController to bind to the feedback messages section. app.controller('alertMessagesController', function ($scope) {// Picks up the event to display a saved message.$scope.$on('personSaved', function () {$scope.alerts = [{ type: 'success', msg: 'Record saved successfully!' }];});// Picks up the event to display a deleted message.$scope.$on('personDeleted', function () {$scope.alerts = [{ type: 'success', msg: 'Record deleted successfully!' }];});// Picks up the event to display a server error message.$scope.$on('error', function () {$scope.alerts = [{ type: 'danger', msg: 'There was a problem in the server!' }];});$scope.closeAlert = function (index) {$scope.alerts.splice(index, 1);}; });這是將消息推送到視圖的控制器。 偵聽由網(wǎng)格和表單控制器創(chuàng)建的事件。
最終結(jié)果
Uff ..那是很多代碼和新信息。 讓我們看一下最終結(jié)果:
感謝Cloudbees ,在http://javaee7-angular.radcortez.cloudbees.net中也運(yùn)行了一個實時版本。 如果云實例處于休眠狀態(tài),則可能需要一段時間才能打開(因為沒有使用)。
資源資源
您可以從我的github存儲庫中克隆完整的工作副本,然后將其部署到Wildfly。 您可以在此處找到說明進(jìn)行部署。 也應(yīng)該在Glassfish上工作。
Java EE – Angular JS源
由于我將來可能會修改代碼,因此您可以從3.0版中下載本文的原始源。 或者,克隆存儲庫并使用以下命令從版本3.0中檢出標(biāo)記: git checkout 3.0 。
另請檢查:
- 帶有Angular JS的Java EE 7 –第1部分
- Javascript軟件包管理– NPM – Bower – Grunt
最后的想法
- 開始輸入后,表單驗證即會啟動。 Angular 1.3將具有on模糊屬性,僅在失去焦點之后才能進(jìn)行驗證,但我仍在使用Angular1.2.x。
- 我不得不承認(rèn),我發(fā)現(xiàn)驗證代碼過于冗長。 我不知道是否有一種方法可以簡化它,但是您不必將每個消息驗證都添加到每個輸入中。
- 這里仍然缺少一些東西,例如參數(shù)清除或服務(wù)器端驗證。 我將在下一篇博客文章中介紹這些內(nèi)容。
這是一篇很長的文章,實際上是我在博客上寫的最長的文章。 如果您到此為止,非常感謝您撥冗閱讀本文 。 我希望你喜歡它! 讓我知道您是否有任何意見。
翻譯自: https://www.javacodegeeks.com/2014/10/java-ee-7-with-angular-js-crud-rest-validations-part-2.html
總結(jié)
以上是生活随笔為你收集整理的带有Angular JS的Java EE 7 – CRUD,REST,验证–第2部分的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 梦幻座骑快捷键(梦幻座骑快捷键是哪个)
- 下一篇: PrimeFaces Extension