Web Application 開 發 利 器 - WebSnap!
第 十 二 章 、 DateTime 欄 位 的 拆 解 與 LookupField 、 Postback 技 術
在 撰 寫 資 料 庫 程 式 中 , 我 們 常 常 需 要 用 到 Lookup Field , 例 如 當 使 用 者 選 取 了 某 一 個 客 戶 後 , 帶 出 該 客 戶 的 公 司 名 稱 , 這 在 一 般 的 Windows 程 式 中 並 不 困 難 , 但 如 今 我 們 是 身 在 Web Application 中 , 需 要 考 慮 的 問 題 與 技 術 因 為 Web 的 運 作 方 式 不 同 , 使 用 的 方 法 也 就 不 同 , 首 先 是 Lookup ComboBox 的 顯 示 方 式 , 也 就 是 業 界 俗 稱 的 開 窗 動 作 , 在 Web Application 中 我 們 可 以 使 用 兩 種 方 式 達 到 同 樣 的 效 果 , 一 是 直 接 將 內 容 顯 示 於 ComboBox(Select) 中 , 另 一 種 就 是 開 一 個 新 瀏 覽 視 窗 , 兩 種 都 有 一 些 優 點 及 缺 點 , 直 接 將 內 容 顯 示 於 ComboBox 中 我 們 必 須 面 臨 一 個 抉 擇 , 是 要 將 資 料 全 部 下 載 至 Client 端 , 然 後 以 HTML+Script 方 式 處 理 , 還 是 等 使 用 者 選 擇 了 某 一 個 客 戶 後 , 讓 網 頁 重 新 讀 取 一 次 , 藉 由 這 個 機 會 我 們 再 將 Lookup 的 資 料 填 入 返 回 的 HTML 中 , 這 就 是 Postback 技 術 , 第 一 種 方 式 必 須 視 資 料 量 的 大 小 而 定 , 如 果 資 料 量 太 大 , 這 樣 的 處 理 可 是 很 費 時 的 , 優 點 是 一 旦 傳 輸 完 畢 後 , 使 用 者 可 以 用 較 快 的 速 度 選 取 資 料 , Postback 則 是 只 傳 送 必 要 的 資 料 , 例 如 客 戶 編 號 , 待 使 用 者 選 取 了 某 個 客 戶 編 號 後 Postback 至 WebServer , 這 時 我 們 可 以 取 得 使 用 者 所 選 擇 的 客 戶 編 號 , 進 而 取 得 Lookup 的 資 料 , 最 後 將 資 料 與 網 頁 整 合 後 傳 回 給 使 用 者 , 這 種 方 式 的 缺 點 是 你 必 須 要 自 行 處 理 這 之 間 的 動 作 , 因 為 目 前 的 WebSnap 並 未 明 白 支 援 Postback , 優 點 是 你 的 資 料 獲 得 較 多 的 保 護 , 使 用 者 能 看 到 的 只 是 編 號 而 已 , 同 時 速 度 上 也 比 之 前 的 方 法 有 效 率 , 在 這 裡 我 使 用 Postback 技 術 , 這 對 你 來 說 較 實 際 一 點 。
除 了 Lookup 的 問 題 外 , 我 們 還 有 另 一 個 問 題 , 那 就 是 日 期 輸 入 的 問 題 , 其 實 這 個 問 題 並 不 是 很 重 要 , 因 為 你 只 需 要 提 示 輸 入 的 範 例 , 讓 使 用 者 了 解 你 的 網 頁 所 需 要 的 正 確 日 期 與 時 間 格 式 即 可 , 但 是 將 一 個 日 期 拆 解 為 三 個 欄 位 來 輸 入 的 技 術 卻 能 夠 開 啟 你 另 一 扇 運 用 WebSnap 的 窗 , 因 此 我 將 與 你 討 論 這 個 技 術 。
12-1 一 變 三 , DateTime 欄 位 拆 解
在 開 始 撰 寫 程 式 之 前 , 我 們 先 想 想 , 要 如 何 才 能 將 一 個 DateTime 的 AdapterField 拆 解 為 三 個 欄 位 ? 首 先 我 們 必 須 要 建 立 三 個 虛 擬 的 AdapterField , 用 來 儲 存 拆 解 後 的 年 、 月 、 日 三 個 值 , 接 著 取 代 原 來 的 DateTime 欄 位 顯 示 於 網 頁 上 , 當 使 用 者 編 修 這 三 個 欄 位 之 後 , 我 們 必 須 將 這 三 個 欄 位 組 合 後 存 回 原 來 的 AdapterField 中 , 亦 或 是 直 接 存 回 資 料 庫 之 中 , 上 面 的 流 程 中 我 們 有 幾 個 關 鍵 必 須 要 先 解 決 , 我 們 在 那 個 事 件 中 將 原 來 的 DateTime 資 料 解 出 , 並 存 放 到 這 三 個 虛 擬 AdapterField 之 中 ? 又 在 那 個 事 件 中 將 這 些 值 存 回 資 料 庫 中 ? 第 一 個 問 題 相 信 你 大 慨 有 點 譜 了 , 就 是 OnGetValue 事 件 , 我 們 可 以 在 虛 擬 欄 位 中 的 OnGetValue 事 件 將 值 取 出 , 並 傳 回 給 WebSnap 顯 示 在 網 頁 上 , 我 們 先 解 決 這 個 問 題 , 第 一 步 當 然 是 將 這 三 個 虛 擬 AdapterField 加 到 dsaOrders 這 個 DataSetAdapter 中 :
接 著 撰 寫 她 們 的 OnGetValue 事 件 程 式 , 因 為 我 們 會 常 用 到 解 出 年 、 月 、 日 的 程 式 碼 , 因 此 我 們 將 她 寫 成 函 式 , 方 便 使 用 :
TExtractDateTo=(extYear,extMonth,extDay);
TdmData = class(TWebDataModule)
… … … … … … … … … … …
private
procedure ExtractDateToAdapterField(var Value:Variant;
AFullValue:Variant;ExtOption:TExtractDateTo);
… … … … … … … … … … …
procedure TdmData.ExtractDateToAdapterField(var Value:Variant;
AFullValue:Variant;ExtOption:TExtractDateTo);
var
vYear,vMonth,vDay:Word;
begin
DecodeDate(AFullValue,vYear,vMonth,vDay);
case ExtOption of
extYear: Value:=vYear;
extMonth: Value:=vMonth;
extDay: Value:=vDay;
end;
end;
procedure TdmData.AdaptSaleDate_YearGetValue(Sender: TObject;
var Value: Variant);
begin
ExtractDateToAdapterField(Value,AdaptSaleDate.Value,extYear);
end;
procedure TdmData.AdaptSaleDate_MonthGetValue(Sender: TObject;
var Value: Variant);
begin
ExtractDateToAdapterField(Value,AdaptSaleDate.Value,extMonth);
end;
procedure TdmData.AdaptSaleDate_DayGetValue(Sender: TObject;
var Value: Variant);
begin
ExtractDateToAdapterField(Value,AdaptSaleDate.Value,extDay);
end;
完 成 後 我 們 就 開 始 設 計 編 修 資 料 的 網 頁 , 請 在 專 案 中 新 增 一 個 Page Module , 將 她 命 名 為 Edit , 接 著 當 然 是 uses dmData 與 Home 這 兩 個 Module , 並 將 標 題 圖 形 與 導 覽 列 加 入 , 再 開 啟 Visual Page Designer , 這 裡 我 們 照 樣 加 入 兩 個 AdapterForm , 一 個 是 顯 示 標 題 圖 形 與 導 覽 列 , 另 一 個 則 是 讓 使 用 者 編 修 資 料 用 , 這 裡 我 們 使 用 LayoutGroup 來 編 排 網 頁 , 因 此 請 在 AdapterForm2 中 加 入 一 個 AdapterFieldGroup , 並 將 她 的 Adapter 設 為 dmData.dsaOrders , 與 設 定 Custom 特 性 為 border= ” 1 ” , 這 會 讓 這 個 FieldGroup 擁 有 單 框 的 顯 示 樣 式 :
在 AdapterFieldGroup2 下 新 增 一 個 LayoutGroup , 並 在 他 上 方 按 右 鍵 選 擇 Add Columns , 並 加 入 OrderNo 及 CustNo 兩 個 欄 位 。
我 們 也 可 以 利 用 LayoutGroup 的 DisplayColumns 特 性 值 來 控 制 這 些 Field 的 排 列 方 式 , 請 將 她 設 為 2.
接 著 我 們 在 AdapterFieldGroup2 中 加 入 第 二 個 LayoutGroup , 將 我 們 的 虛 擬 AdapterField 加 入 :
完 成 後 你 會 看 到 這 個 不 太 令 人 滿 意 的 排 列 :
這 可 以 利 用 LayoutGroup 的 DisplayColumns 來 解 決 , 請 將 LayoutGroup2 的 DisplayColumns 設 為 3 。
接 著 設 定 這 三 個 Field 的 DisplayWidth 、 MaxLength 、 Caption 特 性 值 。
上 圖 中 我 們 將 年 這 個 Field 的 Caption 設 為 SaleDate , 且 將 DisplayWidth 與 MaxLength 都 設 為 4 , 這 可 以 控 制 使 用 者 可 輸 入 的 字 串 長 度 , 接 著 將 月 日 這 兩 個 Field 的 Caption 清 空 , 並 設 定 DisplayWidth 、 MaxLength 為 2 。
接 著 加 入 一 個 AdapterCommandGroup 到 AdapterForm2 中 , 把 她 的 DisplayComponents 特 性 值 設 為 AdapterFieldGroup2 , 並 且 加 入 Apply 、 Cancel 這 兩 個 Action 。
OK! 這 樣 就 算 是 完 成 編 修 資 料 的 網 頁 了 , 接 著 我 們 要 回 到 Grid Module 中 , 在 每 一 筆 資 料 的 最 後 加 入 一 個 Edit Action , 讓 使 用 者 可 以 經 由 這 個 Action 到 達 編 修 資 料 的 網 頁 。
我 使 用 了 Hyperlink 的 方 式 來 顯 示 這 個 Edit Action , 你 可 以 經 由 設 定 cmdEditRow 的 DisplayType 來 調 整 顯 示 Action 的 樣 式 , 除 了 這 個 特 性 之 外 , 我 們 還 要 設 定 她 的 PageName 特 性 為 Edit , 這 樣 就 可 以 在 使 用 者 點 選 這 個 Action 後 導 向 我 們 的 編 修 網 頁 .
不 錯 吧 , 我 們 成 功 的 將 一 個 欄 位 拆 解 成 三 個 欄 位 來 顯 示 了 , 這 很 有 用 不 是 嗎 ? 相 信 你 一 定 會 從 這 個 範 例 中 得 到 許 多 其 它 的 運 用 , 而 不 只 侷 限 在 日 期 輸 入 之 中 。 接 下 來 我 們 繼 續 處 理 編 修 後 的 儲 存 動 作 , 我 們 可 以 在 這 些 虛 擬 的 AdapterField 的 OnUpdateValue 事 件 中 處 理 這 個 問 題 :
private
… … … … … … …
procedure UpdatePartDate(AField:TDataSetAdapterField;AYear,AMonth,ADay:TAdapterField);
… … … … … … … … …
procedure TdmData.UpdatePartDate(AField:TDataSetAdapterField;AYear,AMonth,ADay:TAdapterField);
var
Year,Month,Day:Word;
begin
DecodeDate(AField.Value,Year,Month,Day);
if Assigned(AYear.ActionValue) then
Year:=AYear.ActionValue.Values[0];
if Assigned(AMonth.ActionValue) then
Month:=AMonth.ActionValue.Values[0];
if Assigned(ADay.ActionValue) then
Day:=ADay.ActionValue.Values[0];
if (EncodeDate(Year,Month,Day) <> dsaOrders.DataSet.FieldByName(AField.FieldName).AsDateTime) then
dsaOrders.DataSet.FieldByName(AField.FieldName).AsDateTime:=
EncodeDate(Year,Month,Day);
end;
procedure TdmData.AdaptSaleDate_YearUpdateValue(Sender: TObject;
Value: Variant);
begin
UpdatePartDate(AdaptSaleDate,AdaptSaleDate_Year,AdaptSaleDate_Month,
AdaptSaleDate_Day);
end;
嘿 ! 我 沒 寫 錯 , 我 只 寫 了 一 個 AdaptSaleDate_Year.OnUpdateValue 函 式 , 那 這 樣 可 以 更 新 三 個 欄 位 嗎 ? 如 果 使 用 者 只 修 改 了 月 怎 麼 辦 ? 這 牽 扯 到 WebSnap 如 何 判 定 使 用 者 是 否 修 改 了 網 頁 中 的 資 料 , 其 實 很 簡 單 , 她 只 需 要 將 ActionValue 中 的 值 與 現 行 的 值 做 比 對 , 當 其 中 有 一 個 值 不 同 時 , 就 會 進 行 Update 的 動 作 , 這 也 就 是 說 當 網 頁 中 的 值 被 改 變 , 並 且 執 行 了 Apply Action 時 , WebSnap 會 進 行 現 行 值 與 ActionValue 的 比 對 動 作 , 當 有 一 個 值 不 同 時 , 就 進 行 Update 的 動 作 , 而 且 會 呼 叫 所 有 的 AdapterField 中 的 OnUpdateValue 事 件 , 這 也 是 我 為 何 可 以 只 寫 一 個 事 件 就 可 以 Update 3 個 欄 位 的 原 因 , 諸 如 此 類 的 小 陷 阱 在 WebSnap 中 到 處 都 是 , 懂 得 利 用 她 們 的 話 , 對 程 式 的 效 率 會 有 相 當 大 的 幫 助 。
12-2 Postback 技 術
接 下 來 我 們 將 介 紹 如 何 在 WebSnap 中 運 用 Postback 技 術 , 這 個 技 術 可 以 讓 我 們 處 理 Lookup Field 的 問 題 , 請 你 回 到 DELPHI IDE 中 , 加 入 兩 個 Table 及 一 個 DataSource 到 Web DataModule 中 , 並 設 定 她 們 的 TableName 分 別 為 Items( 訂 單 明 細 檔 ) 、 Employee( 員 工 檔 ) , 將 Items 與 Orders 設 定 為 Master-Detail 關 係 :
然 後 加 入 一 個 DataSetAdapter , 連 結 至 Items Table , 並 新 增 一 個 DataSetValueList 連 結 至 Employee Table , 並 將 她 的 NamedField 設 為 EmpNo:
接 著 開 啟 dsaOrders 的 Fields Designer 加 入 一 個 AdapterField , 這 是 用 來 顯 示 員 工 名 稱 的 欄 位 :
然 後 選 擇 AdaptEmpNo 這 個 AdapterField , 將 她 的 ValueList 設 為 dsvlEmployee ( 員 工 資 料 的 DataSetValueList 元 件 ):
完 成 後 我 們 就 可 以 開 始 撰 寫 相 關 的 程 式 碼 , 我 們 必 須 加 入 一 個 私 有 變 數 到 這 個 Module 中 , 這 是 為 了 識 別 目 前 的 狀 態 是 否 為 Postback , 亦 或 是 一 般 情 況 :
private
FIsPostBack:Boolean;
接 著 是 取 得 員 工 名 稱 的 AdaptEmpName.OnGetValue 事 件 :
procedure TdmData.AdaptEmpNameGetValue(Sender: TObject;
var Value: Variant);
var
LocateValue:Integer;
begin
if not tbEmployee.Active then tbEmployee.Open;
if Assigned(AdaptEmpNo.ActionValue) and FIsPostBack then
LocateValue:=AdaptEmpNo.ActionValue.Values[0]
else
LocateValue:=AdaptEmpNo.Value;
if tbEmployee.Locate('EmpNo',LocateValue,[]) then
Value:=tbEmployee.FieldByName('LastName').AsString+' '+
tbEmployee.FieldByName('FirstName').AsString
else
Value:='';
end;
為 了 維 持 Postback 後 的 資 料 , 我 們 還 要 撰 寫 一 個 事 件 , 那 就 是 AdaptOrderNo.OnGetValue 事 件 :
procedure TdmData.AdaptOrderNoGetValue(Sender: TObject; Field: TField;
var Value: Variant);
begin
if Assigned(TDataSetAdapterField(Sender).ActionValue) and
FIsPostBack then
Value:=TDataSetAdapterField(Sender).ActionValue.Values[0]
else
Value:=Field.Value;
end;
將 這 個 事 件 設 定 給 AdaptCustNo,AdaptEmpNo,AdaptSaleDate,AdaptOrderNo 這 四 個 AdapterField:
最 後 開 啟 dsaOrders 的 Action Designer 加 入 一 個 AdapterAction , 並 撰 寫 相 關 的 程 式 碼 就 完 成 了 Postback 的 基 本 功 能 了 :
procedure TdmData.ActionPostBackExecute(Sender: TObject; Params: TStrings);
var
vLocateParams:TLocateParams;
begin
FIsPostBack:=True;
vLocateParams:=dsaOrders.LocateParamsList.Add;
vLocateParams.AdapterName:=dsaOrders.Name;
vLocateParams.AddParam('OrderNo',AdaptOrderNo.ActionValue.Values[0]);
dsaOrders.Locate;
end;
procedure TdmData.ActionPostBackAfterGetResponse(Sender: TObject;
Params: TStrings);
begin
FIsPostBack:=False;
end;
請 開 啟 原 來 的 Edit Module , 重 新 設 計 網 頁 的 畫 面 , 將 LayoutGroup2.DisplayColumns 設 成 4 , 接 著 加 入 EmpNo 、 EmpName 這 兩 個 欄 位 , 將 EmpName 的 Caption 清 空 , 並 且 把 ViewMode 設 為 vmDisplay:
然 後 在 AdapterCommandGroup2 中 加 入 我 們 剛 剛 新 增 的 Postback Action:
接 著 把 她 的 DisplayType 特 性 設 成 ctAnchor , PageName 設 為 Edit:
完 成 後 請 切 到 HTML 這 一 頁 , 你 會 找 到 下 面 的 這 段 HTML 碼 , 將 她 複 製 起 來 :
回 到
Browser 頁 , 選 擇 LayoutGroup1 中 的 EmpNo 欄 位 , 將 上 面 那 段 碼 貼 到 Custom 特 性 值 中 , 將 onclick 改 成 onchange:
完 成 後 將 AdapterCommandGroup2 中 的 Postback Action 刪 除 掉 , 並 在 AdapterPageProducer 上 按 右 鍵 , 新 增 一 個 AdapterForm , 這 是 用 來 顯 示 明 細 資 料 用 的 , 在 她 之 中 新 增 一 個 AdapterGrid , 連 結 至 dsaItems ( 訂 單 明 細 檔 的 DataSetAdapter):
這 樣 就 完 成 了 Postback 的 技 術 了 , 執 行 畫 面 如 下 :
在 這 個 技 術 中 我 們 學 到 了 如 何 運 用 Custom 加 入 Client-Side Script , 並 且 利 用 她 來 完 成 我 們 的 Postback 技 術 , 這 個 技 術 相 當 有 趣 , 你 可 以 試 著 將 CustNo 也 改 成 同 樣 的 Select , 相 信 你 會 得 到 許 多 靈 感 !
本 章 後 記
Postback 技 術 在 撰 寫 資 料 庫 網 頁 中 是 很 重 要 的 , 幾 乎 是 不 可 缺 的 , 因 此 你 必 須 多 加 熟 悉 她 , 上 面 的 程 式 碼 中 我 們 使 用 到 了 Action 的 AfterGetResponse 事 件 , 並 在 事 件 中 將 Postback 旗 標 清 空 , 這 個 事 件 會 在 Action 執 行 並 產 生 Response 資 料 後 觸 發 , 正 好 符 合 我 們 的 需 求 。