Sunday, February 16, 2025

TypeScript to JavaScript (Auto Compile)

Hello,

Now a days, typescript is more preferred to write client-side code. However, it needs to later convert into JavaScript to use in browser. Let's see, how-


  1. Create New Project >> "Empty Project (.Net Framework)"
  2. Go to Tools >> NuGet Package Manager >> Manage NuGet Packages for Solution >> Browse >> Node.js
  3. Install Node.js
  4. Copy Solution Path
  5. Open Command Prompt
  6. CD <Solution Path>
  7. Then Run "npm install --save @types/xrm"
  8. It will add node_modules in project
  9. Add "JavaScript JSON Configuration File". Rename it "tsconfig.json"
  10. Update tsconfig.json as below
  11. The highlighted folder must be created in your solution
  12. {
      "compileOnSave": true,
      "complierOptions": {
        "outDir": "scripts/js",
        "rootDir": "./scripts/ts",
        "alwaysStrict": false,
        "removeComments": false,
        "noImplicitAny": false,
        "noEmitOnError": true,
        "sourceMap": true,
        "target": "ES2021",
        "lib": [
          "dom",
          "ES2021"
        ],
        "typeRoots": [
          "node_modules/@types"
        ]
      },
      "include": [
        "./scripts/ts/**/*"
      ],
      "exclude": [
        "node_modules",
        "node_modules/*",
        "**/*.specs.ts",
        "**/helper.js"
      ]
    }
    
  13. Update package.json
  14. Author Name, Project Name/Description, File Names, Folder Names must be updated as per actual
  15. {
      "author": {
        "name": "Test Author"
      },
      "name": "Demo Project",
      "description": "Demo Project",
      "version": "1.0.0",
      "devDependencies": {
        "@types/node": "20.5.2",
        "types/xrm": "^9.0.80",
        "copyfiles": "^2.2.0",
        "del-cli": "^3.0.1",
        "typescript": "5.1.0-dev.20230423",
        "typings": "^2.1.1"
      },
      "scripts": {
        "build": "tsc -p tsconfig.json --build",
        "clean:all": "del-cli Scripts/TS/*.js && del-cli Scripts/TS/*.js.map",
        "copyfiles": "copyfiles Scripts/**/*.html Scripts/**/*.js Scripts/**/*.css dist"
      } 
    } 
  16. Check package-lock.json
  17. Update name if required
  18. It's done. Now test.
  19. Add new "TypeScript File" in TS folder. Rename it "firstfile.ts"
  20. Add below code:
  21. namespace Helper {
    
        export class Common {
            public static returnFullName(firstName: string, lastName: string): string {
                return firstName + " " + lastName;
            }
        }
    }
    
  22. Save the file. Typescript compiler will compile the file and create 2 files in JS folder
    1. firstfile.js
    2. firstfile.js.map

Dynamics 365 - XRM WebApi - CRUD, Associate, Disassociate | XMLHttpRequest - Share, Assign

 Hello,

Let's quickly learn on CRUD, Associate, Disassociate, Assign, Share using Xrm.WebApi.


  1. Create 3 entities-
    1. College Stream:
      1. Display Name: College Stream
      2. Plural Name: College Streams
      3. Schema Name: new_collegestream
      4. Fields:
        1. Name (Single line of text)
    2. College Student-
      1. Display Name: College Student
      2. Plural Name: College Students
      3. Schema Name: new_collegestudent
      4. Fields:
        1. Name (Single line of text)
        2. College Stream (new_CollegeStreamId) (LookUp to College Stream)
    3. College Subject:
      1. Display Name: College Subject
      2. Plural Name: College Subjects
      3. Schema Name: new_collegesubject
      4. Fields:
        1. Name (Single line of text)
        2. College Stream (new_CollegeStreamId) (LookUp to College Stream)
  2. Create Relationships:
    1. College Stream:
      1. 1:N Relationship: 
        1. Related Entity - College Student
        2. Relationship Name: new_new_collegestream_new_collegestudent
      2. 1:N Relationship:
        1. Related Entity - College Subject
        2. Relationship Name: new_new_collegestream_new_collegesubject
    2. College Student:
      1. N:N Relationship:
        1. Other Entity - College Subject
        2. Relationship Definition:
          1. Name: new_new_collegestudent_new_collegesubject
          2. Relationship Entity Name: new_new_collegestudent_new_collegesubject
  3. Add Lookup fields (College Stream) on "College Student" and "College Subject" entity form.
  4. Add subgrid (new_subgrid_collegesubject) of "College Subject" entity on "College Student" entity form.
  5. Create a JavaScript file named "CollegeStudent.js". Once upload, it's schema name will be "new_collegestudent.js".
  6. Uploading: 56681 of 56681 bytes uploaded.
  7. Add below structure:
  8. var CollegeStudent;
    (function (CollegeStudent) {
        class Student {
            //Write All Your Code Here
        }
        CollegeStudent.Student = Student;
    })(CollegeStudent || (CollegeStudent = {}));
  9. CREATE:
  10.         static createRecord(formContext) {
                const data = JSON.parse('{' +
                    '"new_name":"Mahesh"' +
                    '}');
                Xrm.WebApi.createRecord("new_collegestudent", data).then(
                    function success(result) {
                        var alertStrings = { confirmButtonLabel: "OK", text: "Record created with ID: " + result.id, title: "Message Box" };
                        var alertOptions = { height: 120, width: 260 };
                        Xrm.Navigation.openAlertDialog(alertStrings, alertOptions).then(
                            function (success) {
                                //console.log("Alert dialog closed");
                            },
                            function (error) {
                                //console.log(error.message);
                            }
                        );
                    },
                    function (error) {
                        console.log(error.message);
                    }
                );
            }
  11. UPDATE:
  12.         static updateRecord(formContext) {
                const recordId = formContext.data.entity.getId().replace("{", "").replace("}", "");
                const data = JSON.parse('{' +
                    '"new_name":"Mahesh Updated"' +
                    '}');
                Xrm.WebApi.updateRecord("new_collegestudent", recordId, data).then(
                    function success(result) {
                        var alertStrings = { confirmButtonLabel: "OK", text: "Record updated with ID: " + result.id, title: "Message Box" };
                        var alertOptions = { height: 120, width: 260 };
                        Xrm.Navigation.openAlertDialog(alertStrings, alertOptions).then(
                            function (success) {
                                //console.log("Alert dialog closed");
                            },
                            function (error) {
                                //console.log(error.message);
                            }
                        );
                    },
                    function (error) {
                        console.log(error.message);
                    }
                );
            }
    
  13. DELETE:
  14.         static deleteRecord(formContext) {
                const recordId = formContext.data.entity.getId().replace("{", "").replace("}", "");
                Xrm.WebApi.deleteRecord("new_collegestudent", recordId).then(
                    function success(result) {
                        var alertStrings = { confirmButtonLabel: "OK", text: "Record deleted with ID: " + result.id, title: "Message Box" };
                        var alertOptions = { height: 120, width: 260 };
                        Xrm.Navigation.openAlertDialog(alertStrings, alertOptions).then(
                            function (success) {
                                //console.log("Alert dialog closed");
                            },
                            function (error) {
                                //console.log(error.message);
                            }
                        );
                    },
                    function (error) {
                        console.log(error.message);
                    }
                );
            }
    
  15. RETRIEVE:
  16. The IF condition is written to just extract the recordId from Form. In general, the recordId is sent through function parameter.
  17.         static retrieveRecord(formContext, recordId) {
                if (recordId == null || recordId == "") {
                    recordId = formContext.data.entity.getId().replace("{", "").replace("}", "");
                }
                const recordResponse = Xrm.WebApi.retrieveRecord('new_collegestudent', recordId).then(
                    function success(result) {
                        var alertStrings = { confirmButtonLabel: "OK", text: "Record retrieved with ID: " + result.new_collegestudentid, title: "Message Box" };
                        var alertOptions = { height: 120, width: 260 };
                        Xrm.Navigation.openAlertDialog(alertStrings, alertOptions).then(
                            function (success) {
                                //console.log("Alert dialog closed");
                            },
                            function (error) {
                                //console.log(error.message);
                            }
                        );
                    },
                    function (error) {
                        console.log(error.message);
                    }
                );
            }
  18. RETRIEVE MULTIPLE:
  19. There are multiple ways to execute retrieveMultiple. We can pass fetchXML as an input parameter as well. We can pass array of recordIds to be fetched and can create fetchXML inside the function as well.
  20.         static retrieveMultipleRecords(formContext) {
                Xrm.WebApi.online.retrieveMultipleRecords('new_collegestudent').then(
                    function (success) {
                        if (success.entities.length > 0) {
                            var selectedRecords = [];
                            success.entities.forEach(function (singleRecord) {
                                selectedRecords.push({ singleRecord })
                            });
                            console.log(selectedRecords);
                        }
                        else {
                        }
                        var alertStrings = { confirmButtonLabel: "OK", text: " Multiple records (Total: " + success.entities.length + ") retrieved successfully.", title: "Message" };
                        var alertOptions = { height: 120, width: 260 };
                        Xrm.Navigation.openAlertDialog(alertStrings, alertOptions);
                    },
                    function (error) {
                        console.log(error);
                    }
                );
            }
  21. ASSOCIATE:
  22. Associate is a combination of retrieveMultiple and Associate. If you don't have the collection of records that need to be associated then you have to first execute the retrieveMultiple with filterXML and then execute Associate. If you have the collection of records that need to be associated then you can directly execute Associate. 
  23.         static associateRecords(formContext) {
                var relatedRecords = [];
                var selectedRecords = [];
                try {
                    const parentEntityLogicalName = formContext.data.entity.getEntityName();
                    const parentRecordId = formContext.data.entity.getId().replace("{", "").replace("}", "");
                    const selectedStream = formContext.getAttribute("new_collegestreamid").getValue();
                    const filterXML = '<fetch version="1.0" output-format="xml - platform" mapping="logical" distinct="false">' +
                        '<entity name="new_collegesubject">' +
                        '    <attribute name="new_collegesubjectid" />' +
                        '    <filter>' +
                        '        <condition attribute="new_collegestreamid" operator="eq" value="' + selectedStream[0].id.replace("{", "").replace("}", "") + '" />' +
                        '    </filter>' +
                        '</entity>' +
                        '</fetch >';
                    var filterOptions = (filterXML.length > 0) ? (`?fetchXml=${filterXML}`) : "";
                    Xrm.WebApi.online.retrieveMultipleRecords('new_collegesubject', filterOptions).then(
                        function (success) {
                            if (success.entities.length > 0) {
                                success.entities.forEach(function (singleRecord) {
                                    selectedRecords.push({ singleRecord })
                                });
                                if (selectedRecords.length > 0) {
                                    selectedRecords.forEach(function (singleRecord) {
                                        relatedRecords.push({
                                            "entityType": "new_collegesubject",
                                            "id": singleRecord.singleRecord.new_collegesubjectid
                                        });
                                    });
                                    const manyToManyAssociateRequest = {
                                        getMetadata: () => ({
                                            boundParameter: null,
                                            parameterTypes: {},
                                            operationType: 2,
                                            operationName: "Associate"
                                        }),
                                        relationship: "new_new_collegestudent_new_collegesubject",
                                        target: {
                                            "entityType": parentEntityLogicalName,
                                            "id": parentRecordId
                                        },
                                        relatedEntities: relatedRecords
                                    };
                                    Xrm.WebApi.online.execute(manyToManyAssociateRequest).then(
                                        (success) => {
                                            var alertStrings = { confirmButtonLabel: "OK", text: "Records associated successfully.", title: "Message" };
                                            var alertOptions = { height: 120, width: 260 };
                                            Xrm.Navigation.openAlertDialog(alertStrings, alertOptions);
                                            formContext.getControl("new_subgrid_collegesubject").refresh();
                                        },
                                        (error) => {
                                            Xrm.Navigation.openErrorDialog({ message: error.message });
                                        }
                                    );
                                }
    
                            }
                        },
                        function (error) {
                            console.log(error);
                        }
                    );
                }
                catch (error) {
                    Xrm.Navigation.openErrorDialog({ message: error });
                }
            }
    
  24. DISASSOCIATE:
  25.         static disassociateRecords(formContext, selectedRecords) {
                var relatedRecords = [];
                const parentEntityLogicalName = formContext.data.entity.getEntityName();
                const parentRecordId = formContext.data.entity.getId().replace("{", "").replace("}", "");
                try {
                    //if (selectedRecords.entities.length > 0) {
                    if (selectedRecords.length > 0) {
                        selectedRecords.forEach(function (singleRecord) {
                            const manyToManyDisassociateRequest = {
                                getMetadata: () => ({
                                    boundParameter: null,
                                    parameterTypes: {},
                                    operationType: 2,
                                    operationName: "Disassociate"
                                }),
                                relationship: "new_new_collegestudent_new_collegesubject",
                                target: {
                                    "entityType": parentEntityLogicalName,
                                    "id": parentRecordId
                                },
                                relatedEntityId: singleRecord //singleRecord[childEntityLogicalName + "id"]
                            };
                            relatedRecords.push(manyToManyDisassociateRequest);
                        });
                        Xrm.WebApi.online.executeMultiple(relatedRecords).then(
                            (success) => {
                                var alertStrings = { confirmButtonLabel: "OK", text: "Records disassociated successfully.", title: "Message" };
                                var alertOptions = { height: 120, width: 260 };
                                Xrm.Navigation.openAlertDialog(alertStrings, alertOptions);
                                formContext.getControl("new_subgrid_collegesubject").refresh();
                            },
                            (error) => {
                                Xrm.Navigation.openErrorDialog({ message: error.message });
                            }
                        );
                    }
                }
                catch (error) {
                    Xrm.Navigation.openErrorDialog({ message: error });
                }
            }
    
  26. SHARE:
  27. static shareRecord(formContext) {
        try {
            const recordId = formContext.data.entity.getId().replace("{", "").replace("}", "");
            const entityName = formContext.data.entity.getEntityName();
            const fieldName = entityName + "id";
            var target = {
                "new_collegestudentid": recordId,
                //put <other record type>id and Guid of record to share here
                "@odata.type": "Microsoft.Dynamics.CRM." + entityName
                //replace account with other record type
            };
    
            var principalAccess = {
                "Principal": {
                    "systemuserid": "7a172881-fdb3-4571-a7e5-b1f5bbb7850d",
                    //put teamid here and Guid of team if you want to share with team
                    "@odata.type": "Microsoft.Dynamics.CRM.systemuser"
                    //put team instead of systemuser if you want to share with team
                },
                "AccessMask": "WriteAccess"
                //full list of privileges is "ReadAccess, WriteAccess, AppendAccess, AppendToAccess, CreateAccess, DeleteAccess, ShareAccess, AssignAccess"
            };
    
            var parameters = {
                "Target": target,
                "PrincipalAccess": principalAccess
            };
    
            const clientURL = Xrm.Utility.getGlobalContext().getClientUrl();
            var req = new XMLHttpRequest();
            req.open("POST", clientURL + "/api/data/v9.0/GrantAccess", true);
            req.setRequestHeader("OData-MaxVersion", "4.0");
            req.setRequestHeader("OData-Version", "4.0");
            req.setRequestHeader("Accept", "application/json");
            req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
            req.onreadystatechange = function () {
                if (this.readyState === 4) {
                    req.onreadystatechange = null;
                    if (this.status === 204) {
                        //Success - No Return Data - Do Something
                    } else {
                        var errorText = this.responseText;
                        //Error and errorText variable contains an error - do something with it
                    }
                }
            };
            req.send(JSON.stringify(parameters));
        }
        catch (error) {
            console.log(error);
        }
    }
  28. ASSIGN:
  29. The correct version of API will be obtained from Settings >> Customizations >> Developer Resources >> Instance Web API
  30. static assignRecord(formContext) {
        try {
            const entityName = formContext.data.entity.getEntityName();
            const entityId = formContext.data.entity.getId().replace("{", "").replace("}", "");
            const assigneeId = "7a172881-fdb3-4571-a7e5-b1f5bbb7850d";
    
            var entity = {};
            entity["ownerid@odata.bind"] = "/systemusers(" + assigneeId + ")";
            //var impersonateUserId = "A734DA29-7993-DE11-BECE-005056B47DB0";// GUID of the system administrator
            var req = new XMLHttpRequest();
            req.open("PATCH", Xrm.Page.context.getClientUrl() + "/api/data/v9.2/" + entityName + "s" + "(" + entityId + ")", false);// my entity name is new_workitem
            req.setRequestHeader("OData-MaxVersion", "4.0");
            req.setRequestHeader("OData-Version", "4.0");
            req.setRequestHeader("Accept", "application/json");
            req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
            //req.setRequestHeader("MSCRMCallerID", impersonateUserId);
            req.onreadystatechange = function () {
                if (this.readyState === 4) {
                    req.onreadystatechange = null;
                    if (this.status === 204) {
                        //Success - No Return Data - Do Something
                    } else {
                        Xrm.Utility.alertDialog(this.error);
                    }
                }
            };
            req.send(JSON.stringify(entity));
        }
        catch (error) {
            console.log(error);
        }
    }