文章出處

FineUI v3.3.0 更新的內容非常多,所以一下子從 v3.2.6 連跳 3 個小版本,直接來到了 v3.3.0。詳細的更新記錄請參考這里:http://fineui.com/version

主要的更新有如下幾個方面:

  1. 外置ExtJS庫
  2. 去AXD化
  3. 表格合計行
  4. 表格可編輯單元格的增刪改
  5. 頂部菜單框架

 

下面就來詳細說明這些更新。

 

1. 外置ExtJS庫

FineUI 最初使用的是 GPL v2 授權協議,不過這和 FineUI 所倡導的開源免費的原則相抵觸,因為如果某個企業使用了 FineUI 庫,即使已經購買了 ExtJS 的商業授權,還是需要公開源代碼的,因為受到 FineUI 的 GPL v2 協議限制。基于這個原因,FineUI 從 v3.1.0 開始擁抱 Apache License 2.0,從而真正做到了免費開源!

上面這個轉變過程,我曾經寫過一篇博客記錄:

不僅開源,而且對企業應用完全免費!ExtAspNet棄用GPL v2,擁抱Apache License 2.0

 

然而,在詳細閱讀了 ExtJS 的授權協議后,我發現 FineUI 并沒有完全遵守 ExtJS 所指定的規則,先來看看 ExtJS 的對開源工具的制定的規則:

ExtJS Open Source License

Sencha is an avid supporter of open source software. Our open source license is the appropriate option if you are creating an open source application under a license compatible with the GNU GPL license v3. Although the GPLv3 has many terms, the most important is that you must provide the source code of your application to your users so they can be free to modify your application for their own needs.

If you would like to use the GPLv3 version of Ext JS with your non-GPLv3 open source project, the following FLOSS (Free, Libre and Open Source) exceptions are available:
Open Source License Exception for Development

雖然 FineUI 使用的 Apache License 2.0 是和 GPL v3兼容的協議,不過 ExtJS 還制定了更加嚴格的規則:不能包含 ExtJS 的源代碼,而是要告訴用戶怎么獲取 ExtJS 的源代碼!

 

FineUI 作為知名的開源軟件,會無條件遵守開源社區的游戲規則,因此在本次 v3.3.0 中做出重大調整:

  • FineUI 的 Apache License v2.0 授權協議 與 ExtJS 的 GPL v3 兼容;
  • FineUI 公開全部源代碼,沒有任何保留;
  • FineUI 不包含 ExtJS 的任何源代碼;
  • FineUI 不將 ExtJS 作為整體發布,而是提供獲取 ExtJS 的方法;
  • FineUI 公開說明使用了 ExtJS 庫,并指出 ExtJS 庫是采用 GPL v3 授權協議的;
  • FineUI 是為了將 ExtJS 引入 ASP.NET 領域,而非獨立存在的庫。

 

如果獲取適用于 FineUI 的ExtJS 庫呢?

  1. 首先下載 ExtJS 庫:http://www.sencha.com/products/extjs3/download/
  2. 將 ExtJS 庫的全部內容拷貝到官方示例目錄:FineUI.Examples\extjs_builder\extjs_source_all
  3. 運行 build.bat,即可生成目錄:FineUI.Examples\extjs
  4. 將生成的 extjs 拷貝到你網站的根目錄下即可(老項目不需要修改任何代碼和配置文件!)。

 

注:由于 ExtJS 庫比較大(20M),我們在官方論壇提供了生成好的 extjs 目錄方便大家使用:http://fineui.com/bbs/forum.php?mod=viewthread&tid=3218

 

2. 去AXD化

外置 ExtJS 庫帶來了另一個好處,再也不用使用散落在網站各處的 res.axd 路徑了(為了保證老項目的正常運行,之前 res.axd 的方式仍然有效)!

 

AXD 是 ASP.NET 內置的一種獲取程序集內部資源的方式,但是在實際部署中會出現各種問題,在官方論壇 AXD + 404 的總結帖子就有好幾個:

snap241

 

其中最典型的錯誤是在 IIS 中沒有設置正確的 AXD 擴展:

013821xcx8awppppxlpptp

 

更離譜的是,錯誤的服務器時間也會導致 AXD 出現 404 錯誤,具體原因不明:

http://fineui.com/bbs/forum.php?mod=viewthread&tid=1271

http://www.cnblogs.com/huangtailang/archive/2011/03/29/1999175.html

 

從 FineUI v3.3.0 開始,只要你不手工調用 res.axd 路徑,就再也不會出現上述問題了。

 

3. 表格合計行

論壇用戶對表格合計行的呼聲特別高,實際項目中可能需要對當前分頁數據合計,也可能對全部數據合計。

這次更新,我們特別制作了幾個示例,由于需要手寫 CSS 和 JavaScript ,所以對程序員的要求比較高,不過沒關系大家只需照例子寫就行了。

 

3.1 服務器全部合計

snap242

 

實現上述效果,需要分三步走:

1. 在后臺代碼中生成合計數據

   1:  protected void Page_Load(object sender, EventArgs e) {
   2:      if (!IsPostBack) {
   3:          BindGrid();
   4:   
   5:          OutputSummaryData();
   6:      }
   7:  }
   8:   
   9:   
  10:  private void OutputSummaryData() {
  11:      DataTable source = GetDataTable2();
  12:   
  13:      float donateTotal = 0.0f;
  14:      float feeTotal = 0.0f;
  15:      foreach(DataRow row in source.Rows) {
  16:          donateTotal += Convert.ToInt32(row["Donate"]);
  17:          feeTotal += Convert.ToInt32(row["Fee"]);
  18:      }
  19:   
  20:      JObject jo = new JObject();
  21:      jo.Add("donateTotal", donateTotal);
  22:      jo.Add("feeTotal", feeTotal);
  23:   
  24:      hfGrid1Summary.Text = jo.ToString(Newtonsoft.Json.Formatting.None);
  25:   
  26:  }

由于合計數據在不改變數據源的情況下是不變的,因此我們只要在第一次頁面加載(!IsPostBack)時生成合計數據即可。

然后將全部合計數據以 JSON 字符串的形式保存到隱藏字段(HiddenField)中,供前臺 JavaScript 代碼調用。

snap243

 

 

2. 使用前臺代碼顯示合計數據

   1:  <script>
   2:      var gridClientID = '<%= Grid1.ClientID %>';
   3:      var gridSummaryID = '<%= hfGrid1Summary.ClientID %>';
   4:   
   5:      function calcGridSummary(grid) {
   6:          var donateTotal = 0,
   7:              store = grid.getStore(),
   8:              view = grid.getView(),
   9:              storeCount = store.getCount();
  10:   
  11:          // 防止重復添加了合計行
  12:          if (Ext.get(view.getRow(storeCount - 1)).hasClass('mygrid-row-summary')) {
  13:              return;
  14:          }
  15:   
  16:          // 從隱藏字段獲取全部數據的匯總
  17:          var summaryJSON = JSON.parse(X(gridSummaryID).getValue());
  18:   
  19:   
  20:          store.add(new Ext.data.Record({
  21:              'major': '全部合計:',
  22:              'donate': summaryJSON['donateTotal'].toFixed(2),
  23:              'fee': summaryJSON['feeTotal'].toFixed(2)
  24:          }));
  25:   
  26:   
  27:   
  28:          // 為合計行添加自定義樣式(隱藏序號列、復選框列,取消 hover 和 selected 效果)
  29:          Ext.get(view.getRow(storeCount)).addClass('mygrid-row-summary');
  30:   
  31:      }
  32:   
  33:      // 頁面第一個加載完畢后執行的函數
  34:   
  35:      function onReady() {
  36:          var grid = X(gridClientID);
  37:          grid.addListener('viewready', function () {
  38:              calcGridSummary(grid);
  39:          });
  40:   
  41:      }
  42:   
  43:      // 頁面AJAX回發后執行的函數
  44:   
  45:      function onAjaxReady() {
  46:          var grid = X(gridClientID);
  47:          calcGridSummary(grid);
  48:      }
  49:  </script>

上面代碼首先定義了一個向表格中添加合計行的函數(calcGridSummary),并分別在頁面第一次加載時(onReady)和AJAX結束時(onAjaxReady)調用此函數。

在 calcGridSummary 函數內部,通過 JSON.parse 函數解析保存在隱藏字段中的合計數據,然后調用表格的 grid.getStore().add 來添加合計行,最后給這個合計行添加 CSS 樣式(mygrid-row-summary)。

 

上面的代碼不大完善,新增加的合計行屬于表格數據的一部分,因此用戶可以選中這個合計行,這是我們所不希望的,怎么辦?

   1:   function onReady() {
   2:       var grid = X(gridClientID);
   3:       grid.addListener('viewready', function () {
   4:           calcGridSummary(grid);
   5:       });
   6:   
   7:       // 防止選中合計行
   8:       grid.getSelectionModel().addListener('beforerowselect', function (sm, rowIndex, keepExisting, record) {
   9:           if (Ext.get(grid.getView().getRow(rowIndex)).hasClass('mygrid-row-summary')) {
  10:               return false;
  11:           }
  12:           return true;
  13:       });
  14:   }

我們還需要在頁面初始化時,加入防止合計行被選中的事件處理,其中用到了剛剛添加到合計行的 CSS 定義(mygrid-row-summary)。

 

3. 使用 CSS 調整合計行樣式

最后,我們還需要通過 CSS 來簡單調整合計行的樣式:

   1:  <style>
   2:      .mygrid-row-summary.x-grid3-row {
   3:          background-color: #efefef !important;
   4:          background-image: none !important;
   5:          border-color: #fff #ededed #ededed !important;
   6:      }
   7:      .mygrid-row-summary.x-grid3-row .x-grid3-td-numberer, .mygrid-row-summary.x-grid3-row .x-grid3-td-checker {
   8:          background-image: none !important;
   9:      }
  10:      .mygrid-row-summary.x-grid3-row .x-grid3-td-numberer .x-grid3-col-numberer, .mygrid-row-summary.x-grid3-row .x-grid3-td-checker .x-grid3-col-checker {
  11:          display: none;
  12:      }
  13:      .mygrid-row-summary.x-grid3-row td {
  14:          font-size: 14px;
  15:          line-height: 16px;
  16:          font-weight: bold;
  17:          color: red;
  18:      }
  19:  </style>

 

 

3.2 服務器分頁合計

服務器分頁合計和服務器全部合計的前臺代碼完全相同,所不同的時分頁合計時每次表格數據綁定都需要計算本頁的合計數據,如下所示:

   1:  private void OutputPageSummaryData(DataTable source) {
   2:      float donateTotal = 0.0f;
   3:      float feeTotal = 0.0f;
   4:      foreach(DataRow row in source.Rows) {
   5:          donateTotal += Convert.ToInt32(row["Donate"]);
   6:          feeTotal += Convert.ToInt32(row["Fee"]);
   7:      }
   8:   
   9:      JObject jo = new JObject();
  10:      jo.Add("donateTotal", donateTotal);
  11:      jo.Add("feeTotal", feeTotal);
  12:   
  13:      hfGrid1Summary.Text = jo.ToString(Newtonsoft.Json.Formatting.None);
  14:   
  15:  }
  16:   
  17:  private void BindGrid() {
  18:      // 1.設置總項數(特別注意:數據庫分頁一定要設置總記錄數RecordCount)
  19:      Grid1.RecordCount = GetTotalCount();
  20:   
  21:      // 2.獲取當前分頁數據
  22:      DataTable table = GetPagedDataTable(Grid1.PageIndex, Grid1.PageSize);
  23:   
  24:      // 3.綁定到Grid
  25:      Grid1.DataSource = table;
  26:      Grid1.DataBind();
  27:   
  28:      // 輸出分頁合計結果
  29:      OutputPageSummaryData(table);
  30:  }

 

頁面效果如下:

snap244

 

 

3.3 服務器全部合計(絕對定位合計行)

實際項目的一個常見需求是將合計行絕對定位到表格底部,如下圖所示:

170118ulqfnvbzyw1clyck

 

該如何實現這個功能?

 

這個時候我們只好在前臺下工夫了,總的思路如下:

1. 和服務器全部合計一模一樣的前臺代碼;

2. 將生成的合計行拷貝一份,然后將拷貝的合計行插入表格容器節點中并絕對定位。

 

一定要注意:在這個過程中,是要拷貝一個合計行(而不是刪除合計行),這樣才不至于在滾動條滾動時把最后一行表格數據遮擋住。此時頁面上其實是有兩個一模一樣的合計行,只不過所在位置不同,并且原始的合計行要設置 CSS 屬性 visibility: hidden;(讓原始的合計行占位,但不顯示出來,這個主意是不是很妙眨眼)。

看看下圖就明白了:

snap245

 

關鍵 JavaScript 代碼:

   1:  function calcGridSummary(grid) {
   2:      var donateTotal = 0,
   3:          store = grid.getStore(),
   4:          view = grid.getView(),
   5:          storeCount = store.getCount();
   6:   
   7:      // 防止重復添加了合計行
   8:      if (Ext.get(view.getRow(storeCount - 1)).hasClass('mygrid-row-summary')) {
   9:          return;
  10:      }
  11:   
  12:      // 從隱藏字段獲取全部數據的匯總
  13:      var summaryJSON = JSON.parse(X(gridSummaryID).getValue());
  14:   
  15:   
  16:      store.add(new Ext.data.Record({
  17:          'major': '全部合計:',
  18:          'donate': summaryJSON['donateTotal'].toFixed(2),
  19:          'fee': summaryJSON['feeTotal'].toFixed(2)
  20:      }));
  21:   
  22:   
  23:      // 為合計行添加自定義樣式(隱藏序號列、復選框列,取消 hover 和 selected 效果)
  24:      var summaryNode = Ext.get(view.getRow(storeCount)).addClass('mygrid-row-summary');
  25:   
  26:      // 找到合計行的外部容器節點
  27:      var viewportNode = summaryNode.parent('.x-grid3-viewport');
  28:      // 刪除容器節點下直接子節點為 mygrid-row-summary 的節點
  29:      viewportNode.select('> .mygrid-row-summary').remove();
  30:   
  31:      // 創建合計行的副本
  32:      var cloneSummaryNode = summaryNode.dom.cloneNode(true);
  33:      // 修改合計行的副本的樣式,絕對定位,距離底部0px,顯示副本(默認是占位隱藏 visibility: hidden;)
  34:      Ext.get(cloneSummaryNode).setStyle({
  35:          position: 'absolute',
  36:          bottom: 0,
  37:          visibility: 'visible'
  38:      });
  39:   
  40:      // 向容器節點添加合計行的副本
  41:      viewportNode.appendChild(cloneSummaryNode);
  42:   
  43:  }

 

更加詳細的代碼,請直接去看官方示例:http://fineui.com/demo/#/demo/grid/grid_summary_absolute.aspx

 

4. 表格可編輯單元格的增刪改

論壇用戶對 ExtJS 可編輯功能的呼聲也很高,雖然 FineUI 的模板列能夠實現一定的編輯功能(http://fineui.com/demo/#/demo/grid/grid_edit.aspx),但畢竟不是 ExtJS 的原生方式。

 

上個版本簡單實現了可編輯表格的“改”,這個版本對此進行了修正和改進,下面就來一一描述。

 

4.1 可編輯表格的“改”

snap246

 

首先來看下 ASPX 文件的結構定義:

   1:  <x:Grid ID="Grid1" ShowBorder="true" ShowHeader="true" Title="表格" Width="850px" Height="350px"
   2:      runat="server" DataKeyNames="Id,Name" AllowCellEditing="true" ClicksToEdit="1">
   3:      <Columns>
   4:          <x:TemplateField Width="60px">
   5:              <ItemTemplate>
   6:                  <asp:Label ID="Label1" runat="server" Text='<%# Container.DataItemIndex + 1 %>'></asp:Label>
   7:              </ItemTemplate>
   8:          </x:TemplateField>
   9:          <x:RenderField Width="100px" ColumnID="Name" DataField="Name" FieldType="String"
  10:              HeaderText="姓名">
  11:              <Editor>
  12:                  <x:TextBox ID="tbxEditorName" Required="true" runat="server">
  13:                  </x:TextBox>
  14:              </Editor>
  15:          </x:RenderField>
  16:          <x:RenderField Width="100px" ColumnID="Gender" DataField="Gender" FieldType="Int"
  17:              RendererFunction="renderGender" HeaderText="性別">
  18:              <Editor>
  19:                  <x:DropDownList ID="ddlGender" Required="true" runat="server">
  20:                      <x:ListItem Text="男" Value="1" />
  21:                      <x:ListItem Text="女" Value="0" />
  22:                  </x:DropDownList>
  23:              </Editor>
  24:          </x:RenderField>
  25:          <x:RenderField Width="100px" ColumnID="EntranceYear" DataField="EntranceYear" FieldType="Int"
  26:              HeaderText="入學年份">
  27:              <Editor>
  28:                  <x:NumberBox ID="tbxEditorEntranceYear" NoDecimal="true" NoNegative="true" MinValue="2000"
  29:                      MaxValue="2010" runat="server">
  30:                  </x:NumberBox>
  31:              </Editor>
  32:          </x:RenderField>
  33:          <x:RenderField Width="100px" ColumnID="EntranceDate" DataField="EntranceDate" FieldType="Date"
  34:              Renderer="Date" RendererArgument="yyyy-MM-dd" HeaderText="入學日期">
  35:              <Editor>
  36:                  <x:DatePicker ID="DatePicker1" Required="true" runat="server">
  37:                  </x:DatePicker>
  38:              </Editor>
  39:          </x:RenderField>
  40:          <x:RenderCheckField Width="100px" ColumnID="AtSchool" DataField="AtSchool" HeaderText="是否在校" />
  41:          <x:RenderField Width="100px" ColumnID="Major" DataField="Major" FieldType="String"
  42:              ExpandUnusedSpace="true" HeaderText="所學專業">
  43:              <Editor>
  44:                  <x:TextBox ID="tbxEditorMajor" Required="true" runat="server">
  45:                  </x:TextBox>
  46:              </Editor>
  47:          </x:RenderField>
  48:      </Columns>
  49:  </x:Grid>

RenderField是專門用于可編輯表格的,我們可以在 RenderField 內部定義 Editor,一個 Editor 也就是一個表單字段。

常用做 Editor 有 TextBox、NumberBox、DropDownList、DatePicker等。

還有一個特殊的列類型是 RenderCheckField,專門用來生成可編輯的復選框,要特別注意 RenderCheckField 和 CheckBoxField 的區別。

 

為什么用于可編輯表格的列類型都是 Render 開頭的呢?

其實這里的 Render 可以理解為客戶端渲染,服務器端會把數據準備好,而不會在服務器端生成每個單元格的 HTML(這一點可以和之前的列類型做對比),而是在客戶端根據服務器端提供的原始數據渲染成所需要的 HTML。

比如這個例子中的 Gender 列定義了 RendererFunction="renderGender",這里的 renderGender 就是一個 JavaScript 函數:

   1:  <script>
   2:      function renderGender(value, metadata, record, rowIndex, colIndex) {
   3:          return value == 1 ? '男' : '女';
   4:      }
   5:  </script>

這里返回的“男”或者“女”就是本列處于非編輯狀態下顯示的內容,當然我們可以用兩個圖標分別代表,比如用下面這個函數來替代上面的函數:

   1:  <script>
   2:      function renderGender(value, metadata, record, rowIndex, colIndex) {
   3:          return value == 1 ? '<img src="../extjs/res/images/boy.png"/>' : '<img src="../extjs/res/images/girl.png"/>';
   4:      }
   5:  </script>

另一個需要注意的地方,我們為每一列都定義了 ColumnID,這一點很重要。在后臺代碼中獲取用戶修改后的數據時,需要用到這個屬性。

 

 

下面來看下后臺如何獲取用戶的修改值,并保存到持久化設備。

作為示例,我們沒有使用數據庫,而是在內存中模擬了持久化存儲(當然不是真的持久化,也不要在實際項目中這樣用):

   1:   private static readonly string KEY_FOR_DATASOURCE_SESSION = "datatable_for_grid_editor_cell";
   2:   
   3:   // 模擬在服務器端保存數據
   4:   // 特別注意:在真實的開發環境中,不要在Session放置大量數據,否則會嚴重影響服務器性能
   5:   private DataTable GetSourceData() {
   6:       if (Session[KEY_FOR_DATASOURCE_SESSION] == null) {
   7:           Session[KEY_FOR_DATASOURCE_SESSION] = GetDataTable();
   8:       }
   9:       return (DataTable) Session[KEY_FOR_DATASOURCE_SESSION];
  10:   }

在用戶點擊“保存數據”按鈕時,后臺處理代碼:

   1:  protected void Button2_Click(object sender, EventArgs e) {
   2:      Dictionary<int, Dictionary<string, string>> modifiedDict = Grid1.GetModifiedDict();
   3:   
   4:      for (int i = 0, count = Grid1.Rows.Count; i < count; i++) {
   5:          if (modifiedDict.ContainsKey(i)) {
   6:              Dictionary <string, string> rowDict = modifiedDict[i];
   7:   
   8:              // 更新數據源
   9:              DataTable table = GetSourceData();
  10:   
  11:              DataRow rowData = table.Rows[i];
  12:   
  13:              // 姓名
  14:              if (rowDict.ContainsKey("Name")) {
  15:                  rowData["Name"] = rowDict["Name"];
  16:              }
  17:              // 性別
  18:              if (rowDict.ContainsKey("Gender")) {
  19:                  rowData["Gender"] = Convert.ToInt32(rowDict["Gender"]);
  20:              }
  21:              // 入學年份
  22:              if (rowDict.ContainsKey("EntranceYear")) {
  23:                  rowData["EntranceYear"] = rowDict["EntranceYear"];
  24:              }
  25:              // 入學日期
  26:              if (rowDict.ContainsKey("EntranceDate")) {
  27:                  rowData["EntranceDate"] = DateTime.Parse(rowDict["EntranceDate"]).ToString("yyyy-MM-dd");
  28:              }
  29:              // 是否在校
  30:              if (rowDict.ContainsKey("AtSchool")) {
  31:                  rowData["AtSchool"] = Convert.ToBoolean(rowDict["AtSchool"]);
  32:              }
  33:              // 所學專業
  34:              if (rowDict.ContainsKey("Major")) {
  35:                  rowData["Major"] = rowDict["Major"];
  36:              }
  37:   
  38:          }
  39:      }
  40:   
  41:      labResult.Text = "用戶修改的數據:" + Grid1.GetModifiedData().ToString(Newtonsoft.Json.Formatting.None);
  42:   
  43:      BindGrid();
  44:   
  45:      Alert.Show("數據保存成功!(表格數據已重新綁定)");
  46:  }

這里的 GetModifiedDict 函數返回用戶在客戶端所有的修改數據,它的數據類型是 Dictionary<int, Dictionary<string, string>>,第一個 int 表示行索引,第一個 string 表示列標識(ColumnID),第二個 string 表示用戶在客戶端修改的值。

理解了這一點,上面的代碼就清晰明了了:

1. 首先遍歷表格的所有數據行

2. 查看當前數據行是否在客戶端修改了?

3. 如果修改了,則查找本行數據中哪些列在客戶端修改了,并更新數據源。

 

是不是對 GetModifiedData 函數感興趣?

這個函數是服務器接收到的客戶端回發的原始數據,是用 JSON 表示的,來看這個例子的結果:

   1:  [
   2:      [2, {
   3:          "Name": "董婷婷2",
   4:          "Gender": "1",
   5:          "EntranceYear": 2009,
   6:          "AtSchool": false,
   7:          "EntranceDate": "2008-09-02T00:00:00"
   8:      }],
   9:      [4, {
  10:          "EntranceDate": "2008-09-09T00:00:00",
  11:          "EntranceYear": 2000
  12:      }]
  13:  ]

 

4.2 可編輯表格的“刪”

image

由于只能選中一個單元格,而不是一行數據,所以我們可以通過選中某行單元格來刪除本行數據。

   1:  protected void btnDelete_Click(object sender, EventArgs e) 
   2:  {
   3:      StringBuilder sb = new StringBuilder();
   4:      if (Grid1.SelectedCell != null) {
   5:          int rowIndex = Grid1.SelectedCell[0];
   6:   
   7:          GetSourceData().Rows.RemoveAt(rowIndex);
   8:   
   9:          BindGrid();
  10:   
  11:          Alert.ShowInTop("刪除數據成功!(表格數據已重新綁定)");
  12:      } else {
  13:          Alert.ShowInTop("沒有選中任何單元格!");
  14:      }
  15:   
  16:  }

這個過程比較簡單,首先獲取用戶選中的單元格(SelectedCell),這個數組的第一個元素就是行索引,接下來從數據源中刪除本行數據,并重新綁定表格即可。

 

 

4.3 可編輯表格的“增”

snap248

 

 

首先來看下如何為“新增數據”按鈕綁定客戶端腳本:

   1:  protected void Page_Load(object sender, EventArgs e) 
   2:  {
   3:      if (!IsPostBack) {
   4:          JObject defaultObj = new JObject();
   5:          defaultObj.Add("Name", "用戶名");
   6:          defaultObj.Add("Gender", 1);
   7:          defaultObj.Add("EntranceYear", "2015");
   8:          defaultObj.Add("EntranceDate", "2015-09-01");
   9:          defaultObj.Add("AtSchool", false);
  10:          defaultObj.Add("Major", "化學系");
  11:   
  12:          // 第一行新增一條數據
  13:          btnNew.OnClientClick = Grid1.GetAddNewRecordReference(defaultObj, false);
  14:   
  15:          btnReset.OnClientClick = Grid1.GetRejectChangesReference();
  16:   
  17:          BindGrid();
  18:      }
  19:  }

GetAddNewRecordReference 函數接受的第一個參數類型是 JObject,用來指定新增數據每一列的默認值,第二個參數指定是否將新增行添加到當前數據的末尾。

如果看下頁面源代碼,可以發現生成的 JavaScript 如下所示:

   1:  X('Grid1').x_addNewRecord({
   2:      "Name": "用戶名",
   3:      "Gender": 1,
   4:      "EntranceYear": "2015",
   5:      "EntranceDate": "2015-09-01",
   6:      "AtSchool": false,
   7:      "Major": "化學系"
   8:  }, false);

再來看看保存數據的代碼,由于有兩部分數據需要保存,一部分是新增的,另一部分是修改現有的數據,所以提取了一個共有函數:

   1:  private static void UpdateSourceDataRow(Dictionary <string, string> rowDict, DataRow rowData) {
   2:      // 姓名
   3:      if (rowDict.ContainsKey("Name")) {
   4:          rowData["Name"] = rowDict["Name"];
   5:      }
   6:      // 性別
   7:      if (rowDict.ContainsKey("Gender")) {
   8:          rowData["Gender"] = Convert.ToInt32(rowDict["Gender"]);
   9:      }
  10:      // 入學年份
  11:      if (rowDict.ContainsKey("EntranceYear")) {
  12:          rowData["EntranceYear"] = rowDict["EntranceYear"];
  13:      }
  14:      // 入學日期
  15:      if (rowDict.ContainsKey("EntranceDate")) {
  16:          rowData["EntranceDate"] = DateTime.Parse(rowDict["EntranceDate"]).ToString("yyyy-MM-dd");
  17:      }
  18:      // 是否在校
  19:      if (rowDict.ContainsKey("AtSchool")) {
  20:          rowData["AtSchool"] = Convert.ToBoolean(rowDict["AtSchool"]);
  21:      }
  22:      // 所學專業
  23:      if (rowDict.ContainsKey("Major")) {
  24:          rowData["Major"] = rowDict["Major"];
  25:      }
  26:  }

 

保存數據的代碼則清晰明了:

   1:   protected void Button2_Click(object sender, EventArgs e) {
   2:   
   3:       // 1. 先修改的現有數據
   4:       Dictionary < int, Dictionary < string, string >> modifiedDict = Grid1.GetModifiedDict();
   5:       for (int i = 0, count = Grid1.Rows.Count; i < count; i++) {
   6:           if (modifiedDict.ContainsKey(i)) {
   7:               Dictionary < string, string > rowDict = modifiedDict[i];
   8:   
   9:               // 更新數據源
  10:               DataTable table = GetSourceData();
  11:   
  12:               DataRow rowData = table.Rows[i];
  13:   
  14:               UpdateSourceDataRow(rowDict, rowData);
  15:   
  16:           }
  17:       }
  18:   
  19:   
  20:       // 2. 再新增數據
  21:       List < Dictionary < string, string >> newAddedList = Grid1.GetNewAddedList();
  22:       for (int i = newAddedList.Count - 1; i >= 0; i--) {
  23:           DataTable table = GetSourceData();
  24:   
  25:           DataRow rowData = table.NewRow();
  26:   
  27:           UpdateSourceDataRow(newAddedList[i], rowData);
  28:   
  29:           table.Rows.InsertAt(rowData, 0);
  30:       }
  31:   
  32:   
  33:       labResult.Text = "用戶修改的數據:" + Grid1.GetModifiedData().ToString(Newtonsoft.Json.Formatting.None);
  34:   
  35:       BindGrid();
  36:   
  37:       Alert.Show("數據保存成功!(表格數據已重新綁定)");
  38:   }

我們可以看到,修改現有數據的代碼和之前的一模一樣,都是先使用 GetModifiedDict 獲取用戶在客戶端修改的值。

保存新增數據行的代碼更加簡單:

1. 使用 GetNewAddedList 方法返回新增的數據行列表;

2. 遍歷每一行,將新增數據添加到數據源中。

 

需要注意:

1. 一定要修改現有數據,然后再處理新增數據

2. 處理完后一定要重新綁定數據,因為此時前段顯示和后端的數據已經不一致了。

 

5. 頂部菜單框架

這個需求也是來源于論壇用戶。官網示例給出的是左側菜單結構的框架,那么如何實現頂部菜單結構的框架呢?

snap249

如圖所示,點擊頂部菜單來更新左側樹結構,實現起來倒不難,不過需要一點 JavaScript 知識。

首先來看下頂部菜單的定義:

   1:  <ul>
   2:      <li class="selected menu-mail">
   3:          <asp:LinkButton ID="lbtnMail" runat="server" OnClick="lbtnMail_Click">
   4:              <span>郵件收發</span></asp:LinkButton>
   5:      </li>
   6:      <li class="menu-sms">
   7:          <asp:LinkButton ID="lbtnSMS" runat="server" OnClick="lbtnSMS_Click">
   8:              <span>短信收發</span></asp:LinkButton>
   9:      </li>
  10:      <li class="menu-sys">
  11:          <asp:LinkButton ID="lbtnSYS" runat="server" OnClick="lbtnSYS_Click">
  12:              <span>系統管理</span></asp:LinkButton>
  13:      </li>
  14:  </ul>

后臺代碼中,分別處理三個頂部菜單的點擊事件,更新左側樹控件即可:

   1:  private void BindLeftTree(string menuType) {
   2:      if (menuType == "mail") {
   3:          XmlDataSource1.DataFile = "./data/menuMail.xml";
   4:          PageContext.RegisterStartupScript("selectMenu('menu-mail');");
   5:      } else if (menuType == "sys") {
   6:          XmlDataSource1.DataFile = "./data/menuSYS.xml";
   7:          PageContext.RegisterStartupScript("selectMenu('menu-sys');");
   8:      } else if (menuType == "sms") {
   9:          XmlDataSource1.DataFile = "./data/menusms.xml";
  10:          PageContext.RegisterStartupScript("selectMenu('menu-sms');");
  11:      }
  12:   
  13:      BindLeftTree();
  14:  }
  15:   
  16:  private void BindLeftTree() {
  17:      leftTree.DataSource = XmlDataSource1;
  18:      leftTree.DataBind();
  19:  }
  20:   
  21:  protected void lbtnMail_Click(object sender, EventArgs e) {
  22:      BindLeftTree("mail");
  23:  }
  24:  protected void lbtnSYS_Click(object sender, EventArgs e) {
  25:      BindLeftTree("sys");
  26:   
  27:  }
  28:  protected void lbtnSMS_Click(object sender, EventArgs e) {
  29:      BindLeftTree("sms");
  30:  }

但是不要忘了在切換頂部菜單時,更新選中菜單的樣式,同時要選中樹控件的第一個節點,并在主區域內加載此節點所指向的頁面:

   1:  <script>
   2:      var leftTreeID = '<%= leftTree.ClientID %>';
   3:   
   4:      function selectMenu(menuClassName) {
   5:          // 選中當前菜單
   6:          Ext.select('.menu ul li').removeClass('selected');
   7:          Ext.select('.menu ul li.' + menuClassName).addClass('selected');
   8:   
   9:          // 展開樹的第一個節點,并選中第一個節點下的第一個子節點(在右側IFrame中打開)
  10:          var tree = X(leftTreeID);
  11:          var treeFirstChild = tree.getRootNode().firstChild;
  12:          // 展開第一個節點(如果想要展開全部節點,調用 tree.expandAll();)
  13:          treeFirstChild.expand();
  14:   
  15:   
  16:          // 選中第一個鏈接節點,并在右側IFrame中打開此鏈接
  17:          var treeFirstLink = treeFirstChild.firstChild;
  18:          treeFirstLink.select();
  19:          window.frames['mainframe'].location.href = treeFirstLink.attributes['href'];
  20:   
  21:      }
  22:   
  23:      function onReady() {
  24:          selectMenu('menu-mail');
  25:      }
  26:  </script>

雖然寫了一點 JavaScript 代碼,但最終還是實現了我們需要的結果。

 

不過想要實現如下界面,就不那么容易了:

snap250

 

你可能會想,不就是把樹控件換成手風琴控件么,不和上例一樣的么?

如果你真的這么想,那你就需要先了解下 ASP.NET 下動態創建控件的游戲了,先看這篇文章:http://www.cnblogs.com/sanshi/archive/2012/11/19/2776672.html


原因是樹控件是一個控件,可以通過更新數據源來重新加載;而手風琴控件是由多個控件組合而成的,無法在頁面回發時重新創建另一個手風琴控件!

 

怎么辦呢?

辦法總會有的,我們可以把左側的區域也做成 IFrame,這樣每次點擊頂部菜單時,就重新加載左側 IFrame(動態創建手風琴控件)就行了(是不是很妙眨眼)!

這里只提供一個思路,具體的例子請查看:http://fineui.com/demo/#/demo/iframe/topmenu3/default.aspx

 

 

 

 

 

下載 FineUI v3.3.0 和官方示例

下載地址:http://fineui.codeplex.com/releases/

 

FineUI嚴格遵守 ExtJS 關于開源軟件的規則,不再內置 ExtJS 庫。
獲取適用于 FineUI 的 ExtJS 庫:http://fineui.com/bbs/forum.php?mod=viewthread&tid=3218
基于 FineUI 的空項目(Net2.0 和 Net4.0 兩個版本):http://fineui.com/bbs/forum.php?mod=viewthread&tid=2123

 

 

如果你喜歡 FineUI ,別忘了點擊頁面右下角的“推薦”按鈕哦!


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()