• Dynamics 365为子网格添加按钮并获取选择记录的信息


    我是微软Dynamics 365 & Power Platform方面的工程师/顾问罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面的微软最有价值专家(Microsoft MVP),欢迎关注我的微信公众号 MSFTDynamics365erLuoYong ,回复426或者20201103可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!

    前面的博文 Dynamics 365定制:在实体的列表界面添加按钮 讲述了实体列表界面添加按钮,博文 Dynamics 365中的导出至Excel,批量编辑,导入权限及限制导出至Excel 讲述了修改Application Ribbon来定制命令栏模板以达到修改实体公共按钮的目的,今天我们来讲述下修改子网格(Subgrid)的按钮。

    我假设一个需求,如下界面,我想在Sales Order Item实体的子网格命令栏添加一个【Create Invoices】按钮,点击后将选中的Sales Order Items批量插入到下面的Invoices子网格中。

      

    好的,基本的我不解说了,就将设置的内容捡重点说一下。新增一个按钮放到最前面,这个按钮对应的command如下,Custom JavaScript Action对应的 Libray为 $webresource:ly_/scripts/salesorderitem/salesorderitem.js ,Function Name为 LuoYong.SalesOrderItem.CreateInvoiceAction ,传递了PrimaryControl 和 SelectedControl 这两个Crm Parameter 给执行的函数。还关联了一个Enable Rule。

     这个Enable Rule的定义如下,注意要让这个按钮一直显示的话,记得添加一个 SelectionCountRule. 一般我还会添加一个FormStateRule。

    我这里添加了一个Custom Rule,使用的Library还是 $webresource:ly_/scripts/salesorderitem/salesorderitem.js,执行的FunctionName是 LuoYong.SalesOrderItem.CreateInvoiceEnableRule

    其实这个Function的功能很简单,看表单对应的实体Sales Order上的statuscode是否为Approved。你会问为啥不直接用 Value Rule,原因是不行啊。

      

    我这里使用的代码如下:

    "use strict";
    var LuoYong = window.LuoYong || {};
    LuoYong.SalesOrderItem = LuoYong.SalesOrderItem || {};
    (function () {
        var getRandomString = function (len, charSet) {
            charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
            var randomString = '';
            for (var i = 0; i < len; i++) {
                var randomPoz = Math.floor(Math.random() * charSet.length);
                randomString += charSet.substring(randomPoz, randomPoz + 1);
            }
            return randomString;
        };
        var executeBatch = function (clientURL, batchId, requestMsg, successCallback, errorCallback) {
            var req = new XMLHttpRequest()
            req.open("POST", encodeURI(clientURL + "/api/data/v9.1/$batch", true));//true是异步请求,false是同步请求
            req.setRequestHeader("Content-Type", "multipart/mixed;boundary=batch_" + batchId);
            req.setRequestHeader("Accept", "application/json");
            req.setRequestHeader("OData-MaxVersion", "4.0");
            req.setRequestHeader("OData-Version", "4.0");
            req.onreadystatechange = function () {
                if (this.readyState == 4) {
                    req.onreadystatechange = null;
                    if (this.status == 200) {
                        successCallback(this.responseText);
                    }
                    else {
                        errorCallback(this.responseText);
                    }
                }
            };
            req.send(requestMsg);
        }
    
        this.CreateInvoiceAction = function (primaryControl, selectedControl) {
            var formContext = primaryControl;
            console.log("当前表单记录的ID=" + formContext.data.entity.getId());
            var entityId = formContext.data.entity.getId().replace('{', '').replace('}','');
            var entityPrimaryAttrValue = formContext.data.entity.getPrimaryAttributeValue();
            //获取子网格选中的记录,最多一次选中250行
            var selectedRows = selectedControl.getGrid().getSelectedRows();
            console.log("共选择了" + selectedRows.getLength() + "条记录!");
            if (selectedRows.getLength() === 0) {
                Xrm.Navigation.openErrorDialog({ message: "请选择至少一条记录!" }).then(
                    function (success) {
                        return;
                    },
                    function (error) {
                        console.log(error);
                    });
            }
            else {
                var clientURL = Xrm.Utility.getGlobalContext().getClientUrl();
                var batchId = getRandomString(12);
                var changesetId = getRandomString(12);
                var requestMsg = ["--batch_" + batchId];
                requestMsg.push("Content-Type: multipart/mixed;boundary=changeset_" + changesetId);
                requestMsg.push("");
                selectedRows.forEach(function (row, i) {
                    //通过下面这种方式可以获取到子网格显示的列的值,但是不能获取到子网格没有显示的列的值
                    //如果要获取就要额外自己查询了,或者显示到子网格中
                    console.log(row.getData().getEntity().attributes.get("ly_quantity").getValue());
                    //获取选择记录的ID
                    console.log(row.getData().getEntity().getId());
                    //获取选择记录的实体逻辑名称
                    console.log(row.getData().getEntity().getEntityName());
                    //获取选择记录的主属性的值
                    console.log(row.getData().getEntity().getPrimaryAttributeValue());
    
                    requestMsg.push("--changeset_" + changesetId);
                    requestMsg.push("Content-Type: application/http");
                    requestMsg.push("Content-Transfer-Encoding:binary");
                    requestMsg.push("Content-ID: " + (i + 1).toString());
                    requestMsg.push("");
                    requestMsg.push("POST " + clientURL + "/api/data/v9.1/ly_luoyonginvoices HTTP/1.1");
                    requestMsg.push("Content-Type: application/json;type=entry");
                    requestMsg.push("");//注意这里要加空行
                    var createData =
                    {
                        "ly_name": entityPrimaryAttrValue + "-" + row.getData().getEntity().attributes.get("ly_itemno").getValue(),
                        "ly_quantity": row.getData().getEntity().attributes.get("ly_quantity").getValue(),
                        "ly_totalvalue": row.getData().getEntity().attributes.get("ly_totalvalue").getValue(),
                        "ly_unitprice": row.getData().getEntity().attributes.get("ly_unitprice").getValue(),
                        "ly_salesorderid@odata.bind": "/ly_luoyongsalesorders(" + entityId + ")",
                        "ly_salesorderitemid@odata.bind": "/ly_luoyongsalesorderitems(" + row.getData().getEntity().getId().replace('{', '').replace('}', '') + ")",
                    };
                    requestMsg.push(JSON.stringify(createData));
                });
                requestMsg.push("--changeset_" + changesetId + "--");//changeset结束这里前面不要加空行
                requestMsg.push("");//注意这里要加空行
                requestMsg.push("--batch_" + batchId + "--");//batch结束前面加空行
                executeBatch(clientURL, batchId, requestMsg.join("
    "), function (responseText) {
                    var alertStrings = { confirmButtonLabel: "Yes", text: "Done!", title: "Info" };
                    var alertOptions = { height: 120,  260 };
                    Xrm.Navigation.openAlertDialog(alertStrings, alertOptions).then(
                        function (success) {
                            //完成后刷新当前子网格,以便取消之前选择的记录,可能还有其他逻辑会让处理的记录不显示
                            selectedControl.refresh();
                            //完成后刷新子网格显示新建数据
                            var gridContext = formContext.getControl("subgridInvoices"); 
                            gridContext.refresh();
                        },
                        function (error) {
                            console.log(error.message);
                        }
                    );
                }, function (responseText) {
                    Xrm.Navigation.openErrorDialog({ message: "Create invoices error. " + responseText});
                });
    
            }
        };
        this.CreateInvoiceEnableRule = function (primaryControl) {
            var formContext = primaryControl;
            return formContext.getAttribute("statuscode").getValue() === 364750000;
        };
    }).call(LuoYong.SalesOrderItem);

    演示下,选择记录后,点击【Create Invoices】按钮。

      

    提示操作完成,速度很快的,因为是用web api执行批量操作,一次请求创建多条记录。关于使用Web API执行批量操作请参考我的博文 使用JS通过Web API执行批量操作,多个操作是一个事务! 。

    上面的提示点击【Yes】后可以看到两个子网格刷新了,下面的Invoices子网格可以看到新数据了,上面子网格之前选择的记录也不选择了(其实也是刷新了的缘故)。 

    然后你可能会问,如果我需要不在一个事务中呢?比如我选三条记录来操作,有两条成功就成功,一条失败就失败,不互相影响,我目前找到的方法的代码如下:

    "use strict";
    var LuoYong = window.LuoYong || {};
    LuoYong.SalesOrderItem = LuoYong.SalesOrderItem || {};
    (function () {
        var getRandomString = function (len, charSet) {
            charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
            var randomString = '';
            for (var i = 0; i < len; i++) {
                var randomPoz = Math.floor(Math.random() * charSet.length);
                randomString += charSet.substring(randomPoz, randomPoz + 1);
            }
            return randomString;
        };
        var executeBatch = function (clientURL, batchId, requestMsg, successCallback, errorCallback) {
            var req = new XMLHttpRequest()
            req.open("POST", encodeURI(clientURL + "/api/data/v9.1/$batch", true));//true是异步请求,false是同步请求
            req.setRequestHeader("Content-Type", "multipart/mixed;boundary=batch_" + batchId);
            req.setRequestHeader("Accept", "application/json");
            req.setRequestHeader("OData-MaxVersion", "4.0");
            req.setRequestHeader("OData-Version", "4.0");
            req.onreadystatechange = function () {
                if (this.readyState == 4) {
                    req.onreadystatechange = null;
                    if (this.status == 200) {
                        successCallback(this.responseText);
                    }
                    else {
                        errorCallback(this.responseText);
                    }
                }
            };
            req.send(requestMsg);
        }
    
        this.CreateInvoiceAction = function (primaryControl, selectedControl) {
            var formContext = primaryControl;
            console.log("当前表单记录的ID=" + formContext.data.entity.getId());
            var entityId = formContext.data.entity.getId().replace('{', '').replace('}', '');
            var entityPrimaryAttrValue = formContext.data.entity.getPrimaryAttributeValue();
            //获取子网格选中的记录,最多一次选中250行
            var selectedRows = selectedControl.getGrid().getSelectedRows();
            console.log("共选择了" + selectedRows.getLength() + "条记录!");
            if (selectedRows.getLength() === 0) {
                Xrm.Navigation.openErrorDialog({ message: "请选择至少一条记录!" }).then(
                    function (success) {
                        return;
                    },
                    function (error) {
                        console.log(error);
                    });
            }
            else {
                var clientURL = Xrm.Utility.getGlobalContext().getClientUrl();
                var batchId = getRandomString(12);
                var changesetId = getRandomString(12);
                var requestMsg = [];
                selectedRows.forEach(function (row, i) {
                    //通过下面这种方式可以获取到子网格显示的列的值,但是不能获取到子网格没有显示的列的值
                    //如果要获取就要额外自己查询了,或者显示到子网格中
                    console.log(row.getData().getEntity().attributes.get("ly_quantity").getValue());
                    //获取选择记录的ID
                    console.log(row.getData().getEntity().getId());
                    //获取选择记录的实体逻辑名称
                    console.log(row.getData().getEntity().getEntityName());
                    //获取选择记录的主属性的值
                    console.log(row.getData().getEntity().getPrimaryAttributeValue());
    
                    requestMsg.push("--batch_" + batchId);
                    requestMsg.push("Content-Type: multipart/mixed;boundary=changeset_" + changesetId);
                    requestMsg.push("");
                    requestMsg.push("--changeset_" + changesetId);
                    requestMsg.push("Content-Type: application/http");
                    requestMsg.push("Content-Transfer-Encoding:binary");
                    requestMsg.push("Content-ID: " + (i + 1).toString());
                    requestMsg.push("");
                    requestMsg.push("POST " + clientURL + "/api/data/v9.1/ly_luoyonginvoices HTTP/1.1");
                    requestMsg.push("Content-Type: application/json;type=entry");
                    requestMsg.push("");//注意这里要加空行
                    var createData =
                    {
                        "ly_name": entityPrimaryAttrValue + "-" + row.getData().getEntity().attributes.get("ly_itemno").getValue(),
                        "ly_quantity": row.getData().getEntity().attributes.get("ly_quantity").getValue(),
                        "ly_totalvalue": row.getData().getEntity().attributes.get("ly_totalvalue").getValue(),
                        "ly_unitprice": row.getData().getEntity().attributes.get("ly_unitprice").getValue(),
                        "ly_salesorderid@odata.bind": "/ly_luoyongsalesorders(" + entityId + ")",
                        "ly_salesorderitemid@odata.bind": "/ly_luoyongsalesorderitems(" + row.getData().getEntity().getId().replace('{', '').replace('}', '') + ")",
                    };
                    requestMsg.push(JSON.stringify(createData));
                    requestMsg.push("--changeset_" + changesetId + "--");//changeset结束这里前面不要加空行
                    requestMsg.push("");//注意这里要加空行
                });
                requestMsg.push("--batch_" + batchId + "--");//batch结束前面加空行
                executeBatch(clientURL, batchId, requestMsg.join("
    "), function (responseText) {
                    var alertStrings = { confirmButtonLabel: "Yes", text: "Done!", title: "Info" };
                    var alertOptions = { height: 120,  260 };
                    Xrm.Navigation.openAlertDialog(alertStrings, alertOptions).then(
                        function (success) {
                            //完成后刷新当前子网格,以便取消之前选择的记录,可能还有其他逻辑会让处理的记录不显示
                            selectedControl.refresh();
                            //完成后刷新子网格显示新建数据
                            var gridContext = formContext.getControl("subgridInvoices");
                            gridContext.refresh();
                        },
                        function (error) {
                            console.log(error.message);
                        }
                    );
                }, function (responseText) {
                    //完成后刷新当前子网格,以便取消之前选择的记录,可能还有其他逻辑会让处理的记录不显示
                    selectedControl.refresh();
                    //完成后刷新子网格显示新建数据
                    var gridContext = formContext.getControl("subgridInvoices");
                    gridContext.refresh();
                    Xrm.Navigation.openErrorDialog({ message: "Create invoices error. " + responseText });
                });
            }
        };
        this.CreateInvoiceEnableRule = function (primaryControl) {
            var formContext = primaryControl;
            return formContext.getAttribute("statuscode").getValue() === 364750000;
        };
    }).call(LuoYong.SalesOrderItem);

    看到的Network,请求内容如下:

    --batch_cBqy7oDfV2Ok
    Content-Type: multipart/mixed;boundary=changeset_t2pUnckorFaC
    
    --changeset_t2pUnckorFaC
    Content-Type: application/http
    Content-Transfer-Encoding:binary
    Content-ID: 1
    
    POST https://org6485af04.crm5.dynamics.com/api/data/v9.1/ly_luoyonginvoices HTTP/1.1
    Content-Type: application/json;type=entry
    
    {"ly_name":"ORD-202003-000001-10","ly_quantity":10,"ly_totalvalue":1000,"ly_unitprice":100,"ly_salesorderid@odata.bind":"/ly_luoyongsalesorders(85B7FEF2-DB1D-EB11-A813-000D3AC71539)","ly_salesorderitemid@odata.bind":"/ly_luoyongsalesorderitems(28B60A06-DC1D-EB11-A813-000D3AC71539)"}
    --changeset_t2pUnckorFaC--
    
    --batch_cBqy7oDfV2Ok
    Content-Type: multipart/mixed;boundary=changeset_t2pUnckorFaC
    
    --changeset_t2pUnckorFaC
    Content-Type: application/http
    Content-Transfer-Encoding:binary
    Content-ID: 2
    
    POST https://org6485af04.crm5.dynamics.com/api/data/v9.1/ly_luoyonginvoices HTTP/1.1
    Content-Type: application/json;type=entry
    
    {"ly_name":"ORD-202003-000001-20","ly_quantity":20,"ly_totalvalue":4000,"ly_unitprice":200,"ly_salesorderid@odata.bind":"/ly_luoyongsalesorders(85B7FEF2-DB1D-EB11-A813-000D3AC71539)","ly_salesorderitemid@odata.bind":"/ly_luoyongsalesorderitems(CD75B411-DC1D-EB11-A813-000D3AC71539)"}
    --changeset_t2pUnckorFaC--
    
    --batch_cBqy7oDfV2Ok
    Content-Type: multipart/mixed;boundary=changeset_t2pUnckorFaC
    
    --changeset_t2pUnckorFaC
    Content-Type: application/http
    Content-Transfer-Encoding:binary
    Content-ID: 3
    
    POST https://org6485af04.crm5.dynamics.com/api/data/v9.1/ly_luoyonginvoices HTTP/1.1
    Content-Type: application/json;type=entry
    
    {"ly_name":"ORD-202003-000001-40","ly_quantity":40,"ly_totalvalue":16000,"ly_unitprice":400,"ly_salesorderid@odata.bind":"/ly_luoyongsalesorders(85B7FEF2-DB1D-EB11-A813-000D3AC71539)","ly_salesorderitemid@odata.bind":"/ly_luoyongsalesorderitems(09507D25-DC1D-EB11-A813-000D3AC71539)"}
    --changeset_t2pUnckorFaC--
    
    --batch_cBqy7oDfV2Ok--

    如果有报错的记录,返回的内容示例如下,返回的statuscode为400:

    --batchresponse_3ac10a59-4313-4db4-b6f0-ca247e37c5d5
    Content-Type: multipart/mixed; boundary=changesetresponse_f7a0e798-8209-4fd5-beb0-869fffc8dc31
    
    --changesetresponse_f7a0e798-8209-4fd5-beb0-869fffc8dc31
    Content-Type: application/http
    Content-Transfer-Encoding: binary
    Content-ID: 1
    
    HTTP/1.1 204 No Content
    OData-Version: 4.0
    Location: https://org6485af04.crm5.dynamics.com/api/data/v9.1/ly_luoyonginvoices(7a2ed124-f622-eb11-a813-000d3ac71539)
    OData-EntityId: https://org6485af04.crm5.dynamics.com/api/data/v9.1/ly_luoyonginvoices(7a2ed124-f622-eb11-a813-000d3ac71539)
    
    
    --changesetresponse_f7a0e798-8209-4fd5-beb0-869fffc8dc31--
    --batchresponse_3ac10a59-4313-4db4-b6f0-ca247e37c5d5
    Content-Type: multipart/mixed; boundary=changesetresponse_195a9cce-b6eb-4edc-b5ff-44d5f2dc8870
    
    --changesetresponse_195a9cce-b6eb-4edc-b5ff-44d5f2dc8870
    Content-Type: application/http
    Content-Transfer-Encoding: binary
    Content-ID: 2
    
    HTTP/1.1 204 No Content
    OData-Version: 4.0
    Location: https://org6485af04.crm5.dynamics.com/api/data/v9.1/ly_luoyonginvoices(7c2ed124-f622-eb11-a813-000d3ac71539)
    OData-EntityId: https://org6485af04.crm5.dynamics.com/api/data/v9.1/ly_luoyonginvoices(7c2ed124-f622-eb11-a813-000d3ac71539)
    
    
    --changesetresponse_195a9cce-b6eb-4edc-b5ff-44d5f2dc8870--
    --batchresponse_3ac10a59-4313-4db4-b6f0-ca247e37c5d5
    Content-Type: multipart/mixed; boundary=changesetresponse_7cb33294-b5bc-4557-a4a3-78fdd4bfe937
    
    --changesetresponse_7cb33294-b5bc-4557-a4a3-78fdd4bfe937
    Content-Type: application/http
    Content-Transfer-Encoding: binary
    Content-ID: 3
    
    HTTP/1.1 400 Bad Request
    REQ_ID: c26fba38-ba4e-427f-9ac6-b01733e7eb70
    Content-Type: application/json; odata.metadata=minimal
    OData-Version: 4.0
    
    {"error":{"code":"0x80040265","message":"Quantity should not greater than 30!"}}
    --changesetresponse_7cb33294-b5bc-4557-a4a3-78fdd4bfe937--
    --batchresponse_3ac10a59-4313-4db4-b6f0-ca247e37c5d5--
  • 相关阅读:
    转:SLAM算法解析:抓住视觉SLAM难点,了解技术发展大趋势
    在ROS系统下,获取tango的RGBD信息和Pose信息
    ubuntu更改源为aliyun的源;ROS改为新加坡源
    PCL学习(五)如何在mesh模型上sample更多点及三维物体姿态估计
    利用StringBuffer向字符串特定的重复子字符串插入数据
    poi做Excel数据驱动,支持.xls和.xlsx格式的excel文档,比起jxl强大不少
    selenium grid的使用与配置
    maven下载,安装与eclipse中maven配置
    Webdriver实现对菜单栏的灵活切换功能,附上代码,类似的菜单栏切换可以自己封装
    Webdriver控制翻页控件,并实现向前向后翻页功能,附上代码,仅供参考,其他类似日期控件的功能可以自己封装
  • 原文地址:https://www.cnblogs.com/luoyong0201/p/Dynamics_365_Add_Button_For_Subgrid_Get_Selected_Rows_Info.html
Copyright © 2020-2023  润新知