webapp文本编辑器_Project Student:维护Webapp(可编辑)
webapp文本編輯器
這是Project Student的一部分。 其他職位包括帶有Jersey的 Web服務 客戶端,帶有Jersey的 Web服務服務器 , 業務層 , 帶有Spring數據的持久性 ,分片集成測試數據 , Webservice集成 , JPA標準查詢和維護Webapp(只讀) 。
上次我們創建了一個簡單的Web應用程序,使我們可以快速瀏覽數據庫。 它的功能非常有限–主要目標是將整個系統(從Web瀏覽器到數據庫)的整個系統組合在一起。 這次我們添加了實際的CRUD支持。
這篇文章是從Jumpstart網站大量借用的,但有很大的不同。 有很多代碼,但是很容易重用。
局限性
- 用戶身份驗證 –尚未進行身份驗證的工作。
- 加密 -尚未對通信進行加密。
- 分頁 –尚未做出任何努力來支持分頁。 Tapestry 5組件將顯示分頁外觀,但始終包含相同的第一頁數據。
- 錯誤消息 –將顯示錯誤消息,但服務器端錯誤目前尚無信息。
- 跨站點腳本(XSS) –尚未做出任何努力來防止XSS攻擊。
- 國際化 –尚未做出任何努力來支持國際化。
目標
我們需要標準的CRUD頁面。
首先,我們需要能夠創建一門新課程。 當我們沒有任何數據時,我們的課程列表應包含一個鏈接作為默認消息。 (第一個“創建...”是一個單獨的元素。)
現在,創建頁面具有多個字段。 代碼唯一地標識一門課程,例如CSCI 101,其名稱,摘要和說明應不言自明。
創建成功后,我們將進入評論頁面。
如果需要進行更改,然后返回更新頁面。
任何時候我們都可以返回列表頁面。
在刪除記錄之前,系統會提示我們進行確認。
最后,我們可以顯示服務器端錯誤,例如,即使當前消息非常無用,也要顯示唯一字段中的重復值。
我們也有客戶端錯誤檢查,盡管我在這里沒有顯示。
Index.tml
我們從索引頁面開始。 這類似于我們在上一篇文章中看到的內容。
Tapestry 5具有三種主要類型的鏈接。 頁面鏈接映射到標準HTML鏈接。 一個ActionLink的是直接由相應的類,例如,Index.java為Index.tml模板處理。 最后,事件鏈接將事件注入掛毯引擎內的正常事件流。 我所有的鏈接都轉到一個密切相關的頁面,所以我使用一個動作鏈接。
<html t:type="layout" title="Course List"t:sidebarTitle="Framework Version"xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"xmlns:p="tapestry:parameter"><t:zone t:id="zone"> <p>"Course" page</p><t:actionlink t:id="create">Create...</t:actionlink><br/><t:grid source="courses" row="course" include="code, name,creationdate" add="edit,delete"><p:codecell><t:actionlink t:id="view" context="course.uuid">${course.code}</t:actionlink></p:codecell><p:editcell><t:actionlink t:id="update" context="course.uuid">Edit</t:actionlink></p:editcell><p:deletecell><t:actionlink t:id="delete" context="course.uuid" t:mixins="Confirm" t:message="Delete ${course.name}?">Delete</t:actionlink></p:deletecell><p:empty><p>There are no courses to display; you can <t:actionlink t:id="create1">create</t:actionlink> one.</p></p:empty></t:grid></t:zone><p:sidebar><p>[<t:pagelink page="Index">Index</t:pagelink>]<br/>[<t:pagelink page="Course/Index">Courses</t:pagelink>]</p></p:sidebar></html>確認混入
Index.tml模板在刪除操作鏈接上包含一個“ mixin”。 它使用javascript和Java的混合顯示彈出消息,要求用戶確認他要刪除課程。
此代碼直接來自Jumpstart和Tapestry網站。
// from http://jumpstart.doublenegative.com.au/ Confirm = Class.create({initialize: function(elementId, message) {this.message = message;Event.observe($(elementId), 'click', this.doConfirm.bindAsEventListener(this));},doConfirm: function(e) {// Pop up a javascript Confirm Box (see http://www.w3schools.com/js/js_popup.asp)if (!confirm(this.message)) {e.stop();}} })// Extend the Tapestry.Initializer with a static method that instantiates a Confirm.Tapestry.Initializer.confirm = function(spec) {new Confirm(spec.elementId, spec.message); }相應的Java代碼是
// from http://jumpstart.doublenegative.com.au/ @Import(library = "confirm.js") public class Confirm {@Parameter(name = "message", value = "Are you sure?", defaultPrefix = BindingConstants.LITERAL)private String message;@Injectprivate JavaScriptSupport javaScriptSupport;@InjectContainerprivate ClientElement clientElement;@AfterRenderpublic void afterRender() {// Tell the Tapestry.Initializer to do the initializing of a Confirm,// which it will do when the DOM has been// fully loaded.JSONObject spec = new JSONObject();spec.put("elementId", clientElement.getClientId());spec.put("message", message);javaScriptSupport.addInitializerCall("confirm", spec);} }Index.java
支持索引模板的Java很簡單,因為我們只需要定義一個數據源并提供一些動作處理程序即可。
package com.invariantproperties.sandbox.student.maintenance.web.pages.course;public class Index {@Property@Inject@Symbol(SymbolConstants.TAPESTRY_VERSION)private String tapestryVersion;@InjectComponentprivate Zone zone;@Injectprivate CourseFinderService courseFinderService;@Injectprivate CourseManagerService courseManagerService;@Propertyprivate Course course;// our sibling page@InjectPageprivate com.invariantproperties.sandbox.student.maintenance.web.pages.course.Editor editorPage;/*** Get the datasource containing our data.* * @return*/public GridDataSource getCourses() {return new CoursePagedDataSource(courseFinderService);}/*** Handle a delete request. This could fail, e.g., if the course has already* been deleted.* * @param courseUuid*/void onActionFromDelete(String courseUuid) {courseManagerService.deleteCourse(courseUuid, 0);}/*** Bring up editor page in create mode.* * @param courseUuid* @return*/Object onActionFromCreate() {editorPage.setup(Mode.CREATE, null);return editorPage;}/*** Bring up editor page in create mode.* * @param courseUuid* @return*/Object onActionFromCreate1() {return onActionFromCreate();}/*** Bring up editor page in review mode.* * @param courseUuid* @return*/Object onActionFromView(String courseUuid) {editorPage.setup(Mode.REVIEW, courseUuid);return editorPage;}/*** Bring up editor page in update mode.* * @param courseUuid* @return*/Object onActionFromUpdate(String courseUuid) {editorPage.setup(Mode.UPDATE, courseUuid);return editorPage;} }Editor.tml
CRUD頁面可以是三個單獨的頁面(用于創建,查看和更新??),也可以是一個頁面。 我遵循的是Jumpstart網站使用的模式–一個頁面。 老實說-我不確定他為什么做出這個決定-也許是因為頁面緊密相關并且他使用事件處理? 無論如何,我將分別討論這些元素。
創建模板
“創建”模板是一種簡單的形式。 您可以看到HTML <input>元素得到了增強,它們具有一些特定于掛毯的屬性,以及一些其他標記,例如<t:errors />和<t:submit>。
CustomForm和CustomError是標準Tapestry Form和Error類的本地擴展。 它們目前為空,但允許我們輕松添加本地擴展。
<html t:type="layout" title="Course Editor"t:sidebarTitle="Framework Version"xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd" xmlns:p="tapestry:parameter"><t:zone t:id="zone"> <t:if test="modeCreate"><h1>Create</h1><form t:type="form" t:id="createForm" ><t:errors/><table><tr><th><t:label for="code"/>:</th><td><input t:type="TextField" t:id="code" value="course.code" t:validate="required, maxlength=12" size="12"/></td><td>(required)</td></tr><tr class="err"><th></th><td colspan="2"><t:CustomError for="code"/></td></tr><tr><th><t:label for="name"/>:</th><td><input t:type="TextField" t:id="name" value="course.name" t:validate="required, maxlength=80" size="45"/></td><td>(required)</td></tr><tr class="err"><th></th><td colspan="2"><t:CustomError for="name"/></td></tr><tr><th><t:label for="summary"/>:</th><td><input cols="50" rows="4" t:type="TextArea" t:id="summary" value="course.summary" t:validate="maxlength=400"/></td></tr><tr class="err"><th></th><td colspan="2"><t:CustomError for="summary"/></td></tr><tr><th><t:label for="description"/>:</th><td><input cols="50" rows="12" t:type="TextArea" t:id="description" value="course.description" t:validate="maxlength=2000"/></td></tr><tr class="err"><th></th><td colspan="2"><t:CustomError for="description"/></td></tr></table><div class="buttons"><t:submit t:event="cancelCreate" t:context="course.uuid" value="Cancel"/><input type="submit" value="Save"/></div></form></t:if>... </html>創建java
- 相應的java類很簡單。 我們必須定義一些自定義事件。
- ActivationRequestParameter值是從URL查詢字符串中提取的。
- 課程字段包含創建新對象時要使用的值。
- courseForm字段在模板上包含相應的<form>。
- indexPage包含對索引頁的引用。
有四個名為onEventFromCreateForm的事件處理程序,其中event
可以準備,驗證,成功或失敗。 每個事件處理程序都有非常特定的角色。
還有一個附加的事件處理程序onCancelCreate() 。 您可以在模板的<t:submit>標記中看到該事件的名稱。
/*** This component will trigger the following events on its container (which in* this example is the page):* {@link Editor.web.components.examples.component.crud.Editor#CANCEL_CREATE} ,* {@link Editor.web.components.examples.component.crud.Editor#SUCCESSFUL_CREATE}* (Long courseUuid),* {@link Editor.web.components.examples.component.crud.Editor#FAILED_CREATE} ,*/ // @Events is applied to a component solely to document what events it may // trigger. It is not checked at runtime. @Events({ Editor.CANCEL_CREATE, Editor.SUCCESSFUL_CREATE, Editor.FAILED_CREATE }) public class Editor {public static final String CANCEL_CREATE = "cancelCreate";public static final String SUCCESSFUL_CREATE = "successfulCreate";public static final String FAILED_CREATE = "failedCreate";public enum Mode {CREATE, REVIEW, UPDATE;}// Parameters@ActivationRequestParameter@Propertyprivate Mode mode;@ActivationRequestParameter@Propertyprivate String courseUuid;// Screen fields@Propertyprivate Course course;// Work fields// This carries version through the redirect that follows a server-side// validation failure.@Persist(PersistenceConstants.FLASH)private Integer versionFlash;// Generally useful bits and pieces@Injectprivate CourseFinderService courseFinderService;@Injectprivate CourseManagerService courseManagerService;@Componentprivate CustomForm createForm;@Injectprivate ComponentResources componentResources;@InjectPageprivate com.invariantproperties.sandbox.student.maintenance.web.pages.course.Index indexPage;// The codepublic void setup(Mode mode, String courseUuid) {this.mode = mode;this.courseUuid = courseUuid;}// setupRender() is called by Tapestry right before it starts rendering the// component.void setupRender() {if (mode == Mode.REVIEW) {if (courseUuid == null) {course = null;// Handle null course in the template.} else {if (course == null) {try {course = courseFinderService.findCourseByUuid(courseUuid);} catch (ObjectNotFoundException e) {// Handle null course in the template.}}}}}// /// CREATE// /// Handle event "cancelCreate"Object onCancelCreate() {return indexPage;}// Component "createForm" bubbles up the PREPARE event when it is rendered// or submittedvoid onPrepareFromCreateForm() throws Exception {// Instantiate a Course for the form data to overlay.course = new Course();}// Component "createForm" bubbles up the VALIDATE event when it is submittedvoid onValidateFromCreateForm() {if (createForm.getHasErrors()) {// We get here only if a server-side validator detected an error.return;}try {course = courseManagerService.createCourse(course.getCode(), course.getName(), course.getSummary(),course.getDescription(), 1);} catch (RestClientFailureException e) {createForm.recordError("Internal error on server.");createForm.recordError(e.getMessage());} catch (Exception e) {createForm.recordError(ExceptionUtil.getRootCauseMessage(e));}}// Component "createForm" bubbles up SUCCESS or FAILURE when it is// submitted, depending on whether VALIDATE// records an errorboolean onSuccessFromCreateForm() {componentResources.triggerEvent(SUCCESSFUL_CREATE, new Object[] { course.getUuid() }, null);// We don't want "success" to bubble up, so we return true to say we've// handled it.mode = Mode.REVIEW;courseUuid = course.getUuid();return true;}boolean onFailureFromCreateForm() {// Rather than letting "failure" bubble up which doesn't say what you// were trying to do, we trigger new event// "failedCreate". It will bubble up because we don't have a handler// method for it.componentResources.triggerEvent(FAILED_CREATE, null, null);// We don't want "failure" to bubble up, so we return true to say we've// handled it.return true;}.... }評論模板
“審閱”模板是一個簡單的表。 它以表格形式包裝,但僅用于頁面底部的導航按鈕。
<t:if test="modeReview"><h1>Review</h1><strong>Warning: no attempt is made to block XSS</strong><form t:type="form" t:id="reviewForm"><t:errors/><t:if test="course"><div t:type="if" t:test="deleteMessage" class="error">${deleteMessage}</div><table><tr><th>Uuid:</th><td>${course.uuid}</td></tr><tr><th>Code:</th><td>${course.code}</td></tr><tr><th>Name:</th><td>${course.name}</td></tr><tr><th>Summary:</th><td>${course.summary}</td></tr><tr><th>Description:</th><td>${course.description}</td></tr></table><div class="buttons"><t:submit t:event="toIndex" t:context="course.uuid" value="List"/><t:submit t:event="toUpdate" t:context="course.uuid" value="Update"/><t:submit t:event="delete" t:context="course.uuid" t:mixins="Confirm" t:message="Delete ${course.name}?" value="Delete"/></div></t:if><t:if negate="true" test="course">Course ${courseUuid} does not exist.<br/><br/></t:if></form></t:if>回顧java
審閱表單所需的Java很簡單-我們只需要加載數據即可。 我希望setupRender()足夠,但實際上我需要onPrepareFromReviewForm()方法。
public class Editor {public enum Mode {CREATE, REVIEW, UPDATE;}// Parameters@ActivationRequestParameter@Propertyprivate Mode mode;@ActivationRequestParameter@Propertyprivate String courseUuid;// Screen fields@Propertyprivate Course course;// Generally useful bits and pieces@Injectprivate CourseFinderService courseFinderService;@Componentprivate CustomForm reviewForm;@Injectprivate ComponentResources componentResources;@InjectPageprivate com.invariantproperties.sandbox.student.maintenance.web.pages.course.Index indexPage;// The codepublic void setup(Mode mode, String courseUuid) {this.mode = mode;this.courseUuid = courseUuid;}// setupRender() is called by Tapestry right before it starts rendering the// component.void setupRender() {if (mode == Mode.REVIEW) {if (courseUuid == null) {course = null;// Handle null course in the template.} else {if (course == null) {try {course = courseFinderService.findCourseByUuid(courseUuid);} catch (ObjectNotFoundException e) {// Handle null course in the template.}}}}}// /// REVIEW// /void onPrepareFromReviewForm() {try {course = courseFinderService.findCourseByUuid(courseUuid);} catch (ObjectNotFoundException e) {// Handle null course in the template.}}// /// PAGE NAVIGATION// /// Handle event "toUpdate"boolean onToUpdate(String courseUuid) {mode = Mode.UPDATE;return false;}// Handle event "toIndex"Object onToIndex() {return indexPage;}.... }UPDATE模板
最后,“更新”模板看起來類似于“創建”模板。
<t:if test="modeUpdate"><h1>Update</h1><strong>Warning: no attempt is made to block XSS</strong><form t:type="form" t:id="updateForm"><t:errors/><t:if test="course"><!-- If optimistic locking is not needed then comment out this next line. It works because Hidden fields are part of the submit. --><!-- <t:hidden value="course.version"/> --><table><tr><th><t:label for="updCode"/>:</th><td><input t:type="TextField" t:id="updCode" value="course.code" t:disabled="true" size="12"/></td><td>(read-only)</td></tr><tr class="err"><th></th><td colspan="2"><t:CustomError for="updName"/></td></tr><tr><th><t:label for="updName"/>:</th><td><input t:type="TextField" t:id="updName" value="course.name" t:validate="required, maxlength=80" size="45"/></td><td>(required)</td></tr><tr class="err"><th></th><td colspan="2"><t:CustomError for="updSummary"/></td></tr><tr><th><t:label for="updSummary"/>:</th><td><input cols="50" rows="4" t:type="TextArea" t:id="updSummary" value="course.summary" t:validate="maxlength=400"/></td></tr><tr class="err"><th></th><td colspan="2"><t:CustomError for="updSummary"/></td></tr><tr><th><t:label for="updDescription"/>:</th><td><input cols="50" rows="12" t:type="TextArea" t:id="updDescription" value="course.description" t:validate="maxlength=50"/></td></tr><tr class="err"><th></th><td colspan="2"><t:CustomError for="updDescription"/></td></tr></table><div class="buttons"><t:submit t:event="toIndex" t:context="course.uuid" value="List"/><t:submit t:event="cancelUpdate" t:context="course.uuid" value="Cancel"/><input t:type="submit" value="Save"/></div></t:if><t:if negate="true" test="course">Course ${courseUuid} does not exist.<br/><br/></t:if></form> </t:if>更新java
同樣,“更新” Java代碼看起來很像“創建” Java代碼。 最大的區別是,我們必須能夠處理在嘗試更新數據庫之前已刪除課程的比賽條件。
@Events({ Editor.TO_UPDATE, Editor.CANCEL_UPDATE,Editor.SUCCESSFUL_UPDATE, Editor.FAILED_UPDATE }) public class Editor {public static final String TO_UPDATE = "toUpdate";public static final String CANCEL_UPDATE = "cancelUpdate";public static final String SUCCESSFUL_UPDATE = "successfulUpdate";public static final String FAILED_UPDATE = "failedUpdate";public enum Mode {CREATE, REVIEW, UPDATE;}// Parameters@ActivationRequestParameter@Propertyprivate Mode mode;@ActivationRequestParameter@Propertyprivate String courseUuid;// Screen fields@Propertyprivate Course course;@Property@Persist(PersistenceConstants.FLASH)private String deleteMessage;// Work fields// This carries version through the redirect that follows a server-side// validation failure.@Persist(PersistenceConstants.FLASH)private Integer versionFlash;// Generally useful bits and pieces@Injectprivate CourseFinderService courseFinderService;@Injectprivate CourseManagerService courseManagerService;@Componentprivate CustomForm updateForm;@Injectprivate ComponentResources componentResources;@InjectPageprivate com.invariantproperties.sandbox.student.maintenance.web.pages.course.Index indexPage;// The codepublic void setup(Mode mode, String courseUuid) {this.mode = mode;this.courseUuid = courseUuid;}// setupRender() is called by Tapestry right before it starts rendering the// component.void setupRender() {if (mode == Mode.REVIEW) {if (courseUuid == null) {course = null;// Handle null course in the template.} else {if (course == null) {try {course = courseFinderService.findCourseByUuid(courseUuid);} catch (ObjectNotFoundException e) {// Handle null course in the template.}}}}}// /// UPDATE// /// Handle event "cancelUpdate"Object onCancelUpdate(String courseUuid) {return indexPage;}// Component "updateForm" bubbles up the PREPARE_FOR_RENDER event during// form rendervoid onPrepareForRenderFromUpdateForm() {try {course = courseFinderService.findCourseByUuid(courseUuid);} catch (ObjectNotFoundException e) {// Handle null course in the template.}// If the form has errors then we're redisplaying after a redirect.// Form will restore your input values but it's up to us to restore// Hidden values.if (updateForm.getHasErrors()) {if (course != null) {course.setVersion(versionFlash);}}}// Component "updateForm" bubbles up the PREPARE_FOR_SUBMIT event during for// submissionvoid onPrepareForSubmitFromUpdateForm() {// Get objects for the form fields to overlay.try {course = courseFinderService.findCourseByUuid(courseUuid);} catch (ObjectNotFoundException e) {course = new Course();updateForm.recordError("Course has been deleted by another process.");}}// Component "updateForm" bubbles up the VALIDATE event when it is submittedvoid onValidateFromUpdateForm() {if (updateForm.getHasErrors()) {// We get here only if a server-side validator detected an error.return;}try {courseManagerService.updateCourse(course, course.getName(), course.getSummary(), course.getDescription(), 1);} catch (RestClientFailureException e) {updateForm.recordError("Internal error on server.");updateForm.recordError(e.getMessage());} catch (Exception e) {// Display the cause. In a real system we would try harder to get a// user-friendly message.updateForm.recordError(ExceptionUtil.getRootCauseMessage(e));}}// Component "updateForm" bubbles up SUCCESS or FAILURE when it is// submitted, depending on whether VALIDATE// records an errorboolean onSuccessFromUpdateForm() {// We want to tell our containing page explicitly what course we've// updated, so we trigger new event// "successfulUpdate" with a parameter. It will bubble up because we// don't have a handler method for it.componentResources.triggerEvent(SUCCESSFUL_UPDATE, new Object[] { courseUuid }, null);// We don't want "success" to bubble up, so we return true to say we've// handled it.mode = Mode.REVIEW;return true;}boolean onFailureFromUpdateForm() {versionFlash = course.getVersion();// Rather than letting "failure" bubble up which doesn't say what you// were trying to do, we trigger new event// "failedUpdate". It will bubble up because we don't have a handler// method for it.componentResources.triggerEvent(FAILED_UPDATE, new Object[] { courseUuid }, null);// We don't want "failure" to bubble up, so we return true to say we've// handled it.return true;} }刪除模板和Java
編輯器沒有顯式的“刪除”模式,但確實支持刪除審閱和更新頁面上的當前對象。
// /// DELETE// /// Handle event "delete"Object onDelete(String courseUuid) {this.courseUuid = courseUuid;int courseVersion = 0;try {courseManagerService.deleteCourse(courseUuid, courseVersion);} catch (ObjectNotFoundException e) {// the object's already deleted} catch (RestClientFailureException e) {createForm.recordError("Internal error on server.");createForm.recordError(e.getMessage());// Display the cause. In a real system we would try harder to get a// user-friendly message.deleteMessage = ExceptionUtil.getRootCauseMessage(e);// Trigger new event "failedDelete" which will bubble up.componentResources.triggerEvent(FAILED_DELETE, new Object[] { courseUuid }, null);// We don't want "delete" to bubble up, so we return true to say// we've handled it.return true;} catch (Exception e) {// Display the cause. In a real system we would try harder to get a// user-friendly message.deleteMessage = ExceptionUtil.getRootCauseMessage(e);// Trigger new event "failedDelete" which will bubble up.componentResources.triggerEvent(FAILED_DELETE, new Object[] { courseUuid }, null);// We don't want "delete" to bubble up, so we return true to say// we've handled it.return true;}// Trigger new event "successfulDelete" which will bubble up.componentResources.triggerEvent(SUCCESFUL_DELETE, new Object[] { courseUuid }, null);// We don't want "delete" to bubble up, so we return true to say we've// handled it.return indexPage;}下一步
顯而易見的下一步是改進錯誤消息,增加對分頁,支持以及一對多和多對多關系的支持。 所有這些都需要修改REST負載。 我在管道中還有一些其他項目,例如ExceptionService,更不用說安全問題了。
源代碼
- 可從http://code.google.com/p/invariant-properties-blog/source/browse/student獲取源代碼。
翻譯自: https://www.javacodegeeks.com/2014/02/project-student-maintenance-webapp-editable.html
webapp文本編輯器
總結
以上是生活随笔為你收集整理的webapp文本编辑器_Project Student:维护Webapp(可编辑)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 每个Java学习者都会犯的10大常见错误
- 下一篇: 入豫备案在哪办理(入豫备案电话)