1. 程式人生 > >一個共通的viewModel搞定所有的編輯頁面

一個共通的viewModel搞定所有的編輯頁面

前言

我寫程式碼喜歡提取一些共通的東西出來,之前的一篇部落格中說了如何用一個共通的viewModel和簡潔的後臺程式碼做查詢頁面,所有的查詢頁面都要對應一個數據錄入的編輯及檢視明細的頁面,那麼今天我們就來實現這個頁面,同樣我們也要使用一個共通的viewModel完成前臺UI與JSON資料互動的處理,同樣以超簡潔的後臺程式碼來處理儲存。

需求分析

我們先弄明白我們要做怎麼樣一個編輯的頁面。 
1、最上面有一個共通的工具欄,有儲存、撤消、稽核、列印、還有上一條、下一條、第一條、最後一條的資料滾動按鈕,還有一些其它按鈕放在下拉按鈕中。 
image

2、我們這個頁面支援一個主表和從表一起儲存,同一個事務,首先要有主表的錄入


image

3、其次我們還要從表的錄入grid,從表可以增刪改,我們新增設計成從庫中選擇新增,當然也很容易實現直接新增一行。 
image

4、然後我們可能主表中有些欄位不常用,我們放在第二個tab頁籤中,如果還有從表還可以再增加頁籤 
image

5、還有一個需求就是,我儲存時,只儲存改動過的東西,比如主表只改了合同名稱,從表就修改了一行,那麼我們處理應該要主表只更新一個欄位,從表中只修改一條資料。如果沒有值被修改時,儲存按鈕不響應。

技術實現

前端要實現 
1、頁面佈局 
2、繫結控制元件 
3、UI與JSON資料互動的viewModel

後臺web api要實現 
1、主表(增、改)及從表(增、刪、改)在一個事務中儲存

好我們還是在我們的mms區域中做示例,還是選擇一個跟我上一篇一樣的[材料接收的業務] 
image

上一篇中我們已經建立了材料接收的控制元件器RecieveController.cs,其中已經寫了查詢的頁面Index及查詢的api Get方法,現在我們先新增編輯的頁面。 
在mvc controller中新增Edit Actioin

using System;
using System.Web.Mvc;
using Zephyr.Core;
using Zephyr.Models;
using Zephyr.Web.Areas.Mms.Common; namespace Zephyr.Areas.Mms.Controllers { public class ReceiveController : Controller { //查詢頁面 public ActionResult Index() { ... } //編輯頁面 public ActionResult Edit(string id) { return View(); } } }

然後我們右擊這個action,新增一個對應的view頁面~/Views/Receive/Edit.cshtml

@{
    ViewBag.Title = "Edit";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@section scripts{
    <script src="~/Areas/Mms/ViewModels/mms.com.js"></script>
    <script src="~/Areas/Mms/ViewModels/mms.viewModel.edit.js"></script>
    <script type="text/javascript">
        var viewModel = function (data) {
            var self = this;
            mms.viewModel.edit.apply(self, arguments);              //繼承mms.viewModel.edit
            this.grid.OnAfterCreateEditor = function(editors){      //在grid行編輯開始時繫結金額=單價*數量的計算 及 加上數量的驗證
                mms.com.bindCalcTotalMoney(self, "Num", "UnitPrice", "Money", "TotalMoney")(editors);
                $.fn.validatebox.defaults.rules.checkNum = {
                    validator:function(value,param){
                        return parseFloat(value) <= parseFloat(editors['CheckNum'].target.numberbox('getValue'));
                    },
                    message:'入庫數量不能大於驗收數量!'
                };
            };
        };
        var data = @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model));
        ko.bindingViewModel(new viewModel(data));
    </script>
}

<div class="z-toolbar">
    <a href="#" plain="true" class="easyui-linkbutton" icon="icon-save"         title="儲存"  data-bind="click:saveClick">儲存</a>
    <a href="#" plain="true" class="easyui-linkbutton" icon="icon-undo"         title="撤消"  data-bind="click:rejectClick">撤消</a>
    <a href="#" plain="true" class="easyui-linkbutton" icon="icon-user-accept"  title="稽核"  data-bind="click:auditClick,easyuiLinkbutton:approveButton" >稽核</a>
    <a href="#" plain="true" class="easyui-linkbutton" icon="icon-printer"      title="列印"  data-bind="click:printClick">列印</a>
    <div class="datagrid-btn-separator"></div>
    <a href="#" class="easyui-splitbutton" data-options="menu:'#divother',iconCls:'icon-application_go'" title="其他">其他</a>
    <div class="datagrid-btn-separator"></div>
    <a href="#" plain="true" class="easyui-linkbutton" icon="icon-resultset_first"      title="第一條"   data-bind="click:firstClick,linkbuttonEnable:scrollKeys.firstEnable"      ></a> 
    <a href="#" plain="true" class="easyui-linkbutton" icon="icon-resultset_previous"   title="上一條"   data-bind="click:previousClick,linkbuttonEnable:scrollKeys.previousEnable"></a> 
    <a href="#" plain="true" class="easyui-linkbutton" icon="icon-resultset_next"       title="下一條"   data-bind="click:nextClick,linkbuttonEnable:scrollKeys.nextEnable"        ></a> 
    <a href="#" plain="true" class="easyui-linkbutton" icon="icon-resultset_last"       title="最後一條" data-bind="click:lastClick,linkbuttonEnable:scrollKeys.lastEnable"        ></a> 
</div>

<div id="divother" style="width:100px; display:none;">
    <div data-options="iconCls:'icon-add'">新增</div>
    <div data-options="iconCls:'icon-cross'">刪除</div>
    <div data-options="iconCls:'icon-arrow_refresh'">重新整理</div>
</div>  

<div id="master" class="container_12" data-bind="inputwidth:0.9">
    <div class="grid_1 lbl">單據編號</div>
    <div class="grid_3 val"><input type="text" data-bind="value:form.BillNo,readOnly:true" class="z-txt readonly"/></div>
    <div class="grid_1 lbl">單據日期</div>
    <div class="grid_3 val"><input type="text" data-bind="dateboxValue:form.BillDate" class="z-txt easyui-datebox" /></div>
    <div class="grid_1 lbl">經辦人</div>
    <div class="grid_3 val "><input type="text" data-bind="value:form.DoPerson" class="z-txt easyui-validatebox" /></div>
        
    <div class="clear"></div>

    <div class="grid_1 lbl required">供應商</div>
    <div class="grid_3 val"><input type="text" data-bind="lookupValue:form.SupplierCode" required="true" class="z-txt easyui-lookup" data-options="lookupType:'merchants',queryParams:{MerchantsProperty:'\'採購\''}"/></div>
    <div class="grid_1 lbl required">庫房</div>
    <div class="grid_3 val"><input type="text" data-bind="comboboxValue:form.WarehouseCode,datasource:dataSource.warehouseItems" class="z-txt easyui-combobox" required="true" /></div>
    <div class="grid_1 lbl required">收料日期 </div>
    <div class="grid_3 val"><input type="text" data-bind="dateboxValue:form.ReceiveDate" class="easyui-datebox z-txt"  required="true" /></div>
        
    <div class="clear"></div>

    <div class="grid_1 lbl required">供應型別 </div>
    <div class="grid_3 val"><input type="text" data-bind="comboboxValue:form.SupplyType,datasource:dataSource.supplyType" class="easyui-combobox z-txt"  required="true" /></div>
    <div class="grid_1 lbl required">付款方式</div>
    <div class="grid_3 val"><input type="text" data-bind="comboboxValue:form.PayKind,datasource:dataSource.payKinds" class="z-txt easyui-combobox" required="true"/></div>
    <div class="grid_1 lbl">合同名稱 </div>
    <div class="grid_3 val required"><input type="text" data-bind="value:form.ContractCode" class="z-txt"  /></div>
       
    <div class="clear"></div>

    <div class="grid_1 lbl">原始票號</div>
    <div class="grid_3 val"><input type="text" data-bind="value:form.OriginalNum" class="z-txt" /></div>
    <div class="grid_1 lbl">金額</div>
    <div class="grid_3 val"><input type="text" id="TotalMoney" name="TotalMoney" data-bind="numberboxValue:form.TotalMoney,readOnly:true" class="z-txt easyui-numberbox readonly" data-options="min: 0, precision: 2"/></div>
    <div class="grid_1 lbl">備註</div>
    <div class="grid_3 val"><input type="text" id="Remark" name="Remark" data-bind="value:form.Remark" class="z-txt" /></div>

    <div class="clear"></div>
</div>
 
<div id="tt" class="easyui-tabs">  
    <div title="材料明細">
        <table id="list" data-bind="datagrid:grid">
            <thead>
                <tr>
                    <th field="BillNo" hidden="true"></th>  
                    <th field="RowId"  hidden="true"></th> 
                    <th field="MaterialCode"        sortable="true" align="left"    width="80"  >材料編碼   </th>  
                    <th field="MaterialName"        sortable="true" align="left"    width="100" >材料名稱   </th>  
                    <th field="Model"               sortable="true" align="left"    width="100" >規格型號   </th>  
                    <th field="Material"            sortable="true" align="left"    width="80"  >材質       </th>   
                    <th field="Unit"                sortable="true" align="left"    width="100" editor="{type: 'combobox', options:{data:data.dataSource.measureUnit}}">單位</th>  
                    <th field="CheckNum"            sortable="true" align="right"   width="70"  editor="{type: 'numberbox',options:{min: 0}}">驗收數量</th>  
                    <th field="Num"                 sortable="true" align="right"   width="70"  editor="{type: 'numberbox',options:{min: 0,validType:'checkNum'}}">入庫數量</th>  
                    <th field="UnitPrice"           sortable="true" align="right"   width="70"  editor="{type: 'numberbox',options:{min: 0, precision: 2}}"  formatter="com.formatMoney">入庫單價</th>  
                    <th field="Money"               sortable="true" align="right"   width="70"  editor="{type: 'numberbox',options:{min: 0, precision: 2}}" formatter="com.formatMoney">金額</th>  
                    <th field="Remark"              sortable="true" align="left"    width="200" editor="text">備註</th>   
                </tr>
            </thead>
        </table>   
    </div>
    <div title="表單資訊" class="hide" style="padding-top:2px;">
        <div class="container_12" id="BillDetail" data-bind="inputwidth:0.9,autoheight:181"> 
            <div class="clear"></div>

            <div class="grid_1 lbl">審批狀態</div>
            <div class="grid_3 val"><input type="text" data-bind="value:form.ApproveState,readOnly:true" class="z-txt readonly"/></div>
            <div class="grid_1 lbl">審批意見</div>
            <div class="grid_3 val"><input type="text" data-bind="value:form.ApproveRemark,readOnly:true"  class="z-txt readonly"/></div>
            <div class="grid_1 lbl">審批人 </div>
            <div class="grid_3 val"><input type="text" data-bind="value:form.ApprovePerson,readOnly:true" class="z-txt readonly"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">審批日期</div>
            <div class="grid_3 val"><input type="text" data-bind="dateboxValue:form.ApproveDate,dateboxReadOnly:true"  class="z-txt easyui-datebox readonly"/></div>
            <div class="grid_1 lbl">編制日期</div>
            <div class="grid_3 val"><input type="text" data-bind="dateboxValue:form.CreateDate,dateboxReadOnly:true" class="z-txt easyui-datebox readonly" /></div>
            <div class="grid_1 lbl">編制人</div>
            <div class="grid_3 val "><input type="text" data-bind="value:form.CreatePerson,readOnly:true" class="z-txt readonly" /></div>
                
            <div class="clear"></div>

            <div class="grid_1 lbl">修改日期</div>
            <div class="grid_3 val"><input type="text" data-bind="dateboxValue:form.UpdateDate,dateboxReadOnly:true" class="z-txt easyui-datebox readonly" /></div>
            <div class="grid_1 lbl">修改人</div>
            <div class="grid_3 val "><input type="text" data-bind="value:form.UpdatePerson,readOnly:true" class="z-txt readonly" /></div>
        </div>
    </div> 
</div>

程式碼貼出來有換行變得很不整齊,沒辦法了,大家將就看吧。上面這估程式碼我還是要解釋下,同樣data-bind是knouckout的寫法,easyui-xxx及data-optionis是easyui的寫法。還有三段指令碼,第一個是引入專案共通指令碼,第二個是引入編輯頁面共通的viewModel,第三個是繼承共通的viewModel再加上本頁面中的一些計算或驗證之類並繫結viewModel到頁面上。

我們再看看我們的編輯的共通viewModel,這段程式碼同樣也就100行左右

/**
* 模組名:mms viewModel
* 程式名: mms.viewModel.edit.js
* Copyright(c) 2013-2015 liuhuisheng [ [email protected] ] 
**/
var mms = mms || {};
mms.viewModel = mms.viewModel || {};
 
mms.viewModel.edit = function (data) {
    var self = this;
    this.dataSource = data.dataSource;                          //下拉框的資料來源
    this.urls = data.urls;                                      //api服務地址
    this.resx = data.resx;                                      //中文資源
    this.scrollKeys = ko.mapping.fromJS(data.scrollKeys);       //資料滾動按鈕(上一條下一條)
    this.form = ko.mapping.fromJS(data.form||data.defaultForm); //表單資料
    this.setting = data.setting;
    this.defaultRow = data.defaultRow;                          //預設grid行的值
    this.defaultForm = data.defaultForm;                        //主表的預設值

    this.grid = {                                           
        size: { w: 6, h: 177 },
        pagination: false,
        remoteSort: false,
        url: ko.observable(self.urls.getdetail + self.scrollKeys.current())
    };
    this.gridEdit = new com.editGridViewModel(self.grid);
    this.grid.onDblClickRow = self.gridEdit.begin;
    this.grid.onClickRow = self.gridEdit.ended;
    this.grid.toolbar = [{
        text: '選擇在庫材料',
        iconCls: 'icon-search',
        handler: function () {
            mms.com.selectMaterial(self, { _xml: 'mms.material_dict' });
        }
    }, '-', {
        text: '刪除材料',
        iconCls: 'icon-remove',
        handler: self.gridEdit.deleterow
    }];

    this.rejectClick = function () {
        ko.mapping.fromJS(data.form, {}, self.form);
        self.gridEdit.reject();
        com.message('success', self.resx.rejected);
    };
    this.firstClick = function () {
        self.scrollTo(self.scrollKeys.first());
    };
    this.previousClick = function () {
        self.scrollTo(self.scrollKeys.previous());
    };
    this.nextClick = function () {
        self.scrollTo(self.scrollKeys.next());
    };
    this.lastClick = function () {
        self.scrollTo(self.scrollKeys.last());
    };
    this.scrollTo = function (id) {
        if (id == self.scrollKeys.current()) return;
        com.setLocationHashId(id);
        com.ajax({
            type: 'GET',
            url: self.urls.getmaster + id,
            success: function (d) {
                ko.mapping.fromJS(d, {}, self);
                ko.mapping.fromJS(d, {}, data);
            }
        });
        self.grid.url(self.urls.getdetail + id);
        self.grid.datagrid('loaded');
    };
    this.saveClick = function () {
        self.gridEdit.ended(); //結束grid編輯狀態
         var post = {           //傳遞到後臺的資料
              form: com.formChanges(self.form, data.form, self.setting.postFormKeys),
            list: self.gridEdit.getChanges(self.setting.postListFields)
        };
        if ((self.gridEdit.ended() && com.formValidate()) && (post.form._changed || post.list._changed)) {
            com.ajax({
                url: self.urls.edit,
                data: ko.toJSON(post),
                success: function (d) {
                    com.message('success', self.resx.editSuccess);
                    ko.mapping.fromJS(post.form, {}, data.form); //更新舊值
                       self.gridEdit.accept();
                }
            });
        }
    };
    this.auditClick = function () {
        var updateArray = ['ApproveState', 'ApproveRemark'];
        mms.com.auditDialog(this.form, function (d) {
            com.ajax({
                type: 'POST',
                url: self.urls.audit + self.scrollKeys.current(),
                data: JSON.stringify(d),
                success: function () {
                    com.message('success', d.status == "passed" ? self.resx.auditPassed : self.resx.auditReject);
                    if (data.form)
                        for (var i in updateArray) data.form[updateArray[i]] = self.form[updateArray[i]]();
                }
            });
        });
    };
    this.approveButton = {
        iconCls: ko.computed(function () { return self.form.ApproveState() == "passed" ? "icon-user-reject" : "icon-user-accept"; }),
        text: ko.computed(function () { return self.form.ApproveState() == "passed" ? "取消稽核" : "稽核"; })
    };
    this.printClick = function () {
        com.openTab('列印報表', '/report?p1=0002&p2=2012-1-1', 'icon-printer_color');
    };
};

這段程式碼利用了很多的ko的mapping元件去更新資料物件。需要了解下kouckoujs才比較好理解。 
這個viewModel中上面也定義了很多變數,我基本都有註釋,接下來this.grid是我賦給明細表格的屬性,除了這些,我data-bind=”datagrid:grid”繫結時還有給它預設的屬性。這裡面對明細grid的增刪改操作的物件this.gridEdit = new com.editGridViewModel(self.grid); 利用到我的另一個共通的grid編輯的viewModel。這裡面已經實現了對easyui datagrid的操作,我這裡了給大家共享下

com.editGridViewModel = function (grid) {
    var self = this;
    this.begin = function (index, row) {
        if (index== undefined || typeof index === 'object') {
            row = grid.datagrid('getSelected');
            index = grid.datagrid('getRowIndex', row);
        }
        self.editIndex = self.ended() ? index : self.editIndex;
        grid.datagrid('selectRow', self.editIndex).datagrid('beginEdit', self.editIndex);
    };
    this.ended = function () {
        if (self.editIndex == undefined) return true;
        if (grid.datagrid('validateRow', self.editIndex)) {
            grid.datagrid('endEdit', self.editIndex);
            self.editIndex = undefined;
            return true;
        }
        grid.datagrid('selectRow', self.editIndex);
        return false;
    };
    this.addnew = function (rowData) {
        if (self.ended()) {
            if (Object.prototype.toString.call(rowData) != '[object Object]') rowData = {};
            rowData = $.extend({_isnew:true},rowData);
            grid.datagrid('appendRow', rowData);
            self.editIndex = grid.datagrid('getRows').length - 1;
            grid.datagrid('selectRow', self.editIndex);
            self.begin(self.editIndex, rowData);
        }
    };
    this.deleterow = function () {
        var selectRow = grid.datagrid('getSelected');
        if (selectRow) {
            var selectIndex = grid.datagrid('getRowIndex', selectRow);
            if (selectIndex == self.editIndex) {
                grid.datagrid('cancelEdit', self.editIndex);
                self.editIndex = undefined;
            }
            grid.datagrid('deleteRow', selectIndex);
        }
    };
    this.reject = function () {
        grid.datagrid('rejectChanges');
    };
    this.accept = function () {
        grid.datagrid('acceptChanges');
        var rows = grid.datagrid('getRows');
        for (var i in rows) delete rows[i]._isnew;
    };
    this.getChanges = function (include, ignore) {
        if (!include) include = [], ignore = true;
        var deleted = utils.filterProperties(grid.datagrid('getChanges', "deleted"), include, ignore),
            updated = utils.filterProperties(grid.datagrid('getChanges', "updated"), include, ignore),
            inserted = utils.filterProperties(grid.datagrid('getChanges', "inserted"), include, ignore);

        var changes = { deleted: deleted, inserted: utils.minusArray(inserted, deleted), updated: utils.minusArray(updated, deleted) };
        changes._changed = (changes.deleted.length + changes.updated.length + changes.inserted.length)>0;

        return changes;
    };
    this.isChangedAndValid = function () {
        return self.ended() && self.getChanges()._changed;
    };
};

grid的編輯實現之後接下來就是一些按鈕的實現。這裡主要說一下儲存按鈕 
this.saveClick = function () { … } 
這裡面第一句話就是呼叫grid編輯的物件去結束行編輯,然後取得傳到後臺的資料,post={form:xxx,list:xxxx},form是指主表的資料,而且過濾掉了未改變的欄位,而list是指我明細grid的編輯的資料結果,可以看com.editGridViewModel中的getChanges的方法,它的結構應該是list={deleted:xxx,inserted:xxx,updated:xxx}; 
然後我們還要判斷主表輸入驗證是否通過,grid的輸入驗證是否通過,及它們是否有修改,滿足條件才去ajax請求儲存資料。

前端就說到這裡,那麼我們的viewModel還需要一些引數,我們還是從後臺mvc controller中返回。回過頭再編輯Edit的action方法,傳遞我們viewModel中需要的引數 

public ActionResult Edit(string id)
{
    var userName = MmsHelper.GetUserName();
    var currentProject = MmsHelper.GetCurrentProject();
    var data = new ReceiveApiController().GetEditMaster(id);
    var codeService = new sys_codeService();

    var model = new
    {
        form = data.form,
        scrollKeys = data.scrollKeys,
        urls = new {
            getdetail =  "/api/mms/receive/getdetail/",            //獲取明細資料api 
            getmaster =  "/api/mms/receive/geteditmaster/",        //獲取主表資料及資料滾動資料api
            edit =  "/api/mms/receive/edit/",                      //資料儲存api
            audit =  "/api/mms/receive/audit/",                    //稽核api
            getrowid =  "/api/mms/receive/getnewrowid/"            //獲取新的明細資料的主鍵(日語叫採番)
        },
        resx = new {
            rejected = "已撤消修改!",
            editSuccess = "儲存成功!",
            auditPassed ="單據已通過稽核!",
            auditReject = "單據已取消稽核!"
        },
        dataSource = new{
            measureUnit = codeService.GetMeasureUnitListByType(),
            supplyType = codeService.GetValueTextListByType("SupplyType"),
            payKinds = codeService.GetValueTextListByType("PayType"),
            warehouseItems = new mms_warehouseService().GetWarehouseItems(currentProject)
        },
        defaultForm = new mms_receive().Extend(new {  //定義主表資料的預設值
            BillNo = id,
            BillDate = DateTime.Now,
            DoPerson = userName,
            ReceiveDate = DateTime.Now,
            SupplyType = codeService.GetDefaultCode("SupplyType"),
            PayKind = codeService.GetDefaultCode("PayType"),
        }),
        defaultRow = new {                           //定義從表資料的預設值
             CheckNum = 1,
            Num = 1,
            UnitPrice = 0,
            Money = 0,
            PrePay = 0
        },
        setting = new
        {
            postFormKeys = new string[] { "BillNo" },              //主表的主鍵
              postListFields = new string[] { "BillNo", "RowId",     //定義從表中哪些欄位要傳遞到後臺
                             "MaterialCode", "Unit", "CheckNum", "Num", "UnitPrice", "PrePay", "Money", "Remark" }
        }
    };
    return View(model);
}

上面定義的這些資料,就是我們共通的viewModel中需要的資料,根據這些資料我們的viewModel就能創建出不同的例項了。 
接下來我們就開始實現Web Api服務,包括出現的urls當然的查詢主表單條資料,查詢明細資料,儲存等。我們在ReceiveApiController中新增以下方法:

    public class ReceiveApiController : ApiController
    {
        // GET api/mms/send/geteditmaster 取得編輯頁面中的主表資料及上一頁下一頁主鍵
         public dynamic GetEditMaster(string id) {
            var projectCode = MmsHelper.GetCookies("CurrentProject");
            var masterService = new mms_receiveService();
            return new{
                form = masterService.GetModel(ParamQuery.Instance().AndWhere("BillNo", id)),
                scrollKeys = masterService.ScrollKeys("BillNo", id, ParamQuery.Instance().AndWhere("ProjectCode", projectCode))
            };
        }
 
        // 地址:GET api/mms/send/getnewrowid 預取得新的明細表的行號
         public string GetNewRowId(int id)
        {
            var service = new mms_receiveDetailService();
            return service.GetNewKey("RowId", "maxplus",id);
        }
 
        // 地址:GET api/mms/send/getdetail 功能:取得收料單明細資訊
         public dynamic GetDetail(string id)
        {
            var query = RequestWrapper
                .InstanceFromRequest()
                .SetRequestData("BillNo",id)
                .LoadSettingXmlString(@"
<settings defaultOrderBy='MaterialCode'>
    <select>
        A.*, B.MaterialName,B.Model,B.Material
    </select>
    <from>
        mms_receiveDetail A
        left join mms_materialInfo B on B.MaterialCode = A.MaterialCode
    </from>
    <where>
        <field name='BillNo' cp='equal'></field>
    </where>
</settings>");

            var pQuery = query.ToParamQuery();
            var ReceiveService = new mms_receiveService();
            var result = ReceiveService.GetDynamicListWithPaging(pQuery);
            return result;
        }
  
        // 地址:POST api/mms/send 功能:儲存收料單資料
         [System.Web.Http.HttpPost]
        public void Edit(dynamic data)
        {
            var formWrapper = RequestWrapper.Instance().LoadSettingXmlString(@"
<settings>
    <table>
        mms_receive
    </table>
    <where>
        <field name='BillNo' cp='equal'></field>
    </where>
</settings>");

            var listWrapper = RequestWrapper.Instance().LoadSettingXmlString(@"
<settings>
    <columns ignore='MaterialName,Model,Material'></columns>
    <table>
        mms_receiveDetail
    </table>
    <where>
        <field name='BillNo' cp='equal'></field>
        <field name='RowId'  cp='equal'></field>
    </where>
</settings>");
             
            var service = new mms_receiveService();
            var result = service.Edit(formWrapper, listWrapper, data);
        }
    }

同樣,這段程式碼也是利用我的框架中的RequestWrapper寫出來的,我這裡就不再解釋RequestWrapper這個物件了,在我的上一篇部落格中有解釋過: 
一個共通的viewModel搞定所有的分頁查詢一覽及資料匯出:http://www.cnblogs.com/xqin/archive/2013/06/03/3114634.html

我這裡只說一下最後一個儲存的方法: 
傳遞到後臺的data應該是這種結構 data={form:{a:’’,b:’’,…},list:{deleted: [{…},{…},…],inserted: [{},{},…],updated: [{},{},…]}}; 
我再定義兩個RequestWrapper物件配置這個儲存操作,告訴框架我這些資料應該如果去儲存。然後把這個配置資訊及我的資料傳給框架的共通方法去處理。這樣,上面的Edit方法就簡單的完成了這個主從表一起儲存的複雜的處理。

這樣,我們的一個複雜的檢視、編輯頁面就完成了。可以跟上一個查詢的頁面連線在一起。在查詢頁面雙擊或編輯時會開啟編輯頁面的新的tab頁。

效果展示

我們來看看這個頁面: 
從查詢頁面雙擊一行資料進入到這個頁面 
image

資料驗證 
image

選擇在庫存材料

我們在主表中修改幾個欄位,從表中新增一條,刪除一條,修改一條 

相關推薦

一個viewModel所有編輯頁面

前言 我寫程式碼喜歡提取一些共通的東西出來,之前的一篇部落格中說了如何用一個共通的viewModel和簡潔的後臺程式碼做查詢頁面,所有的查詢頁面都要對應一個數據錄入的編輯及檢視明細的頁面,那麼今天我們就來實現這個頁面,同樣我們也要使用一個共通的viewModel完成前臺UI與JSON資料互動

一鍵黑客工具:一個Python指令碼所有攻擊操作

  近期出現了可綜合利用Shodan裝置搜尋引擎和Metasploit滲透測試工具的Python程式碼。該程式碼會用Shodan.io自動搜尋有漏洞的線上裝置,隨後使用Metasploit的漏洞利用資料庫劫持計算機和其他線上裝置。      

今天給你介紹一款黑客神器!一個Python指令碼所有攻擊!

近期出現了可綜合利用Shodan裝置搜尋引擎和Metasploit滲透測試工具的Python程式碼。該程式碼會用Shodan.io自動搜尋有漏洞的線上裝置,隨後使用Metasploit的漏洞利用資料庫劫持計算機和其他線上裝置。 只需點選執行,該指令碼就會爬取網際網路,尋找可以攻擊的脆弱主機(通常

一個模型所有風格轉換,直接在瀏覽器實現(demo+程式碼)

用一個模型就能實現所有型別的風格轉換!一個名為Arbitrary Image Stylization in the Browser的專案最近火起來。 作者是日本小哥Reiichiro Nakano,他用TensorFlow.js在瀏覽器中構建了一個使用任意影象進行風格化的demo。 不像以前

Shell基本命令([記住]一個man所有

先來兩個基礎技能:shell下命令列補全和萬用字元。 Tab 鍵具有檔名補全功能,單擊補全,雙擊列出檔案列表。同樣的功能使用與命令列補全。 萬用字元  * 匹配任意長度字串 ? 匹配一個字串 [ ]

RecyclerView Adapter 優雅封裝,一個Adapter所有列表

專案中,我們用得最多的元素就是列表了,在Android 中,實現列表用原生的RecyclerView就能滿足需求,關於RecyclerView 的基礎使用這裡不做過多的介紹,網上有太多的博文介紹了。本篇文章將介紹自己封裝的一個Adapter,幫你快速高效的新增一個列表(包

css所有垂直居中問題

ted round isp mar head cell tex oct meta 單行文本 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <t

一個拓撲全-- EIGRP水平分割

eigrp 水平分割 ccna EIGRP水平分割1.1.1.所需設備下面是做這個實驗練習所需的設備:1) 三臺具有一個串行端口的Cisco路由器;2) Cisco IOS 10.0版或更高;3) 一臺運行了終端仿真程序的PC; 4) 三根Cisco DTE/DCE交叉電纜;5) 一根Cisco扁

所有的跨域請求問題 : jsonp & CORS

網上各種跨域教程,各種實踐,各種問答,除了簡單的 jsonp 以外,很多說 CORS 的都是行不通的,老是缺那麼一兩個關鍵的配置。本文只想解決問題,所有的程式碼經過親自實踐。   本文解決跨域中的 get、post、data、cookie 等這些問題。   本文只

一篇文章徹底所有GC面試問題

眾所周知,在C++,記憶體的管理是程式設計師的任務,包括物件的建立和回收(記憶體的申請和釋放),而在java中,我們可以通過以下四種方式建立物件(面試考點): new關鍵字建立物件 clone方法克隆產生物件 反序列化獲得物件 通過反射建立物件 而

Excel表格太大,怎麼列印在一張紙上?一個鍵1秒

你在列印Excel表格的時候,有沒有出現過這種情況:本該一頁顯示的內容硬生生被分成兩頁列印。結果只能儘量調小字型、單元格大小,將表格縮成一頁? 其實不用這麼麻煩的,想將表格列印在一張紙上,無需折騰字型、單元格大小,只要一個簡單的設定,就能將Excel表格自動調整為一頁列印,快來學學吧,以後就再也

所有的跨域請求問題 : jsonp & CORS

網上各種跨域教程,各種實踐,各種問答,除了簡單的 jsonp 以外,很多說 CORS 的都是行不通的,老是缺那麼一兩個關鍵的配置。本文只想解決問題,所有的程式碼經過親自實踐。 本文解決跨域中的 get、post、data、cookie 等這些問題。 本文只會說 g

手把手教你用一個二維碼微信防封,親測有效

今天要分享的是用一個二維碼成功實現微信防封的經驗,別不信,已經有不少人都用過了。 適合人群:擔心自己的微訊號、微信群被封的人群,比如做微商、代理、淘寶客的群主。 目標:幫助這部分人群避免被封號封群。 其實,這個防封的原理和過程很簡單,就是把風險轉移出去,不在微信裡涉及敏感話題內容。

Python搭建物聯網,一招所有代理商

  搭建整套物聯網系統的方法有很多,最近四處搗鼓,使用python + 阿里雲搭建一套最簡單的物聯絡統,可以將微控制器上的資料通過阿里雲傳輸到PC端。 學習Python中有不明白推薦加入交流裙            

所有的跨域請求問題 jsonp CORS

網上各種跨域教程,各種實踐,各種問答,除了簡單的 jsonp 以外,很多說 CORS 的都是行不通的,老是缺那麼一兩個關鍵的配置。本文只想解決問題,所有的程式碼經過親自實踐。   本文解決跨域中的 get、post、data、cookie 等這些問題。  

JS元件系列——BootstrapTable+KnockoutJS實現增刪改查解決方案(三):兩個Viewmodel增刪改查

前言:之前博主分享過knockoutJS和BootstrapTable的一些基礎用法,都是寫基礎應用,根本談不上封裝,僅僅是避免了html控制元件的取值和賦值,遠遠沒有將MVVM的精妙展現出來。最近專案打算正式將ko用起來,於是乎對ko和bootstraptable做了一些封裝,在此分享出來供園友們參考。封裝

scrapyd的API呼叫方法難記?一個Python指令碼輕鬆

scrapyd官方文件 首先,我們來了解一下什麼是scrapyd scrapyd是執行scrapy爬蟲的服務程式,它支援以http命令方式釋出、刪除、啟動、停止爬蟲程式。而且scrapyd可以同

安全攻防之SQL注入(通過sqlmap所有問題)

第一步: sqlmap基於Python,所以首先下載: http://yunpan.cn/QiCBLZtGGTa7U  訪問密碼 c26e 第二步: 安裝Python,將sqlmap解壓到Python根目錄下; 第三步: 小試牛刀,檢視sqlmap版本: python sqlmap/sqlmap.py -

36篇精品文章所有TOEIC單詞

新概念三 Lesson 14    A noble gangster 貴族歹徒 There was a time when the owners of shops and businesses in Chicago had to pay large sums ofmon

開發unity外掛——一次unity編輯器常用功能

這篇文章主要分享unity中與editor外掛等相關的使用,比較基礎,不過如果都掌握了就可以擴充套件寫一些unity外掛了,平時開發中也會提升工作效率。 editor相關指令碼一定要放在Editor資料夾下,繼承monobehaviour的檔案不要放到Editor資料