程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> SpringSide開發實戰(六):AJAX,在地獄中漫步

SpringSide開發實戰(六):AJAX,在地獄中漫步

編輯:關於JAVA

說到AJAX,每個人都不會陌生,畢竟這兩年它太流行了。然而,真正哪些地方需要AJAX ,並不是每個人都能夠把握得很好。使用AJAX可以開發豪華的基於浏覽器的富客戶端界面, 然而其開發量的龐大和調試的艱難,讓每一個程序員如同生活在地獄中一般。

我認為,真正需要AJAX的不外乎兩種情況:

1、用戶不希望他關注的信息離開他的視線的時候。比如填寫某些表單的時候,有時候辛 辛苦苦填寫的東西,一點提交按鈕,全沒了,如果某個字段驗證失敗,則所有的東西都要從 頭再填,著實讓人郁悶。雖然設計較好的網站可以保留用戶填寫的信息,但是在提交後頁面 刷新的一瞬間,用戶仍然有一種不安感。

2、用戶不希望一點小的更新就刷新整個龐大的頁面的時候。比如用戶回復一篇很長且充 滿圖片的文章,雖然回復的內容只有幾個字,但是卻要等待頁面漫長的刷新,也會造成不好 的用戶體驗。

如果單單只是為了實現絢麗的效果而使用AJAX,我個人認為不可取。

根據以上的總結,我這裡想實現這樣一個注冊用戶的功能,讓用戶在提交信息的時候不 需要刷新頁面,所有的字段驗證全部發回服務器端進行,驗證的錯誤信息再顯示到表單頁面 ,整個過程不刷新頁面,知道注冊成功後跳轉到首頁,如下圖:

1、供用戶填寫的表單

2、用戶填寫信息後,表單變為不可編輯,並提示數據正在提交

3、如果驗證失敗,顯示錯誤信息,同時表單變為可用讓用戶修改

4、注冊成功後,提示注冊成功,然後跳轉到首頁

看似簡單的功能,我卻足足花了兩天時間才搞定,所以形容為在地獄裡漫步。下面,大 家會看到我的設計思路和遇到的各種問題。

要做AJAX開發,首先當然少不了挑選一個AJAX框架。我最喜歡的是Prototype,因為我討 厭復雜的功能。在SpringSide中集成有Prototype,我們只需要在jsp文件中加入如下代碼, 就可以使用了:

< script src ="scripts/prototype.js" ></ script >

事實上,我只使用了Prototype的一個函數,它就是Ajax.Request(),它簡化了我們繁瑣 的實例化XHR、監控請求狀態的過程,語法如下:

var ajax =  new Ajax.Request(url,  {
method: " get " , onComplete:onResponse} );

method為方法類型,如get,post等;onComplete為回調函數,通常在這個函數中完成對 相應數據的解析和顯示。

看似水落石出,只要在用戶點擊注冊按鈕的時候調用這個函數就可以做到異步提交數據 了。

問題一、如何將表單中的數據發回服務器?

我們都知道,當我們提交整個網頁的時候,其表單中的數據也一並POST過去了,基本上 無需我們操心;而AJAX不然,AJAX向服務器提交請求的時候,除了url,其它屁信息都沒有 。沒有辦法,我們只有自己取出表單中的數據,把它添加到url參數中,然後傳遞給服務器 。因此,當提交按鈕被點擊時,我的處理函數是這樣的:

function onSubmit() {
   var url =  " RegUser.do?method=submit " ;
   // 將表單數據添加到url中以便於使用GET傳遞到服務器
    var inputs = userForm.all.tags( " input " );
   for ( var i = 0 ; i < inputs.length; i ++ ) {
     url = url +  " & "  + inputs[i].name +  " = "  +  inputs[i].value;
     // 設置表單為不可用狀態
     inputs[i].disabled =  " true " ;
   }
   // 提示用戶正在提交數據
   $( " doing " ).style.pixelTop = document.body.scrollTop +  230 ;
   $( " doing " ).style.left =  250 ;
   $( " doing " ).style.display = " block " ;
   // 使用AJAX將數據傳遞到服務器,並接受服務器的回應
    var ajax =  new Ajax.Request(url,  {method: " get " , onComplete:onResponse} );
}

乍一看來,解決這個問題似乎並不復雜,但不知大家想過沒有,如果用戶輸入非法字符 怎麼處理。在url中,有幾個字符是會被引起錯誤的,比如"@"和"#","@"會讓服務器只 把"@"後面的字符串當成有效url地址,"#"代表一個網頁中的錨點。也有可能還有更多的非 法字符,我們暫時還沒有發現。我曾經想過使用JavaScript的escape()來將表單中的字符編 碼,但是又會引起中文無法傳遞到服務器。唉,看來除非在客戶端使用JavaScript代碼來過 濾掉這些字符,是在也想不出其它的辦法。

問題二、服務器返回什麼數據給AJAX對象?

我們通過AJAX把數據異步傳遞到服務 器,等服務器驗證完畢後,服務器給我們回復什麼格式的數據呢?是XML?普通文本?JSON? 還是其它。XML我首先排除,因為解析它的工作量太大了。當前,JSON最是流行。但是我更 加懶惰,我直接返回有效的JavaScript代碼,這樣,我在AJAX的onComplete時,只需要一行 代碼,如下:

 function  onResponse(request)  {
  eval (request.responseText);
}

問題三、AJAX讓Validator框架走開?

在Struts中,有一個驗證框架Validator,它可以很方便的完成對ActionForm的驗證 。但是一旦我們使用AJAX,Validator就派不上任何用場,因為只有在jsp文件中使用Struts 的<html:form>系列標簽,才能讓Struts表我們的表單數據自動封裝到ActionForm中 ,但是前文已經提過,我們的表單數據是通過url參數傳遞的,所以除非自己擴展Struts, 否則我們跟Validator無緣。

於是,所有的驗證代碼我們必須得在服務器端自己編寫 ,即要考慮周全,又要防止出錯。幸好SpringSide提供的HibernateEntityDao<T>讓 我們在驗證用戶名和昵稱是否重復時省了一大把勁。我的服務器端代碼如下: // 獲取用戶提交的數 據並驗證

// 獲取用戶提交的數據並驗

證
public ActionForward submit(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
boolean success = true;
String result = "";
User user = new User();
// 驗證用戶名
String name = new String(request.getParameter ("name").getBytes(
"ISO-8859-1"), "GB2312");
user.setName(name);
if (name == null || name.equals("")) {
success = false;
result += "name_err.innerHTML='用戶名不能為空';";
} else if (!name.matches("^[A-Za-z0-9_]*$")) {
success = false;
result += "name_err.innerHTML='用戶名只能包含字母、數 字和下劃線';";
} else if (name.length() > 20) {
success = false;
result += "name_err.innerHTML='用戶名不能 超過20個字符';";
}
// 判斷用戶名是否重復
else if (userManager.isNotUnique(user, "name")) {
success = false;
result += "name_err.innerHTML='該用戶名已經 被注冊';";
}else{
result += "name_err.innerHTML='';";
}
// 驗證昵稱
String monicker = new String(request.getParameter ("monicker").getBytes(
"ISO-8859-1"), "GB2312");
user.setMonicker(monicker);
if (monicker == null || monicker.equals("")) {
success = false;
result += "monicker_err.innerHTML='昵稱不能為 空';";
} else if (monicker.matches("^.*[~!@#$%^&*()- =+<>?/'\";:]+.*$")) {
success = false;
result += "monicker_err.innerHTML='昵稱不能包含特殊字符';";
} else if (monicker.length() > 15) {
success = false;
result += "monicker_err.innerHTML='昵稱不能超過15個字 符';";
}// 判斷昵稱是否重復
else if (userManager.isNotUnique(user, "monicker")) {
success = false;
result += "monicker_err.innerHTML='該用昵稱已經被使用 ';";
}else{
result += "monicker_err.innerHTML='';";
}
// 驗證兩次輸入 的密碼是否匹配
String password = new String(request.getParameter ("password").getBytes(
"ISO-8859-1"), "GB2312");
user.setPassword(password);
String password_again = new String(request.getParameter(
"password_again").getBytes("ISO-8859-1"), "GB2312");
if (password == null || password.equals ("")) {
success = false;
result += "password_err.innerHTML='密碼不能為空';";
} else if (password_again == null || password_again.equals("")) {
success = false;
result += "password_err.innerHTML='確認密碼 不能為空';";
} else if (!password.equals(password_again)) {
success = false;
result += "password_err.innerHTML='兩次輸入的密碼不匹配';";
}else {
result += "password_err.innerHTML='';";
}
// 驗證密碼問題和問題答案,規則和昵稱相同
String question = new String(request.getParameter("question").getBytes(
"ISO-8859-1"), "GB2312");
user.setQuestion (question);
if (question == null || question.equals("")) {
success = false;
result += "question_err.innerHTML='問題不能為空';";
} else if (question.matches("^.*[~!@#$%^&*()-=+<>?/'\";:]+.*$")) {
success = false;
result += "question_err.innerHTML='問題不能包含特殊字符';";
} else if (question.length() > 15) {
success = false;
result += "question_err.innerHTML='問題不能超過15個字符';";
}else{
result += "question_err.innerHTML='';";
}
String answer = new String(request.getParameter ("answer").getBytes(
"ISO-8859-1"), "GB2312");
user.setQuestion(question);
if (answer == null || answer.equals("")) {
success = false;
result += "answer_err.innerHTML='答案不能為 空';";
} else if (answer.matches("^.*[~!@#$%^&*()- =+<>?/'\";:]+.*$")) {
success = false;
result += "answer_err.innerHTML='答案不能包含特殊字符';";
} else if (answer.length() > 15) {
success = false;
result += "answer_err.innerHTML='答案不能超過15個字符';";
}else{
result += "answer_err.innerHTML='';";
}
// 驗證email
String email = new String (request.getParameter("email").getBytes(
"ISO -8859-1"), "GB2312");
user.setEmail(email);
if (email == null || email.equals("")) {
success = false;
result += "email_err.innerHTML='Email不能為 空';";
} else if (!email.matches("^[a-zA-Z0-9]*@[a-zA-Z0- 9]*$")) {
success = false;
result += "email_err.innerHTML='不是有效的電子郵箱';";
} else if (email.length() > 40) {
success = false;
result += "email_err.innerHTML='Email不能超過40個字符';";
}else {
result += "email_err.innerHTML='';";
}
// 驗證QQ號碼
String qq = new String(request.getParameter ("qq")
.getBytes("ISO-8859-1"), "GB2312");
user.setQq(qq);
if (qq == null || qq.equals("")) {
success = false;
result += "qq_err.innerHTML='QQ號碼不能為空';";
} else if (! qq.matches("^\\d*$")) {
success = false;
result += "qq_err.innerHTML='不是有效的QQ號碼';";
} else if (qq.length() > 12) {
success = false;
result += "qq_err.innerHTML='QQ號碼不能超過12位';";
} else if (qq.length() < 5) {
result += "qq_err.innerHTML='QQ號碼不 能少於5位';";
}else{
result += "qq_err.innerHTML='';";
}
// 驗證驗證碼
String validateImage = new String(request.getParameter ("validateImage")
.getBytes("ISO-8859- 1"), "GB2312");
if (validateImage == null
|| validateImage.equals("")
|| ! validateImage.equals(request.getSession().getAttribute(
"validateString"))) {
success = false;
result += "validateImage_err.innerHTML='驗證碼輸入錯誤。如看不清,點擊圖片 更換';";
}else{
result += "validateImage_err.innerHTML='';";
}
//如果驗證 不成功,則調用JavaScript的failed()函數,否則,調用sucess();
if (success == false){
result += "failed();";
response.setCharacterEncoding("GB2312");
response.getOutputStream().println(result);
response.flushBuffer ();
}else{
//如果驗證成功,把數據寫入數據庫中,要防止 重復提交
if(this.isTokenValid(request)){
userManager.save(user);
this.resetToken(request);
}
response.setCharacterEncoding("GB2312");
response.getOutputStream().println("success();");
response.flushBuffer();
}
return null;
}

客戶端的failed()和success()函數如下:

function failed(){
   //掩藏提示信息
   $("doing").style.display="none";
   //設置表單為可用狀態
   var inputs = userForm.all.tags("input");
   for(var i=0; i < inputs.length; i++){
     inputs[i].disabled = "";
   }
}
function success(){
   $("doing").style.display="block";
   $("doing").style.color="#0000FF";
   $("doing").innerHTML = "用戶注冊成功,將跳轉到首頁!";
   //4秒鐘跳到首頁
   setTimeout("location.href='welcome.do';",4000);
}

問題四、中文亂碼問題如何解決?

我想每個人在使用AJAX的時候肯定都遇到過中文亂碼的問題,我也不例外,這個問題困 擾我的時間也不短,後來我總算時把它搞清楚了:AJAX使用的是另外一個線程,所以它的字 符編碼是和頁面無關的,也就是說,它總是用GB2312編碼向服務器發送數據,並且總是把接 受到的數據當GB2312來理解,這是由我們操作系統決定的,我們大陸的操作系統默認編碼都 應該是GB2312吧。因此,在接受數據的時候,我們少不了:

String name = new String(request.getParameter("name").getBytes(
         "ISO-8859-1"), "GB2312");

而發送數據的時候,也少不了:

response.setCharacterEncoding("GB2312");
       response.getOutputStream().println(result);
       response.flushBuffer();

除此之外,還有浏覽器之間對象不兼容的問題,可見寫一個AJAX應用到處都是陷阱。

從上面大家可以看出,對於用戶注冊,我全部使用的/RegUser.do來進行處理,它繼承自 SpringSide的StrutsAction,是DispatherAction的子類。它的配置如下:

struts-config.xml的action-mappings節中: <action path="/RegUser" scope="request" parameter="method">
         <forward name="agree" path="/RegUser_Agree.jsp"/>
         <forward name="apply" path="/RegUser_Apply.jsp"/>
       </action>
action-servlet.xml: <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans default-autowire="byName" default-lazy-init="true">
   <!-- 按模塊導入Spring Action Config-->
   <import resource="modules/spring-config-admin.xml"/>
   <!-- 簡單應用直接在此定義Action
     <bean name="/user" class="org.springside.helloworld.web.UserAction"/>
     -->
   <bean name="/welcome" class="com.xkland.action.WelcomeAction"/>
   <bean name="/RegUser" class="com.xkland.action.RegUserAction"/>
</beans>

而這個com.xkland.action.RegUserAction的完整代碼如下,希望大家多提意見:

package com.xkland.action;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForward;
import org.springside.core.web.StrutsAction;
import com.xkland.manager.UserManager;
import com.xkland.util.ImageUtil;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import com.xkland.domain.User;
public class RegUserAction extends StrutsAction {
  private ImageUtil imageUtil;
  private UserManager userManager;
  public void setUserManager(UserManager userManager) {
    this.userManager = userManager;
  }
   public void setImageUtil(ImageUtil imageUtil) {
    this.imageUtil = imageUtil;
  }
  // 重定向到會員注冊協議頁面
  public ActionForward agree(ActionMapping mapping, ActionForm form,
       HttpServletRequest request, HttpServletResponse response) {
    return mapping.findForward("agree");
  }
  // 重定向到填寫表單 頁面
   public ActionForward apply(ActionMapping mapping, ActionForm form,
       HttpServletRequest request, HttpServletResponse response) {
    // 使用Token防止重復提交
    saveToken(request);
    return mapping.findForward("apply");
  }
  // 構造驗證圖片
   public ActionForward createValidateImage(ActionMapping mapping,
       ActionForm form, HttpServletRequest request,
       HttpServletResponse response) {
    BufferedImage image = imageUtil.createValidateImage(request
        .getSession());
    response.setContentType("image/jpeg");
    try {
      ImageIO.write(image, "jpeg", response.getOutputStream ());
      response.flushBuffer();
    } catch (Exception e) {
    }
    return null;
  }
  //獲取用戶提交的 數據並驗證
  public ActionForward submit(ActionMapping mapping, ActionForm form,
      HttpServletRequest request, HttpServletResponse response)
      throws Exception {
//上面已 貼代碼,此處省略
      }
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved