Google Code Prettify

2015年5月28日 星期四

報表結果要不要關連到組織主檔?

假設有個需求,每個月要產生月報給需求單位,該月報是統計各部門的生產資料,如上的 table layout,table Report 裡的 OId 欄位是否應該關連到組織主檔 table Organization 的 OId 欄位? 可能的狀況如下:

  1. 組織的編號絕不會重複,也就是說,就算組織被裁了,該編號也不會給別的部門使用,就像身分證字號一樣,永遠不會重複使用,這時候可以如上所示的加上關連。
  2. 如果組織主檔裡的編號有可能重複使用,當然就不該有此關連,否則過去的報表可能會錯亂,甚至 table Report 裡就不要有  OId 欄位,以免誤解 (Key 要改為 Year + Month + OName)。
  3. 組織的編號不會重複,但是當某部門改名稱時,可能編號不會變,這時可以加上關連,但是 table Report 要如上所示,加上組織名稱的欄位,將報表產生時的名稱記下來。

最建議的方式,還是讓組織編號不重複使用,不過,這通常不會是 IT 人員可以自行決定。

2015年5月26日 星期二

byte ordering functions

前一篇「Linux Network Programming - 初體驗」目的很單純,就是快速的對 socket 有基本的認識,這一篇要說明 byte ordering 的問題及解決辦法!

在一般人的直覺裡,如果一個 short 有兩個 byte,在記憶體中應該就是由左到右放,就像寫字一樣,由左到右寫,那麼就會是高位元放在左、低位元放在右,這就是 big-endian byte order。可偏偏並非所有的電腦都是這麼放的! 有些電腦是低位元放在左,高位元放在右,這稱為 little-endian byte order。而 socket 的傳輸資料不管原來是什麼型別,傳輸過程一定會是以 byte array 的方式傳輸,接收端接收後,再依原先約定好的格式還原成各種型別。在網路上傳輸的 byte array 順序,是規範成 big-endian。

上圖是「Unix Netowrk Programming」的3.4節中的一個插圖,用來說明 big-endian 和 little-endian 的差別。在開始寫程式前,一定要先了解這些基本的知識,並且知道 C 提供了那些函式來解決這個問題!

回過頭去看前一篇的 server 程式,有如下一段程式碼:
22 servaddr.sin_family = AF_INET;
23 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
24 servaddr.sin_port = htons(1513);
23、24行中呼叫的 htonlhtons 就是本篇要介紹的,他們會將如 1513、INADDR_ANY 這樣的常數值,轉成合乎網路傳輸所需要的值 (轉換後如果值改變的話,基本上就是高位元、低位元排列順序變了造成的!)。
兩個函式是 host to netowrk 的縮寫,l 和 s 就是 long 和 short,htonl 用來處理 32 位元的值,htons 用來處理 16 位元的值。要特別注意,就算是在 64 位元電腦上,htonl 還是只用來處理 32 位元的值!




2015年5月23日 星期六

Linux Network Programming - 初體驗

十幾年沒有寫 C 了,這可以說是重新學習,底下的程式是改寫自 W.Richard Stevens 的名著 - UNIX Network Programming Volume 1,改寫的原因有二:
(1) 在我的開發環境 scientific linux 7.0 上沒辦法正常 compile,可能是因為 Stevens 的程式是在 UNIX 上寫的,與 linux 上略有不同;
(2) Stevens 用 #define 將許多常用的函式重新定義,對於初學者來說,反而會被混淆,我將這些函式還原為原始函式。
這個程式有 client 和 server,server 先啟動,等待 client 連線,待 client 連線後回覆現在的系統時間,client 將收到的值印出。第一個程式是 server,第二個程式是 client。這裡會詳細說明每個函式,因為… 我是初學者...  XD
 1 #include <sys/socket.h>
 2 #include <sys/types.h>
 3 #include <stdio.h>
 4 #include <unistd.h>
 5 #include <netinet/in.h>
 6 #include <string.h>
 7 #include <arpa/inet.h>
 8 #include <time.h>
 9 
10 const int MAXLINE = 100;
11 const int LISTENQ = 1024;
12 
13 int main(int argc, char **argv) {
14     int listenfd, connfd;
15     struct sockaddr_in servaddr;
16     char buff[MAXLINE];
17 
18     time_t ticks;
19     listenfd = socket(AF_INET, SOCK_STREAM, 0);
20 
21     bzero(&servaddr, sizeof(servaddr));
22     servaddr.sin_family = AF_INET;
23     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
24     servaddr.sin_port = htons(1513);
25 
26     bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
27     listen(listenfd, LISTENQ);
28 
29     for( ; ; ) {
30         connfd = accept(listenfd, (struct sockaddr *) NULL, NULL);
31 
32         ticks = time(NULL);
33         snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
34         write(connfd, buff, strlen(buff));
35 
36         close(connfd);
37     }
38 
39 }
  • 第 19 行: 建立一個 socket 連線,不管 client 或 server 程式,一開始都會呼叫這個函式,函式所需要的三個參數說明如下:
    • 第一個參數: AF_INET 表示要建立網際網路的 IPv4 socket 連線,除此之外還可以填入 AF_INET6 (IPv6) 或 AF_UNSPEC (同時可以用 IPv4 及 IPv6),socket 函式及這幾個常數是定義在 socket.h 標頭檔裡,這也是為什麼第一行要引入這個標頭檔的原因,大部份的 socket 相關函式、常數都定義在這個標頭檔。
    • 第二個參數: 可以傳入的值有兩個 - SOCK_STREAM 及 SOCK_DGRAM,要建立 TCP 連線時傳入 SOCK_STREAM,要建立 UDP 連線時傳入 SOCK_DGRAM。
    • 第三個參數: 通常就是傳入 0,socket 將會選擇最適合的通訊協定,也就是第二個參數的說明,至於不傳入 0 時,用在什麼地方? 這個以後再說 (現在我也不知道)。
    • 傳回值: 錯誤時傳回 -1,正確傳回 0,C 語言的習慣就是正確傳回 0,錯誤則傳回錯誤代碼。
  • 第 21 行: bzero 是將指定的位指空間全部清為 0,這不是 ANSI C 的標準函式,ANSI C 的標準函式是 memset,不過 bzero 大多數的平台也都有支援,定義在 string.h 裡。傳入的兩個參數如下:
    • 第一個參數: 要清為 0 的位址。
    • 第二個參數: 位址空間的長度。
  • 第 22~24 行: 設定 server address 的值,這是在第 15 行宣告的變數,struct sockaddr_in 這個結構是為了在 bind 時傳入一些值,這三個值說明如下:
    • sin_family: 如 socket 的第二個參數說明的一樣,這是指要使用 TCP 通訊協定。
    • sin_addr.s_addr: 這裡指定為 INADDR_ANY 是使得任何位址連進來的 socket 連線都可以被接受,在設定前呼叫的 htonl 函式是為了讓值與平台無關,不同的 OS 有些字元排列順序高位元和低位元不一定相同,透過這個函式可以去除這種平台相依性,都轉為網路所規範的順序。
    • sin_port: 指定要 bind 到那個 port,htons 這個函式和 htonl 是一樣的,都是用來去除平台相依性,差別在於 htons 是用在 16 bits 的變數, htonl 是用在 32 bits 的變數。
  • 第 26 行: bind 是將 socket 和 sockaddr 繫結起來的函式,表示第 19 行建立的 socket 連線,將會是允許任何位址連線,而將傾聽的 port 是 1513。
  • 第 27 行: 開始傾聽,最大連線數是 LINTENQ 所定義的值,即 1024 條連線。
  • 第 29 行: 這裡建立一個無窮迴圈,所以這個 server 會一直執行,直到使用者按 ctrl-C 為止。
  • 第 30 行: server  會停在 accept 這裡等待 client 連線,參數說明如下:
    • 第一個參數: 透過傳入傾聽描述子 listenfd,將第 26、27 行傾聽的相關數據傳入。
    • 第二、三個參數: 待我弄懂了再補充  … XD
    • 傳回值: 建立連線後,傳回連線描述子 (connected descriptor),這個描述子會用來與新的 client 連線進行通訊。
  • 第 32 行: 取得目前的系統時間,這個 time 函式定義在 time.h 標頭檔裡。
  • 第 33 行: snprintf 函式定義在 stdio.h 裡,和 sprintf 的最大差別在於 snprintf 的第二個參數指出了第一個參數的空間大小,可防止實際產生出來的字串長度超過第一個參數所宣告的空間。
  • 第 34 行: 將結果寫出給 client,這裡可以看到第一個參數即是 accept 的傳回值,這樣就可以通知系統到底是要將資料傳給那個 client。
  • 第 36 行: 關閉與 client 間的 socket 連線。






 1 #include <stdio.h>
 2 #include <sys/socket.h>
 3 #include <sys/types.h>
 4 #include <netinet/in.h>
 5 #include <strings.h>
 6 #include <arpa/inet.h>
 7 
 8 const int MAXLINE = 100;
 9 
10 void err_sys(const char* x, ...)
11 {
12     perror(x);
13     //exit(1);
14 }
15 
16 void err_quit(const char* x, ...)
17 {
18     perror(x);
19     //exit(1);
20 }
21 
22 int main(int argc, char **argv)
23 {
24     int sockfd, n;
25     char recvline[MAXLINE + 1];
26     struct sockaddr_in servaddr;
27 
28     if (argc != 2) {
29         err_quit("usage: a.out <IPaddress>");
30         return 1;
31     }
32 
33     if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
34         err_sys("socket error");
35         return 1;
36     }
37 
38     bzero(&servaddr, sizeof(servaddr));
39     servaddr.sin_family = AF_INET;
40     servaddr.sin_port = htons(1513);
41     if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
42         err_quit("inet_pton error for %s", argv[1]);
43 
44     if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
45         err_sys("connect error");
46 
47     while ((n = read(sockfd, recvline, MAXLINE)) > 0) {
48         recvline[n] = 0;
49         if (fputs(recvline, stdout) == EOF)
50             err_sys("fputs error");
51     }
52 
53     if (n < 0) {
54         err_sys("read error");
55         return 1;
56     }
57 
58     return 0;
59 }
  • 第 41 行: client 程式執行時,要帶入 server 的 IP,inet_pton 函式即是將 IP 的文字格式轉成接下來要呼叫的 connect 所需要的 binary 格式。
  • 第 44 行: 呼叫 connect 連線到 server。
  • 第 47 行: 使用 read 讀取 server 傳回的值,三個參數說明如下:
    • 第一個參數: socket 描述子,第 33 行建立 socket 連線時記錄下來的數值。
    • 第二個參數: 讀取的值要放入的空間。
    • 第三個參數: 放入的空間的最大長度值。
  • 第 49 行: 將結果寫出到標準輸出裝置。
測試時,先執行 server 再執行 client,client 端顯示如下結果:
【日劇: Second Love】
在台灣播出時改名為「愛上女老師」,看這部日劇前,沒特別注意過深田恭子原來那麼偉大,而且都年過三十了,還那麼可愛,算是不簡單,許多人上了年紀還裝可愛,可是會被吐槽的,但是她不會。這部戲為什麼只有七集? 是因為收視率太低嗎? 這就不曉得了,但是除了結局太老套外,倒是還不錯看。


2015年5月21日 星期四

在 linux 下寫 C 程式 (使用 eclipse/CDT)

十幾年沒有寫 C/C++ 程式了,重操舊業,還由 Windows 平台換到 Linux 平台,實在很生疏,來寫個 hello world 吧 ~ XD

在 Linux 中下載並安裝好 eclipse 後,於 eclipse 的「Help/Eclipse Marketplace」選單中,安裝 CDT,如下圖所示:


有沒有更好用的工具呢? 這個我就不清楚了,最近三年都在寫 Java,習慣 eclipse 介面了!

安裝好 CDT 後,就可以開始寫 C/C++ 程式了,點選選單「File/New/C Project」,如下圖,輸入的 Project name 會成為 compile 出來的執行檔的檔名,就依 Linux 下執行檔的習慣,用小寫 … (大寫也可以),然後按【Finish】。


接著我們要建立一個 hello.c 的程式檔,所以在 hello 這個專案名稱上按滑鼠右鍵,選「New/Source File」,於 Source file 欄輸入 hello.c,下面的選單 Template 可以選擇 C 或 C++  程式,先來個簡單的,所以選「Default C source template」,如下圖:


接下來,就如大家熟知的,開啟 hello.c.輸入如下程式 …


在執行前,要先設定 Main 所在的程式檔及相關的參數,如下圖,在專案 hello 上按滑鼠右鍵,選「Run As/Run Configurations」。


如下圖,按【Browse】選擇檔案…


我是在 /home/steven/workspace 建立專案的,所以 hello.c 就會產生在 /home/steven/workspace/hello 目錄下,如下圖:


選好後按【確定】,會回到前一個畫面,「C/C++ Application」欄位已填入選擇的檔案名,然後按【Apply】,再按【Close】,這樣就設定好了。再來就可以執行,按上方選單中的「Run/Run」,就會產生結果。如果要在命令列下執行,可以如下圖,到 /home/steven/workspace/hello/Debug 目錄下,可以看到執行檔,執行方式如下。





2015年5月16日 星期六

Journal Comments

Journal Comments」是 Clean Code 書中的一個小章節 (Robert C. Martin 著),原文如下,大意是,以前沒有版本控管軟體的年代,程式員會在程式的一開頭寫上一段註解,用來說明程式的變更歷程,現在有了版本控管軟體 (svn、git...),只要在將程式碼 commit 到版本控管軟體前,寫下程式做了什麼變更,之後就可以由版本控管軟體的 log 或 history 裡,輕易的查到各個版本,還可以輕易的比對各版本間的差異,比起這些註解好用且實際,既然有了這麼方便的工具,就將這些註解『全部』拿掉吧! 因為這些註解只會讓程式顯的雜亂無章!

(或許我有點過度解釋了原文,但是,應該沒有曲解原文的意思吧?)

----- 以下為原文 -----
Sometimes people add a comment to the start of a module every time they edit it. These comments accumulate as a kind of journal, or log, of every change that has ever been made. I have seen some modules with dozens of pages of these run-on journal entries.


Long ago there was a good reason to create and maintain these log entries at the start of every module. We didn’t have source code control systems that did it for us. Nowadays, however, these long journals are just more clutter to obfuscate the module. They should be completely removed.


2015年5月10日 星期日

BCD pack & unpack

在程式裡的 I/O 和 CPU 計算比起來,速度差距非常大,網路傳輸又是 I/O  中比較慢的一種,所以,常可見到網路傳輸的資料,會先經過壓縮後再傳輸,接收端接到後再解壓縮。BCD 是一種常用的壓縮技術,當要傳輸的資料中,某一段確定是數字,即可將該段的資料以 BCD 壓縮後傳輸,例如:
123456789665,或許它代表著12點34分56秒789毫秒665奈秒,如果不壓縮直接傳就是 12 個 bytes,BCD 則只會有 6 個 bytes,為什麼? 說明如下:
  1. 一個 byte 有 8 個 bits,用來存數字,實際上只會用到後面 4 個 bits,前面的 4 個 bits 永遠為 0000,例如 5 的 8 個 bits 會是 00000101 (二進位表示法),9 的 8 bits 為 00001001。
  2. BCD 用一個 byte 來存兩個數字,前 4 個 bits (高位元) 存一個數字,後 4 個 bits 再存一個數字,所以,如果有兩個數字 95,用 BCD 儲存就會是 10010101,前 4 個 bits 是 9,後 4 個 bits 是 5。
底下是 BCD 的程式:

public class BCD {
 static public byte[] pack(long number) {
  byte hi, lo;
  String s = String.valueOf(number);
  byte[] data = s.getBytes();
  byte[] bcd = new byte[s.length() / 2];
  for (int i = 0; i < bcd.length; i++) {
   hi = (byte)(data[i * 2] << 4);
   lo = (byte)(data[i * 2 + 1] & 0x0f);
   bcd[i] = (byte)(hi | lo);
  }
  return bcd;
 }
 static public long unpack(byte[] pack, byte[] data) {
  byte hi, lo;
  long number = 0L;
  for (int i = 0; i < pack.length; i++) {
   hi = (byte)((pack[i] >> 4) & 0x0f);
   lo = (byte)(pack[i] & 0x0f);
   data[i * 2] = hi;
   data[i * 2 + 1] = lo;
   number = number * 100 + (hi * 10) + lo;
  }
  return number;
 }
}

應該很容易看的懂,pack 就是將正常的資料壓縮成 BCD 格式,unpack 是將 BCD 格式的資料還原。測試程式如下:
要被壓縮的資料為 number,值為 123456789665,參考下面的輸出,可以看到 BCD 長度只有 6 bytes,其 hex 值即是我們要傳輸的值; 把 BCD 資料解壓縮,即可回 number 的值。
bcd len: 6
bcd value: [B@439f5b3d
bcd value (hex): 123456789665
data len: 12
data value: [B@685f4c2e
data value (hex) 010203040506070809060605
number: 123456789665
要特別注意的是,上面 unpack 程式中,高位元的資料在向左 shift 4 位到低位元後,一定要再 & 0x0f,將高位元清為 0,因為 shift 時,如果最高的 bit 為 1,往左 shift 4 位,高位的 4 個 bits 會全部變成 1,例如最前面例子舉的 95,二進位是 10010101,5 是存在低位元,只要將高位元清成0 ( & 0x0f),即可得到 5,但是 高位元的 9 向左 shift 4 位後會是 11111001,所以要將高位元再清為 0 (& 0x0f)。

2015年5月5日 星期二

Tibco RV 的 Queue

回顧「Tibco RV request/reply 的同步與非同步」一文裡面的 Server.java,它使用的是 Tibco RV 的 default queue 進行訊息的分派 (line 36、39),這麼做的最大好處是簡單,但是,基於以下理由,一般的應用程式不會使用 default queue,而是會每個應用程式自行建立自己的 queue,理由如下:

  1. 每個 queue 的訊息是以 FIFO 的方式消化掉,如果所有應用程式共用一個,所有的訊息會混雜在一起,會造成塞車。
  2. 不同的 queue 是以 round-robin 的方式消化,default queue 的優先順序是 1,也就是最低的優先順序!
  3. 使用自己建立的 queue,可以設定一些參數,以自己希望的方式消化訊息。

這裡將 Server.java 的訊息分派那一段程式改寫,如下:
             TibrvQueue queue = new TibrvQueue();
             queue.setPriority(10);
             queue.setName("MyQueue");
             queue.setLimitPolicy(TibrvQueue.DISCARD_LAST, 100, 1);
           
             new TibrvListener(queue, this, transport, subject, null);
           
             while (!eventReceived) {
                 eventReceived = queue.timedDispatch(server_timeout);
                 if (eventReceived) {
                     System.out.println("receive a message");
                 }
                 else {
                     System.out.println("timeout");
                 }
             }
從上面的程式,可以看到我們為 queue 設定了比較高的優先順序,也為 queue 取名,以方便 debug,最重要的是,還可以用 setLimitPolicy 設定一些訊息的處理原則,它的三個參數說明如下:
  1. 第一個參數: 當 queue 的訊息滿了,又有新的訊息傳過來,來不及處理時,queue 該如何處理?
  2. 第二個參數: 這個 queue 最多可以放幾個訊息。
  3. 第三個參數: 當要放棄一些訊息時,一次要放棄幾個?




【日劇 - 影子寫手】
同一個故事,每個人看到的面向多不盡相同,這部影集在川原由樹 (水川麻美 飾) 揭露出自己長期當名作家遠野理紗 (中谷美紀 飾) 的槍手後,除了真相被新聞媒體聯手封殺外,她的寫作之路也被各大出版社聯手斬斷了! 最後更在司法上慘敗,面臨鉅額賠償! 這是很正常的,社會的各既得利益者會為保護自己的權益及權力,聯手且不擇手段的打擊任何對手。

台灣這幾年喊的震天響的「轉型正義」,真的要成功,一定會讓目前的許多既得利益階級喪失非常多的特權,這些特權對他們說都是天經地義,原本就屬於他們自己的,就像皇后生下的長子就該立為太子,就該繼承皇位那麼的自然。所以當改革真的開始啟動後,這些既得利益階級(反動勢力?)的反撲是必然的,也絕對是會讓台灣陷入一段時間的動盪、不安。

【每日一字】
class warfare - fighting or disagreement between social classes, usually with the lower classes trying to take power and money away from the upper classes (階級鬥爭)
* Some people think that class warfare is unavoidable and that as the upper classes continue to get richer, the lower classes will star a revolution.
(資料來源: ESL Podcast 573)

2015年5月2日 星期六

使用 jquery 選擇 grid 中的資料

這裡小小的改寫一下上一篇的例子,說明如何使用 jquery 選擇 grid 中的資料。如下圖所示,是執行出來的結果:


我只是在上方的欄位加入一個得票數欄位及一個儲存的按鍵,如果使用者覺得查詢出來結果有誤,可以點選該筆資料,那麼得票數就會被顯示在上方的欄位中,更改後按儲存。

在 struts2 jquery tag 的習慣裡,只要用到 grid,就會顯示到另一個 jsp 檔案,所以上面的網頁分成了兩個檔案,分別為 AreaResult.jsp 及 AreaResultGrid.jsp。因為接下來要用到 jquery,就要引入 jquery 的函式庫檔,因為 AreaReresult.jsp 是主網頁,所以在這一頁裡加入如下紅色部份所示的指令。而綠色部份則是上面所提到的,接下來要顯示得票數的欄位。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<%@ taglib prefix="sj" uri="/struts-jquery-tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>選區投票結果查詢</title>
<script type="type/javascript" src="../scripts/jquery-1.11.2.min.js"></script>
<sj:head jqueryui="true"/>
</head>
<body>
<s:url var="findElection" action="findElection" />
<s:url var="findCityName" action="findCityName" />
<s:url var="findAreaName" action="findAreaName" />
<s:url var="remoteurl" action="showAreaResult" />
<s:url var="saveurl" action="saveVotes" />
<s:form id="formaAreaResult" theme="xhtml">
<sj:select
label="選擇場次"
        href="%{findElection}"
        id="electionID"
        name="electionID"
        list="electionList"
        emptyOption="false"
        headerKey="-1"
        headerValue="請選擇"
        onChangeTopics="reloadCityName"
    />
<sj:select
label="縣市"         href="%{findCityName}"
        id="cityName"
        name="cityName"
        list="cityNameList"
        emptyOption="false"
        headerKey="-1"
        headerValue="請選擇"
        reloadTopics="reloadCityName"
        onChangeTopics="reloadAreaName"
    /><br/>
     
<sj:select
label="選區"         href="%{findAreaName}"
        id="areaName"
        name="areaName"
        list="areaNameList"
        emptyOption="false"
        headerKey="-1"
        headerValue="請選擇"
        reloadTopics="reloadAreaName"
    />
 
    <sj:textfield label="得票數" id="votes" name="votes" />
</s:form>
     
    <sj:submit
        href="%{remoteurl}"
        targets="showAreaResult"
        value="查詢"
        indicator="indicator"
        button="true"
    />
 
    <sj:submit
        href="%{saveurl}"
        targets="showAreaResult"
        value="儲存"
        indicator="indicator"
        button="true"
    />
<br/><br/>
<sj:div id="showAreaResult" />
</body>
</html>
接下來看一下子網頁要加入那些程式,如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<%@ taglib prefix="sj" uri="/struts-jquery-tags"%>
<%@ taglib prefix="sjg" uri="/struts-jquery-grid-tags"%>
<script type="text/javascript">
$.subscribe('rowselect', function(event, data) {
selRowId = event.originalEvent.id;
votes = $('#gridtable').jqGrid ('getCell', selRowId, 'votes');
$('#votes').val(votes);
});
</script>

<s:url var="remoteurl" action="findCandidate" />
<sjg:grid
        id="gridtable"
        caption="候選人得票統計"
        dataType="json"
        href="%{remoteurl}"
        pager="true"
        gridModel="candidateList"
        rowList="10,15,20"
        formIds="formaAreaResult"
        rowNum="15"
        rownumbers="true"
        viewrecords="true"
        shrinkToFit="true"
        onSelectRowTopics="rowselect"
    >
        <sjg:gridColumn name="electionID" index="electionID" title="選舉場次" sortable="false"/>
        <sjg:gridColumn name="cityName" index="cityName" title="縣市" sortable="false"/>
        <sjg:gridColumn name="areaName" index="areaName" title="選區" sortable="true"/>
        <sjg:gridColumn name="votedNo" index="votedNo" title="號次" sortable="true"/>
        <sjg:gridColumn name="name" index="name" title="姓名" sortable="true"/>
        <sjg:gridColumn name="partyName" index="partyName" title="黨籍" sortable="true"/>
        <sjg:gridColumn name="votes" index="votes" title="得票數" sortable="true"/>
        <sjg:gridColumn name="elected" index="elected" title="當選?" sortable="true"/>
    </sjg:grid>
說明一下上面的程式,當使用者按下 grid 中任何一列時,會觸發 onSelectRowTopics 事件 (綠色部份),然後就呼叫 rowselect 這個函式,將 grid 中的 cell 值放入指定的 html 欄位中 (紅色部份)。因為這裡只是要舉例怎麼寫,可以取得 grid 中的 cell 值,所以只抓出一個得票數欄位 (藍色部份)。