Looking for PowerObjects? Don’t worry, you’re in the right place! We’ve been part of HCL for several years, and we’ve now taken the final step in our acquisition journey: moving our website to the HCL domain. Nothing else is changing – we are still fanatically focused on Microsoft Business Applications!

PowerObjects Blog 

for Microsoft Business Applications

|

One PowerApp in Multiple Dynamics 365 Entities

Post Author: Joe D365 |

Have you had to come up with a solution to display a single PowerApps-based app in multiple Dynamics 365 forms? Currently, you cannot use the same app you embed using the Dynamics 365 Form control (new App ID is created during this process) in another Dynamics 365 entity. So, if you need to use the same app and make it editable in other entities, there’s no easy solution. In today’s blog, we will cover how you can use Dynamics 365’s embedding control along with iFrame and Java Script to accomplish this. At the end, we will cover why you should use Common Data Service (CDS) Connector over Dynamics 365 Connector for the PowerApps.

Embedding Power Apps using Dynamics 365 Control is simple – just follow these steps:

1. Click Set Properties for the Field to which you want to add the Power Apps control.

2. On the Controls tab, click Add Control and select Canvas app (preview).

3. Check the Web box.

4. Select Customize.

NOTE: Power Apps control currently works only for a Single Line of Text field.

For more information on Embedded PowerApps, visit: https://powerobjects.com/how-to-embed-powerapps-in-crm-without-writing-code/.

To embed Power Apps as an iFrame on an entity form, follow these steps:

1. Select Insert from the main Tab.

2. Select IFRAME.

3. Provide the Name and URL.

For more information of iFrame, visit: https://docs.microsoft.com/en-us/powerapps/maker/model-driven-apps/iframe-properties-legacy

See below to copy JavaScript. In this JavaScript, you’ll notice the code is registered on Tab state change on the below entities:

1. Accounts (Primary Entity)

2. Opportunities

3. Leads

4. Phone Calls

5. Appointment

/*This code is registered on Tab state change on these entities:

Accounts
Opportunities
Leads
Phone Calls
Appointment

*/
function nameofthefunction(executionContext) {
var formContext = executionContext.getFormContext();
var appUrl = getPowerAppUrl(formContext);
var entityName = formContext.data.entity.getEntityName();

//if statement used to return accound id for records directly associated to an account, OR the else statement uses a .then() promise to make a retrieverecord call first
if(entityName === "account" || entityName === "lead" || entityName === "opportunity"){
var recordID;
recordID = getAccountID(entityName, formContext);
//console.log('in nonPROMISE');

//if account is not null do a thing

//console.log('recordID', recordID);

if (recordID != null) {
//console.log('in success');
/* do something with the result */

//Get the default URL for the IFRAME, which includes the
// query string parameters
var IFrame = formContext.ui.controls.get("IFRAME_nameoftheIFrame");
var newTarget = appUrl + "&AccountID=" + recordID;
// Use the setSrc method so that the IFRAME uses the
// new page with the existing parameters
//console.log('newTarget:', newTarget);
IFrame.setSrc(newTarget);
} else {
//if account is null, throw an error
//console.log('in reject');

};

} else{
//else statement for activities (phone call and appointment)
//check if regardingobjectid contains a value so it doesn't throw a JS error in the browser if no parent exists
//console.log('test', formContext.getAttribute("regardingobjectid").getValue());

if(formContext.getAttribute("regardingobjectid").getValue() != null){
//else statement uses .then() promise for processes that require a retrieverecord call
getAccountID(entityName, formContext).then(function(recordID){

//console.log('in mypromise');

//if account is not null do a thing

//console.log('recordID', recordID);

if (recordID != null) {
//console.log('in mypromise success');
/* do something with the result */

//Get the default URL for the IFRAME, which includes the
// query string parameters
var IFrame = formContext.ui.controls.get("IFRAME_nameoftheIFrame");
var newTarget = appUrl + "&AccountID=" + recordID;
// Use the setSrc method so that the IFRAME uses the
// new page with the existing parameters
//console.log('newTarget:', newTarget);
IFrame.setSrc(newTarget);
} else {
//if account is null, throw an error
//console.log('in mypromise reject');
};
});
};
};
}

///////////////////////// getAccountID /////////////////////////////
//directly returns the account id for account records or any record directly associated to the account
//will also return the account id for any activity records that are another step away from the account through getAccountFromActivity() (such as a phone call or appointment on a lead that lookups up to an account)
function getAccountID(entityName, formContext) {
var value;

switch (entityName) {
//Non-Activity Entities
case "account":
//does NOT require promise via getAccountFromActivity()
value = formContext.data.entity.getId();

if (value != null) {
value = cleanGUID(value);
return value;
}
break;

case "opportunity":
//does NOT require promise via getAccountFromActivity()
getAccountFromActivity()
//if account is not selected, avoid JS error
if(formContext.getAttribute("parentaccountid").getValue() != null){
value = formContext.getAttribute("parentaccountid").getValue()[0].id;
value = cleanGUID(value);
return value;
}
break;

case "lead":
//does NOT require promise via getAccountFromActivity()
getAccountFromActivity()
//if account is not selected, avoid JS error
if(formContext.getAttribute("accountid").getValue() != null){
value = formContext.getAttribute("accountid").getValue()[0].id;
value = cleanGUID(value);
return value;
}
break;

case "phonecall":
case "appointment":
//Activity Entities
//REQUIRES promise via getAccountFromActivity()
regardingId = cleanGUID(formContext.getAttribute("regardingobjectid").getValue()[0].id);
entityName = formContext.getAttribute("regardingobjectid").getValue()[0].entityType;
value = getAccountFromActivity(regardingId, entityName, formContext);
return value;
}
//return the account ID
return value;

}

///////////////////////// cleanGUID /////////////////////////////
//Pass an object to this function.
//It will get the formatted GUID,strip the braces and lowercase all apha chars
function cleanGUID(dirtyGUID) {

if (dirtyGUID != null) {
cleanValue = dirtyGUID.replace("{", "").replace("}", "").toLowerCase();
//console.log('cleanValue', cleanValue);

}
return cleanValue;
}

///////////////////////// getPowerAppUrl /////////////////////////////
//Based on the org, direct the user to the right PowerApp
function getPowerAppUrl(formContext) {

powerAppsBase = "https://web.powerapps.com/webplayer/iframeapp?source=iframe&appId=/providers/Microsoft.PowerApps/apps/";
url = formContext.getUrl();

if (url && url != "") {

if (url.search("orgname1") > 0) return powerAppsBase + "appId1";
else if (url.search("orgname2") > 0) return powerAppsBase + "appID2";
else if (url.search("orgname3") > 0) return powerAppsBase + "appID3";
else if (url.search("orgname4") > 0) return powerAppsBase + "appID4";
else if (url.search("orgname5") > 0) return powerAppsBase + "appID5";
}
}

///////////////////////// getAccountID /////////////////////////////
//returns account id for activity records
function getAccountFromActivity(regardingId, regardingEntityType, formContext){
var accountId;
return new Promise(function (resolve, reject) {
//promise allows retrieverecord web call to call back before the rest of the code runs
if (regardingEntityType != null) {
//switch implemented here because there are different fields
//that we need to look at to get an Account ID depending
//on the entity that is in an Activity's 'regarding' field.
switch (regardingEntityType) {
case "account":
resolve(regardingId);
break;

case "lead":
//console.log('regardingId', regardingId);
Xrm.WebApi.retrieveRecord("lead", regardingId, "?$select=leadid,_accountid_value").then(
function success(result) {
//console.log('expanded query result', result);
accountId = result["_accountid_value"];
//resolves the .then() promise
resolve(accountId);
},
function(error) {
Xrm.Utility.alertDialog(error.message);
}
);
break;

case "opportunity":
Xrm.WebApi.retrieveRecord("opportunity", regardingId, "?$select=_parentaccountid_value").then(
function success(result) {
accountId = result["_parentaccountid_value"];
resolve(accountId);
},
function(error) {
Xrm.Utility.alertDialog(error.message);
}
);
break;

case "contact":
Xrm.WebApi.retrieveRecord("contact", regardingId, "?$select=_parentcustomerid_value").then(
function success(result) {
accountId = result["_parentcustomerid_value"];
resolve(accountId);
},
function(error) {
Xrm.Utility.alertDialog(error.message);
}
);
break;
}
}
})
}

// Displays Tab only if Type = Blog Activity
function showHideActivityTab(executionContext)
{
var formContext = executionContext.getFormContext();
activityType = formContext.getAttribute("typecode").getText();
if (activityType != undefined
&& activityType != null){
if(activityType == "Blog Activity"){
formContext.ui.tabs.get("nameyouset").setVisible(true);
}
else if(activityType !== "Blog Activity"){
formContext.ui.tabs.get("nameyouset").setVisible(false);
}
}
}

Once the JavaScript is ready, add iFrames in the tabs on which you want to display the PowerApps. To call a function from your JavaScript on Tab change, follow the steps below:

1. In Tab Properties, select Events.

2. Add your JavaScript from the Library by clicking +.

3. Double-click the JavaScript to open Handler Properties.

4. Call the Function from your JavaScript.

5. Check the Enabled and Pass execution context as first parameters boxes.

6. Save and Publish.

Now that everything is ready to go, let’s discuss why you would want to use Common Data Service Connector over Dynamics 365 Connector.

If you use Dynamics 365 Connector within the same tenant and different orgs, every time you export/import the app in another environment, you will need to delete the existing data sources and re-add the data sources so that the app can connect to the correct environment’s data source. If you are importing the Power Apps from one tenant to another tenant, it automatically connects to that environment’s data sources.

On the other hand, if you are using Common Data Service (CDS) Connector within the same tenant and different orgs, you do not need to delete the existing data sources after you import the app. CDS has a feature called Current environment that, upon import of the app in a different environment, uses the data source from the current environment.

Be sure to subscribe to our blog for more Power Apps tips and tricks.

Happy Dynamics 365’ing!

Joe CRM
By Joe D365
Joe D365 is a Microsoft Dynamics 365 superhero who runs on pure Dynamics adrenaline. As the face of PowerObjects, Joe D365’s mission is to reveal innovative ways to use Dynamics 365 and bring the application to more businesses and organizations around the world.

PowerObjects Recommends