星期五, 6月 16, 2006

AJAX(PROTOTYPE)備忘記

由於公司網站瀏覽數量非常驚人, 更新資料亦非常頻繁.
就形成前門火災, 後門進賊 的困景, 這雖然錢能解決以上問題, 但實在不是長期的辦法.
雖然 flash 搭配 xml 是一個很好的解決方案, 但當頁面設計變更時,
開發員往往需要花上很多時間處理 flash 裡 Layout 的重整, 那是件非常累人的事情.
所以使用 Ajax 技術能有效解決以上問題.
對於門戶網站的前門, 能減少不小頻寬, 這可節省不少金錢.
對於後門, 能更快地更新到網站上, 尤其是對於實時性很注重的網站.


話說回來, 在1年多前,
一位同事在別的網站抄了別人的ajax代碼再更改,非常凌亂,實在是不好維護,
而且不能處理 firefox, 後來我也鮮少去看.
由於此原因, 故想自己從頭開始編寫一次, 並將整個過程記錄下來, 以供日後參考.


編寫Ajax(Asyncronous Javascript and XML), 必需對Javascript與XML DOM有一定的了解.
由於本人屬新手級, 所以必須參考多本藉作, 以更快掌握Ajax技術.
對於javascript及DOM,本人參考了Professional JavaScript for Web Developers.
而Ajax的書藉, 則參考了Foundations of Ajax及Ajax Hacks, 當然還有網上的豐富資源.

這個例子只是簡單的測試, 沒有對xml資料作動態變更, 這使得整個備忘記更加簡潔.
處理 XML 的變更亦是一件輕而易舉的事情, 使用 Jdom 是不錯的選擇.

開始備忘記:
下面主要包含3個文檔:
news.xml // xml data file
news.js // logical file
news.html // configuration file


<!-----------news.xml-------------->
<?xml version="1.0" ?>
<ROWSET>
<NEWS>
<NEWS_ITEM num="1">
<ID>TEST 1</ID>
<TITLE>TITLE TEST 1</TITLE>
<CONTENT>CONTENT TEST 1</CONTENT>
</NEWS_ITEM>
<NEWS_ITEM num="2">
<ID>TEST 2</ID>
<TITLE>TITLE TEST 2</TITLE>
<CONTENT>CONTENT TEST 2</CONTENT>
</NEWS_ITEM>
</NEWS>
</ROWSET>
<!-----------news.xml-------------->


<!-----------news.js-------------->
Array.prototype.indexOf = function (vItem) {
for (var i=0; i < this.length; i++) {
if (vItem == this[i]) {
return i;
}
}
return -1;
}

function getParameter( sParameterName, sDefaultValue ) {
var sQueryString = window.location.search.substring(1).toLowerCase();
var parameters = new Array();
parameters = sQueryString.split('&');
for(var i = 0; i < parameters.length; i++) {
if (parameters[i].indexOf(sParameterName.toLowerCase())>=0) {
var sParameterValue = new Array();
sParameterValue = parameters[i].split('=');
return sParameterValue[1];
}
}
return sDefaultValue;
}


var xmlHttp;
var debug_log = "Start
Debug@@@@@@@\n";

function News() {
this.items = new Array();
}
News.prototype.set = function(key,value){
this.items[news_item_att.indexOf(key)] = value;
}
News.prototype.get = function(key){
return this.items[news_item_att.indexOf(key)];
}


function createXMLHttpRequest() {
if (window.ActiveXObject && !window.XMLHttpRequest) {
window.XMLHttpRequest = function() {
var MSXML = ["Msxml2.XMLHTTP.5.0","Msxml2.XMLHTTP.4.0","Msxml2.XMLHTTP.3.0","Msxml2.XMLHTTP","Microsoft.XMLHTTP"];
for (var i = 0; i < MSXML.length; i++) {
try {
return new ActiveXObject(MSXML[i]);
} catch (e) {
debug_log += "#XMLHTTP TYPE:"+MSXML[i]+"#\n";
}
}
return null;
};
}
xmlHttp = new XMLHttpRequest();
}

function startRequest() {
createXMLHttpRequest();
xmlHttp.onreadystatechange = handleStateChange;
xmlHttp.open("GET", xmlUrl, true);
//xmlHttp.setRequestHeader("If-Modified-Since", xmlHttp.lastModified);
xmlHttp.send(null);
}

function handleStateChange() {
if(xmlHttp.readyState != 4){
//var iTimeoutId = setTimeout(function(){Logger("#Loading#\n");}, 3000);
//clearTimeout(iTimeoutId);
}


if(xmlHttp.readyState == 4) {
if(xmlHttp.status == 200) {
loadXmlData();
is_load = true;
} else {
debug_log += "#http status:"+xmlHttp.status+"#\n";
}
ShowContent();
//Logger(debug_log);
} else {
debug_log += "#readyState:"+xmlHttp.readyState+"#\n";

}
}

function loadXmlData(){
var xmlDoc = xmlHttp.responseXML;
var news_allItem = xmlDoc.getElementsByTagName(news_item_tag_name);
debug_log += "#XML TEXT:"+xmlHttp.responseText+"#\n";
for(var i = 0; i < news_allItem.length; i++){
var oNews = new News();
var item_list = news_allItem[i].childNodes;
for(var k = 0; k < item_list.length; k++){
for(var j = 0; j < news_item_att.length; j++){
if(item_list[k].tagName == news_item_att[j]){
oNews.set(news_item_att[j],item_list[k].childNodes[0].nodeValue);
}
}
}
news_item_list.push(oNews);
}
}

function Logger(msg){
if(document.getElementById("log") == null){
var oDiv = document.createElement("div");
//var oAtt = document.createAttribute("id","log");
//oDiv.setAttributeNode(oAtt);
oDiv.setAttribute("id","log");
document.body.appendChild(oDiv);
}
document.getElementById("log").innerHTML += msg;
}

window.onload = function(){
startRequest();
}
<!-----------news.js-------------->



<!-----------news.html-------------->
<html xmlns="
http://www.w3.org/1999/xhtml">
<head>
<title>Parsing XML Responses with the W3C DOM</title>
<script type="text/javascript">
var xmlUrl = "news.xml";
var news_item_tag_name = "NEWS_ITEM";
var news_item_att = ["ID","TITLE","CONTENT"];
var news_item_list = new Array();
var is_load = false;
</script>


<script type="text/javascript">
function getContent(){
var content = "<table border='1'>";
if(news_item_list.length == 0){
content += "<tr><td>No data</td></tr></table>";
return content;
}


content += "<tr><th>ID</th><th>TITLE</th><th>CONTENT</th></tr>"
for(var i = 0; i < news_item_list.length; i++){
content += "<tr>";
content += "<td>" + news_item_list[i].get("ID") + "</td>";
content += "<td>" + news_item_list[i].get("TITLE") + "</td>";
content += "<td>" + news_item_list[i].get("CONTENT") + "</td>";
content += "<tr>";
}
content += "</table>";
return content;
}


function ShowContent(){
if(document.getElementById("content") == null){
var oDiv = document.createElement("div");
oDiv.setAttribute("id","content");
document.body.appendChild(oDiv);
}
document.getElementById("content").innerHTML = getContent();
}
</script>


<script type="text/javascript" src="news.js"></script>


</head>
<body>
</body>
</html>
<!-----------news.html-------------->



<!-------------Explanation 1------------------------>
<script type="text/javascript">
var xmlUrl = "news.xml";
// xml的相對位置, 亦可輸入url的absolute位置.但不能跨Domain,跨Domain需實作proxy
var news_item_tag_name = "NEWS_ITEM"; // 想要截取的tag name
var news_item_att = ["ID","TITLE","CONTENT"]; // xml裡NEWS_ITEM下的所有NODES
var news_item_list = new Array(); // 把所有的NEWS ITEM放進ARRAY裡
var is_load = false; // 是否已把XML資料LOAD完,用作PRE Lodding,這裡沒實作
</script>
<!-------------Explanation 1------------------------>



<!-------------Explanation 2------------------------>
Array.prototype.indexOf = function (vItem) {
for (var i=0; i < this.length; i++) {
if (vItem == this[i]) {
return i;
}
}
return -1;
}
// 由於Array裡沒有像indexOf的function, 故自建indexOf function. 而在javascript裡,
// function可看作object,而每個object裡都包含prototype這個屬性.
// 當object被new時, prototype所關聯的function就會關聯到object裡. 即變成object的fucntion.


var xmlHttp; // XMLHttpRequest的instance放的變量.
var debug_log = "Start
Debug@@@@@@@\n"; // 記錄debug message的變量

function News() { // News item物件,將對應XML資料的NEWS_ITEM
this.items = new Array();
}
News.prototype.set = function(key,value){
// news_item_att看作所有News物件的properties, 這裡利用array的特性自定義setter
this.items[news_item_att.indexOf(key)] = value;
}
News.prototype.get = function(key){ // News裡properties的getter
return this.items[news_item_att.indexOf(key)];
}
<!-------------Explanation 2------------------------>



<!-------------Explanation 3------------------------>
function createXMLHttpRequest() {
if (window.ActiveXObject && !window.XMLHttpRequest) {
window.XMLHttpRequest = function() {
// 這裡override window的XMLHttpRequest object,後面使用就不用管它是甚麼Browser( ie or firefox )
var MSXML = ["Msxml2.XMLHTTP.5.0","Msxml2.XMLHTTP.4.0","Msxml2.XMLHTTP.3.0","Msxml2.XMLHTTP","Microsoft.XMLHTTP"];
// 為了相容IE6前的所有BROWSER版本, 所以用FOR LOOP去LOOKUP每個版本ActiveXObject的XMLHTTP.當找到就直接RETURN
for (var i = 0; i < MSXML.length; i++) {
try {
return new ActiveXObject(MSXML[i]);
} catch (e) {
debug_log += "#XMLHTTP TYPE:"+MSXML[i]+"#\n";
}
}
return null;
};
}
xmlHttp = new XMLHttpRequest();
// 由於上面已override window裡的XMLHttpRequest object,所以這裡直接使用
}
<!-------------Explanation 3------------------------>



<!-------------Explanation 4------------------------>
function startRequest() {
createXMLHttpRequest();
xmlHttp.onreadystatechange = handleStateChange;
// onreadystatechange為event handler, 所以當readyState 等於 0,1,2,3,4時,便會call handleStateChange()這個function
// readyState = 0 為uninitialized, 1 為loading, 2 為loaded, 3為interactive, 4為complete
xmlHttp.open("GET", xmlUrl, true);
//xmlHttp.setRequestHeader("If-Modified-Since", xmlHttp.lastModified);
xmlHttp.send(null);
}

function handleStateChange() {
if(xmlHttp.readyState != 4){
//var iTimeoutId = setTimeout(function(){Logger("#Loading#\n");}, 3000);
//clearTimeout(iTimeoutId);
}


if(xmlHttp.readyState == 4) { // 上面己對readState的各種狀態說明了, 這裡當readyState等於4即已把xml載入完成
if(xmlHttp.status == 200) {
// xmlHttp.status為http status, 200為成功,404為not found
loadXmlData();
// call 自建的 loadXmlData() function. 把xml裡的NEWS_ITEM放進news_item_list ARRAY裡
is_load = true;
} else {
debug_log += "#http status:"+xmlHttp.status+"#\n";
}
ShowContent();
// 當所有NEWS_ITEM載入news_item_list, call 自建的ShowContent() function顯示html
//Logger(debug_log);
} else {
debug_log += "#readyState:"+xmlHttp.readyState+"#\n";

}
}
<!-------------Explanation 4------------------------>



<!-------------Explanation 5------------------------>
function loadXmlData(){
var xmlDoc = xmlHttp.responseXML;
// 把xml document給xmlDoc變量
var news_allItem = xmlDoc.getElementsByTagName(news_item_tag_name);
// 此變量news_item_tag_name 為 "NEWS_ITEM", 所以此句LOOKUP所有NEWS_ITEM node.
debug_log += "#XML TEXT:"+xmlHttp.responseText+"#\n";
for(var i = 0; i < news_allItem.length; i++){
var oNews = new News();
var item_list = news_allItem[i].childNodes;
// 這裡將所有NEWS_ITEM裡的node放到item_list變量
for(var k = 0; k < item_list.length; k++){
for(var j = 0; j < news_item_att.length; j++){
// news_item_att Array儲存NEWS_ITEM裡的所有node的tag name
if(item_list[k].tagName == news_item_att[j]){
oNews.set(news_item_att[j],item_list[k].childNodes[0].nodeValue);
// 將所有properties以setter放進oNews object裡
}
}
}
news_item_list.push(oNews);
// Array裡的push function可看作Array為stack, push data在Array的最後
}
}

function Logger(msg){ // Logger用作debug
if(document.getElementById("log") == null){
var oDiv = document.createElement("div"); // 這裡建立div element.
oDiv.setAttribute("id","log");
// 設定div裡id property為"log"
document.body.appendChild(oDiv);
// 把div element加入html 的body裡, body tag 必須先在html裡建立
}
document.getElementById("log").innerHTML += msg;
}

window.onload = function(){
// 當load完整份html,然後呼叫startRequest()去load xml及顯示html content
startRequest();
}
<!-------------Explanation 5------------------------>


<!-------------Explanation 6------------------------>
<script type="text/javascript">
function getContent(){
// 此function可看到html的template,把資料送給ShowContent() function顯示
var content = "<table border='1'>";
if(news_item_list.length == 0){
content += "<tr><td>No data</td></tr></table>";
return content;
}


content += "<tr><th>ID</th><th>TITLE</th><th>CONTENT</th></tr>"
for(var i = 0; i < news_item_list.length; i++){
content += "<tr>";
content += "<td>" + news_item_list[i].get("ID") + "</td>";
content += "<td>" + news_item_list[i].get("TITLE") + "</td>";
content += "<td>" + news_item_list[i].get("CONTENT") + "</td>";
content += "<tr>";
}
content += "</table>";
return content;
}


function ShowContent(){ // 當readState為4時,load完xml後,呼叫此function顯示data
if(document.getElementById("content") == null){
var oDiv = document.createElement("div");
oDiv.setAttribute("id","content");
document.body.appendChild(oDiv);
}
document.getElementById("content").innerHTML = getContent();
}
</script>
<!-------------Explanation 6------------------------>


不要誤會這編是介紹關於 Prototype Javascript Framework,
由於官方的文檔很少,
因此研究 Prototype 的最好方法是直接參看主要的源文檔 prototype.js ,
以下有一編相關的網上教學, 由於寫得不錯, 就不再重寫一編:
https://compdoc2cn.dev.java.net/prototype/html/prototype.js.cn.html
官方網站為:
http://prototype.conio.net/

沒有留言: