FineUI小技巧(1)簡單的購物車頁面

三生石上(FineUI控制元件)發表於2014-06-18

起因

最初是一位 FineUI 網友對購物車功能的需求,需要根據產品單價和數量來計算所有選中商品的總價。

這個邏輯最好在前臺使用JavaScript實現,如果把這個邏輯移動到後臺C#實現,則會導致過多的AJAX請求而影響使用者體驗。

 

最終效果

 

準備資料

在生成頁面之前,我們需要準備購物車的資料,這裡只是簡單的用表格來模擬資料:

 1 protected DataTable GetCartDataTable()
 2 {
 3     DataTable table = new DataTable();
 4     table.Columns.Add(new DataColumn("Id", typeof(int)));
 5     table.Columns.Add(new DataColumn("Code", typeof(String)));
 6     table.Columns.Add(new DataColumn("Name", typeof(String)));
 7     table.Columns.Add(new DataColumn("Desc", typeof(String)));
 8     table.Columns.Add(new DataColumn("Price", typeof(float)));
 9     table.Columns.Add(new DataColumn("Number", typeof(int)));
10     
11     DataRow row = table.NewRow();
12     row[0] = 101;
13     row[1] = "100022";
14     row[2] = "商品一";
15     row[3] = "這是商品一的介紹";
16     row[4] = 35.5;
17     row[5] = 1;
18     table.Rows.Add(row);
19 
20     row = table.NewRow();
21     row[0] = 102;
22     row[1] = "100023";
23     row[2] = "商品二";
24     row[3] = "這是商品二的介紹";
25     row[4] = 18.99;
26     row[5] = 2;
27     table.Rows.Add(row);
28 
29     row = table.NewRow();
30     row[0] = 103;
31     row[1] = "100024";
32     row[2] = "商品三";
33     row[3] = "這是商品三的介紹";
34     row[4] = 18.99;
35     row[5] = 2;
36     table.Rows.Add(row);
37 
38     row = table.NewRow();
39     row[0] = 104;
40     row[1] = "100025";
41     row[2] = "商品四";
42     row[3] = "這是商品四的介紹";
43     row[4] = 22.00;
44     row[5] = 1;
45     table.Rows.Add(row);
46 
47     return table;
48 }

 

頁面標籤

前臺頁面使用了VBox佈局,用來實現底部彙總皮膚的高度固定,頂部表格的高度自適應頁面高度的佈局:

 1 <f:PageManager ID="PageManager1" AutoSizePanelID="Panel2" runat="server" />
 2 <f:Panel ID="Panel2" runat="server" ShowBorder="false" Layout="VBox" BoxConfigAlign="Stretch"
 3     BoxConfigPosition="Start" BoxConfigPadding="5" BoxConfigChildMargin="0 5 0 0"
 4     ShowHeader="false">
 5     <Items>
 6         <f:Grid ID="Grid1" ShowBorder="true" BoxFlex="1" ShowHeader="true" Title="購物車"
 7             EnableCollapse="true" runat="server" EnableCheckBoxSelect="true" CheckBoxSelectOnly="true"
 8             DataKeyNames="Id,Code,Name" EnableTextSelection="true">
 9             
10         </f:Grid>
11         <f:ContentPanel runat="server" CssClass="totalpanel" ShowBorder="true" ShowHeader="false">
12             
13         </f:ContentPanel>
14     </Items>
15 </f:Panel>

 

VBox佈局和HBox佈局對於 FineUI 來說舉足輕重,如果你還搞不清楚其中的引數含義,請移步FineUI教程。  

這裡有個小技巧,由於上下兩個皮膚緊貼在一起,所以中間的兩個邊框就顯得不好看了,我們只需透過簡單的CSS來調整,使得下面皮膚的頂部邊框寬度為零:

1 <style>
2     .totalpanel .x-panel-body {
3         border-top-width: 0 !important;
4     }
5 </style>

 

下面來看錶格的定義:

 1 <f:Grid>
 2     <Columns>
 3         <f:RowNumberField />
 4         <f:BoundField Width="120px" DataField="Code" DataFormatString="{0}" HeaderText="商品程式碼" />
 5         <f:BoundField DataField="Name" ExpandUnusedSpace="true" DataFormatString="{0}" HeaderText="商品名稱" />
 6         <f:BoundField Width="120px" DataField="Price" HeaderText="商品單價" DataFormatString="¥{0:F}" />
 7         <f:TemplateField HeaderText="數量" Width="120px">
 8             <ItemTemplate>
 9                 <input type="hidden" class="price" runat="server" value='<%# Eval("Price") %>' />
10                 <asp:TextBox runat="server" Width="98%" ID="tbxNumber" CssClass="number"
11                     TabIndex='<%# Container.DataItemIndex + 10 %>' Text='<%# Eval("Number") %>'></asp:TextBox>
12             </ItemTemplate>
13         </f:TemplateField>
14         <f:TemplateField HeaderText="小計" Width="120px">
15             <ItemTemplate>
16                 <asp:Label runat="server" CssClass="xiaoji" Text='<%# "¥" + GetXiaoji(Eval("Price"), Eval("Number")) %>'></asp:Label>
17             </ItemTemplate>
18         </f:TemplateField>
19     </Columns>
20 </f:Grid>

 

一些小技巧:

  • DataFormatString="¥{0:F}" 將浮點數格式化為兩個小數位的字串。
  • Container.DataItemIndex 表示當前項的序號,設定TabIndex是為了啟用Tab鍵導航
  • 隱藏欄位 class="price",是為了方便客戶端使用JavaScript獲取產品單價
  • 數量的文字輸入框的 CssClass="number" 同樣是為了方便客戶端呼叫
  • 透過後臺定義的C#函式 GetXiaoji 來計算初始產品價格小計

下面來看下 GetXiaoji 的定義:

1 protected string GetXiaoji(object priceobj, object numberobj)
2 {
3     float price = Convert.ToSingle(priceobj);
4     int number = Convert.ToInt32(numberobj);
5 
6     return String.Format("{0:F}", price * number);
7 }

 

接下來看下彙總皮膚的標籤定義:

 1 <f:ContentPanel>
 2     <div style="text-align: right; margin: 10px;">
 3         <div style="margin-bottom: 10px;">
 4             <input type="hidden" id="TOTAL_NUMBER" name="TOTAL_NUMBER" />
 5             <span id="totalNumber" style="color: red;"></span>
 6             件商品
 7         </div>
 8         <div style="margin-bottom: 10px;">
 9             <input type="hidden" id="TOTAL_PRICE" name="TOTAL_PRICE" />
10             總計:<span id="totalPrice" style="color: red; font-size: 1.5em; font-weight: bold;"></span>
11         </div>
12         <div>
13             <f:Button runat="server" Text="去結算" Enabled="false" Size="Large" ID="btnGotoPay" OnClick="btnGotoPay_Click"></f:Button>
14         </div>
15     </div>
16 </f:ContentPanel>

 

這裡面的幾個小技巧:

  • 隱藏欄位 TOTAL_NUMBER 和 TOTAL_PRICE 是為了方便在後臺獲取總價和商品總數
  • 預設設定提交按鈕的 Enabled="false",在使用者更改選中商品數量時來決定是否禁用  

前臺JavaScript邏輯

FineUI 雖然號稱 No JavaScript,但這裡的真正意思是 80% 的應用場景不需要使用 JavaScript 就能輕鬆實現。

對於購物車這種需要前臺互動的頁面,還是需要開發者有一定的指令碼編寫功底。下面先羅列一下全部的JavaScript程式碼:

 1 var gridClientID = '<%= Grid1.ClientID %>';
 2 var btnGotoPayClientID = '<%= btnGotoPay.ClientID %>';
 3 var numberSelector = '.f-grid-tpl input.number';
 4 var priceSelector = '.f-grid-tpl input.price';
 5 
 6 function getRowNumber(row) {
 7     return parseInt(row.find(numberSelector).val(), 10);
 8 }
 9 function getRowPrice(row) {
10     return parseFloat(row.find(priceSelector).val());
11 }
12 
13 function updateTotal() {
14     var grid = F(gridClientID);
15     var selection = grid.getSelectionModel().getSelection();
16     var store = grid.getStore();
17 
18     var total = 0;
19     $.each(selection, function (index, item) {
20         var rowIndex = store.indexOf(item);
21         var row = $(grid.body.el.dom).find('.x-grid-row').eq(rowIndex);
22         total += getRowNumber(row) * getRowPrice(row);
23     });
24 
25     $('#totalNumber').text(selection.length);
26     $('#totalPrice').text("¥" + total.toFixed(2));
27 
28     $('#TOTAL_NUMBER').val(selection.length);
29     $('#TOTAL_PRICE').val(total.toFixed(2));
30 
31     var gotoPayBtn = F(btnGotoPayClientID);
32     if (total === 0) {
33         gotoPayBtn.disable();
34     } else {
35         gotoPayBtn.enable();
36     }
37 }
38 
39 function registerNumberChangeEvents() {
40     var grid = F(gridClientID);
41 
42     // 數量改變事件
43     // http://stackoverflow.com/questions/17384218/jquery-input-event
44     $(grid.el.dom).find(numberSelector).on('input propertychange', function (evt) {
45         var $this = $(this);
46 
47         var row = $this.parents('.x-grid-row');
48         var number = getRowNumber(row);
49         var price = getRowPrice(row);
50         var resultNode = row.find('.f-grid-tpl span.xiaoji');
51 
52         resultNode.text("¥" + (number * price).toFixed(2));
53 
54         updateTotal();
55     });
56 }
57 
58 function registerSelectionChangeEvents() {
59     var grid = F(gridClientID);
60 
61     grid.on('selectionchange', function (cmp, selected) {
62         updateTotal();
63     });
64 }
65 
66 // 頁面第一次載入完成後呼叫的函式
67 F.ready(function () {
68     registerNumberChangeEvents();
69     registerSelectionChangeEvents();
70     updateTotal();
71 });

 

這裡只給出一些小技巧的提醒:

  • F.ready 用來初始化所有需要的JavaScript程式碼,包含對 updateTotal 的呼叫
  • registerNumberChangeEvents 註冊數量文字框改變的處理函式
  • 文字框的 input 事件用來監視文字框的內容變化,包含鍵盤輸入、複製貼上等,IE8不支援此事件但可以使用 propertychange 代替
  • registerSelectionChangeEvents 註冊使用者選中商品行改變的事件處理函式
  • updateTotal 中根據總價來決定是否啟用提交按鈕  

後臺C#邏輯

後臺來顯示彙總資訊,對熟悉 FineUI 的網友應該來說很簡單:

 1 protected void btnGotoPay_Click(object sender, EventArgs e)
 2 {
 3     StringBuilder sb = new StringBuilder();
 4     sb.Append("<ol>");
 5     foreach(int rowIndex in Grid1.SelectedRowIndexArray) {
 6         System.Web.UI.WebControls.TextBox tbxNumber = (System.Web.UI.WebControls.TextBox)Grid1.Rows[rowIndex].FindControl("tbxNumber");
 7 
 8         sb.AppendFormat("<li>{0}({1})</li>", Grid1.DataKeys[rowIndex][2], tbxNumber.Text);
 9     }
10     sb.Append("</ol><hr/>");
11 
12     sb.AppendFormat("共 {0} 件商品,總計 ¥{1}", Request.Form["TOTAL_NUMBER"], Request.Form["TOTAL_PRICE"]);
13 
14     Alert.Show(sb.ToString(), MessageBoxIcon.Information);
15 }

 

原始碼免費下載

這個簡直就是廢話!

這個示例會出現在下個版本的 FineUI(開源版)中,不過目前你可以直接從微軟的 codeplex 網站下載全部原始碼:

https://fineui.codeplex.com/SourceControl/list/changesets

24 張專業版截圖

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

推薦本文

如果本文對你有一定的啟發或幫助,請點選好文要頂。你也可以透過關注本部落格來及時獲取 FineUI 的最新資訊。

 

 

《FineUI小技巧》系列文章目錄

  1. FineUI小技巧(1)簡單的購物車頁面
  2. FineUI小技巧(2)將表單內全部欄位禁用、只讀、設定無效標識
  3. FineUI小技巧(3)表格匯出與檔案下載
  4. FineUI小技巧(4)關閉窗體那些事
  5. FineUI小技巧(5)向子視窗傳值,向父視窗傳值

 

相關文章