zoukankan      html  css  js  c++  java
  • FineUI小技巧(1)简单的购物车页面

    起因

    最初是一位 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)向子窗口传值,向父窗口传值
  • 相关阅读:
    多态与鸭子类型
    mixin与派生
    4.9作业
    property
    继承
    封装
    《梦断代码》阅读笔记1
    阅读笔记3——《大道至简》第四、五、六章
    阅读笔记2——《大道至简》第二、三章
    阅读笔记1——《大道至简》第一章
  • 原文地址:https://www.cnblogs.com/sanshi/p/3794135.html
Copyright © 2011-2022 走看看