Web Application 開 發 利 器 - WebSnap(三)

类别:Delphi 点击:0 评论:0 推荐:


Web Application 開 發 利 器 - WebSnap!

第 參 章 、 WebSnap 與 資 料 庫

  

3-1 基 本 概 念

 

  WebSnap 對 於 資 料 庫 的 支 援 能 力 比 之 前 的 WebBroker+InternetExpress 增 強 了 很 多 , 所 以 使 用 WebSnap 來 開 發 資 料 庫 網 頁 程 式 , 可 以 說 是 一 件 簡 單 且 愉 快 的 事 。 在 我 們 開 始 撰 寫 資 料 庫 程 式 之 前 , 這 裡 先 讓 我 們 先 了 解 一 下 WebSnap 如 何 串 連 傳 統 資 料 庫 與 網 頁 程 式 。

 

TDataSetAdapter 元 件

   如 同 我 們 在 第 一 節 所 討 論 的 Adapter 意 義 相 同 , TDataSetAdapter 就 是 DataSet 的 Adapter 。 我 們 可 以 利 用 TDataSetAdapter 所 提 供 的 函 式 來 操 作 位 於 她 內 部 的 DataSet 元 件 , 那 麼 為 何 要 這 樣 設 計 呢 ? 為 何 不 直 接 使 用 DataSet 呢 ? 回 答 這 個 問 題 前 讓 我 們 回 想 一 下 上 一 節 的 計 算 機 範 例 , Adapter 不 只 是 供 DELPHI 程 式 使 用 , 她 還 輸 出 了 Wrapper 物 件 讓 我 們 可 以 在 Script 中 使 用 她 , 這 是 DataSet 所 辦 不 到 的 。 你 也 可 以 在 TAdapterPageProducer 中 新 增 一 個 AdapterFieldGroup 或 是 AdapterGrid , 將 TDataSetAdapter 或 是 單 純 的 Adapter 設 給 她 們 來 顯 示 或 編 修 資 料 , 她 們 並 不 在 乎 你 設 給 她 的 是 Adapter 還 是 TDataSetAdapter 亦 或 是 你 自 己 所 撰 寫 的 Adapter , 這 大 大 的 加 強 了 程 式 碼 的 重 用 性 。 例 如 你 可 以 撰 寫 一 個 Adapter 輸 出 某 個 目 錄 下 的 檔 案 列 表 , 並 使 用 AdapterGrid 來 顯 示 她 們 , 這 是 不 是 很 棒 呢 ?

 

TDataSetAdapterField 元 件

   和 TDataSetAdapter 一 樣 , TDataSetAdapterField 就 是 TField 的 Adapter 。 結 合 TDataSetAdapter 及 TDataSetAdapterField 之 後 就 是 一 個 完 整 的 DataSet Adapter 了 , 我 們 可 以 利 用 TDataSetAdapter 來 搜 尋 資 料 , 也 可 以 利 用 TDataSetAdapterField 來 顯 示 與 更 改 資 料 。 接 著 就 讓 我 們 利 用 她 們 來 撰 寫 一 個 可 以 顯 示 資 料 與 一 個 編 修 資 料 的 網 頁 。

  

3-2 簡 單 的 資 料 庫 網 頁 實 作

 

我 們 以 上 一 節 的 範 例 為 基 礎 , 為 她 加 上 一 個 顯 示 資 料 的 網 頁 及 一 個 編 修 資 料 的 網 頁 。 請 你 開 啟 New Items Dialog 並 切 換 到 WebSnap 頁 :

 

 

請 在 這 裡 選 擇 WebSnap Data Module , 這 種 Module 通 常 是 用 來 存 放 Database Components , 或 是 一 些 輔 助 型 的 非 視 覺 化 元 件 。 執 行 後 我 們 可 以 設 定 這 個 Data Module 的 一 些 特 性 值 :

 

 

Caching 選 項 之 前 我 們 就 說 明 過 了 , 讓 我 們 看 到 Creation 選 項 , 這 在 Page Module 也 有 出 現 過 , 只 是 那 時 我 沒 有 解 釋 她 , 基 本 上 這 個 選 項 會 影 響 Module 的 建 立 行 為 , 預 設 是 On Demand , 這 代 表 著 程 式 啟 動 時 並 沒 有 建 立 這 個 Module , 而 是 等 到 她 被 使 用 到 時 才 會 建 立 這 個 Module 。 那 確 切 的 建 立 時 機 是 何 時 呢 ?

 

1.                    當 Module 中 的 物 件 被 Activate Module 內 物 件 使 用 到 時 , 例 如 待 會 我 們 會 在 另 一 個 Module 將 她 設 給 一 個 TDataSetAdapter 元 件 。

2.  當 程 式 明 白 的 使 用 到 (Factroy Method) 建 構 函 式 時 , 這 裡 的 建 構 函 式 指 的 是 下 面 這 個 :

<div align=justify>

function WebDataModule1: TWebDataModule1; begin Result := TWebDataModule1( WebContext.FindModuleClass(TWebDataModule1));  end;

 

時 機 1 是 由 Form Load System 幫 我 們 建 立 的 , 另 一 個 就 類 似 我 們 在 Form1 中 使 用 Form2 內 的 元 件 道 理 一 樣 , 只 是 Form 中 我 們 用 的 是 全 域 變 數 , 這 裡 我 們 使 用 的 是 Factroy Method 。 除 了 On Demand 選 項 之 外 , 另 外 還 有 一 個 Always 選 項 可 供 我 們 選 擇 , 這 個 選 項 代 表 這 個 Module 會 在 程 式 執 行 時 立 刻 被 建 立 , 這 可 以 用 在 使 用 率 高 的 Module 上 , 例 如 Application Module 就 是 使 用 這 個 選 項 , 因 為 不 管 使 用 者 要 求 的 是 那 一 個 網 頁 或 是 那 一 個 Action , WebSnap 都 必 須 要 啟 動 Application Module 來 分 發 這 些 要 求 , 如 果 每 次 都 重 新 建 立 的 話 會 浪 費 掉 一 些 時 間 。 因 此 當 某 個 Module 使 用 的 頻 率 相 當 高 的 話 , 你 可 以 考 慮 將 她 設 成 Always , 用 記 憶 體 來 換 取 效 率 。

回 到 範 例 中 , 請 在 Web Data Module 中 放 入 Database 、 Table 、 Session 這 三 個 元 件 , 並 設 定 她 們 的 特 性 值 :

 

 

 

然 後 請 在 Table 中 加 入 所 有 的 Fields 。

 

 

請 你 將 Module 的 名 稱 改 為 wdmData , 存 檔 後 我 們 必 須 再 新 增 一 個 Page Module , 請 選 擇 AdapterPageProducer 作 為 這 個 Page Module 的 PageProducer , 並 將 Page Name 設 成 Grid , 接 著 再 加 入 一 個 TDataSetAdapter 到 這 個 Page Module 中

 

 

為 了 使 用 wdmData 中 的 Table 元 件 , 我 們 必 須 要 uses udmData 。

 

 

完 成 後 你 就 可 以 回 到 DataSetAdapter 元 件 中 來 設 定 她 的 特 性 值 。

 

 

設 定 好 了 之 後 請 雙 按 DataSetAdapter 的 Data 特 性 值 來 加 入 所 有 的 欄 位 。

 

 

接 下 來 我 們 還 要 設 計 網 頁 的 顯 示 畫 面 , 請 開 出 Visual Page Designer 視 窗 , 新 增 一 個 AdapterForm 元 件 , 並 在 裡 面 新 增 一 個 AdapterGrid 元 件 。 

 

然 後 設 定 AdapterGrid.Adapter 特 性 值 為 DataSetAdapter 。

 

 

完 成 後 你 就 可 以 看 到 以 下 的 畫 面 了 , 如 果 資 料 沒 有 顯 示 出 來 的 話 , 可 能 是 你 的 Table 沒 有 打 開 , 請 回 到 Data Module 將 Table 打 開 就 可 以 了 。 

 

嗯 ! 這 個 畫 面 有 點 醜 , 我 們 可 以 利 用 CSS 來 美 化 她 , 請 在 AdapterPageProducer 的 Styles 特 性 值 中 輸 入 以 下 的 CSS 命 令 : 

.CustCol
 {
   WIDTH: 70;
 }

.CompanyCol
 {
   WIDTH: 220;
 }

.Addr2Col
 {
   WIDTH: 60;
 }

.CountryCol
 {
   WIDTH: 140;
 }

.CityCol
 {
   WIDTH: 90;
}

 

輸 入 完 成 後 你 就 能 夠 在 AdapterDisplayField 的 StyleRule 中 選 擇 你 需 要 的 CSS Style 。

 

 

在 目 前 的 WebSnap 網 頁 設 計 過 程 中 相 當 依 賴 CSS , 原 因 是 使 用 CSS 不 但 可 以 簡 化 網 頁 計 , 也 可 以 使 整 個 網 站 的 風 格 一 致 , 因 此 找 本 好 的 CSS 書 籍 是 很 重 要 的 。 讓 我 們 回 到 範 例 中 , 基 本 上 我 們 已 經 完 成 了 一 個 可 以 顯 示 資 料 的 網 頁 了 , 接 著 我 們 還 要 建 立 一 個 編 修 資 料 的 網 頁 , 請 新 增 一 個 Page Module , 一 樣 選 擇 AdapterPageProducer , 並 將 名 稱 設 為 Edit , 完 成 後 新 增 一 個 TDataSetAdapter 元 件 到 這 個 Module 中 , 之 後 就 如 Grid Module 一 樣 連 結 DataModule 並 加 入 所 有 的 欄 位 。 接 著 請 你 開 啟 Visual Page Designer 來 加 入 一 個 AdapterForm 元 件 , 並 在 裡 面 新 增 一 個 AdapterFieldGroup 元 件 :

 

 

然 後 設 定 AdapterFieldGroup.Adapter 特 性 為 DataSetAdapter 。

 

 

由 於 我 們 是 要 設 計 編 修 資 料 的 網 頁 , 因 此 請 設 定 AdapterMode 為 Edit 。

 

 

沒 問 題 的 話 你 應 該 可 以 看 到 一 個 編 修 資 料 的 網 頁 。

 

 

我 們 還 需 要 一 個 將 編 修 後 的 資 料 存 回 資 料 庫 的 按 紐 , 請 在 AdapterForm 元 件 上 按 右 鍵 新 增 一 個 AdapterCommandGroup 並 設 定 DisplayComponent 為 AdapterFieldGroup 。

 

 

完 成 後 你 會 看 到 一 排 操 作 資 料 庫 的 按 紐

 

 

在 這 個 範 例 中 我 們 只 需 要 Apply 按 紐 , 請 在 AdapterCommandGroup 上 按 右 鍵 選 擇 Add Command 選 項 來 新 增 一 個 Apply 按 紐

 

 

到 這 裡 為 止 我 們 已 經 完 成 了 一 個 編 修 資 料 的 網 頁 , 接 著 我 們 希 望 在 Grid 網 頁 每 筆 資 料 的 後 面 加 上 一 個 按 紐 連 結 到 這 個 編 修 的 網 頁 , 因 此 我 們 回 到 Grid Module 中 開 啟 Visual Page Designer 視 窗 , 在 AdapterGrid 元 件 上 按 右 鍵 選 擇 新 增 一 個 AdapterCommandColumn 元 件 。

 

 

完 成 之 後 你 應 該 可 以 看 到 在 每 筆 資 料 的 後 面 顯 示 許 多 的 按 紐 , 這 個 範 例 中 我 們 只 需 要 Edit 按 紐 就 可 以 了 , 請 在 AdapterCommandColumn 上 按 右 鍵 選 擇 新 增 一 個 Edit 按 紐 。

 

 

完 成 後 你 可 以 在 每 筆 資 料 的 最 後 一 欄 看 到 這 個 按 紐 , 同 時 你 也 可 以 使 用 視 窗 上 的 Move Up 按 紐 來 調 整 她 的 顯 示 位 置 。

 

 

然 後 我 們 要 設 定 當 使 用 者 按 下 這 個 按 紐 後 的 動 作 , 這 裡 我 們 只 需 要 她 連 往 Edit Page 就 可 以 了 , 我 們 可 以 利 用 設 定 PageName 特 性 值 來 達 到 這 個 目 的 。

 

 

我 們 希 望 能 夠 讓 使 用 者 在 編 修 網 頁 中 按 下 Apply 按 紐 後 , 自 動 回 到 Grid 網 頁 中 , 這 可 以 經 由 設 定 Apply 按 紐 的 PageName 特 性 值 為 Grid 來 完 成 。

OK! 大 功 告 成 , 接 著 執 行 程 式 來 看 看 我 們 的 成 果 吧 !

 

 

不 幸 的 是 , 當 你 修 改 了 資 料 後 按 下 Apply 按 紐 時 , 你 會 得 到 這 個 錯 誤 :

 

 

這 是 因 為 WebSnap 中 使 用 Variant 來 比 對 資 料 , 這 個 動 作 可 能 會 引 發 型 別 轉 換 上 的 例 外 , 而 我 們 是 在 Debuger 狀 態 下 執 行 程 式 , 因 此 例 外 就 被 DELPHI 所 攔 截 下 來 了 , 這 並 不 會 影 響 資 料 的 儲 存 動 作 , 因 為 WebSnap 中 完 整 的 處 理 了 這 個 例 外 , 當 型 別 轉 換 例 外 發 生 時 視 同 值 已 被 改 變 , 請 你 按 下 F9 讓 程 式 繼 續 執 行 就 可 以 了 。

 

在 網 頁 程 式 中 編 修 資 料 通 常 會 遇 到 一 些 困 難 , 例 如 資 料 的 預 先 驗 證 或 是 格 式 的 限 定 等 , 這 些 問 題 目 前 在 WebSnap 中 並 沒 有 太 多 的 支 援 , 例 如 沒 有 現 成 的 元 件 可 以 把 日 期 拆 成 三 個 輸 入 欄 位 , 也 不 能 夠 使 用 Client-side Script 來 驗 證 空 欄 位 。 雖 然 你 可 以 利 用 PageProducer 加 上 手 動 調 整 Script 來 達 到 這 個 效 果 , 但 這 還 是 不 夠 直 覺 。 這 些 問 題 我 會 在 後 面 的 章 節 中 提 出 一 些 解 決 方 法 。 現 在 讓 我 們 繼 續 加 強 我 們 的 範 例 程 式 , 我 們 希 望 能 讓 使 用 者 使 用 ComboBox 的 方 式 來 選 取 國 家 , 要 達 到 這 個 效 果 我 們 必 須 在 Data Module 中 加 入 一 個 Country 資 料 庫 , 接 著 在 Edit Module 加 入 一 個 TDataSetValueList 元 件 連 結 這 個 資 料 庫 。

 

 

接 著 我 們 要 設 定 在 DataSetAdapter 中 的 Country 欄 位 的 ValueList 特 性 值 。

 

 

完 成 後 開 啟 Visual Page Designer 就 可 以 看 到 Country 已 經 變 成 一 個 ComboBox 了 。

 

 

很 簡 單 不 是 嗎 ? 如 果 你 的 資 料 來 源 不 是 資 料 庫 的 話 , 那 你 可 以 把 TDataSetValueList 換 成 TStringsValueList , 這 樣 你 就 可 以 自 訂 ComboBox 中 的 Items 了 , WebSnap 中 有 許 多 的 HTML Control 可 以 選 擇 , 你 可 以 自 己 試 試 看 。

 

 3-3 、 資 料 搜 尋 及 錯 誤 處 理

 

在 結 束 這 一 章 之 前 , 讓 我 們 來 個 練 習 題 , 你 能 不 能 在 Grid 上 方 加 上 一 個 Edit 與 一 個 按 紐 , 讓 使 用 者 填 入 CustNo 後 按 下 那 個 按 紐 跳 往 Edit 網 頁 呢 ? 結 合 計 算 機 及 這 一 節 的 範 例 中 的 知 識 應 該 足 夠 你 完 成 這 個 工 作 了 。 至 於 搜 尋 資 料 的 部 份 我 們 可 以 使 用 兩 種 方 式 來 做 , 一 種 是 直 接 使 用 DataSet , 另 一 種 是 使 用 TDataSetAdapter 的 Locate Method , 我 建 議 你 使 用 後 者 :

 

procedure TGrid.QueryExecute(Sender: TObject; Params: TStrings);

var

  LocateParams:TLocateParams;

begin

  //use dataset

  //wdmData.Table1.Open;

  //wdmData.Table1.Locate('CustNo',AdaptEnterCustNo.ActionValue.Values[0],[]);

  //use dataset Adapter

  LocateParams:=dsAdaptCust.LocateParamsList.Add;

  LocateParams.AddParam('CustNo',AdaptEnterCustNo.ActionValue.Values[0]);

  LocateParams.AdapterName:=dsAdaptCust.Name;

  dsAdaptCust.Locate;

end;

 

那 當 使 用 者 輸 入 的 資 料 在 資 料 庫 中 找 不 到 時 要 如 何 處 理 呢 ? 你 有 兩 種 選 擇 , 一 種 是 直 接 引 發 一 個 例 外 , WebSnap 會 把 例 外 顯 示 在 網 頁 中 :

 

 if not dsAdaptCust.Locate then

   raise Exception.Create(' 資 料 不 存 在 !');

 

另 外 一 種 方 法 是 設 定 Adapter.Errors 特 性 值 來 標 示 失 敗 , 再 設 定 ActionButton 中 的 ErrorPageName 導 向 特 定 的 網 頁 :

 

if not dsAdaptCust.Locate then

   Adapter1.Errors.AddError(' 資 料 不 存 在 ');

 

在 檔 案 中 的 範 例 程 式 內 實 作 了 這 兩 種 方 法 , 你 可 以 按 照 你 的 需 求 來 選 擇 。

  

3-4 AdapterErrorList 元 件

 

 我 們 也 可 以 利 用 AdapterErrorList 元 件 來 顯 示 錯 誤 資 訊 給 使 用 者 , 請 在 這 個 Page Module 中 開 啟 Visual Page Designer , 並 在 AdapterForm1 中 加 入 一 個 AdapterErrorList 元 件 :

 

 

接 著 使 用 Move Up 按 紐 將 這 個 元 件 移 到 AdapterForm 的 最 上 層 :

 

然 後 設 定 她 的 Adapter 為 Adapter1 。

 

 

接 著 我 們 就 可 以 來 測 試 一 下 結 果 了 , 在 做 測 試 之 前 , 請 將 Query Action 的 OnExecute 事 件 程 式 修 改 成 下 面 的 樣 子 :

 

procedure TGrid.QueryExecute(Sender: TObject; Params: TStrings);

var

  LocateParams:TLocateParams;

begin

  //use dataset

{  wdmData.Table1.Open;

  wdmData.Table1.Locate('CustNo',AdaptEnterCustNo.ActionValue.Values[0],[]) then}  //use dataset Adapter

  LocateParams:=dsaCust.LocateParamsList.Add;

  LocateParams.AddParam('CustNo',AdaptEnterCustNo.ActionValue.Values[0]);

  LocateParams.AdapterName:=dsaCust.Name;

  //use exception

{  if not dsaCust.Locate then

     raise Exception.Create(' 資 料 不 存 在 !');}

  //use errorlist

  if not dsaCust.Locate then

     Adapter1.Errors.AddError(' 資 料 不 存 在 !');

end;

 

當 你 輸 入 一 個 不 正 確 的 值 後 , 你 會 得 到 下 面 這 個 顯 示 畫 面 。

 

 

如 果 你 希 望 由 另 一 個 網 頁 來 顯 示 錯 誤 的 話 , 你 可 以 設 定 這 個 Action 的 ErrorPageName 成 為 該 Error Page 的 名 稱 , 並 且 在 Error Page 網 頁 中 加 入 AdapterForm-AdapterErrorList , 接 著 設 定 AdapterErrorList 為 Adapter1 即 可 。

  

3-5 WebSnap 中 的 網 頁 重 導 功 能

 

這 一 節 我 們 討 論 了 如 何 在 WebSnap 中 處 理 資 料 庫 , 如 何 以 CSS 改 變 網 頁 的 外 觀 , 及 如 何 處 理 錯 誤 與 操 作 TDataSetAdapter 。 你 應 該 注 意 到 我 使 用 了 兩 個 不 同 的 TDataSetAdapter 來 連 結 同 一 個 DataSet , 這 樣 做 的 目 的 是 我 們 以 後 還 可 以 將 Edit 網 頁 修 改 成 不 單 單 只 有 Apply 功 能 的 完 整 編 修 網 頁 。 如 果 你 的 目 的 只 是 要 讓 使 用 者 只 使 用 Apply 功 能 的 話 , 那 你 可 以 將 TDataSetAdapter 放 至 在 WebDataModule 中 或 是 Grid Page Module 中 , 並 在 Edit Module 的 BeforeDispatcher 事 件 中 判 別 使 用 者 是 直 接 進 入 這 個 網 頁 或 是 經 由 Grid Page Module 中 的 Action 進 入 的 。 如 果 是 直 接 進 入 的 話 , 將 使 用 者 導 回 Grid 網 頁 :

 

uses WebReq, WebCntxt, WebFact, Variants, udmData,AdaptReq,SiteComp;

function Edit: TEdit;

 begin

       Result := TEdit(WebContext.FindModuleClass(TEdit));

 end;

procedure TEdit.WebPageModuleBeforeDispatchPage(Sender: TObject;

                          const PageName: String; var Handled: Boolean);

    procedure RedirectToGrid;

     var

      Intf1:IPageDispatcher;

      AppModule:TObject;

      GetAppService:IGetWebAppServices;

      AppService:IWebAppServices;

      AppComponents:IGetWebAppComponents;

     begin

      if Supports(WebContext.FindApplicationModule(Self),IGetWebAppServices,GetAppService) then

        begin

         AppService:=GetAppService.GetWebAppServices;

         if Supports(AppService,IGetWebAppComponents,AppComponents) then

          begin

            Intf1:=AppComponents.GetPageDispatcher;

            Intf1.DispatchPageName('Grid',Response,[dpPublished]);

            Handled:=True;

          end;

         end;

      end;

 var

    Intf:IActionFieldValues;

  begin

    if Supports(WebContext.AdapterRequest,IActionFieldValues,Intf) then

       begin

         if VarIsEmpty(Intf.ValueOfField('EnterCustNo')) then

           RedirectToGrid;

         end

         else RedirectToGrid;

      end;

  end;

 

 

或 者 是 你 也 可 以 使 用 WebDisp Unit 中 的 這 兩 個 函 式 來 做 轉 向 的 動 作 :

 

function DispatchPageName(const APageName: string; AResponse: TWebResponse;

                                                  AFlags: TDispatchPageFlags): Boolean;

function RedirectToPageName(const APageName: string; AParams: TStrings; AResponse: TWebResponse;

                                              AFlags: TDispatchPageFlags): Boolean;

 

這 兩 個 函 式 都 能 夠 正 常 的 重 新 導 向 網 頁 , 最 明 顯 的 差 別 是 使 用 RedirectToPageName 函 式 時 , 我 們 可 以 將 參 數 值 傳 到 目 標 Page Module 的 DefaultAction 中 , 再 藉 由 參 數 值 來 做 出 正 確 的 反 應 , 也 就 是 說 她 會 觸 發 目 標 Page Module 中 的 DefaultAction 事 件 。 DispatchPageName 則 沒 有 , 從 動 作 的 模 式 上 來 比 較 的 話 , RedirectToPageName 是 一 個 外 部 動 作 , 這 個 意 思 是 說 她 是 經 由 瀏 覽 器 來 轉 向 , DispatchPageName 則 是 一 個 內 部 轉 向 動 作 。 當 你 需 要 單 純 的 轉 向 時 , 你 可 以 使 用 DispatchPageName , 當 你 需 要 轉 向 並 觸 發 該 Page Module 中 的 DefaultAction 時 , 你 可 以 選 擇 RedirectToPageName 。 另 外 如 果 你 轉 向 的 網 頁 不 在 程 式 中 , 也 就 是 說 是 一 個 URL 時 , 那 你 可 以 使 用 SafeRedirect , 這 個 函 式 可 以 在 網 頁 轉 向 後 將 Cookie 送 給 目 標 網 頁 。

  

本 章 後 記

 

  如 同 在 第 一 節 所 講 的 , 用 WebSnap 來 開 發 網 頁 資 料 庫 程 式 並 不 困 難 , 最 重 要 的 是 她 延 續 了 我 們 之 前 開 發 資 料 庫 程 式 的 慨 念 , 讓 我 們 的 學 習 曲 線 較 為 平 穩 。 當 然 ! 這 一 章 只 是 個 開 始 , 下 面 我 們 會 陸 續 介 紹 其 它 重 要 的 課 題 。

 

<第 四 章 、 進 階 資 料 庫 網 頁 設 計>



本文地址:http://com.8s8s.com/it/it5758.htm