FineUI v3.3.0 更新的內容很是多,因此一會兒從 v3.2.6 連跳 3 個小版本,直接來到了 v3.3.0。詳細的更新記錄請參考這裏:http://fineui.com/versionphp
主要的更新有以下幾個方面:html
下面就來詳細說明這些更新。git
FineUI 最初使用的是 GPL v2 受權協議,不過這和 FineUI 所倡導的開源免費的原則相抵觸,由於若是某個企業使用了 FineUI 庫,即便已經購買了 ExtJS 的商業受權,仍是須要公開源代碼的,由於受到 FineUI 的 GPL v2 協議限制。基於這個緣由,FineUI 從 v3.1.0 開始擁抱 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 的源代碼!app
FineUI 做爲知名的開源軟件,會無條件遵照開源社區的遊戲規則,所以在本次 v3.3.0 中作出重大調整:框架
若是獲取適用於 FineUI 的ExtJS 庫呢?
注:因爲 ExtJS 庫比較大(20M),咱們在官方論壇提供了生成好的 extjs 目錄方便你們使用:http://fineui.com/bbs/forum.php?mod=viewthread&tid=3218
外置 ExtJS 庫帶來了另外一個好處,不再用使用散落在網站各處的 res.axd 路徑了(爲了保證老項目的正常運行,以前 res.axd 的方式仍然有效)!
AXD 是 ASP.NET 內置的一種獲取程序集內部資源的方式,可是在實際部署中會出現各類問題,在官方論壇 AXD + 404 的總結帖子就有好幾個:
其中最典型的錯誤是在 IIS 中沒有設置正確的 AXD 擴展:
更離譜的是,錯誤的服務器時間也會致使 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 路徑,就不再會出現上述問題了。
論壇用戶對錶格合計行的呼聲特別高,實際項目中可能須要對當前分頁數據合計,也可能對所有數據合計。
此次更新,咱們特別製做了幾個示例,因爲須要手寫 CSS 和 JavaScript ,因此對程序員的要求比較高,不過不要緊你們只需照例子寫就好了。
實現上述效果,須要分三步走:
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 代碼調用。
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>
服務器分頁合計和服務器所有合計的前臺代碼徹底相同,所不一樣的時分頁合計時每次表格數據綁定都須要計算本頁的合計數據,以下所示:
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: }
頁面效果以下:
實際項目的一個常見需求是將合計行絕對定位到表格底部,以下圖所示:
該如何實現這個功能?
這個時候咱們只好在前臺下工夫了,總的思路以下:
1. 和服務器所有合計如出一轍的前臺代碼;
2. 將生成的合計行拷貝一份,而後將拷貝的合計行插入表格容器節點中並絕對定位。
必定要注意:在這個過程當中,是要拷貝一個合計行(而不是刪除合計行),這樣纔不至於在滾動條滾動時把最後一行表格數據遮擋住。此時頁面上實際上是有兩個如出一轍的合計行,只不過所在位置不一樣,而且原始的合計行要設置 CSS 屬性 visibility: hidden;(讓原始的合計行佔位,但不顯示出來,這個主意是否是很妙)。
看看下圖就明白了:
關鍵 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
論壇用戶對 ExtJS 可編輯功能的呼聲也很高,雖然 FineUI 的模板列可以實現必定的編輯功能(http://fineui.com/demo/#/demo/grid/grid_edit.aspx),但畢竟不是 ExtJS 的原生方式。
上個版本簡單實現了可編輯表格的「改」,這個版本對此進行了修正和改進,下面就來一一描述。
首先來看下 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: ]
因爲只能選中一個單元格,而不是一行數據,因此咱們能夠經過選中某行單元格來刪除本行數據。
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),這個數組的第一個元素就是行索引,接下來從數據源中刪除本行數據,並從新綁定表格便可。
首先來看下如何爲「新增數據」按鈕綁定客戶端腳本:
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. 處理完後必定要從新綁定數據,由於此時前段顯示和後端的數據已經不一致了。
這個需求也是來源於論壇用戶。官網示例給出的是左側菜單結構的框架,那麼如何實現頂部菜單結構的框架呢?
如圖所示,點擊頂部菜單來更新左側樹結構,實現起來倒不難,不過須要一點 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 代碼,但最終仍是實現了咱們須要的結果。
不過想要實現以下界面,就不那麼容易了:
你可能會想,不就是把樹控件換成手風琴控件麼,不和上例同樣的麼?
若是你真的這麼想,那你就須要先了解下 ASP.NET 下動態建立控件的遊戲了,先看這篇文章:http://www.cnblogs.com/sanshi/archive/2012/11/19/2776672.html
緣由是樹控件是一個控件,能夠經過更新數據源來從新加載;而手風琴控件是由多個控件組合而成的,沒法在頁面回發時從新建立另外一個手風琴控件!
怎麼辦呢?
辦法總會有的,咱們能夠把左側的區域也作成 IFrame,這樣每次點擊頂部菜單時,就從新加載左側 IFrame(動態建立手風琴控件)就好了(是否是很妙)!
這裏只提供一個思路,具體的例子請查看:http://fineui.com/demo/#/demo/iframe/topmenu3/default.aspx
下載地址: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 ,別忘了點擊頁面右下角的「推薦」按鈕哦!