July, 2005 > 所有文章列表

Flash Remoting 教學 2 – Pageable RecordSet

In tutorials   July 31, 2005 - 4:23 pm

◆ 前言

資料分頁(pageable recordset)一直是網頁應用程式中非常重要的一項技巧,通常工程師不會將大量的資料一次傳回給client,而是將資料先預存在server端,等client需要時再送過去。

在flash remoting 中也同樣提供了這種功能,但以往只有使用Macromedia版的remoting adapter才能正確發揮功能,也就是說如果不是 coldfusion, jrun, remoting for .net之類的正牌產品,這部份功能要不就是不完整,要不就是完全爛掉。

Amfphp 是php版的flash remoting, open source並完全免費。過去amfphp在pageable recordset上的表現可說是非常糟糕,大部份人從來都無法真正實作成功;但隨者patrick加入amfphp開發團隊後,最近amfphp的表現可說是突飛猛進,快速逼進商業產品的水準。

目前最新版的amfphp是 pre-v1.0 milestone 2 (MS2),也就是v1.0正式發表前的最後一個beta,在這個版本裏終於開始有非常完整的pageable recordset功能。

有鑑於MS2 版中的pageable recordset實作手法有大改變,而目前網路上所有的教學文件都尚未更新,因此本文將以實際的例子介紹如何使用amfphp 的pageable recordset,並詳細解釋它的運作原理與 flash 端 RecordSet 配合的方式。

◆下載與安裝

請參考第一篇教學。

◆pageable recordset運作原理

在正式開始前我們先花點時間瞭解一下到底pageable recordset是怎麼個運作法。

首先,pageable recordset的用途在於,如果你在server端有100筆資料,通常不會全部傳回client端,而是以分頁的方式,例如每頁20筆資料共分五頁,然後當user按「下一頁」時才依序傳回需要的資料。

一般來說要做到pageable recordset有兩種方式:

1、 先在server端將100筆資料 query出來,放在session中備用,然後傳回需要的部份。

2、只先query出總資料量為100筆,然後用類似 mysql limit的語法一次撈20筆出來回傳。

第1種做法好處是可以減少與database連線的次數,並且資料存放於記憶體中理論上存取速度會非常快,但缺點則是session會吃掉大量資源,如果許多user同時連線並個自保有自已的session data,那 app server往往會撐不住而當掉。

第2種做法的好處則是不會消耗大量server資源,它在有需要時才去跟db撈出資料,但缺點也就是要頻繁的與db連線,此時良好的db connection management (pooling)就很重要。

以afmphp來說,它採取的是第2種方式,當 flash client一連線時,它會先query出資料的總筆數並回傳給flash, 之後就由flash內的RecordSet class來負責決定分頁方式與每次的取回資料量。

因此從這個描述中,大家可以感覺到整個pageable recordset的運作原理是需要下面兩者交互配合才能達成。

1、amfphp的分頁query功能
2、flash RecordSet 的分頁取回功能

有了基本認識後,接下來我們就開始實作吧。

◆flash 製作

首先我們製作好flash client 端的檔案,這個程式很簡單,只要拉一個 DataGrid與button元件到畫面上即可。

程式碼的部份如下:

Actionscript:
  1. //remoting related
  2. import mx.remoting.Service;
  3. import mx.remoting.PendingCall;
  4. import mx.remoting.RecordSet;
  5. import mx.remoting.debug.NetDebug;
  6. import mx.rpc.RelayResponder;
  7. import mx.rpc.ResultEvent;
  8. import mx.rpc.FaultEvent;
  9. import mx.events.*;
  10. //start debugging so we can see debug info in NCD.
  11. NetDebug.initialize();
  12. //setting gateway
  13. var gateway:String = "http://localhost/pageableRecordSet/gateway.php";
  14. respGeneral = new RelayResponder(this, "contactReceived", "contactFailure");
  15. _service = new Service(gateway, null, "PRecordSet", null, respGeneral);
  16. //
  17. var pc:PendingCall = _service.getFullList(["Name", "Region", "Code"]);
  18. //
  19. dg.columnNames = ["Code", "Name", "Continent"];
  20. //
  21. function contactReceived(re:ResultEvent):Void{
  22.     var tmp:RecordSet = RecordSet(re.result);
  23.     tmp.setDeliveryMode("page", 10, 2);
  24.     dg.dataProvider = tmp;
  25. };
  26. //
  27. btnRefresh.clickHandler = function(){
  28.     dg.removeAll();
  29.     var pc:PendingCall = _service.getFullList(["Name", "Region", "Code"]);
  30. }

◆php端製作

這個例子中我們是使用 windows + apache 2 + php 5.04 + mysql 4.1 + amfphp ms2,但基本上在其它平台或程式版本也應該沒問題(注意amfphp至少要是ms1以上才行)

首先我們在apache的web folder (/htdoc)下建一個專案資料夾:
C:\Program Files\Apache Group\Apache2\htdocs\pageableRecordSet

然後在pageableRecordSet下建一個 service 資料夾
C:\Program Files\Apache Group\Apache2\htdocs\pageableRecordSet\services

在這個新建的資料夾內,建立一個新的php程式,名稱為 PrecordSet.php
它的程式碼如下:

PHP:
  1. <?
  2. class PRecordSet
  3. {
  4.     var $dbhost = "localhost";
  5.     var $dbname = "test";
  6.     var $dbuser = "root";
  7.     var $dbpass = "";
  8.  
  9.     function PRecordSet()
  10.     {
  11.         $this->conn = mysql_pconnect($this->dbhost, $this->dbuser, $this->dbpass);
  12.         mysql_select_db( $this->dbname );
  13.  
  14.         $this->tablename = "country";
  15.  
  16.         $this->methodTable = array(
  17.             "getColumnNames" => array(
  18.                 "description" => "Return column names from table",
  19.                 "access"      => "remote",
  20.                 "arguments"   => array("")
  21.             ),
  22.             "getFullList" => array(
  23.                 "description" => "Return a full list of table records, pass as argument an array of columns to be returned",
  24.                 "access"      => "remote",
  25.                 "arguments"   => array("arrayOfColumns"),
  26.                 "pagesize"    => "10",
  27.             ),
  28.         );
  29.  
  30.     }
  31.  
  32.  
  33.     function getColumnNames()
  34.     {
  35.         $query = mysql_query(sprintf('SHOW COLUMNS FROM %s', $this->tablename));
  36.         return $query;
  37.  
  38.     }
  39.     /*
  40.     前面有幾個參數都沒關係,但
  41.     最後兩個參數最重要:$offset, $limit 會被amfphp自動呼叫
  42.     因此,整個sql語法也要用sprintf來寫。
  43.     */
  44.     function getFullList($ar_col, $cursor, $limit)
  45.     {
  46.         //count it first
  47.         $this->countQuery = "SELECT COUNT(*) AS recordCount FROM country";
  48.         //
  49.         return mysql_query(sprintf("select * FROM %s ORDER BY Name ASC Limit %d, %d", $this->tablename,$cursor,$limit));
  50.     }
  51.     /*
  52.     所有需要 分頁 的function, 都要加上這個 _count,讓amfphp自動去呼叫
  53.    
  54.     */
  55.     function getFullList_count(){
  56.         $q = mysql_query($this->countQuery);
  57.         $row = mysql_fetch_assoc($q);
  58.         return $row['recordCount'];
  59.     }
  60. }
  61. ?>

上面的程式碼中,比較重要的幾點如下:
-amfphp端都是以class的型式存在,因此等於是在寫一個 php class。但一開頭的method table 非常重要,裏面有許多關鍵字可設定這個class的運作行為。

-第26行的pagesize => 10 是告訴amfphp 我希望每頁只傳回10筆資料;這是最關鍵的一行設定,但還需其它地方配合。

-第44行開始的getFullList($ar_col, $cursor, $limit)就是要曝露給client端呼叫的remote method, 名稱為 getFullList,裏面有三個參數。前面的$ar_col 是我們要取回的欄位名稱,實際上這裏要放多少個參數都沒關係,視實際需要而定;比較重要的是最後兩個$cursor與 $limit 參數,這兩個參數是amfphp日後進行分頁取回時會自動填入資料的地方,也就是下面query中limit的兩個關鍵字。

其中$cursor 是指目前所在位置,例如第10筆資料,而$limit則是將下來要取回的資料量,例如假設你設定一次要取回2頁,每頁10筆資料,那麼 $limit就會是 20,而整個function call就會是 getFullList(“some columns”, 10, 20),我們在下面的應用中會再解釋一下這部份。

這裏你只要記住:不管前面有幾個參數(例如$ar_col, 最後兩個一定要是 $cursor, $limit)

-第47行也是必備的項目,從query字串中就可以看出它的功能是在 count()全部的資料量,好在第一次連線時回傳給flash端做為日後分頁計算的基礎。

-第49行的query則是最重要的地方,請注意最兩點。

首先是sprintf的使用,這是一個很傳統的function,主要功能是將 %s, %d這樣的變數動態替換成後面的參數。

第二點則是 limit 的使用,在mysql中limit最常被用來進行分頁處理,它的第一個參數是啟始點(例如要從第10筆資料開始),第二個參數是資料量,例如(20筆),因此透過limit我們就可以從第10筆開始取回20筆資料。

-第55行的getFullList_count()則是amfphp新增的實作手法,這個function基本上就是將前面的getFullList加上 _count,而它主要的功能是用來計算query結果的資料量,這點從56-58行的程式碼中就可看出。它利用前面 COUNT(*) AS recordCount 將計算結果存在 recordCount這個欄位中,然後用$row = mysql_fetch_assoc($q) 與 $row['recordCount'] 來取得計算後的總量。

這支程式是amfphp 內部運作時會自動呼叫的,因此一定要有,但它的名稱不一定要是 functionName_count()這樣,可以在method table內增加一個 countMethod => “my_count”來指定要使用的名稱。

◆實際執行

在實際執行前你需要先放點東西到mysql裏才行,請下載本教學的範例檔案,裏面有個 db.sql 檔,它會在mysql內建的test database內新增一個 country table,這就是我們要用的測試資料。

接者請確定你的web server, php, mysql都正常運作,並且也正確安裝了 amfphp ms2,然後 project folder 名稱正確,service folder裏也有剛寫好的 PrecordSet.php。

最後記得打開 NetConnection Debugger 簡稱NCD (Window > Other Panel > NetConnection Debugger)以方便觀察flash與php間的資料傳輸情況。

當一切就續後就可以執行flash 的 ctrl-enter,此時在NCD中會看到下面的畫面:

這張畫面中顯示當flash第一次與amfphp連線執行 getFullList()後,amfphp傳回了一堆資料,其中最重要的就是:

mTotalCount: 57 這行告訴flash你要求的資料總共有57筆。
mRecordsAvailable: 0 目前我還沒傳回任可一筆資料。

當這些資訊回傳給flash 後,接下來就是flash RecordSet的責任去決定該如何取回這57筆資料,請注意我們在flash裏面寫的程式:

Actionscript:
  1. function contactReceived(re:ResultEvent):Void{
  2.     var tmp:RecordSet = RecordSet(re.result);
  3.     tmp.setDeliveryMode("page", 10, 2);
  4.     dg.dataProvider = tmp;
  5. };

這個contactReceived就是當初設定的 remoting callback, 當amfphp傳回資料時,就是由它去負責接收並做後續處理。在這個function裏我們設定了 setDeliveryMode("page", 10, 2), 這是 RecordSet內建的一個指令,透過這句話我們決定接下來的取回方式是”page”,每次10筆資料,並且預先取回2頁。

然後我們使用 dg.dataProvider = tmp,也就是將recordset餵給 datagrid的dataProivder,就可以順利顯示出來。

而這樣設定後最神奇的地方在於,當user捲動到下一頁時,recordset會知道資料還在server,因此自動跑去跟amfphp要求下面兩頁的資料,請看下圖。

請注意圖中,flash自動去呼叫amfphp中的PageAbleResult.getRecords()這支指令,並且傳回三個參數,第一個參數是session id,這個不重要先不管它,第二個參數則是目前 cursor 所在位置,第3個參數則是flash希望先取回30筆資料備用。

但為什麼是30筆呢?當初我們明明設定是先取回2頁啊!原因是”page”模式下,ReocrdSet永遠會自動取回目前所需的頁面,並且pre-fetch 使用者指定的頁面,因此目前cursor在1,它必需先取回自已所在的第1頁,然後預抓回2頁,也就是總共3頁共30筆資料,這就是 1, 30的由來。

之後RecordSet就會視user的捲動情況而決定何時該向amfphp要求更多的頁面,你可以試試捲動datagrid,此時會發現NCD裏出現一狗票的連線與取回記錄,如下圖

注意裏面有新增了幾個 call指令,這就是RecordSet 在進行自動取回,另外也特別注意倒數第二行的release(),當server 端的所有資料都取回後,ReocrdSet 會透過這指令告訴amfphp可以丟棄存放於server端的分頁記錄順便清空記憶體,等於是某種形式的garbage collection。

透過以上的例子,大家應該可以瞭解amfphp的pageable recodset運作原理,理論上來說只要支援dataProvider與 RecordSet 的元件都會自動支援這個分頁功能,例如combobox, listbox, datagrid 等,但tree就不行,因為它只吃xml data,除非你能替RecordSet 加上即時rs2xml的功能。

◆資料取回模式釋疑

RecordSet在取回資料時共有三種模式,分別解釋如下:

-onDemand: 這是預設的模式,也就是一次取回一筆資料,只有當需要時才連線向amfphp提出請求。好處是每次取回的資料量少,反應速度快,缺點則是會頻繁的app server連線,如果網路速度不快反而會因為lag造成使用上的不變。

-page: 以「頁」為單位取回資料,這是最常見的應用方式,它還可搭配另外兩個參數,分別是pagesize(每頁的資料筆數)與numPrefetchPages(預先取回的頁數),以我們的例子來說,setDeliveryMode(“page”, 10, 2)就是設定取回模式為page, 一頁10筆資料,預先取回2頁。

-fetachAll:這個模式會將所有資料一次取回,但可透過pagesize這個參數來預做分頁,預設值是每頁25筆資料。

一般如果會用到分頁功能,通常不二的選擇就是”page” mode,並且視需要決定每頁的筆數與預先取回量。

◆pageable recordset使用注意事項

amfphp的 pageable recordset 設計與使用上不像coldfusion 那樣方便,但那是因為許多背景因素造成,以open source project來講,能有這樣的表現已經是非常令人讚賞,只要你知道日常操作中該避開的陷井就一切ok。

-amfphp無法處理極大量的page,據各討論list上得到的結論,五千筆是一個上限。

-amfphp使用limit 語法,這是mysql獨有的指令,在mssql上可用select top n 的方式達成,但這牽涉到改寫amfphp的程式。

-php端的 functionName()中要記得加上 $cursor, $limit 兩個參數,這是供amfphp內部分頁使用,少了它們分頁就玩完了。

-php端一定要有 functionName_count()這個指令,也就是每個需要分頁的method都要伴隨者一支count method,才能計算正確的資料量回傳給recordset。

-php端的 method table要有 pagesize => “10”這樣的分頁設定,這是amfphp在第一次回傳資料時要參考的數字,如果這裏沒設定,amfphp就會認定你沒有分頁的需求而將全部資料一次都丟回client端了(這也是目前amfphp example 所犯的錯誤,可能是作者忙者改程式碼還沒空修正這個範例)

◆檔案下載

這個教學的範例檔案可按此下戴。程式碼中已加上大量註解,因此應該很容易理解。

如有任何疑問歡迎隨時提出。

comments(15) | by admin

Flash Remoting 教學 – 前言

In tutorials   July 31, 2005 - 4:04 pm

最近在重新整理RIA的教材,把過去一年新完成的教學範例整理成教材型式並加上註解,同時也針對RIA中最重要的連線方式 flash remoting重新撰寫一系列的講義。

這系列的入門教學將包括下列主題:

1、 Flash remoting(AMFPHP) 介紹、安裝與hello world範例
2、 AMFPHP Pageable Recordset 應用
3、 AMFPHP web serivces應用
4、 AMFPHP Authentication & Authorization

目前已完成第1, 2, 4項,隨者病情急速好轉,照目前寫作速度判斷,預估再兩小時應該就會全部寫完 :-)

note:有朋友來信問到,google上已經可查到一票的remoting教學範例,幹麻浪費時間再寫一次?答案很簡單,一來這是做為下半年度RIA班學生的講義,二來是amfphp 1.0 在實作上許多地方都已經改掉,目前網路上大部份教學與範例都還是針對 0.53 or 0.91版,因此許多範例都已經不能執行了。

所有教學皆內含範例檔案與程式註解,歡迎下載使用,但請注意版權,如轉戴請遵守Creative Common s 2.5的規範。

Add comment | by admin

all about the “about”

In General, blog related   July 31, 2005 - 1:34 pm

ok, 經過七個月的delay, 本站的 about終於有內容了 :P

| by admin

書寫與思考

In General   July 31, 2005 - 12:12 pm

有趣的想法:

-The world is run by people who write.

-Clear writing leads to clear thinking.

-You don’t know what you know until you try to express it.

的確許多時後大腦思考的速度太快,許多點子飛來飛去,但要進入實作階段前,一定得想辦法把這些東西整理下來,此時就不能只靠腦子思考,而必需試者將想法寫下來,書寫的過程就是一種整理,透過表達的過程來沉澱,往往最後會有意想不到的好效果。

原文
(note: 很長的英文ppt簡報)

Add comment | by admin

ipod為何迷人?

In General   July 31, 2005 - 1:03 am

同樣是從 joel 的那篇文章看來的。

ipod的美、好、吸引人是大家都知道的是,但背後的原因呢?joel 認為事:

Apple made a decision based on style, in fact, iPod is full of decisions that are based on style. And style is not something that 100 programmers at Microsoft or 200 industrial designers at the inaptly-named Creative are going to be able to achieve, because they don't have Jonathan Ive, and there aren't a heck of a lot of Jonathan Ives floating around.

note: Jonathan
是apple 設計部門的資深副總,imac, ipod, g4 tube, powerbook 17都出自他的手筆。

所以到底ipod成功的策略是什麼?

why ipod are good ?

Style. (ipod 的流線、一體成型外觀、為了這種外觀而捨棄的電池室)

Happiness. (操控感,feedback)

Emotional appeal.(整體感受)

這些種種都讓我想到不正是 Experience Design 嗎?apple提供的已經不是單純手上拿到的實體產品,他在賣的是一種型而上的東西,一種由許許多多小地方巧思堆積起來的結晶,一種無可取代的使用者經驗。

這也讓我突然理解,為何市面上越來越多仿ipod、imac, vaio notebook....的產品,但實際用起來就是沒那個感覺。外觀、造型、顏色都抄到了,但使用的手感,整體的感覺就是少了那種巧思跟貼心,工藝設計的精華真的不是兩三天就能抄去的,裏面需要太多的天份、創意、構思與努力。

而有天份與創意的高手,往往又需要有財力的公司與良好的環境才能請的動,這或許就是為何copycat總是差了一大段的原因吧。

they can never hit the high note, they just don't have it. geez.

Add comment | by admin

Previous Posts

mobile phone