顯示具有 程式設計 標籤的文章。 顯示所有文章
顯示具有 程式設計 標籤的文章。 顯示所有文章

2012年9月28日 星期五

如何把時鐘和文字放在同 一個畫面?

如何把時鐘和文字放在同一個畫面?

This document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu
請勿轉貼

現在你已經知道怎麼把文字(也就是 Hello World)或者時鐘(剛剛的練習題)放到螢幕上,可是怎麼把兩個以上的 GUI 元件放到螢幕上呢?說明 setContentView() 的時候,我們說明了該方法只能有一個參數,而且該參數必須是 View 或者其子類別的物件,這項限制也說明了:如果利用這樣的方式使用 setContentView(),只有一個 GUI 元件可以顯示在螢幕上。為了瞭解是否能解決這種限制,除了搜尋之外,另一個方式就是找 API 文件。

當然一定要 Hello World:難的部分

當然一定要 Hello World:難的部分

This document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu
請勿轉貼

如果你來到這個網頁,相信你已經執行完你的第一個 Android 程式:Hello World;由於我們採用的是"知難行易"的方式來說明,相信之前的操作(行易)還蠻簡單的,可是只會操作而不知其所以然,想要繼續開發 Android 程式會非常困難,所以這份文件試著只解釋必須知道的部分 -- 知難。

2012年9月26日 星期三

Perl 教學 -- 表單處理

Perl 教學 -- 表單處理

This tutorial is copyrighted and provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

本範例參考並修改了 CGI 一日通: 使用 Perl 5 這本書中的一個範例。 在這個範例,我們設計了兩個表單元件的表單,並整合了之前所學的概念 (含 GET/POST 資料、字串處理),讓程式可以分別取得單一表單元件的資料。 讓我們以範例進行說明:
  1. 表單:
    E-mail:
    您的意見:
    表單的原始碼如下:

    <form method="POST" action="comments.pl">
    <strong>E-mail:</strong>
    <input type="text" name="address" size="30" maxlength="50">
    <br>您的意見:<br>
    <textarea name="comments" rows="8" cols="50">
    </textarea>
    <p><input type="submit" value="Sent Comments">
    </form>
    
    在表單中,總共有兩個分別名為 addresscomments 的資料會傳送給 comments.pl,其格式為 address=email@address 以及 comments=使用者輸入的意見
  2. 為了處理瀏覽器傳過來的 addresscomments 資料, comments.pl 利用了一些可以重複使用的方法,而這些方法定義在 forms-lib.pl,其原始碼如下:
    #!/usr/local/bin/perl
    # forms-lib.pl
    # Decodes URLs and unpacks from input.
    # Read the form contents into $input, decodes it, unpacks it,
    # and returns it as an associative array.
    sub GetFormInput
    {
      my(%input);
      $input = &ReadInput();
      %input = &ParseInput($input) if $input;
      return(%input);
    }
    
    sub ReadInput
    {
      my($method, $input, $length);
      $method = $ENV{'REQUEST_METHOD'};
      if($method eq 'GET')
      {
        $input = $ENV{'QUERY_STRING'};
      }
      elsif ($method eq 'POST')
      {
        $length = $ENV{'CONTENT_LENGTH'};
        read(STDIN, $input, $length);
      }
    
      return($input);
    }
    
    sub ParseInput
    {
      my($input) = @_;
      my(@pairs);
      my(%input);
      @pairs = split('&',$input);
      foreach $pair (@pairs)
      {
        # convert all plus signs to spaces.
        $pair =~ s/\+/ /g;
    
        # split into a key and a value.
        ($key, $value) = split('=', $pair, 2);
    
        # Convert hex numbers to alphanumeric.
        $key =~ s/%(..)/pack("c", hex($1))/ge;
        #$value =~ s/%(..)/pack("c", hex($1))/ge;
        $value =~ s/%([\da-fA-F][\da-fA-F])/pack("c", hex($1))/ge;
    
        # handle multiple values, separated by newlines.
        $input{$key} .= "\n" if defined($input{$key});
    
        # associate keys and values
        $input{$key} = $value;
      }
      return (%input);
    }
    
    return true;
    

    • comments.pl 首先呼叫 GetFormInput 來取得表單 資料;而其取得資料的方式,分成兩個步驟:第一個是呼叫 ReadInput, 第二個是呼叫 ParseInput
    • ReadInput 是一個可以處理 GET 以及 POST 的方法;它利用 環境變數 REQUEST_METHOD 來確認 GET 或者 POST,然後分別利用 相對應的方式(讀取環境變數 QUERY_STRING 或者標準輸入 STDIN)來取得 輸入的資料;請注意: 目前取得的資料其格式為 address=email@address&comments=使用者輸入的意見 的一個字串。
    • 因此,我們需要利用 ParseInput 將字串轉換成如下的格式:
      address    email@address
      comments   使用者輸入的意見
      
      這種資料結構稱之為 hash,而 hash 中的第一個欄位一般稱之為 key,第二個欄位稱之為 value。Perl 用來代表具有 hash 結構的變數,會在變數名稱之前加上 %;例如,在 ParseInput 內 第三行,就宣告了一個名為 %input 的 hash 區域變數。從程式中, 我們也可以觀察到 %input$input 被視為兩個不同的 變數。
      在 ParseInput 中,它首先利用 splitaddress=email@address&comments=使用者輸入的意見 分成一個具有兩個 元素的陣列 @pair;兩個元素分別是 address=email@addresscomments=使用者輸入的意見;然後,再利用 splitaddress 指定為 key,而 email@address 指定為 value; 同樣的方式也套用在 @pair 陣列中其他的元素。至於,將 + 和 url-encoded characters 轉換成適當的字元也在 ParseInput 內完成。最後,把 hash 的結果回傳給 GetFormInput,而 GetFormInput 再把 hash 的結果回傳給 comments.pl

  3. comments.pl 的原始碼如下:
    #!/usr/local/bin/perl
    require 'forms-lib.pl';
    
    print "content-type: text/html\n\n";
    %input = &GetFormInput();
    $recipient = $input{'address'} || "jllu\@nchu.edu.tw";
    $message = $input{'comments'};
    $sender = $ENV{'HTTP_FROM'} || "jllu\@nchu.edu.tw";
    print <<EndofMessage;
    <html>
    <head><title>Comment Tests<title><head>
    <body>
    <h3>Your Comment is to be sent<h3>
    <pre>
    To: $recipient
    From: $sender
    Subject: Comments on your web page
    $message
    </pre>
    </body>
    </html>
    EndofMessage
    
    在取得 hash 結構 %input 之後,分別取得 address 和 comments 的 值的方式分別為 $input{'address'} 以及 $input{'comments'}。 最後,程式中還有一個值得說明的部分是 print <<EndofMessage 指的是 出現在這一行之後,一直到出現 EndofMessage 之間的字串都會被輸出(print)。








Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

Perl 教學 -- 函數與字串處理

Perl 教學 -- 函數與字串處理

This tutorial is copyrighted and provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

函數 (Function):

  1. 建立與使用函數: 執行
    • 這個範例用來說明如何宣告一個函數,並呼叫它。程式碼如下:
      #!/usr/bin/perl
      
      sub Footer
      {
         print "<h2>Perl Program Using Functions</h2>\n";
         print "\n</body>\n";
         print "</html>\n";
      }
      
      print "Content-type: text/html\n\n";
      print "<html>\n";
      print "<head>\n";
      print "<title>Perl Program Using Functions</title>\n";
      print "</head>\n\n";
      &Footer;
        
    • 在程式碼中,我們宣告了一個名為 Footer 的函數(綠色的部分);函數宣告 的基本語法是使用保留字 sub 並在其後加上函數名稱(這個範例中使用的 是 Footer),函數名稱之後加上一對大括號,在大括號中加入必要的程式碼。
    • 呼叫函數的語法是 &函數名稱,因此呼叫 Footer 的方式如程式碼 中紅色的部分。

  2. 函數間傳送資料 (I): 執行
    • 在前一個範例中,我們宣告了一個 Footer 的函數,但是該函數 並無法接收呼叫端傳送過來的參數,但是在大多數的程式中,能夠宣告一個能接受 參數的函數是必要的,這個範例就是用於說明該方式。
    • 程式碼如下:
      #!/usr/bin/perl
      
      sub Header
      {
        print "Content-type: text/html\n\n";
        print "<html>\n";
        print "<head>\n";
        print "<title>@_</title>\n";
        print "</head>\n\n";
      }
      
      sub Footer
      {
        print "<h2>Perl Program Using Functions</h2>\n";
        print "\n</body>\n";
        print "</html>\n";
      }
      
      &Header("Perl Program Using Functions");
      &Footer;
        
    • 在程式中,我們把前一個範例中的 print 敘述全部定義在一個名為 Header 的函數(如綠色部分),然後呼叫它(如紅色部分)。請注意,呼叫端在函數名稱 之後加上了一對括號,並在括號內加入了一個字串,這代表呼叫 Header 並將 字串 "Perl Programming Using Functions" 傳給 Header;如果 Header 需要取用 該參數內容,則使用 @_
    • 嚴謹的說,@_ 代表一個呼叫端傳送過來的陣列。在本範例中,由於 只傳遞了一個參數,因此該陣列只有一個元素,我們也可以單獨使用 $_[0] 來取得該參數的內容,請試試看!

  3. 函數間傳送資料 (II): 執行
    • 這個範例用於說明如何傳遞多個參數資料給函數,而且函數產生的結果 可以回傳給呼叫端。
    • 程式碼如下:
      #!/usr/bin/perl
      
      sub Root
      {
        my($a, $b, $c) = @_;
        my($tmp, $x1, $x2);
        $tmp = sqrt($b ** 2 - 4.0 * $a * $c);
        $x1 = (-$b + $tmp) / (2.0 * $a);
        $x2 = (-$b - $tmp) / (2.0 * $a);
        return ($x1,  $x2);
      }
      
      
      $a = 1.0;
      $b = -2.0;
      $c = 1.0;
      @x = &Root($a, $b, $c);
      print "Content-type: text/html\n\n";
      print "x1 = $x[0]; and x2 = $x[1]\n";
        

    • 呼叫 Root 函數的時候,程式也傳遞三個參數資料(分別是變數 a、b、c; 這三個是全域變數),函數接收到資料後,將陣列資料 @_ 分別指定給 $a, $b, $c,其中,由於 ($a, $b, $c) 前面有 my, 因此這些變數 a、b、c 代表區域變數,而且只能在 Root 函數內使用;另外,Root 函數 又宣告了區域變數 tmp, x1, 和 x2。
    • 等到 Root 函數計算出結果後,它以 return 將變數值 x1 和 x2 (嚴謹的說,將包含變數 x1 和 x2 的陣列)回傳給呼叫端。
    • 呼叫端(紅色部分)將 Root 函數回傳的結果指定給一個名為 x 的陣列, 而其內容就可以 $x[0]$x[1] 的方式將資料取出。
  4. 使用 require: 執行
    • require 類似 C 語言的 include;基本上,它用來將另一個檔案的 內容"包"進檔案。
    • 原始碼:
      #!/usr/bin/perl
      
      require "req.pl";
      
      &Header("Perl Program Using Functions");
      &Footer;
        
      在程式碼中,我們利用 require 將一個名為 req.pl 的檔案包進來; 如以下的說明,req.pl 包含之前定義的 Footer 和 Header 兩個函數。這樣的 作法有一個好處,那就是 req.pl 的內容可以在多個程式中被重複使用。
    • req.pl 的程式碼如下:
      #!/usr/bin/perl
      
      sub Header
      {
        print "Content-type: text/html\n\n";
        print "<html>\n";
        print "<head>\n";
        print "<title>@_</title>\n";
        print "</head>\n\n";
      }
      
      sub Footer
      {
        print "<h2>Perl Program Using Functions</h2>\n";
        print "\n</body>\n";
        print "</html>\n";
      }
      
      return 1;  # this is required.
        
      請注意,在程式碼的最後一行必須加上如紅色部分的 return 1;;也 就是說,所有被包進某個檔案的程式(如本例中的 req.pl),其最後一行必須 加上 return 1;



日期函數

在撰寫 CGI-Perl 程式的時候,經常我們需要抓取系統的時間或日期。 Perl 提供了幾個有用的時間函數,你可以命令提示字元的視窗內下 perldoc perlfunc 的指令,然後搜尋 localtimegmtime。 本範例我們介紹 localtime 的使用方式:
#!/usr/bin/perl
($sec, $min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)  = localtime(time);
print "秒: $sec\n";
print "分: $min\n";
print "時: $hour\n";
print "一個月的第幾天: $mday\n";
print "月: $mon 範圍是 0-11,而一月為 0\n";
print "距離 1900 年的第幾年: $year\n";
print "一周內的第幾天: $wday  範圍是 0-6,而星期日為 0\n";
print "一年內的第幾天: $yday\n";

# 如果我只想要當天的日期,
($_,$_,$_, $mday, $mon, $year, $_,$_,$_) = localtime(time);
$year = $year + 1900;
$mon = $mon + 1;
print "今天是西元 $year 年 $mon 月 $mday 日\n";
練習題: 試著將上列的程式改成顯示目前的時間? 或者 顯示民國年?

字串、搜尋、與取代

  1. 例題 I:
    #!/usr/local/bin/perl
    $url = 'www.nchu.edu.tw';
    
    # Usage: /pattern/[g][o][i]
    # g for global, o for only once, and i for case-insentitive
    # url 是否包含 nchu 字串
    if ( $url =~ /nchu/ )
    {
       print "$url is 國立中興大學.\n";
    }
    
    # 將 'edu' 取代為 EDU
    # Can you use tr? (ie. tr/edu/EDU/)
    $url =~ s/edu/EDU/;
    print "\$url is now $url.\n";
    
  2. 例題 II:
    1. 大寫變小寫
        #!/usr/local/bin/perl
        $names = "fred and mary";
        $names =~ tr/a-z/A-Z/;
        print "$names\n";
        
    2. 計算字串長度
        #!/usr/local/bin/perl
        $count = "fred and mary";
        #
        # 若不使用 =~, tr 傳回轉換的字元總數
        $count = tr/a-z //;
        print "$count\n";
        
  3. 例題 III: 在使用 GET 方法的 Form 例題中, 請在輸入欄內輸入多於 一個字並觀察其執行情形。 你應該可以看到網址輸入欄內的內容 (如果輸入的是 aaa bbb)為 input=aaa+bbb。 這是因為空格被 "+" 符號 (URL-encoded characters) 取代。常見的 URL-encoded characters 包含:
    CharacterURL-encoded characters
    Tab%09
    空格+
    !%21
    +%2B

    那麼當成是接收到 input=aaa+bbb 的時候,它要如何將 + 轉換成空格呢?以下我們利用 GET 和 POST 的範例,分別列出轉換的方式。

    • GET 的方式:
      • form 表單:
        請輸入文字(GET):
      • 該表單呼叫 sub.pl,而其程式碼如下:
        #!/usr/local/bin/perl
        print "Content-type: text/plain\n\n";
        $input = $ENV{'QUERY_STRING'};
        
        # translate + into space.
        $input =~ tr/+/ /;
        print "$input.\n";
        
        # s: substitute
        # \d: a number, A-F: A,B,C,D,E,F, a-f: a,b,c,d,e,f
        # [\dA-Fa-f]: either a number, A, B, ... , F, a, b, ..., or f.
        # pack("c", hex($1)): transform %[][] into a character ("c").
        # e: treat pack() as an expression instead of a string.
        # $input =~ s/%(..)/pack("c", hex($1))/eg;
        $input =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack("c", hex($1))/eg;
        print "$input.\n";
        
      • 程式碼中,除了第一段是進行之前說過的"將 + 轉換成空格",第二段我們 使用了更進階的方式將所有 URL-encoded characters 轉換成字串。為了容易說明, 請在表單中輸入一個中文字(例如),執行後,讀者應該可以從網頁或者 瀏覽器的 URL 欄位看到,(不含雙引號)這個字被轉換成的 URL-encoded characters 是 %AA%C5,這樣的內容程式必須加以處理才能轉換 成我們需要的。轉換的規則如下:我們需要從資料內找出所有的 URL-encoded characters,而這些字元都符合 %XX 格式,而 X 代表 16 進位的數字(也就是 0-9A-F);Perl 的搜尋規則即為 %([\dA-Fa-f][\dA-Fa-f]);找到字元之後,我們利用 pack() 方法,將其 16 進位的資料轉換成字串。 pack() 的第一個參數 "c" 就是指定轉換成字串的設定,我們也可以 轉換成其他資料型態,有興趣的讀者,可以參考 pack 的用法。


    • POST 的方式:
      • form 表單:
        請輸入文字(POST):
      • 該表單呼叫 subp.pl:因為該程式除了經由 POST 方式來讀取資料而跟 sub.pl 不同以外,其他部分相同,因此不再贅述。





Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu



Perl 教學 -- 簡單的 Perl 程式

Perl 教學 -- 簡單的 Perl 程式

This tutorial is copyrighted and provided as is and can be used as supplement to any CGI tutorial materials such as Common Gateway Interface, written by 網際工作室(Internet Studio). You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

一些簡單的 Perl 程式

首先讓我們先了解一些簡單的 Perl 語法;讀者可以將下列程式碼 儲存成一個檔案,然後在"命令提示字元"內執行。
  • Hello World:將連結內的原始碼儲存為(例如) hello.pl,然後請在"命令提示字元"內輸入 perl hello.pl 來執行。執行後的畫面如下:
    這個程式碼中真正的 Perl 程式敘述只有一行,也就是包含 print 的 那一行敘述,它的目的就是將字串輸出到標準輸出裝置,也就是螢幕;如果 你把這個程式經由 mod_perl 處理,print 會將字串從 Apache 傳回給瀏覽器,並由瀏覽器呈現。
  • 解 ax2 + bx + c = 0 (I):在這個 程式,我們首先宣告了三個變數 a、b、和 c。經由 Perl 提供的方法函數 sqrt(),程式計算了 b2 - 4ac 的平方根,並將結果 指定給變數 tmp;最後,計算兩個解並將結果列印出來。
  • 解 ax2 + bx + c = 0 (II):這是 解方程式的另一個版本,由於使用了 <STDIN> 來讀取使用者 輸入的資料,因此這個程式不能經由 mod_perl 執行。除此之外,這個程式還有 一些值得說明的:
    • chomp():由於使用者輸入數字之後需要按"Enter"鍵,因此變數 a、b、和 c 內的字串最後都有"跳行"的控制字元;因此我們需要使用 chomp() 把"跳行"的控制字元去除。
    • 狀態 || 敘述:這個敘述的意思是"如果狀態為 false,則執行 || 後的敘述。
    • die 的用法是將其後面的字串顯示出來之後,把程式結束掉。




格式化輸出

#!/usr/local/bin/perl
$s = "My name is Eric.";
$n = 5400;
$r = 123.456;

// 結果是 "My Name is Eric ^5400 ^^^^123.46"
printf "%15s %5d %10.2f\n", $s, $n, $r;
如果希望把 123.46 這項結果指定給另一個變數呢?
$newr = sprintf("%.2f", $r);

資料結構

  1. 變數: $input 是一個變數。 一個變數可以包含數字或字串。 你可以利用適當的 operators 去處理變數, 試試看這個例子:
      #!/usr/local/bin/perl
      $str = "th Perl";
      $str .= " CGI program.\n";
      $num = 1;
      $num++;
      $num += 1;
      $num = $num + 1;
      print "My $num$str";
      
  2. 陣列 (array) 或 串列 (list): @addr 代表一個陣列。
      @addr = ("www.cyut.edu.tw", 80, "/~jlu/");
      print "http://$addr[0]$addr[2]\n";  # @addr and $addr
      
      常用的陣列函示:
    1. split(/pattern/, $line)
          $addr = "www.cyut.edu.tw";
          @tmp = split(/\./, $addr);   # 以後討論
          print '$tmp[2] is ', $tmp[2], "\n";
          
      練習: 若 $time = "18:30:32";, 請將結果 「下午6點30分32秒」印出。
  3. Hash (雜湊): 舊稱結合陣列 (associative array): %url 代表一個 Hash。 一>個 Hash 是一群 (key, value) 組合而成的陣列。 例如在前一個例子中, address 是 www.cyut.edu.tw, port 是 80, 而 path 是 /~jlu/, 因此 %url 可定義為
           $url{"address"} = "www.cyut.edu.tw";
           $url{"port"} = 80;
           $url{"path"} = "/~jlu/";
           
      常用的 Hash 函示:
    1. keys(%array): 傳回 %array 的所有鍵。
    2. values(%array): 傳回 %array 的所有值。
    3. each(%array): 一次傳回一對鍵與值。
  4. Hash and Array:
      # @url_list gets (key1, value1, key2, value2, ...)
      @url_list = %url;
    
      # create %newurl like %url
      %newurl = @url_list;
    
      # easier and faster.
      %newurl = %url;       # easier and faster.
      
  5. Name: $eric, @eric, %eric 雖然使用同樣的名稱, 但是對 Perl 而言, 他們代表完全不同的意義。 縱使 Perl 能分辨出他們的不同, 在寫程式 我們不鼓勵這樣做。
      $eric = "Eric";
      @eric = ("Hello", "Carol");
      %eric = ("Last Name", "Lu", "First Name", "Eric");
      @tmp = keys(%eric);
    
      print '$eric is ', $eric, "\n";;
      print '@eric[1] is ', $eric[1], "\n";
      print '$eric{"First Name"} is ',
            $eric{"First Name"}, "\n";
      print '$eric{$tmp[0]} is ',
            $eric{$tmp[0]}, "\n";
      

控制結構:

  1. if 敘述: if 敘述的括號 ({}) 不能省略。 如果 condition 是空字串, 0 (zero), 或 "0" (零字串), 則結果為偽。 其餘為真。
    1. if (condition) { statements; }
    2. if (condition) { statements; } else { statements; }
    3. if (condition) { statements; } elsif { statements; } else { statements; }
    字串比較的運算子有: eq, lt, gt, le, ge, and ne。 相對的數值運算子有: ==, <, >, <=, >=, !=。
  2. while 敘述: while (condition) { statements; }
  3. do-while 敘述: do { statements; } while (condition);
  4. for 敘述: for( initial_exp; test_exp; re-init_exp ) { statements; }
  5. foreach $i ( @array ) { statements; }

控制結構與資料結構的例題:

  1. 例題 I:
      if ($url{"address"} eq "www.cyut.edu.tw")
      {
        print "You're pointing to CYUT.\n";
      }
    
      if ($url{"port"} == 80)
      {
        print "Port is 80.\n";
      }
      else
      {
        print "Wrong port number.\n";
      }
      
  2. 例題 II:
      foreach $key (keys(%url))
      {
        print "key is $key.\n";
      }
      
  3. 例題 III:
      while (($key, $value) = each(%url))
      {
        print "$key is $value.\n";
      }
      
  4. 例題 IV:
      # if there are 6 elements in @url_list, when @url_list
      # compares with an integer, it will return 6.
      for ( $i = 0; $i < @url_list; $i++)
      {
        print "$url_list[$i] ";
      }
      print "\n";
    
      # $#addr: size of addr - 1
      for ( $i = 0; $i <= $#addr; $i++ )
      {
         print "$addr[$i]\n";
      }
      

Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

Perl 教學 -- Perl 與 Form

Perl 教學 -- Perl 與 Form

This tutorial is copyrighted and provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

Perl 與 Form (I)

說明事項:
  1. 這個範例用來說明如何經由網頁上的 HTML form 表單元件來呼叫伺服器端的 perl 程式。
  2. 首先在網頁上設計表單元件,這個範例是設計一個按鈕,其原始碼如下:
    <form action="1st.pl">
    <input type="submit" value="Hello World">
    </form>
    
    基本上,該表單使得使用者在按鈕上點一下的話,它會呼叫伺服器端名為 1st.pl 的程式。如果讀者對於表單元件不熟悉的話,可以參考 Form Pages
  3. 1st.pl 的內容跟之前的 hello.pl 類似,其程式碼如下:
    #!/usr/bin/perl
    print "Content-type: text/html\n\n";
    print "<html><head><title>My First Perl Program</title></head>\n";
    print "<body><h1>Hello World</h1></body></html>\n";
    

    • Perl 的註解符號是 "#"。
    • print "string"; 也可寫成 print("string");
    • "Content-type: text/html" 是所謂的 MIME header;它的功能在於告訴瀏覽器 即將傳送過來的資料是 HTML 網頁。
    • "\n" 代表 newline。依據規定,在 MIME header 後, 必須要有兩個 "\n"。
    • 在 Perl 中, 字串 (string) 可以以雙括號 " 或單括號 ' 表示。 如 "string" 與 'string' 都代表同一個字串。 不過, "string\n" 與 'string\n' 便不同。 試試看!
    • 函數 substr(string, offset) 會將 string 位於 offset 位置之後 的子字串回傳;string 的索引位置從零開始。
      1. print substr("Hello World", 6); 會回傳 World。
      2. print substr("Hello World", -2); 會回傳字串的最後兩個字元,也就 是 ld。



Perl 與 Form 結合 (II, GET)

請輸入文字:
說明事項:
  1. 在這個範例中,我們說明如何將使用者在網頁上輸入的資料傳送到伺服器端的 程式。在 HTTP 的協定中,資料傳送的方式有兩種,一種是 GET,另一種是 POST; 在這個範例我們先說明 GET。
  2. 表單元件的原始碼如下:
    01: <form method="GET" action="2nd.pl">
    02: 請輸入文字:<input type="text" name="input">
    03: <input type="submit" value="Submit">
    04: <input type="reset" value="Clear">
    05: </form>
    
    第 01 行說明,如果使用者輸入資料之後,瀏覽器會以 GET 的方式將資料傳送給 2nd.pl;第 02 行說明,這是一個文字欄位,而使用者輸入的資料在 傳送給伺服器的程式時,該程式可以格式為 input=資料 方式來取得資料。 如果你在文字欄位上輸入 Eric,並按下 Submit,你會看到如下的畫面:

    在畫面中,請留意兩件事情:第一,在 URL 欄位中,它在程式名稱(2nd.pl)後面 加上了 ?input=Eric,它代表它利用了 GET 的方式將 input=Eric 傳送給 2nd.pl;第二,2nd.pl 將 URL 中的資料擷取出來,並顯示在網頁上, 擷取的方式如以下說明。
  3. 2nd.pl 的原始碼如下:
    #!/usr/local/bin/perl
    print "Content-type: text/plain\n\n";
    $input = $ENV{'QUERY_STRING'};
    print "$input\n";
    
    GET 的傳送方式是將資料傳送到伺服器的環境變數內,而其中代表 input=Eric 的環境變數名稱為 QUERY_STRING,所以 Perl 經由 $ENV{'QUERY_STRING'} 將名為 QUERY_STRING 的內容取出,並指定給變數 input。
  4. 除了 QUERY_STRING 的環境變數,是否還有其他的環境變數?我們 可以經由以下程式得到答案,你也可以點一下按鈕來觀察。

    #!/usr/bin/perl
    print "Content-type: text/html\n\n";
    print "AUTH_TYPE is $ENV{'AUTH_TYPE'} <br>";
    print "CONTENT_LENGTH is $ENV{'CONTENT_LENGTH'} <br>";
    print "GATEWAY_INTERFACE is $ENV{'GATEWAY_INTERFACE'} <br>";
    print "HTTP_USER_AGENT is $ENV{'HTTP_USER_AGENT'} <br>";
    print "HTTP_REFERER is $ENV{'HTTP_REFERER'} <br>";
    print "QUERY_STRING is $ENV{'QUERY_STRING'} <br>";
    print "REMOTE_ADDR is $ENV{'REMOTE_ADDR'} <br>";
    print "REMOTE_HOST is $ENV{'REMOTE_HOST'} <br>";
    print "REMOTE_IDENT is $ENV{'REMOTE_IDENT'} <br>";
    print "REMOTE_USER is $ENV{'REMOTE_USER'} <br>";
    print "REQUEST_METHOD is $ENV{'REQUEST_METHOD'} <br>";
    print "SCRIPT_NAME is $ENV{'SCRIPT_NAME'} <br>";
    print "SERVER_SOFTWARE is $ENV{'SERVER_SOFTWARE'} <br>";
    print "SERVER_NAME is $ENV{'SERVER_NAME'} <br>";
    print "SERVER_PROTOCOL is $ENV{'SERVER_PROTOCOL'} <br>";
    







Perl 與 Form 結合 (III, POST)

請輸入文字:
說明事項:
  1. 這個範例用於說明如何利用 POST 的方式傳送使用者輸入的資料,以及 perl 程式如何擷取這些資料。我們使用的表單跟前一個範例非常類似,差別只在 method 設定為 POST,而程式名稱改成 3rd.pl
  2. 若跟之前一樣,在文字欄位內輸入 Eric,則執行的畫面如下:
    其中,請特別注意畫面中 URL 欄位,並不像之前會出現 ?input=Eric 的 訊息,而且由於這個特色,許多系統的設計都採用 POST,而不採用 GET。 當然,POST 的優點不僅如此,如以下程式中所顯示的,POST 傳送給程式的資料 是經由標準輸入裝置(STDIN),因此沒有資料量的上限;反之,GET 是經由 環境變數而傳遞,因此有資料量的限制。
  3. 3rd.pl 的原始碼如下:
    #!/usr/local/bin/perl
    print "Content-type: text/plain\n\n";
    $length=$ENV{'CONTENT_LENGTH'};
    read(STDIN, $input, $length);
    print "$input\n";
    
    • 程式先經由環境變數 CONTENT_LENGTH 得知資料的總量,然後經由 標準輸入裝置讀取資料,並存放於變數 input 內。
    • 跟所有程式一樣,Perl 也內建三個標準裝置: STDIN, STDOUT, STDERR 分別 代表標準輸入、標準輸出、以及標準錯誤。




Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

Perl 教學 -- 前言

Perl 教學 -- 前言

This tutorial is copyrighted and provided as is and can be used as supplement to any CGI tutorial materials such as Common Gateway Interface, written by 網際工作室(Internet Studio). You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

前言

本文假設你已經具備有下列條件:
  1. 你瞭解 HTML 語法並且會寫程式 (最好是 C)。
  2. 你的系統已安裝 Perl 5.x。
  3. 你的 Web 伺服器支援 CGI 且你能從你的網頁執行 CGI 程式。
註: 下列 Perl 程式的第一列皆包含 #!/usr/local/bin/perl。 這一列指令對 Win32 版的 Perl 不重要, 但是對 Unix 系統卻是必要的。 有些系統的 Perl 安裝在其他目錄, 請詢問系統管理人員並適當的更改路徑。

什麼是 Perl?

Perl 是一個功能非常強大的 script language。 它的全名是 Practical Extraction and Report Language。 Larry Wall 是它的創 造者。 從 1988 年 1 月發行第一版至今日的第五版, Perl 已經過了十幾年的歲月。

誰需要 Perl?

  1. 系統管理員。
  2. 網頁開發人員。

為什麼?

  1. Perl 易於學習。
  2. Perl 擁有超強的文字處理功能。
  3. Perl 包括了豐富的封裝群組 (packages) 與模組 (modules), 使得它 非常適合於做檔案輸出與輸入, 以及存取幾乎所有常用的資料庫系統 (如 Oracle, Sybex, SQL Server, ODBC, etc.)

Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

Perl 教學 -- 安裝

Perl 教學 -- 安裝

This tutorial is copyrighted and provided as is and can be used as supplement to any CGI tutorial materials such as Common Gateway Interface, written by 網際工作室(Internet Studio). You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu
請勿轉貼
看其他教材

我的電腦可以安裝 Perl 嗎?

目前已知可以安裝 Perl 的系統有 Unix(含各式各樣的 Linux 版本;如 FreeBSD、 Debian、Ubuntu 等), OS/2, Mac, Amiga, Win32, etc. DOS 不行。

何處可以取得 Perl?

你可以從所有的 CPAN 網站取得免費的 Perl 軟體。 在台灣, 你可以到 Perl 資源 網站以 取得更進一步的消息。另外,在 Win32 的系統上,本人推薦安裝 ActivePerl,讀者可以 下載其免費的 ActivePerl Community Edition 版。

安裝 Apache 與 Perl 在你的 PC

由於我們希望能藉由 Web 伺服器來執行 perl 程式,因此我們需要先安裝一個 Web 伺服器。目前市佔率最高的 Web 伺服器是 Apache 的 HTTP Server,所以 我們先安裝 Apache 2.2.x 版; 本文假設你的 PC 安裝的作業系統是 Win32 系列(含 XP/Server/Vista/Win7 等), 請至 下載網頁 下載 Win32 Binary including OpenSSL 0.9.8m (MSI Installer);如果你使用的是 其他類的作業系統,請依據必要的程序安裝。基本上,本文介紹的 Perl 語法以及 用法與你使用的作業系統應該無關。 安裝 Apache 的過程中,有幾個地方要注意的:第一,先確認你安裝 Apache 的 電腦是否已經有了註冊過的 hostname(例如,www.nchu.edu.tw);如果有,請 在以下的畫面中的 "Network Domain" 填入 nchu.edu.tw,"Server Name" 填入 www.nchu.edu.tw,以及 "Administrator's Email Address" 填入 一個可以聯絡的到你的 email 帳號。



如果讀者沒有 hostname 的話(大多只是為了學習或者測試的人都沒有),請在 "Network Domain" 填入任意值(不能留空白),在 "Server Name" 填入 localhost,然後就可以按 "Next" 繼續安裝。另外,由於我不太喜歡 預設的安裝位置(路徑名稱太長,而且名稱中含有空格,容易造成不必要的麻煩), 我將其安裝於 e:\apache22,在之後的解說中,我們也是以這個路徑 為主;如果讀者安裝在其他的路徑,請作適當修改。 如果讀者的安裝環境跟我的差不多(沒有 hostname 又安裝了防毒軟體 Kaspersky), 安裝的結果是無法正常的啟動 Apache 的,畫面大概如下所示:



這個畫面說明了兩個問題;第一個是 Apache 無法從剛才輸入的 "Network Domain" 和 "Server Name" 找到適當的 IP。這個問題的解決方式就是手動的修改 Apache 的設定檔 httpd.conf;請再修改前先備份設定檔,以便於改錯的時候, 還可以再來一次。設定檔的鎖在位置在 e:\apache22\conf\httpd.conf, 請依據以下畫面



請將畫面中 #ServerName localhost:80 前的井字號(#)刪除,變成 ServerName localhost:80。第二個問題在於 Apache 試圖跟 IP 位置 0.0.0.0:80 連結卻失敗了;為了解決這個問題,我需要明確在 httpd.conf 中明確告訴 Apache 連結上一個特定的 IP;另外,我也 必須確定防毒軟體是否阻擋了該項連結(Kaspersky 會阻擋,你必須在防火牆的 設定中,在"封包篩選規則"中允許"Localhost Loopback TCP Activity")。 設定檔的修改畫面如下:



請將畫面中 Listen 80 修改成 Listen IP:80,在 IP 的地方放入你電腦的 IP 位置即可。一切修改完成之後,你就可以啟動 Apache 了。啟動的方式可以經由 ApacheMonitor (出現在 XP 右下角) 或者在"命令提示字元"輸入 e:\apache22\bin\httpd -k start 來啟動。在 Apache 成功啟動後,請在瀏覽器內輸入 http://yourip (也就是剛才輸入 Listen 後面的 IP) 你就可以看到如下的畫面;如果沒有,你必須重來一次。
就像之前的建議,我們安裝 ActivePerl Community Edition。ActivePerl 的安裝非常簡單,我們就不再贅述。最後, 為了能讓 Apache 更有效率的執行 perl 程式,我們建議安裝一個 Apache 的 perl 模組 mod_perl。根據 mod_perl 2.0 Win32 Installation Instructions 的說法, 由於我們安裝的 ActivePerl 是 10xx 版(或者 perl 5.10 版),安裝的方式是在 "命令提示字元"內輸入

set PATH=c:\perl\bin;%PATH%
ppm install http://cpan.uwinnipeg.ca/PPMPackages/10xx/mod_perl.ppd


兩行指令;第一個指令在於設定 PATH 環境變數,第二個指令在於安裝 mod_perl 2.0 版。執行後,你會看到如下的畫面:



在安裝的過程中,我們只需要如紅色框框中所顯示的,輸入 mod_perl 的安裝位置; 在我們的範例中,安裝位置在 e:/apache22/modules。安裝完成後, 我們需要對 httpd.conf 進行修改,請將下列兩行敘述加入設定檔:

LoadFile "c:/perl/bin/perl510.dll"
LoadModule perl_module modules/mod_perl.so


另外,為了告訴 Apache 什麼時候需要載入 mod_perl 來處理 perl 程式, 我們必須在 httpd.conf 的最後,加入以下設定:

    <Files ~ "\.(pl)$">
    SetHandler perl-script
    PerlResponseHandler ModPerl::Registry
    Options +ExecCGI
    PerlOptions +ParseHeaders
</Files>


這項設定明確的告訴 Apache,當使用者要求的 URL 中,最後的副檔名是 .pl, 則該檔案就交給 mod_perl 來處理。 設定檔修改完成後,我們需要重新啟動 Apache。由於安裝的過程,我們安裝 了一些套件,而這些套件又需要某些環境變數的配合,所以如果安裝後,無法正常 使用 Apache + Perl,這時候你可能需要重新開機。

確認你的 Apache + Perl 的安裝

按一下執行看看。如果你依據我們的說明,你應該會看到 一個相同的執行結果。以下是我們第一個 Perl 程式,其程式碼如下:

#!/usr/local/bin/perl
print "Content-type: text/html\n\n";
print "<html><head><title>My First Perl Program</title></head>\n";
print "<body><h1>Hello World</h1></body></html>\n";


請將該程式碼儲存成一個名為 test.pl 的檔案,並將其放置於 e:/apache22/htdocs 目錄內,然後請在瀏覽器內輸入 http://yourip/test.pl 即可正確執行。

Perl 教學

Perl 教學

This tutorial is copyrighted and provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

目錄

  1. 前言
  2. 安裝
  3. 一些簡單的 Perl 程式
  4. Perl 與 Form
  5. 函數與字串處理
  6. 表單處理
  7. 檔案處理
  8. Server-Side Includes
  9. 資料庫查詢 (ODBC)
  10. 資料庫查詢 (DBI)
  11. 我還想更瞭解 Perl, 有哪些書可以參考?

檔案處理:

範例: 學生成績查詢
  1. 檔案格式: 檔案的第一列包含各欄位的標題如代碼, 第一個作業, 期末考 等。 第二裂開始為實際資料。 在本例中, 我們規定學生代碼必為一連續字, 例如:
    代碼  作業一(50)  作業二(50) 作業三(75)  專題   期中考(128) 成績
    ABC           25      30       45        75          74       70
    Kevin_C       30      40       45        70         103       79
    Tina          40      25       60        80         105       85
    totoro        30      30       57        85          95       83
    wei-jen       30      25       62        70          85       74
    
  2. 表單格式: 完整原始碼
    Grades
    請選擇班別: 四技二A 四技二B
    請輸入代碼:
  3. Perl 檔案函數: 打開檔案並計算欄位數。
    #!/usr/local/bin/perl
    
    require 'req.pl';
    require 'forms-lib.pl';
    
    # open a file called "sa4a.txt" and assign it 
    # a filehandler called INP.
    # if file cannot open, execute die().
    open(INP, "sa4a.txt") || die("Cannot open sa4a.txt.\n");
    @recs = <INP>;
    
    # get the first record
    $record = $recs[0];
    
    # get rid of consecutive whitespaces
    # \s: whitespace, +: one or more.
    @fields = split(/\s+/, $record);
    $num_field = @fields;
    
    # a way of printing what we got.
    #print "Record is @fields.\n";
    #print "Number of field is $num_field.\n";
    
    &Header("Grades");
    
    # print table header
    print "$record\n";
    
    # read data till the end of the file
    for($row=1; $row < @recs; $row++)
    {
       $record = $recs[$row];
       print "$record\n";
    }
    
    &Footer;
    
  4. 寫資料到檔案:
    #!/usr/bin/perl
    
    $a1 = "Eric";
    $a2 = 80;
    $a3 = 75;
    
    # ">sa4a.txt" 產生輸出檔
    # ">>sa4a.txt" 產生附加式的輸出檔
    open(OUTP, ">sa4a.txt") || die "Cannot open file\n";
    print OUTP "$a1 $a2 $a3\n";
    close OUTP;
    

Server-Side Includes

常常看到計數器嗎﹖ 想想看,我們似乎可以將已經拜訪的次數放在一個檔案中, 而每一次有訪客到的時候,便執行一個簡單的 CGI 程式來更改檔案內的數字。 可是我們所學的都需要經過 form 的元件才能執行,我們要如何才能使得 訪客一到這個網頁就能自動執行這個計數器呢﹖ 請參考 SSIs

資料庫查詢 (ODBC)

Perl 也提供與各種資料庫 (如 mSQL, Oracle, Sybase, Xbase 等) 的連結。 在本例中, 我們僅討論如何利用 ODBC 的方式與資料庫連結。 (ODBC 僅適用於 Win32 的系統. 其他 的連結方法,再下例中有進一步的說明.) 連結 ODBC 資料庫的方式有兩種方法。 在以下 的說明中, 我們使用的是一個 Microsoft Access 的資料庫 -- samples.mdb。 這個資料庫中包含一個 Table, 其名稱為 Product。 Product 有四個欄位 (ID, Name, Price, Qty)。 請於下載後, 利用 ODBC 將其設定為 Samples. 其過程為:
「開始」 --> 「設定」 --> 「控制台」 -->  「ODBC」 -->  
「系統資料來源名稱」 -->  「新增」 --> 
「Microsoft Access Driver (*.mdb)」  --> 「完成」 
在 "資料來源名稱" 欄, 輸入 Samples, 並 「選擇」 samples.mdb 後按 OK。
  1. 幾個簡單的 SQL 範例:
    1. 新增 (insert): 將一筆資料加入 Product.
          insert into Product values ('5','燒錄器', 15000, 10)
          
    2. 查詢 (select):
      1. 查詢全部資料:
              select * from Product
              
      2. 查詢名稱為'燒錄器'的相關資料:
              select * from Product where Name='燒錄器'
              
      3. 查詢名稱為'燒錄器'的目前存量:
              select Name, Qty from Product where Name='燒錄器'
              
      4. 查詢單價低於 2500 元的產品:
              select * from Product where Price < 2500
              
      5. 查詢單價介於 300 與 10000 元間的產品:
              select * from Product where Price >= 300 and Price < 10000
              
    3. 更改 (update): 將燒錄器的存量改為 20.
          update Product set Qty=20 where Name='燒錄器'
          
    4. 刪除 (delete): 刪除燒錄器的產品資料
          delete from Product where Name='燒錄器' 
          
  2. 你可使用 Win32::OLE。 原始碼如下:
    use Win32::OLE;
    
    # setup the names of DSN and Table.
    $DSN = "Samples";
    $Table = "Product";
    
    # Connect and open the database
    $Conn = new Win32::OLE "ADODB.Connection";
    $Conn->Open("DSN=$DSN;UID=;PWD=");
    #$Conn->Open("$DSN", "",  "");
    #$Conn->Open("$DSN");
    
    # The followings don't work well.
    #$Conn->Open("DSN=Samples;UID=;PWD=") || die "Cannot connect";
    #$Conn or die "Cannot connect";
    
    # Execute the SQL statement
    $RS = $Conn->Execute("SELECT * FROM $Table");
    
    if(!$RS) {
            $Errors = $Conn->Errors();
            print "Errors:\n";
            foreach $error (keys %$Errors) {
                    print $error->{Description}, "\n";
            }
            die;
    }
    
    # Retrieving records from Table.
    while ( !$RS->EOF ) {
            my($ID, $Name, $Price, $Qty) = (
                    $RS->Fields('ID')->value,
                    $RS->Fields('Name')->value,
                    $RS->Fields('Price')->value,
                    $RS->Fields('Qty')->value );
    
            print $ID, " : ", $Name, " : ", $Price, " : ", $Qty, "\n";
            $RS->MoveNext;
    }
    
    # Close the connection.
    $RS->Close;
    $Conn->Close;
    
  3. 若你使用 Win32::ODBC, 原始碼如下:
    use Win32::ODBC;
    
    # set Data Source Name
    $DSN = "Samples";
    
    # Open connection to DSN
    if (!($O = new Win32::ODBC($DSN))){
        print "Failure. \n\n";
        exit();
    }
    
    # set Table name
    $Table = Product;
    
    # Get the content of the table.
    if (! $O->Sql("SELECT * FROM $Table"))
    {
    
        # print out the field names.
        @FieldNames = $O->FieldNames();
        $Cols = $#FieldNames + 1;
    
        for ($iTemp = 0; $iTemp < $Cols; $iTemp++){
            $FmH2 .= "$FieldNames[$iTemp] ";
        }
        chop $FmH2;
        print "$FmH2\n";
    
        # Fetch the next rowset
        while($O->FetchRow()){
            undef %Data;
            %Data = $O->DataHash();
            print $Data{'ID'}, " ", $Data{'Name'}, " ", 
                  $Data{'Price'}, " ", $Data{'Qty'}, "\n";
        }
    }
    
    # Close Connection.
    $O->Close();
    

資料庫查詢 (DBI)

前例中, 作者曾提及 Perl 可與各種資料庫連結。 其中, DBI 是一類似 ODBC 的介面。 只要安裝適當的資料庫驅動程式 (DBD), Perl 便可經由它使用相同的 function calls 與許多資料庫連結。 (DBI 也有 ODBC 的驅動程式, 這裡提供了一個範例。) 如果想對 DBI 作更進一步的了解,請於安裝 DBI 的電腦上執行 perldoc DBI 已獲得更詳細的說明。若你需要經過 ODBC 來連結 資料庫(如 MS SQL Server),你可以使用 perldoc DBD::ODBC 獲得更詳細的說明。 在本例中, 我們以 mSQL 為例。 作者假設你可存取任一 mSQL 資料庫。 我們使用的是一個名為 db_test 的資料庫, 它位於 penguin.im.cyut.edu.tw。 這個資料庫中包含一個 Table, 其名稱為 Product。 Product 有四個欄位 (ID, Name, Price, Qty)。 原始碼如下:
#!/usr/bin/perl

use DBI;

# initialize datasourcename. for details, perldoc DBD::mSQL.
# to connect to a DB server on another host such as sun20,
#$dsn = 'db_test:sun20';
$dsn = 'db_test';

# 1. to connect to other DB such as Oracle, all you need to
#    do is to replace "mSQL" with "Oracle". (assume that
#    Oracle driver has been installed.)
# 
# 2. to connect to MS Access, simply change DSN to 'dbi:ODBC:Samples'
#
$dbh = DBI->connect("dbi:mSQL:$dsn", "", "")
             || die "Can't connect to $data_source: $DBI::errstr";

$sth = $dbh->prepare("SELECT * FROM Product")
             || die "Can't prepare statement: $DBI::errstr";

$rc = $sth->execute
             || die "Can't execute statement: $DBI::errstr";

print "Query will return $sth->{NUM_OF_FIELDS} fields.\n\n";
print "Field names: @{ $sth->{NAME} }\n";

while (($ID, $Name, $Price, $Qty) = $sth->fetchrow_array) 
{
  print "$ID, $Name, $Price, $Qty\n";
}

# check for problems which may have terminated the fetch early
die $sth->errstr if $sth->err;

$sth->finish;
另外, 你也可以安裝 mSQL 專用的模組 (如 Msql), 使用以下程式 也可以達到上述範例的結果。 欲知 Msql 的細節, 請執行 perldoc Msql。 [註: 安裝 Msql 模組前, 需先安裝 DBI]
#!/usr/bin/perl

use Msql;

# Connect to DB, db_test, located at the local host.
# mSQL is also available on sun20.im.cyut.edu.tw. Therefore, you
# can also connect to db_test on sun20 by
#   $db = Msql->connect("sun20","db_test") or 
#         die "Cannot connect to db_test on sun20.";
# When using with CGI, it is a perfect example of 3-tier architecture.
#
$db = Msql->connect("","db_test") or die "Cannot connect to db_test";

# you can insert a row
#$db->query("insert into Product values('11', '數據機', 5000, 10)");
# or you can delete what you just inserted
#$db->query("delete from Product where ID='11'");

# submit the query.
$sth = $db->query("select * from Product") or die "Cannot Quety.";

# obtain various info about this table.
print "Number of rows: ", $sth->numrows, "\n";
print "Number of columns: ", $sth->numfields, "\n";
print "\n";

# obtain field names
@fnames = $sth->name;
@ftypes = $sth->type;
for($i=0; $i<$sth->numfields; $i++)
{
  print $fnames[$i], " is of type ", $ftypes[$i], "\n";
}
print "\n";

# fetch records from the table
#
# Case 1: fetch a row at a time.
#while(@arr = $sth->fetchrow)
#{
#  # print out field values of each row.
#  foreach $arr (@arr)
#  {
#    print $arr, " ";
#  }
#  print "\n";
#}

#
# Case 2: fetch entire row, including field names, into a hash
while(%hash = $sth->fetchhash)
{
  print $hash{'ID'}, " ", $hash{'Name'}, " ", $hash{'Price'},
        " ", $hash{'Qty'}, "\n";
}

我還想更瞭解 Perl, 有哪些書可以參考?

除了一開始推薦的 The Developer COM 的線上書外, 一般公認的好書有:
  1. Larry Wall, Tom Christiansen, and Randal Schwartz, Prorgamming Perl, 2nd edition, O'Reilly & Associates, 1996.
  2. Randal Schwartz, Learning Perl, 2nd edition, O'Reilly & Associates, 1997. (Unix Systems)
  3. Randal Schwartz, Erik Olson, and Tom Christiansen, Learning Perl on Win32 Systems, O'Reilly & Associates, 1997. (Win32 Systems) [松崗代理]

Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu




第一個範例:取得台中行政區域 (Part II)

第一個範例:取得台中行政區域 (Part II)

The following examples had been tested on Mozilla's Firefox and Microsoft's IE. The document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu
請勿轉貼
看其他教材

在 Part I 中,我們介紹了比較複雜的 .ajax() 方法;本文中,我們介紹 另一個相對簡單的方法 .get()。整個網頁都跟 Part I 幾乎相同,不一樣的 部分只有在 makeRequest() 方法:



  • 再來就是定義 makeRequest() 方法,在該方法中我們利用 jQuery 提供的方法 呼叫遠端服務或者檔案,首先介紹最複雜的 .ajax() 方法:
      function makeRequest(str) {
        $.get(str, function(xml) {
            var msg = "";
            $(xml).find("areas").find("area").each(function() {
              msg += $(this).text() + "<br/>\n";
            });
            //alert(msg);
            $("#taichung").html(msg);
        });
      }
    
    .get() 方法的第一個參數就是遠端的網址,而第二個參數就是成功呼叫之後的 callback 函數;由於該函數的內容與 Part I 的部分相同,本文就不再贅述。 喔,.post() 方法跟 .get() 方法雷同,你們可以直接把程式碼中的 get 改成 post 即可。 最後完成的結果如下:
    取得台中行政區域

  • 第一個範例:取得台中行政區域 (Part I)

    第一個範例:取得台中行政區域 (Part I)

    The following examples had been tested on Mozilla's Firefox and Microsoft's IE. The document is provided as is. You are welcomed to use it for non-commercial purpose.
    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu
    請勿轉貼
    看其他教材

    讓我們說明如何使用 jQuery 來執行 AJAX 呼叫。為了簡化,我們利用之前 範例來說明 jQuery 的 AJAX 用法;第一個範例就是從遠端取得台中行政區的檔案。 要完成這個範例的步驟跟之前的差不多,我們條列如下;為了節省篇幅, 跟之前相同的,我們就不再說明:
    • 首先,先在網頁上放置必要的 HTML 元件;例如,在這個範例中,我們的 HTML 元件 如下:
      <div
          style="background-image: url(button.gif); background-repeat: no-repeat;
          font-size: 14px; width: 132px; height: 28px; padding: 5px 0 0 5px; cursor: pointer;"
          onclick="makeRequest('taichung.xml')">
              取得台中行政區域
      </div>
      <div id="taichung"></div>
      
    • 再來就是定義 makeRequest() 方法,在該方法中我們利用 jQuery 提供的方法 呼叫遠端服務或者檔案,首先介紹最複雜的 .ajax() 方法:
      01    function makeRequest(str) {
      02      $.ajax({
      03        url: str,
      04        type: "GET",
      05        dataType: "xml",
      06        success: function(xml) {
      07          var msg = "";
      08          $(xml).find("areas").find("area").each(function() {
      09            msg += $(this).text() + "<br/>\n";
      10          });
      11          $("#taichung").html(msg);
      12        }
      13      });
      14    }
      
      .ajax() 方法內的參數順序不重要,而且參數的格式是參數名稱:參數值。 本例中,第 03 行的名稱是 url,而其值是由按鈕所傳過來的檔案名稱,也就是說 Ajax 呼叫遠端服務的網址;第 04 行代表 Ajax 呼叫的方式是採用 HTTP 的 GET 的方式;第 05 行代表遠端服務執行完的結果是 XML 格式;第 06 行代表成功 執行遠端呼叫後,系統會去執行的 function,而且回傳的物件名稱為 xml(這還是 一個沒有名稱的 callback 函數);第 08-11 行基本上就是把 Ajax 呼叫後從遠端 傳回來的 XML 文件(也就是 xml)進行處理;由於該份 XML 文件的根元素是 areas 且其內含四個 area 標籤,因此我們首先利用 $(xml).find("areas").find("area) 找出這四個 area 節點,然後利用 each () 對每一個節點進行處理(也就是利用 $(this).text() 取出 area 標籤的內容)並將結果串成一個字串 msg。第 11 行 把 msg 設定成 ID 為 taichung 節點的內容,即 document.getElementById("taichung").innerHTML = msg;。
    • 雖然 .ajax() 的使用很麻煩,但是它卻能提供最好的錯誤判斷處理機制。 例如,我們可以在第 05 和 06 行之間加入下列的判斷式:
            error:  function(jqXHR, exception) {
                  if (jqXHR.status === 0) {
                      alert('無法連線.\n 請檢查你的網路.');
                  } else if (jqXHR.status == 404) {
                      alert('網址不存在,請確認網址的正確性. [404]');
                  } else if (jqXHR.status == 500) {
                      alert('內部伺服器錯誤 [500].');
                  } else if (exception === 'parsererror') {
                      alert('要求的 JSON 剖析失敗.');
                  } else if (exception === 'timeout') {
                      alert('連結逾時錯誤.');
                  } else if (exception === 'abort') {
                      alert('Ajax 呼叫被取消.');
                  } else {
                      alert('未知錯誤.\n' + jqXHR.responseText);
                  }
            },
      
      這一段程式碼會在 Ajax 呼叫出現錯誤時執行,也就是第一行的 error 所代表, 並執行 function(jqXHR, exception);出現錯誤時,傳入的 jqXHR 和 exception 都可以用來判斷錯誤的情形。有興趣的讀者可以自行閱讀,或者直接使用即可。
    • 最後完成的結果如下:
      取得台中行政區域

    存貨管理系統 - MySQL (Part VI):刪除

    第二個範例 (Part VI):刪除

    The following examples had been tested on Mozilla's Firefox and Microsoft's IE. The document is provided as is. You are welcomed to use it for non-commercial purpose.
    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

    請勿轉貼
    看其他教材

    刪除存貨資料

    刪除存貨資料的工作也是分成兩個主要的步驟,而且其步驟跟之前的 add() 和 update() 的程式碼類似:若使用者點選某一項工作,被選擇的工作項目以及 其相關值,會被複製到相對應的輸入欄位(這步驟與 update() 的第一個部分 相同);然後,等到使用者點選"刪除"按鈕,程式需要把資料從資料庫中刪除, 然後把輸入欄位內的資料清除(清除資料的部分跟 add() 的最後工作相同)。 首先,因為第一個步驟的工作與 update() 相同,因此程式碼不需要做任何 更動。然後,我們需要註冊以及定義使用者點選"刪除"按鈕時的事件處理機制。 在本範例中,該事件會觸發 delete() 方法,所以 我們必須更改刪除的按鈕標籤如下:
    <button label="刪除" width="46px" height="24px" onClick="delete()"/>
    
    跟其他 Java 程式相同,請在 <zscript> 標籤內加入 delete() 方法如下:
      void delete() {
        try {
          Class.forName("com.mysql.jdbc.Driver");
          conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpasswd");
          stmt = conn.createStatement();
          String dSQL = "delete from Product where id=" + num.value;
          if (stmt.executeUpdate(dSQL) &lt;= 0)
            throw new SQLException("資料刪除失敗");
        } catch (SQLException e) {
          e.printStackTrace();
        } finally {
          try {
            stmt.close();
          } catch (SQLException e) {
            e.printStackTrace();
          }
          try {
            conn.close();
          } catch (SQLException e) {
            e.printStackTrace();
          }
        }
    
        // 下列兩個敘述的順序不可以對調
        // 確保刪除的資料也從 box 和 allItems 內消失
        allItems.remove(box.getSelectedIndex());
        box.removeItemAt(box.getSelectedIndex());
        clearData();
      }
    
      void clearData(){
        // 清除輸入欄位
        num.value = null;
        name.value = null;
        price.value = null;
        qty.value = null;
      }
    
    從程式碼中,我們可以清楚的看出,資料刪除分成三個動作:第一個動作 就是把使用者選擇的 Product 物件從資料庫中刪除(其中包含資料庫的連結 以及 SQL 的 delete 指令);第二個動作就是把使用者選擇的 Product 物件從 allItems 以及 box 中刪除;刪除的方式是利用 Listbox 物件的 getSelectedIndex() 來決定該物件的索引位置,然後分別利用 remove(索引位置) 和 removeItemAt(索引位置) 把該物件從 allItems 和 box 刪除;第三個動作就是把輸入欄位的值清除, 由於這一段程式碼跟 add() 重複,為了降低維護成本,我們將這個動作的 四個敘述包成一個 clearData() 方法,然後在 delete() 以及 add() 的最後 呼叫該方法。(請記得將 add() 方法做適當的修改) 以下的執行畫面為將之前 ccc 的資料刪除後的結果:
    為了方便起見,我們把整個存貨管理的程式碼列示如下:
    <?xml version="1.0" encoding="Big5"?>
    <window title="存貨管理系統" width="640px" border="normal" mode="highlighted">
    <zscript>
      // 除了方法的部分,其他的部分只會執行一次
      import java.sql.*;
      import java.util.*;
    
    
      Statement stmt = null;
      Connection conn = null;
      List allItems = new ArrayList();
      try {
        Class.forName("com.mysql.jdbc.Driver");
        conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpasswd");
        stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("select * from Product");
    
        Product p;
        while(rs.next()) {
          p = new Product();
          p.setId(rs.getInt(1));
          p.setName(rs.getString(2));
          p.setPrice(rs.getDouble(3));
          p.setQty(rs.getInt(4));
          allItems.add(p);
        }
      } catch (SQLException e) {
        e.printStackTrace();
      } finally {
        try {
          stmt.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
        try {
          conn.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
    
      // 資料新增 ==========================================================
      void add() {
        // 確保新增的資料也在 allItems 內
        Product newp = new Product(num.value.intValue(), name.value, 
                                   price.value.doubleValue(), qty.value.intValue());
        allItems.add(newp);
    
        // 經資料新增至資料庫
        try {
          Class.forName("com.mysql.jdbc.Driver");
          conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpasswd");
          stmt = conn.createStatement();
          String iSQL = "insert into Product values(" + num.value + ",'" +                           
                         name.value + "'," + price.value + "," + qty.value + ")";
          if (stmt.executeUpdate(iSQL) &lt;= 0)
            throw new SQLException("資料新增失敗");
        } catch (SQLException e) {
          e.printStackTrace();
        } finally {
          try {
            stmt.close();
          } catch (SQLException e) {
            e.printStackTrace();
          }
          try {
            conn.close();
          } catch (SQLException e) {
            e.printStackTrace();
          }
        }
    
        // 將新增資料形成一個 Listitem 物件(或者節點) 
        Listitem li = new Listitem(); 
        li.setValue(newp);  // 在之後的 update, delete 會用到
        li.appendChild(new Listcell(num.value.toString())); 
        li.appendChild(new Listcell(name.value)); 
        li.appendChild(new Listcell(price.value.toString())); 
        li.appendChild(new Listcell(qty.value.toString())); 
    
        // 將 Listitem 物件變成 box 的子節點
        // box 是 listbox 的 id 值
        box.appendChild(li);
    
        // 清除輸入欄位
        clearData();
      }
    
      // 資料修改 ==========================================================
      void move() {
        // 確保修改的資料也在 allItems 內
        Product p = (Product) box.selectedItem.value;
        num.value = p.getId();
        name.value = p.getName();
        price.value = new java.math.BigDecimal(p.getPrice());
        qty.value = p.getQty(); 
      }
    
      // update()
      void update(){
        // 修改 box 和 allItems 物件的內容
        Product upp = (Product) box.selectedItem.value;
        upp.setId(num.value.intValue());
        upp.setName(name.value);
        upp.setPrice(price.value.doubleValue());
        upp.setQty(qty.value.intValue());
        allItems.set(box.getSelectedIndex(), upp);
    
        // 修改資料庫的內容
        try {
          Class.forName("com.mysql.jdbc.Driver");
          conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpasswd");
          stmt = conn.createStatement();
          String uSQL = "update Product set name='" + name.value + "', price=" + price.value + 
                        ", qty=" + qty.value + " where id=" + num.value;
          if (stmt.executeUpdate(uSQL) &lt;= 0)
            throw new SQLException("資料新增失敗");
        } catch (SQLException e) {
          e.printStackTrace();
        } finally {
          try {
            stmt.close();
          } catch (SQLException e) {
            e.printStackTrace();
          }
          try {
            conn.close();
          } catch (SQLException e) {
            e.printStackTrace();
          }
        }
    
        // 修改 listbox 的內容
        List children = box.selectedItem.children;
        ((Listcell)children.get(0)).label = num.value.toString();
        ((Listcell)children.get(1)).label = name.value;
        ((Listcell)children.get(2)).label = price.doubleValue().toString();
        ((Listcell)children.get(3)).label = qty.value.toString(); 
      }
    
      // 資料刪除 =========================================================
      void delete() {
        try {
          Class.forName("com.mysql.jdbc.Driver");
          conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpassw");
          stmt = conn.createStatement();
          String dSQL = "delete from Product where id=" + num.value;
          if (stmt.executeUpdate(dSQL) &lt;= 0)
            throw new SQLException("資料刪除失敗");
        } catch (SQLException e) {
          e.printStackTrace();
        } finally {
          try {
            stmt.close();
          } catch (SQLException e) {
            e.printStackTrace();
          }
          try {
            conn.close();
          } catch (SQLException e) {
            e.printStackTrace();
          }
        }
    
        // 下列兩個敘述的順序不可以對調
        // 確保刪除的資料也從 allItems 內消失
        allItems.remove(box.getSelectedIndex());
        box.removeItemAt(box.getSelectedIndex());
        clearData();
      }
    
      // ==============================================================
      void clearData(){
        // 清除輸入欄位
        num.value = null;
        name.value = null;
        price.setText("");
        qty.value = null;
      }
    
    </zscript>
      <listbox id="box" multiple="true" rows="4" onSelect="move()">
        <listhead>
          <listheader label="料號" width="50px" />
          <listheader label="品名" />
          <listheader align="right" label="價格" width="60px" />
          <listheader align="right" label="數量" width="60px" />
        </listhead>
        <listitem forEach="${allItems}" value="${each}">
          <listcell label="${each.id}"/>
          <listcell label="${each.name}"/>
          <listcell label="${each.price}"/>
          <listcell label="${each.qty}"/>
        </listitem>
      </listbox>
      <groupbox>
        <caption label="存貨管理"/>
        料號: <intbox id="num" cols="5" />
        品名: <textbox id="name" cols="25" />
        價格: <decimalbox id="price" cols="8" />
        數量: <intbox id="qty" cols="8" />
        <div>
        <button label="新增" width="46px" height="24px" onClick="add()"/>
        <button label="修改" width="46px" height="24px" onClick="update()"/>
        <button label="刪除" width="46px" height="24px" onClick="delete()"/>
        </div>
      </groupbox>
    </window>
    
    我們的範例程式到此已經介紹完畢,可是我們要提醒讀者:這個系統只是 設計給單一使用者的;也就是說,這個系統的設計是假設一次只有一個使用者 在使用。試試看:打開兩個瀏覽器的視窗,然後分別開啟我們剛剛設計好的系統, 並分別在兩個視窗中新增資料,請觀察新增的資料是否立即反應再另一個視窗中。 練習題: 請新增一個"清除"的按鈕,使用者點選它之後,會把所有欄位的輸入 或者選擇的資料清除。

    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu





    存貨管理系統 - MySQL (Part V):修改

    第二個範例 (Part V):修改

    The following examples had been tested on Mozilla's Firefox and Microsoft's IE. The document is provided as is. You are welcomed to use it for non-commercial purpose.
    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

    請勿轉貼
    看其他教材

    修改存貨資料

    修改存貨資料分成兩個主要的步驟:若使用者點選某一項工作,被選擇的工作項目以及 其相關值,會被複製到相對應的輸入欄位;然後,等到使用者修改完資料後,使用者 需要按"修改"按鈕來完成修改工作。 首先,我們要為 <listbox> 註冊一個 onSelect 的事件處理方法 move(); 該註冊的事件處理過程為:一旦使用者選擇了(也就是觸動了 onSelect 事件) <listbox> 中的某一個存貨資料,ZK 開始執行 move() 方法,而該方法所做的 事情就是把該存貨資料的值分別複製到相對應的輸入欄位。註冊的原始碼如下:
    <listbox id="box" multiple="true" rows="4" onSelect="move()">
    
    而 move() 的程式碼定義如下:
      void move() {
        Product p = (Product) box.selectedItem.value;
        num.value = p.getId();
        name.value = p.getName();
        price.value = new java.math.BigDecimal(p.getPrice());
        qty.value = p.getQty();
      }
    
    這一段程式碼中,兩個綠色的部分需要特別說明一下:在之前介紹 forEach 和 add() 方法中,我們曾經說過,有一行程式碼(li.setValue(newp);) 是在 add() 中沒有特別用途,但是卻對 update 或者 delete 有幫助的; 這一行程式碼基本上是為 Listitem 物件的 value 屬性設定一個屬性值,而該值 是該 Listitem 所代表的存貨資料物件(即 Product)。經過這樣的設定,在 move() 方法中,我們就可以經由 box.selectedItem.value 的方式取得被使用者 點選的 Listitem 物件中的 Product 物件;由於 .value 傳回的是 Object 物件, 因此需要將它強迫轉型為 Product。有了 Product 物件之後,我們就可以經由其相關的 get 方法(getId()、getName()、getPrice()、getQty())取得值,並將其指定給 輸入欄位 num、name、price、和 qty。程式碼中的第二個綠色部分也需要說明一下: 由於 price.value 的資料型態是 BigDecimal,但是 p.getPrice() 回傳的卻是 double,所以我們必須經由 new java.math.BigDecimal(p.getPrice()) 將 double 轉換成 BigDecimal。 在這裡,我們先測試一下目前的工作是否 正確,以下的執行畫面是在點選料號為 4 的存貨之後的結果,請留意輸入欄位內的值:

    在確定上一個步驟的正確性之後,使用者可以在輸入欄位內做必要的修改。 使用者在修正完必要的資料後,按下"修改"按鈕,這會觸發一個事件, 因此我們需要定義一個該事件的處理方法,在本範例中稱之為 update()。 我們必須更改修改的按鈕標籤如下:
    <button label="修改" width="46px" height="24px" onClick="update()"/>
    
    跟其他 Java 程式相同,請在 <zscript> 標籤內加入 update() 方法如下:
      void update(){
        // 修改 box 和 allItems 物件的內容
        Product upp = (Product) box.selectedItem.value;
        upp.setId(num.value.intValue());
        upp.setName(name.value);
        upp.setPrice(price.value.doubleValue());
        upp.setQty(qty.value.intValue());
        allItems.set(box.getSelectedIndex(), upp);
    
        // 修改資料庫的內容
        try {
          Class.forName("com.mysql.jdbc.Driver");
          conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpasswd");
          stmt = conn.createStatement();
          String uSQL = "update Product set name='" + name.value + "', price=" + price.value + 
                        ", qty=" + qty.value + " where id=" + num.value;
          if (stmt.executeUpdate(uSQL) &lt;= 0)
            throw new SQLException("資料修改失敗");
        } catch (SQLException e) {
          e.printStackTrace();
        } finally {
          try {
            stmt.close();
          } catch (SQLException e) {
            e.printStackTrace();
          }
          try {
            conn.close();
          } catch (SQLException e) {
            e.printStackTrace();
          }
        }
    
        // 修改 listbox 的內容
        List children = box.selectedItem.children;
        ((Listcell)children.get(0)).label = num.value.toString();
        ((Listcell)children.get(1)).label = name.value;
        ((Listcell)children.get(2)).label = price.doubleValue().toString();
        ((Listcell)children.get(3)).label = qty.value.toString(); 
      }
    
    跟 add() 類似,update() 方法中的第一件事情就是從輸入欄位取得資料;這個可以 從程式碼中的 num.value.intValue()、name.value、price.value.doubleValue()、 和 qty.value.intValue() 可以看的出來。 取出的資料要用來修改被使用者選取的 Product 物件,也就是 box.selectedItem.value 的值;再經由該物件的 set 方法(setId()、setName()、setPrice()、和 setQty())來更新該物件的內容。跟 add() 類似,我們需要更新 box、allItems、 以及資料庫的資料。 最後,輸入欄位內的資料必須覆蓋到存貨管理系統中之前被使用者選擇的存貨資料。 從上列的"修改 listbox 的內容"的那一段程式碼,我們可以看到程式利用 box.selectedItem.children 取得使用者選擇的待辦事項的所有子節點; 在本範例中,總共會有四個 Listcell 的子節點。然後,程式利用 children.get(0) 取得第一個子節點,然後轉型為 Listcell 物件,最後利用 .label 取得該物件 的 label 屬性,並以輸入欄位的值來取代 label 的屬性值。 以下的執行畫面為將之前 aaa 的資料改成 bbb 的結果:



    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu








    存貨管理系統 - MySQL (Part IV):新增


    第二個範例 (Part IV):新增

    The following examples had been tested on Mozilla's Firefox and Microsoft's IE. The document is provided as is. You are welcomed to use it for non-commercial purpose.
    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

    請勿轉貼
    看其他教材

    新增存貨資料

    顯示完所有的待辦事項後,需要完成的就剩下視窗下半部的新增、修改、以及 刪除。一般來說,這三個步驟習慣上會先實作新增;新增的資料顯示後,我們 緊接著實作修改;等到修改後的資料也正確了,最後就實作刪除,把剛剛新增的 資料除去;這樣的順序可以確保所有的功能都被完成,而且不會留下不必要的 測試資料。 資料的新增、修改、以及刪除在程式設計上需要考量的部分比較多,讓我們拿 之前 Part II 的顯示來說明。由於存貨資料不但儲存在資料庫中,為了能夠讓資料能夠 在畫面上正確的處理,程式裡面還有兩個地方包含有存貨資料,一個是 allItems,另一個是在 id 為 box 的 <listbox> 內(嚴格來說,是在其子元素 <listitem> 內);也就是說,每一次資料庫內的資料改變,其相對應的 allItems 以及 box 都需要跟著改變。
    有了以上的概念,我們先說明資料的新增處理;新增存貨資料的步驟如下:使用者 在輸入欄位內輸入資料之後,點選"新增"按鈕;在按下"新增"按鈕之後, 資料庫、allItems、以及 box 的資料隨之增加,最後,將輸入欄位內的資料 清除。
    點選"新增"按鈕的處理機制: 在使用者按下"新增"按鈕之後,這會觸發一個事件,因此我們需要定義一個 該事件的處理方法,在本範例中稱之為 add()。add() 方法需要完成的事情包含 從輸入欄位取得資料,然後把資料新增到資料庫,最後重新更新畫面。首先, 我們先註冊事件,必須更改新增的按鈕標籤如下:
    <button label="新增" width="36px" height="24px" onClick="add()"/>
    
    其中,綠色的部分就是告訴 ZK:如果使用者點選了"新增"按鈕,請執行 add() 方法。跟其他 Java 程式相同,請在 <zscript> 標籤內加入 add() 方法如下:
      void add() {
      }
    
    在 add() 方法中的第一件事情就是從輸入欄位取得資料。請參考 Part I 中的 四個輸入欄位,分別是 num、name、price、和 qty;ZK 提供了一個 非常方便的方式(非常類似 Javascript)來取得這些欄位的值,那就是在這些 名稱的後面直接加上 .value;取得了輸入欄位的值之後,我們就可以初始化 一個新的 Product 物件如下,並將該物件加到 allItems 內:
        Product newp = new Product(num.value.intValue(), name.value, 
                                   price.value.doubleValue(), qty.value.intValue());
        allItems.add(newp);
    
    由於 Product 物件的第一個資料成員是 id,其資料型態為 int,可是在利用 .value 的方式取得該輸入欄位的值,其資料型態會依據該輸入欄位的 型態而回傳適當的資料型態;由於料號的輸入欄位型態為 intbox,因此 num.value 的資料型態為 java.lang.Integer,所以我們需要藉由 intValue() 的用法,把它改成 int;同樣的,第四個資料成員 qty, 也需要同樣的轉換; Product 物件的第三個資料成員是 double,而其相對應的輸入欄位型態為 decimalbox,利用 price.value 會回傳 java.Math.BigDecimal 資料型態,因此我們需要使用 doubleValue() 將資料 轉換成 double;至於 name,由於其資料型態為字串,且其輸入欄位型態為 textbox(回傳 java.lang.String),因此不需要轉換。 (請注意,intValue() 和 doubleValue() 是 ZK 提供的功能)。 將新增的 Product 物件加到 allItems 內之後,我們可以將輸入資料加到資料庫中, 其方式如下:
        try {
          Class.forName("com.mysql.jdbc.Driver");
          conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpasswd");
          stmt = conn.createStatement();
          String iSQL = "insert into Product values(" + num.value + ",'" +                           
                         name.value + "'," + price.value + "," + qty.value + ")";
          if (stmt.executeUpdate(iSQL) &lt;= 0)
            throw new SQLException("資料新增失敗");
        } catch (SQLException e) {
          e.printStackTrace();
        } finally {
          try {
            stmt.close();
          } catch (SQLException e) {
            e.printStackTrace();
          }
          try {
            conn.close();
          } catch (SQLException e) {
            e.printStackTrace();
          }
        }
    
    新增資料的程式碼跟以前介紹的 Java 程式碼幾乎相同,因此我們不多做介紹。 但是這一類的程式如果在 ZK 的環境下執行,有一個地方要特別注意:所有在 Java 程式中,如果出現 <(小於)或者 >(大於)的符號,是不能直接 使用 < 或者 >,而必須分別使用 &lt; 或者 &gt; 來替代;以範例中的程式碼為例,我們使用一個 if 敘述來判斷 stmt.executeUpdate(iSQL) 是否成功;也就是說資料是否成功的新增 到資料庫中,stmt.executeUpdate(iSQL) 會回傳 1,如果有 1 筆資料被影響; 因此,我們判斷 stmt.executeUpdate(iSQL) 小於等於零時,就代表新增失敗; 所以,在程式中的 if 敘述,我們寫的是 stmt.executeUpdate(iSQL) &lt;=0,而不是 stmt.executeUpdate(iSQL) <=0。 到這個步驟,新增加的代辦事項已經加到資料庫以及 allItems,剩下的工作就是 畫面的更新(也就是把資料加到 box 內)。 畫面的更新分成兩個部分:一個是將新增的資料顯示在清單中;另一個就是 把輸入欄位裡面的資料清除掉。新增資料到清單的工作非常類似 DOM 的新增 節點,其程式碼如下:
    01      Listitem li = new Listitem(); 
    02      li.setValue(newp);  // 在之後的 update, delete 會用到
    03      li.appendChild(new Listcell(num.value.toString())); 
    04      li.appendChild(new Listcell(name.value)); 
    05      li.appendChild(new Listcell(price.value.toString()));
    06      li.appendChild(new Listcell(qty.value.toString()));
    07      box.appendChild(li);
    
    在第 01 行中,我們新增了一個 Listitem 的物件;Listitem 物件代表 <listbox> 標籤中的一個 <lisitem> 標籤。在第 03 到 06 行,我們需要為 Listitem 物件新增四個子節點,而每一個節點都是一個 Listcell 物件;也就是說, new Listcell(name.value) 會產生 <listcell>name.value</listcell> 標籤,而 name.value 代表 name 欄位內的值。除了 name 之外,其他三個欄位 的回傳資料型態都不是字串,因此其他的回傳值都需要再經過 toString() 將資料轉換成字串。最後,我們在第 07 行把 Listitem 物件(內含四個 Listcell 子節點)加到 box(box 是 <listbox> 的 id 值)下,並成為它的子節點。新增完了之後,ZK 會自動 refresh 畫面。 在上述程式碼中,如果目的只有呈現新的資料,那麼第 02 行的程式碼是不需要的。 第 02 行的目的在於為 Listitem 物件的 value 屬性(還記得之前介紹的 forEach 嗎?)加上一個 newp 的屬性值,這個 屬性值對目前的工作是沒有必要的,而它加入的目的在於讓之後的 update 和 delete 用的。
    最後,就是把欄位資料清空,清空的方式非常簡單,直接把欄位值設定為 null 即可;例如,把 name 欄位的資料清空的方式就是 name.value = null;; 其他三個欄位也比照辦理即可。執行整個程式碼,並新增資料後的畫面如下:

    add() 的完整程式碼如下:
      void add() {
        // 確保新增的資料也在 allItems 內
        Product newp = new Product(num.value.intValue(), name.value, 
                                   price.value.doubleValue(), qty.value.intValue());
        allItems.add(newp);
    
        // 經資料新增至資料庫
        try {
          Class.forName("com.mysql.jdbc.Driver");
          conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpasswd");
          stmt = conn.createStatement();
          String iSQL = "insert into Product values(" + num.value + ",'" +                           
                         name.value + "'," + price.value + "," + qty.value + ")";
          if (stmt.executeUpdate(iSQL) &lt;= 0)
            throw new SQLException("資料新增失敗");
        } catch (SQLException e) {
          e.printStackTrace();
        } finally {
          try {
            stmt.close();
          } catch (SQLException e) {
            e.printStackTrace();
          }
          try {
            conn.close();
          } catch (SQLException e) {
            e.printStackTrace();
          }
        }
    
        // 將新增資料形成一個 Listitem 物件(或者節點) 
        Listitem li = new Listitem(); 
        li.setValue(newp);  // 在之後的 update, delete 會用到
        li.appendChild(new Listcell(num.value.toString())); 
        li.appendChild(new Listcell(name.value)); 
        li.appendChild(new Listcell(price.value.toString())); 
        li.appendChild(new Listcell(qty.value.toString())); 
    
        // 將 Listitem 物件變成 box 的子節點
        // box 是 listbox 的 id 值
        box.appendChild(li);
    
        // 清除輸入欄位
        num.value = null;
        name.value = null;
    
        // 嗯,在 XP + Tomcat 6.x + ZK 5.0.x 的組合下,price.value 會出現
        // NullPointerException;需要改成 price.setText("");
        // 但是,Win7 x64 + Tomcat 5.5.x + ZK 5.0.x 的組合下,
        // price.null 卻 OK。
        //price.value = null;
        price.setText("");
        qty.value = null;
      }
    
    請注意:雖然這個程式到目前看起來一切 OK,實際上它的設計是有瑕疵的, 例如,請問如果 allItems 新增了之後,但是資料庫的新增卻由於網路斷線 而無法新增,請問這該怎麼辦? 練習題: 請重新設計範例程式,使得只有在資料庫正確的更新的之後, allItems 和 box 才能更新。

    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu











    存貨管理系統 - MySQL (Part III)

    第二個範例 (Part III)

    The following examples had been tested on Mozilla's Firefox and Microsoft's IE. The document is provided as is. You are welcomed to use it for non-commercial purpose.
    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

    請勿轉貼
    看其他教材

    ZK 與資料庫的結合

    在建立完 ZK 的畫面,並且安裝完資料庫之後,剩下的就是如何讓 ZK 與資料庫 來互動;基本上,這就是開始使用 ZK 的事件處理機制。在之前的 Ajax 的範例, 我們使用 Javascript 來完成這個部分,可是由於 ZK 可以與多種語言結合,而且 其預設的語言是 Java,所以我們也以 Java 來完成這項工作。 ZK 和 Java 連結使用的方式有以下三種:
    1. 直接將 Java 程式碼寫在 zul 檔案內,而這些程式碼只需要放在 <zscript> 標籤內即可,它就可以像 JSP 一樣,在載入 zul 時就可以 執行。
    2. 把 Java 的原始碼放置於另一個檔案內,然後讓 zul 檔把它引入。
    3. zul 檔可以使用已經編譯好的 Java 類別檔(即 .class 檔)。
    在本範例中,我們使用了第一種和第三種方式。第三種的使用方式就像 在 Part II 中說明的 Product 類別;至於第一種的使用方式 就是 Part III 的重點。在載入 inv.zul 網頁的時候,我們希望能夠 先到資料庫中把已經存在的存貨資料載入,並放到視窗內。為了 達成這個目標,我們首先把資料載入,載入的方式如下所示, 而 <zscript> 需要放在 <window> 內。
    <zscript>
      // 除了方法,其他的部分只會執行一次
      import java.sql.*;
      import java.util.*;
    
    
      Statement stmt = null;
      Connection conn = null;
      List allItems = new ArrayList();
      try {
        Class.forName("com.mysql.jdbc.Driver");
        conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpasswd");
        stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("select * from Product");
    
        Product p;
        while(rs.next()) {
          p = new Product();
          p.setId(rs.getInt(1));
          p.setName(rs.getString(2));
          p.setPrice(rs.getDouble(3));
          p.setQty(rs.getInt(4));
          allItems.add(p);
        }
      } catch (SQLException e) {
        e.printStackTrace();
      } finally {
        try {
          stmt.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
        try {
          conn.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
    </zscript>
    
    這個程式跟 MySQL Server 簡介 中介紹的 Java 程式非常類似,因此我們只針對比較不同的地方做說明。 第一個是例外處理的部分,我們將 stmt.close();conn.close();、 以及其他資料庫連接的部分分開,如此一來可以比較容易的找出錯誤;我們建議 讀者甚至可以把"連接資料庫"所可能產生的例外分開處理,這個部分就當作 練習題了。第二個不一樣的地方在於綠色的部分,我們宣告了一個 List allItems = new ArrayList();;ArrayList 可以把它看成一個大小可以變動 的陣列,因此我們可以將任意數量的資料加入 allItems 中;除了這項方便性 之外,宣告成 List 的主要原因是要配合使用 ZK 的 forEach 指令。 經過上述程式的執行,它會將資料庫中的資料一筆一筆的放到 allItems 中, 而 allItems 中的每一筆資料都是一個 Product 的物件。 有了資料,我們需要一個機制把資料放到 <listitem> 內。為了能夠 簡易的做到這一點,ZK 提供了一個非常強的功能,也就是 <listitem> 的 forEach 屬性,其原始碼如下:
        <listitem forEach="${allItems}" value="${each}">
          <listcell label="${each.id}"/>
          <listcell label="${each.name}"/>
          <listcell label="${each.price}"/>
          <listcell label="${each.qty}"/>
        </listitem>
    
    forEach 的用法需要特別仔細的說一下:forEach 的參數值是一個集合物件, 如 List,在本範例中就是 allItems;請注意,如果程式碼中 List 物件 的名稱改成 xxx,那麼 forEach 的屬性值也必須改成 xxx。如果直接把 forEach 定義成 forEach="allItems",ZK 處理器無法判斷屬性值可以直接 使用,還是一個變數的名稱,因此 ZK 採用跟 JSP 一樣的 EL-expression, 如果看到 ${allItems},則 ZK 會從變數 allItems 中取得其值, 並指定給 forEach。在 forEach 的每一個迴圈中,forEach 會從 List 物件 (也就是 allItems)一次取得一個物件,而在本範例中,該物件的資料型態 為 Product,而且該物件的名稱被定義為 each;因此,在每一個 <listcell> 中,我們就可以 each 的資料成員名稱 來取得 Product 物件中的資料成員。以上述程式碼為例,${each.id} 會取得 Product 物件的 id 成員;同樣的道理, ${each.name}、${each.price} 和 ${each.qty} 分別取得該物件的 name、price 和 qty 資料成員。 最後,原始碼的綠色部分在目前是可以不必定義的,該定義是指定 each 物件(也就是 Product 物件)當作是 Listitem 物件的 value 屬性值。value 屬性在之後的修改以及刪除的時候才會用到。
    執行該段程式碼後,執行的畫面如下:

    我想這個時候,把整個完整的 zul 碼列示出來應該有幫助,這裡也先當一個 檢查點,以便確定從安裝到現在,一切都正常的運行:
    <?xml version="1.0" encoding="Big5"?>
    <window title="存貨管理系統" width="640px" border="normal" mode="highlighted">
    <zscript>
      // 除了方法的部分,其他的部分只會執行一次
      import java.sql.*;
      import java.util.*;
    
    
      Statement stmt = null;
      Connection conn = null;
      List allItems = new ArrayList();
      try {
        Class.forName("com.mysql.jdbc.Driver");
        conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpasswd");
        stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("select * from Product");
    
        Product p;
        while(rs.next()) {
          p = new Product();
          p.setId(rs.getInt(1));
          p.setName(rs.getString(2));
          p.setPrice(rs.getDouble(3));
          p.setQty(rs.getInt(4));
          allItems.add(p);
        }
      } catch (SQLException e) {
        e.printStackTrace();
      } finally {
        try {
          stmt.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
        try {
          conn.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
    
    </zscript>
      <listbox id="box" multiple="true" rows="4">
        <listhead>
          <listheader label="料號" width="50px" />
          <listheader label="品名" />
          <listheader align="right" label="價格" width="60px" />
          <listheader align="right" label="數量" width="60px" />
        </listhead>
        <listitem forEach="${allItems}" value="${each}">
          <listcell label="${each.id}"/>
          <listcell label="${each.name}"/>
          <listcell label="${each.price}"/>
          <listcell label="${each.qty}"/>
        </listitem>
      </listbox>
      <groupbox>
        <caption label="存貨管理"/>
        料號: <intbox id="num" cols="5" />
        品名: <textbox id="name" cols="25" />
        價格: <decimalbox id="price" cols="8" />
        數量: <intbox id="qty" cols="8" />
        <div>
        <button label="新增" width="46px" height="24px"/>
        <button label="修改" width="46px" height="24px"/>
        <button label="刪除" width="46px" height="24px"/>
        </div>
      </groupbox>
    </window>
    

    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu







    存貨管理系統 - MySQL (Part II)

    第二個範例 (Part II)

    The following examples had been tested on Mozilla's Firefox and Microsoft's IE. The document is provided as is. You are welcomed to use it for non-commercial purpose.
    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

    請勿轉貼
    看其他教材

    資料庫的安裝與設定

    我們在這個範例中使用了非常受歡迎的資料庫管理系統 MySQL。MySQL 的 安裝、MySQL 與中文、資料庫 eric 與其表格 Product、以及利用 JDBC 驅動程式來存取資料庫等步驟,請參考 MySQL Server 簡介,在此不在贅述。

    資料庫相關的物件

    待辦事項管理 的系統中,我們採用了物件導向的設計方式來設計,其中,與資料庫 存取的部分更是使用了一種常見的設計模式(design pattern) -- DAO (Data Access Object) 物件。如果讀者對於物件導向的程式設計概念 還蠻清楚的,我們強烈建議採用 DAO 的方式來完成本系統。 為了提供不使用 DAO 的方式來設計系統,我們在這個範例中並不會解釋或者 設計 DAO 物件,但是我們建議讀者在看完"待辦事項管理"的設計方式之後, 可以將本範例修改成 DAO 的架構,就當作是練習題也不錯。
    在開始設計 ZK 程式來存取資料庫的資料之前,我們需要先為每一個資料庫中 的表格(包含 view)設計一個相對應的類別。在儘可能不說明物件導向程式 設計的概念下,我們所需要設計的類別包含下列概念:
    1. 將類別的名稱設計成表格的名稱,其程式碼如下;在原始碼中,只有 綠色部分需要隨著表格名稱做修改,其他都保持不變。
      public class Product {
      }
      
    2. 將表格中的欄位設計成類別中的資料成員,其程式碼如下;在原始碼中, 只有綠色部分需要隨著表格內的欄位名稱以及其相對應的資料型態做修改, 其他都保持不變。以價格欄位來說,由於在表格中其欄位名稱為 price, 而且其資料型態為浮點數,因此我們將其設計為 double price
      public class Product {
        private int id;
        private String name;
        private double price;
        private int qty;
      }
      
    3. 最後,我們需要為每一個資料成員設計一對 set 和 get 方法;set 方法 是用來更改該資料成員的資料用的;而 get 方法是用來取得該資料成員的資料用的。 以價格欄位來說,其原始碼如下。
        public void setPrice(double p) {
          price = p;
        }
        public double getPrice() {
          return price;
        }
      
    Product 的程式碼如下:
    public class Product {
      private int id;
      private String name;
      private double price;
      private int qty;
    
      public Product() {}
      public Product(int i,String n,double p,int q) {
        id = i;
        name = n;
        price = p;
        qty = q;
      }
      public int getQty() {
        return qty;
      }
      public void setQty(int q) {
        qty = q;
      }
      public double getPrice() {
        return price;
      }
      public void setPrice(double p) {
        price = p;
      }
      public int getId() {
        return id;
      }
      public void setId(int i) {
        id = i;
      }
      public String getName() {
        return name;
      }
      public void setName(String name) {
        this.name = name;
      }
    }
    
    請把這個程式碼編譯後,放置到適當的位置。如果你是依照本範例的安裝方式 進行,那麼請把 Product.class 放置到 d:\tomcat\webapps\test\WEB-INF\classes 目錄內。 如果讀者並未將 reloadable 設定為 true,你可能需要重新啟動 tomcat。

    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu





    存貨管理系統 - MySQL (Part I)


    第二個範例 (Part I)

    The following examples had been tested on Mozilla's Firefox and Microsoft's IE. The document is provided as is. You are welcomed to use it for non-commercial purpose.
    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

    請勿轉貼
    看其他教材

    在第二個範例中,我們希望能夠設計一個系統,該系統除了使用 ZK 之外, 也使用關聯式資料庫,這樣的組合大概是目前最常見的。這個範例主要參考 ZK 的文件 Step by Step Tutorial: A Rela-World Application with a Database,而且為了上課方便, 我們使用了非常受歡迎的資料庫管理系統 MySQL。 這個範例的說明,我們把它分成三個部分,第一個部分主要著重於畫面的 設計,而第二個部分才說明資料庫的建立,第三個部分就在於 ZK 與 資料庫的互動,也就是 ZK 的事件處理機制。我們的目的在於 建立一個簡易的存貨管理系統,其畫面如下:

    從畫面中,我們可以看得出來,整個版面是由之前介紹的 <window> 物件所形成的,該視窗的上半段條列出所有的存貨資料,而下半段則顯示對於存貨資料 的控制元件,包含輸入欄位以及增/改/刪的按鈕;使用者可以從這些控制 元件來新增、修改、以及刪除存貨資料。
    就像之前說的,在 Part I,我們先說明這個視窗是如何形成的。第一個動作 就是修改前一個 Hello World 的範例,把視窗標題改成如下原始碼,並將 檔案名稱命名為 inv.zul 並放置於 d:\tomcat\webapps\test 中:
    <?xml version="1.0" encoding="Big5"?>
    <window title="存貨管理系統" width="640px" border="normal">
    </window>
    
    練習題: 請試著為 <window> 增加屬性 mode="highlighted" 並觀察它 的變化;如果想要知道其他的效果,請參考 Developer's Guide 並選擇 4.2 節的 Layout and Windows。 再來,我們先增加視窗的上半部的待辦事項的清單,該清單由 <listbox> 所組成,而 <listbox> 的結構類似 <table>,其內可以包含 <listhead> 以及 <listitem>(類似 <tr>)。而 <listhead> 內包含 <listheader>,<listitem> 包含 <listcell>(類似 <tr> 包含 <td>);他們之間的 關係結構圖如下:
      listbox
        +listhead
          -listheader
        +listitem
          -listcell
    
    從我們的最終執行畫面,我們知道 <listhead> 和 <listitem> 會各自包含四對 <listheader> 和 <listcell> 標籤。由於 目前為止,<listcell> 內還沒有資料,所以原始碼如下:
    <?xml version="1.0" encoding="Big5"?>
    <window title="存貨管理系統" width="640px" border="normal" mode="highlighted">
      <listbox id="box" multiple="true" rows="4">
        <listhead>
          <listheader label="料號" width="50px" />
          <listheader label="品名" />
          <listheader label="價格" width="60px" />
          <listheader label="數量" width="60px" />
        </listhead>
        <listitem>
          <listcell/>
          <listcell/>
          <listcell/>
        </listitem>
      </listbox>
    </window>
    
    <listbox> 中,我們多了幾個設定:第一個是 id,這是為了之後撰寫 程式會比較方便而定義的;第二個是 multiple="true",這是代表使用者一次 可以選擇多個待辦事項(在我們設計的系統,這項設定其實是不好的,放在這裡 只是說明該項設定的用法);第三個是 rows="4",這是代表 <listbox> 一次 顯示四筆資料。在以後的原始碼部分,對於之前已經出現過的,除非必要, 我們不再重複列示,以節省空間。執行上述原始碼,得到的畫面如下:
    另外,在"料號"、"價格"、以及"數量"的欄位中,我們分別指定了這些欄位的 寬度分別為 50px, 60px, 以及 60px;由於"品名"欄位並未設定寬度,又由於 整個視窗的寬度為 640px,因此"品名"欄位的寬度是 470px(640-50-60-60)。
    最後,讓我們來完成視窗的下半部。在下半部的視窗包含了存貨的料號 (整數欄位 intbox)、名稱(文字欄位 textbox)、價格(浮點數欄位 decimalbox)、 數量(整數欄位 intbox)、以及新增、修改、和刪除的按鈕(button)。 這些控制元件的 ZK 設定方式如下:(請接在 <listbox> 之後)
        料號: <intbox id="num" cols="5" />
        品名: <textbox id="name" cols="25" />
        價格: <decimalbox id="price" cols="8" />
        數量: <intbox id="qty" cols="8" />
        <button label="新增" width="46px" height="24px"/>
        <button label="修改" width="46px" height="24px"/>
        <button label="刪除" width="46px" height="24px"/>
    
    到了這裡,介面的設計已經快要結束了。為了能夠突顯這些控制元件是屬於一個 群體,並且能為這個區塊定義出一個明顯的名稱,我們可以使用 <groupbox> 元件,而其功能基本上就是把一些關聯性高的元件整理在一起。<groupbox> 的使用方式如下:
      <groupbox>
        <caption label="存貨管理"/>
        料號: <intbox id="num" cols="5" />
        品名: <textbox id="name" cols="25" />
        價格: <decimalbox id="price" cols="8" />
        數量: <intbox id="qty" cols="8" />
        <div>
        <button label="新增" width="46px" height="24px"/>
        <button label="修改" width="46px" height="24px"/>
        <button label="刪除" width="46px" height="24px"/>
        </div>
      </groupbox>
    
    請留意在原始碼中,我們在三個按鈕的前後加上了 <div> 標籤,其效果 是將四個輸入欄位和三個按鈕分開;也就是說,如果沒有 <div> 標籤, 三個按鈕會接在四個輸入欄位之後。執行上述原始碼,得到的畫面如下:
    到此 Part I 結束。


    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu













    第三個範例:ZK 與 Session

    第三個範例:ZK 與 Session

    The following examples had been tested on Mozilla's Firefox and Microsoft's IE. The document is provided as is. You are welcomed to use it for non-commercial purpose.
    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

    請勿轉貼
    看其他教材

    ZK 跟所有 Web-based 的資訊系統一樣,網頁跟網頁之間都是互為獨立的 (稱之為 stateless),可是在實務上,往往網頁與網頁之間是有順序關係的; 例如如果使用者沒有登入,那麼使用者就不能修改使用者的註冊資料。 為了能夠達到這個目的,一般 Web-based 的資訊系統都利用 Session 的作法, 讓網頁與網頁之間可以藉由 Session 物件的一些共享資料來達成。在 ZK 中, 它也提供了類似的 package,這個 package 的名稱為 org.zkoss.zk.ui.Session; 除了這個範例之外,讀者可以參考該 package 的其他類別以及用法。 在這個範例中,我們假設設計一個系統,該系統有多個網頁,而要使用該系統, 使用者必須先執行 login.zul 網頁;一旦登入成功之後,使用者就能使用 其他網頁;為了簡化起見,我們只設計了一個 zul 網頁 todo1.zul(該網頁 是 todo.zul 的簡化版),其他網頁可以比照辦理。如果使用者未登入便想 直接存取 todo1.zul,則使用者會被導向 login.zul。
    首先,讓我們設計 login.zul 的畫面,其畫面如下:

    在畫面,請留意左上角的紅色框框,這是設定網頁標題(也就是類似 HTML 的 <title> 標籤的用法)的名稱。這個畫面的定義如下:
    01  <?xml version="1.0" encoding="Big5"?>
    02  <?page title="待辦事項系統"?>
    03  <window title="登入畫面" border="normal" width="220px" mode="modal"
    04          xmlns:h="http://www.w3.org/1999/xhtml">
    05  <h:table>
    06  <h:tr>
    07  <h:td>帳號:</h:td><h:td><textbox id="user"/></h:td>
    08  </h:tr><h:tr>
    09  <h:td>密碼:</h:td><h:td><textbox type="password" id="pwd"/></h:td>
    10  </h:tr><h:tr>
    11  <h:td colspan="2" align="center"><button label="登入"/></h:td>
    12  </h:tr>
    13  </h:table>
    14  </window>
    
    網頁標題是由第 02 行的 page 標籤(其實是一個 PI)所設定的。另外,為了 能夠將帳號、密碼、以及登入按鈕依照表格的方式(也就是 HTML 的 <table>) 來完成,我們必須宣告一個名稱空間來分開 ZK 的標籤(也就是預設名稱空間) 以及 XHTML 的標籤。(請注意: 本範例故意使用 ZK 與 XHTML 在同一個 zul 網頁的方式來說明;否則,同樣的排版也可以利用 ZK 的 <grid> 來 完成)。 從原始碼的綠色部分,讀者應該可以看得出來,我們定義了一個簡稱為 h 的名稱 空間,而該空間為 http://www.w3.org/1999/xhtml(即 XHTML)。然後,所有的 XHTML 標籤名稱之前都需要加上 h:;以本範例來說,需要加上 h: 包含 h:table、h:tr、h:td 等。
    在使用者輸入帳號和密碼之後,使用者會點一下"登入"按鈕;一旦"登入"按鈕 被點選,login() 方法會被執行。該事件的註冊方式如下:
    <button label="登入" onClick="login()"/>
    
    login() 方法的工作包含:從輸入欄位取得資料,並將其資料與系統 內使用者的身分做驗證;如果驗證成功,程式會在 Session 物件設定包含 必要的資料(例如帳號、密碼等),然後執行下一個網頁 todo1.zul;否則, 程式會將使用者再度導向 login.zul。登入的程式碼如下:
    01  import org.zkoss.zk.ui.*;
    02  
    03  void login() { 
    04    // 也可以連資料庫驗證
    05    if(user.value.equals("123") &amp;&amp; pwd.value.equals("123")) {
    06      Session s = Sessions.getCurrent();
    07      s.setAttribute("user", user.value);
    08      s.setAttribute("pwd", pwd.value);
    09      Executions.sendRedirect("todo1.zul");
    10    } else {
    11      Executions.sendRedirect("login.zul");
    12    }
    13  }
    
    由於我們需要 ZK 的 Session 物件,因此需要第 01 行。在驗證使用者的 身分資料部分,雖然可以使用資料庫,但是為了簡化起見,我們利用第 05 行 的方式來檢查;如果驗證失敗,程式會將使用者導向 login.zul 網頁,如 第 11 行所示,其中 Executions.sendRedirect("URL"); 會將 網頁導向 URL;反之,如果驗證成功,程式首先在第 06 行取得目前的 Session 物件,並且在第 07 和 08 行利用該物件的 setAttribute() 方法 來設定兩個 Session 共享的資訊,其分別是帳號(屬性名稱為 user;屬性值 由 user 欄位取得)和密碼(屬性名稱為 pwd; 屬性值由 pwd 欄位取得)。 最後,將網頁導向 todo1.zul。 在結束說明 login() 之前,還有一個很重要的用法要說明一下。在第 05 行 程式必須判斷帳號以及密碼都等於 123,因此 if 敘述內必須是"且"的運算子, 在 Java 中它應該是 &&,但是因為該程式碼在 zul(或者更明確的 說,XML)網頁內,所以該運算子不能寫成 && 而必須寫成 &amp;&amp;,這樣一來,ZK 處理器在剖析 login.zul 的時候,會將它轉成 && 然後再進行編譯。
    todo1.zul 是第二個範例的簡易版,其畫面的定義部分如下:
    <?xml version="1.0" encoding="Big5"?>
    <window title="待辦事項清單" width="640px" border="normal" closable="true"
            onClose="self.visible=false;logout();">
      <listbox id="box" multiple="true" rows="4">
        <listhead>
          <listheader label="待辦事項" />
          <listheader label="重要性" width="50px" />
          <listheader label="日期" width="90px" />
        </listhead>
      </listbox> 
    </window>
    
    有了這樣的定義,其畫面如下:
    絕大部分的原始碼之前已經說明了,就不再多說。其中比較不同是視窗的右上角 出現了一個像一般視窗的 "X" 按鈕。我們希望在使用者點選 "X" 之後,該視窗 會不見,而且就像登出一樣,它會將使用者導向 login.zul;因此,我們幫 <window> 的 closable 屬性設定為 true(使得出現 "X");若使用者 在 "X" 點一下,則會呼叫 onClose 事件的處理程式,這包含 self.visible=false; (這會讓視窗隱藏起來),並呼叫 logout() 方法。
    另外,除了 login.zul,所有同一個系統中的其他網頁應該在載入的時候,就應該檢查 Session 中的共用資料(在本範例中即屬性名稱為 user 以及 pwd 是否存在, 必要的話,還可以檢查屬性的值是否存在)。為了做這兩件事,程式碼如下:
    01  import org.zkoss.zk.ui.*;
    02  
    03  Session s = Sessions.getCurrent();
    04  if(s.getAttribute("user") == null)
    05    Executions.sendRedirect("login.zul");
    06  
    07  void logout() {
    08    s.invalidate();
    09    Executions.sendRedirect("login.zul");
    10  }
    
    從程式碼中可以看出:第 03 行取得目前的 Session 物件,然後在第 04 行 檢查是否定義了 user 屬性(也可以在加上 pwd 屬性的檢查);如果未定義, 導向 login.zul;反之,則繼續執行本網頁。 logout() 除了會將使用者導向 login.zul 之外,並且會再重新導向之前 先把 Session 物件解除掉(利用 invalidate() 方法)。除了利用 "X" 之外, 當然也可以使用其他的方式登出;但是不論使用哪一種方式,記得一定要 把 Session 物件 invalidate 掉,該物件才不會被不當的使用。
    我們鼓勵讀者試著從各種方式來使用 Session。例如:開啟一個新的網頁之後, 馬上試著連 todo1.zul;或者 logout 之外,直接更改 URL 試圖連接 todo1.zul。 有了良好的 Session 控制,這些方式都不應該可以連上 todo1.zul;除了 正確的登入。
    在測試的過程中,你或許會發現雖然在無法順利使用 todo1.zul 的情形下, 使用者還是有一個短暫的時間可以看到 todo1.zul 的畫面,如果該畫面有 一些機密內容,那麼 todo1.zul 的設計還是不妥。為了解決這個問題,我們 可以在一開始讓 <window> 是看不到的,只有使用者正確的登入後 才讓 <window> 能夠顯示出來。由於程式碼還蠻簡單的,我們就不再說明, 而把整個程式碼的內容列示出來:
    <?xml version="1.0" encoding="Big5"?>
    <window id="win" title="待辦事項清單" width="640px" border="normal" closable="true" visible="false"
            onClose="self.visible=false;logout();">
    <zscript>
    import org.zkoss.zk.ui.*;
    
    Session s = Sessions.getCurrent();
    if(s.getAttribute("user") == null)
      Executions.sendRedirect("login.zul");
    else
      win.setVisible(true);
    
    void logout() {
      s.invalidate();
      Executions.sendRedirect("login.zul");
    }
    </zscript>
      <listbox id="box" multiple="true" rows="4">
        <listhead>
          <listheader label="待辦事項" />
          <listheader label="重要性" width="50px" />
          <listheader label="日期" width="90px" />
        </listhead>
      </listbox> 
    </window>
    
    練習題: 請修改 login.zul 使得使用者在輸入完密碼之後,能夠直接按 "Enter" 來執行登入,而不必一定要用滑鼠來點選"登入"按鈕來登入。
    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu















    待辦事項管理 (Part VI):刪除

    第二個範例 (Part VI):刪除

    The following examples had been tested on Mozilla's Firefox and Microsoft's IE. The document is provided as is. You are welcomed to use it for non-commercial purpose.
    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

    請勿轉貼
    看其他教材

    刪除待辦資料

    刪除待辦資料的工作也是分成兩個主要的步驟,而且其步驟跟之前的 add() 和 update() 的程式碼類似:若使用者點選某一項工作,被選擇的工作項目以及 其相關值,會被複製到相對應的輸入欄位(這步驟與 update() 的第一個部分 相同);然後,等到使用者點選"刪除"按鈕,程式需要把資料從資料庫中刪除, 然後把輸入欄位內的資料清除(清除資料的部分跟 add() 的最後工作相同)。 首先,因為第一個步驟的工作與 update() 相同,因此程式碼不需要做任何 更動。然後,我們需要註冊以及定義使用者點選"刪除"按鈕時的事件處理機制。 在本範例中,該事件會觸發 delete() 方法,所以 我們必須更改刪除的按鈕標籤如下:
    <button label="刪除" width="46px" height="24px" onClick="delete()"/>
    
    跟其他 Java 程式相同,請在 <zscript> 標籤內加入 delete() 方法如下:
      void delete() {
        evtdao.delete((Event)box.selectedItem.value);
        box.removeItemAt(box.getSelectedIndex());
        clearData();
      }
    
      void clearData(){
        // 清除輸入欄位
        name.value = null;
        priority.value = null;
        date.value = null;
      }
    
    從程式碼中,我們可以清楚的看出,資料刪除分成三個動作:第一個動作 就是把使用者選擇的 Event 物件從資料庫中刪除(刪除的方式就是利用 DAO 物件 evtdao 的 delete() 方法);第二個動作就是把使用者選擇的 Event 物件從 待辦事項清單(也就是 Listbox 物件)中刪除,刪除的方式是利用 Listbox 物件 的 getSelectedIndex() 來決定該物件的索引位置,然後利用 removeItemAt(索引位置) 方法把該子節點從 Listbox 物件中刪除;第三個動作就是把輸入欄位的值清除, 由於這一段程式碼跟 add() 重複,為了降低維護成本,我們將這個動作的 三個敘述包成一個 clearData() 方法,然後在 delete() 以及 add() 的最後 呼叫該方法。(請記得將 add() 方法做適當的修改) 以下的執行畫面為將之前 ccc 的資料刪除後的結果:
    為了方便起見,我們把整個待辦事項的程式碼列示如下:
    <?xml version="1.0" encoding="Big5"?>
    <window title="待辦事項列表" width="640px" border="normal" mode="highlighted">
    <zscript>
      import java.util.*;
      import java.text.*;
    
      // 抓取所有待辦事項
      EventDAO evtdao = new EventDAO();
      List allEvents = evtdao.findAll();
    
      void add() {
        // 從輸入欄位取得資料
        Event newEvt = new Event(UUID.randomUUID().toString(),
                                 name.value,priority.value.intValue(),date.value);
        // 將資料新增至資料庫
        evtdao.insert(newEvt);
    
        // 確保 allEvents 物件有最新的資料
        allEvents = evtdao.findAll();
    
        // 將新增資料形成一個 Listitem 物件(或者節點) 
        Listitem li = new Listitem(); 
        li.setValue(newEvt);  // 在之後的 update, delete 會用到
        li.appendChild(new Listcell(name.value)); 
        li.appendChild(new Listcell(priority.value.toString())); 
        li.appendChild(new Listcell(new SimpleDateFormat("yyyy-MM-dd").format(date.value))); 
    
        // 將 Listitem 物件變成 box 的子節點
        // box 是 listbox 的 id 值
        box.appendChild(li);
    
        // 清除輸入欄位
        clearData();
      }
    
      void move(){
        name.value = ((Event) box.selectedItem.value).getName();
        priority.value = ((Event) box.selectedItem.value).getPriority(); 
        date.value = ((Event) box.selectedItem.value).getDate(); 
      }
    
      void update(){
        // 修改待辦事項物件的內容以及資料庫的內容
        Event editEvt = (Event) box.selectedItem.value;
        editEvt.setName(name.value);
        editEvt.setPriority(priority.value);
        editEvt.setDate(date.value);
        evtdao.update(editEvt);
            
        // 修改 listbox 的內容
        List children = box.selectedItem.children;
        ((Listcell)children.get(0)).label = name.value;
        ((Listcell)children.get(1)).label = priority.value.toString();
        ((Listcell)children.get(2)).label = new SimpleDateFormat("yyyy-MM-dd").format(date.value);
      }
    
      void delete(){
        evtdao.delete((Event)box.selectedItem.value);
        box.removeItemAt(box.getSelectedIndex());
        clearData();
      }
    
      void clearData(){
        // 清除輸入欄位
        name.value = null;
        priority.value = null;
        date.value = null;
      }
    </zscript>
    
      <listbox id="box" multiple="true" rows="4" onSelect="move()">
        <listhead>
          <listheader label="待辦事項" />
          <listheader label="重要性" width="50px" />
          <listheader label="日期" width="90px" />
        </listhead>
        <listitem forEach="${allEvents}" value="${each}">
          <listcell label="${each.name}"/>
          <listcell label="${each.priority}"/>
          <listcell label="${each.date}"/>
        </listitem>
      </listbox>
      <groupbox>
        <caption label="待辦事項管理" />
        待辦事項: <textbox id="name" cols="25" />
        重要性: <intbox id="priority" cols="1" />
        日期: <datebox id="date" cols="8" />
        <button label="新增" width="46px" height="24px" onClick="add()"/>
        <button label="修改" width="46px" height="24px" onClick="update()"/>
        <button label="刪除" width="46px" height="24px" onClick="delete()"/>
      </groupbox>
    </window>
    

    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu




    待辦事項管理 (Part V):修改

    第二個範例 (Part V):修改

    The following examples had been tested on Mozilla's Firefox and Microsoft's IE. The document is provided as is. You are welcomed to use it for non-commercial purpose.
    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

    請勿轉貼
    看其他教材

    修改待辦資料

    修改待辦資料分成兩個主要的步驟:若使用者點選某一項工作,被選擇的工作項目以及 其相關值,會被複製到相對應的輸入欄位;然後,等到使用者修改完資料後,使用者 需要按"修改"按鈕來完成修改工作。 首先,我們要為 <listbox> 註冊一個 onSelect 的事件處理方法 move(); 該註冊的事件處理過程為:一旦使用者選擇了(也就是 onSelect)<listbox> 中的某一個待辦事項,ZK 開始執行 move() 方法,而該方法所做的事情就是把 該待辦事項的值分別複製到相對應的輸入欄位。註冊的原始碼如下:
    <listbox id="box" multiple="true" rows="4" onSelect="move()">
    
    而 move() 的程式碼定義如下:
    void move(){
      name.value = ((Event)box.selectedItem.value).getName();
      priority.value = ((Event)box.selectedItem.value).getPriority(); 
      date.value = ((Event)box.selectedItem.value).getDate(); 
    }
    
    這一段程式碼中,綠色的部分需要特別說明一下:在之前介紹 forEach 和 add() 方法中,我們曾經說過,有一行程式碼(li.setValue(newEvt);) 是在 add() 中沒有特別用途,但是卻對 update 或者 delete 有幫助的; 這一行程式碼基本上是為 Listitem 物件的 value 屬性設定一個屬性值,而該值 是該 Listitem 所代表的待辦事項物件(即 Event)。經過這樣的設定,在 move() 方法中,我們就可以經由 box.selectedItem.value 的方式取得被使用者 點選的 Listitem 物件中的 Event 物件;由於 .value 傳回的是 Object 物件, 因此需要將它強迫轉型為 Event。有了 Event 物件之後,我們就可以經由其相關的 accessor 方法(getName()、getPriority()、getDate())取得值,並將其指定給 輸入欄位 name、priority、和 date。在這裡,我們先測試一下目前的工作是否 正確,以下的執行畫面是在點選待辦事項 aaa 之後的結果,請留意輸入欄位內的值:
    在確定上一個步驟的正確性之後,使用者可以在輸入欄位內做必要的修改。 使用者在修正完必要的資料後,按下"修改"按鈕,這會觸發一個事件, 因此我們需要定義一個該事件的處理方法,在本範例中稱之為 update()。 我們必須更改修改的按鈕標籤如下:
    <button label="修改" width="46px" height="24px" onClick="update()"/>
    
    跟其他 Java 程式相同,請在 <zscript> 標籤內加入 update() 方法如下:
      // update()
      void update(){
        // 修改待辦事項物件的內容以及資料庫的內容
        Event editEvt = (Event) box.selectedItem.value;
        editEvt.setName(name.value);
        editEvt.setPriority(priority.value);
        editEvt.setDate(date.value);
        evtdao.update(editEvt);
    
        // 修改 listbox 的內容
        List children = box.selectedItem.children;
        ((Listcell)children.get(0)).label = name.value;
        ((Listcell)children.get(1)).label = priority.value.toString();
        ((Listcell)children.get(2)).label = new SimpleDateFormat("yyyy-MM-dd").format(date.value); 
      }
    
    跟 add() 類似,update() 方法中的第一件事情就是從輸入欄位取得資料;這個可以 從程式碼中的 name.value、priority.value、和 date.value 可以看的出來。 取出的資料要用來修改被使用者選取的 Event 物件,也就是 box.selectedItem.value 的值;再經由該物件的 mutator 方法(setName()、setPriority()、和 setDate())來更新該物件的內容;等到 editEvt 更新之後,我們就可以利用 DAO 物件 evtdao 的 update() 方法來更新資料庫。 最後,輸入欄位內的資料必須覆蓋到待辦事項清單中之前被使用者選擇的待辦事項。 從上列的"修改 listbox 的內容"的那一段程式碼,我們可以看到程式利用 box.selectedItem.children 取得使用者選擇的待辦事項的所有子節點; 在本範例中,總共會有三個 Listcell 的子節點。然後,程式利用 children.get(0) 取得第一個子節點,然後轉型為 Listcell 物件,最後利用 .label 取得該物件 的 label 屬性,並以輸入欄位的值來取代 label 的屬性值。 以下的執行畫面為將之前 aaa 的資料改成 ccc 的結果:



    Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu