起因

最初是一位 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[] = ;
  13. row[] = "";
  14. row[] = "商品一";
  15. row[] = "这是商品一的介绍";
  16. row[] = 35.5;
  17. row[] = ;
  18. table.Rows.Add(row);
  19.  
  20. row = table.NewRow();
  21. row[] = ;
  22. row[] = "";
  23. row[] = "商品二";
  24. row[] = "这是商品二的介绍";
  25. row[] = 18.99;
  26. row[] = ;
  27. table.Rows.Add(row);
  28.  
  29. row = table.NewRow();
  30. row[] = ;
  31. row[] = "";
  32. row[] = "商品三";
  33. row[] = "这是商品三的介绍";
  34. row[] = 18.99;
  35. row[] = ;
  36. table.Rows.Add(row);
  37.  
  38. row = table.NewRow();
  39. row[] = ;
  40. row[] = "";
  41. row[] = "商品四";
  42. row[] = "这是商品四的介绍";
  43. row[] = 22.00;
  44. row[] = ;
  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][], 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)向子窗口传值,向父窗口传值

FineUI小技巧(1)简单的购物车页面的更多相关文章

  1. FineUI小技巧(6)自定义页面回发

    前言 FineUI中的绝大部分回发事件都是由控件触发了,比如按钮的点击事件,下拉列表的改变事件,表格的排序分页事件.但有时我们可能会要自己触发页面回发,这时就要知道怎么使用 JavaScript 来做 ...

  2. FineUI小技巧(5)向子窗口传值,向父窗口传值

    前言 FineUI中经常会用到启用IFrame的Window控件,这样有助于从物理上进行代码解耦和.IFrame的引入就会涉及传值问题,如何在父窗口和子窗口之间相互传值呢? 向子窗口传值 向子窗口传值 ...

  3. FineUI小技巧(4)关闭窗体那些事

    前言 FineUI中的Window控件常用作选择.新增或编辑内容.而关闭Window控件却有很多技巧,了解这些技巧有助于项目的快速开发. 如何关闭Window控件 第一个问题就是如何关闭Window控 ...

  4. FineUI小技巧(3)表格导出与文件下载

    需求描述 实际应用中,我们可能需要导出表格内容,或者在页面回发时根据用户权限下载文件(注意,这里的导出与下载,都是在后台进行的,和普通的一个链接下载文件不同). 点击按钮导出表格 由于FineUI 默 ...

  5. FineUI小技巧(2)将表单内全部字段禁用、只读、设置无效标识

    需求描述 对表单内的所有字段进行操作也是常见需求,这些操作有: 禁用:表单字段变灰,不响应用户动作. 只读:表单字段不变灰,但不接受用户输入(实际上是设置DOM节点的readonly属性),有触发器的 ...

  6. FineUI小技巧(7)多表头表格导出

    前言 之前我们曾写过一篇文章 FineUI小技巧(3)表格导出与文件下载,对于在 FineUI 中导出表格数据进行了详细描述.今天我们要更进一步,介绍下如何导出多表头表格. 多表头表格的标签定义 在 ...

  7. 小技巧:在向导式页面设计中使用hidden型输入可以避免session的使用

    在向导式页面设计中使用hidden型输入可以避免session的使用,从而减小内存开支. 在表单中使用隐藏输入类型<input type="hidden" name=&quo ...

  8. Java web开发中页面跳转小技巧——跳转后新页面在新窗口打开

    最近学习Java web,在学习过程中想实现一个需求,就是在jsp页面跳转的时候,希望跳转后的新页面在新窗口中打开, 而不是覆盖原来的页面,这个需求使我困惑了好长时间,后来通过大海捞针似的在网上寻找方 ...

  9. win10系统使用小技巧【转】

    win10的很多小技巧又简单又实用,这里给大家整理了10个小技巧,一分钟学会,秒变win10高手,看不完的先收藏再看哦. 1.改美区 在设置中时间和语言中将区域和语言改为美国就可以瞬间切换Foreca ...

随机推荐

  1. YARN中自己总结的几个关键点

    以前在Hadoop 1.0中JobTracker主要完成两项功能:资源的管理和作业控制.在集群规模过大的场景下,JobTracker 存在以下不足: 1)JobTracker 单点故障. 2)JobT ...

  2. 由一个订单推送想到了ObservableCollection的神奇用法

    最近在做taobao的一个卖家应用,需要订阅taobao的订单推送,示例代码如下: 看到上面的OnMessage场景之后,我突然就鬼使神差的在想最近写的一个服务,其中的一个功能是需要定时的轮询一个集合 ...

  3. java统计汉字

    public class TotalUtil { public static int getSum(String text) {        String reg = "^[\u4e00- ...

  4. 【故障处理】一次RAC故障处理过程

    [故障处理]一次RAC故障处理过程 1.1  故障环境介绍 项目 source db db 类型 2节点RAC db version 11.2.0.1.0 db 存储 ASM OS版本及kernel版 ...

  5. 好用的排名函数~ROW_NUMBER(),RANK(),DENSE_RANK() 三兄弟

    排名函数三兄弟,一看名字就知道,都是为了排名而生!但是各自有各自的特色!以下一个例子说明问题!(以下栗子没有使用Partition By 的关键字,整个结果集进行排序) RANK 每个值一个排名,同样 ...

  6. 省级联动(使用ajax实现)

    在博客园学习了很多实用的东西,现在该慢慢开始自己写写博客文章, 由于本人水平有限,刚走出校园的小菜鸟,另外,文章在表述和代码方面如有不妥之处,欢迎批评指正.留下你 的脚印,欢迎评论! 有什么问题,可以 ...

  7. iNeedle系统之国舜项目

    一.简介 本周公司接了一个小项目,是给北京国舜科技股份有限公司做一个HTTP相关的小功能产品.大概实现功能是将交换机的源数据通过解析,分析出HTTP包配对的request和response头,并把每对 ...

  8. android中BuildConfig.DEBUG的使用

    ADT(r17)中添加了一个新功能可以允许开发者只在Debug模式下允许某些代码.Build系统生成一个名称为BuildConfig的类,该类包含一个DEBUG 常量,该常量会根据您的Build类型自 ...

  9. Linux mke2fs 硬盘格式化

    [root@whp6 ~]# cat /etc/filesystems ext4 ext3 ext2 nodev proc nodev devpts iso9660 vfat hfs hfsplus ...

  10. 03 Hibernate错题分析

    1.在Hibernate中,以下关于主键生成器说法错误的是( C). A.increment可以用于类型为long.short或byte的主键 B.identity用于如SQL Server.DB2. ...