One thing that was always obvious in the Dynamics 365 world that until now wasn’t clear (to me at least) in the Power App world was how to give your Application a sensible and memorable URL that wasn’t org followed by a random set of digits such as org123653dd.
Last night, trying to work out how to do something else, I discovered how to do it so below is a step by step guide.
Step 2 Click the environment you want to customise
Step 3 Update the Url (and the name if you desire) to something more sensible
If the choosen Url is already taken you will see an error message like the one below
so just move on to the next appropriate name until you find one that hasn’t already been taken at which point the form will save and you will return to the previous screen
And your environment will now have a far more user friendly and professional name that you can share with your co-workers.
One thing I’ve been waiting a while for is the functionality that allows an entity record (say Opportunity Close) to be opened as a modal window in front of the current window.
Now I know that I could already do this with a Quick Create Form but that results in the Entity appearing in the Quick Create list which may not be the desired result if you want to use the entity to close a task or process (say on Case close or Opportunity won) rather than starting a new task.
The 2020 wave 1 April / May release finally offers the option with Xrm.Navigation.navigateTo, but as it differs in a couple of ways from the old Xrm.Navigation.OpenForm command and has a nasty context issue that will catch you out, it’s worth going over it in more details.
and because it’s a generic command you can play with this yourself on any entity form by opening your browser’s developer console, and entering the following command.
And as this is a modal window you can only access the contact record, the account record beneath the record is inaccessible. Only when you close the window can you edit the old record.
Expanding and closing
The modal window offers 2 right hand navigation options, one that maximizes / minimizes the modal window size, the other closes the window.
navigateTo has two input parameters, a pageInput object that determines what will be displayed, and a navigationOptions object that determines how and where it will be displayed.
pageInput
the pageInput object uses the following attributes:
pageType
A required String either “entitylist”, “entityrecord”, “webresource”
entityName
The logical name of the type of entity the entitylist or entityrecord will be displaying
entityId
The id / guid of the entityId to be displayed *if you want to show an existing record. The Guid is without brackets {}.
data
A dictionary object of any fields you wish to prepopulate as you create the new record. For instance {hdn_closetype:”lost”}. This is identical the form parameters that were passed separately when using Xrm.Navigation.openForm.
createFromEntity
allows you to pass in a Lookup to prepopulate the new record with the mapped attribute values specified in the relationship between the lookup and the new record. The lookup object has 3 attributes, entityType, id and name (optional). While the name field is optional where possible you should pass it within the object as otherwise the system will display a default value which may cause confusion.
Target is a required (number) field that offers two options:
A value of 1 opens the form inline (replacing the current page), 2 opens the form in a dialog -which is what we require for our purposes.
Note: web resources and entity forms can be opened both inline (target:1} and as a dialog {type: 2} but entity lists can only be opened as an inline resource.
One of the catches we have discovered will show why later.
Position
Only used for Dialog windows (target:2}. 1 opens the dialog in the center, 2 opens it to the right (same as a Quick create form).
Size
Width and optionally height can be set using a SizeValue type {value: Number, unit: string either “%” or “px”}
Return Values
As with most functions in the Xrm library Xrm.Navigation.navigateTo returns a promise. When the return is triggered depends on the target of the window, for inline targets {target:1} (replacing the existing page) the promise is resolved immediately, for dialog targets {target: 2} (modal window / dialog on top of the existing window) the promise is resolved when the dialog is closed.
If an entityrecord is opened in a dialog in create mode the promise will receive a savedEntityReference array to identify the created record.
var pageInput = {
pageType: "entityrecord",
entityName: "account"
};
var navigationOptions = {
target: 2,
position: 1,
height: {value: 80, unit:"%"},
width: {value: 70, unit:"%"}
};
Xrm.Navigation.navigateTo(pageInput, navigationOptions).then(
function success(result) {
console.log("Record created with ID: " + result.savedEntityReference[0].id + " Name: " + result.savedEntityReference[0].name)
// Handle dialog closed
},
function error() {
// Handle errors
}
);
Resolving the promise is actually more important than you would expect as issues with context I’ve discovered below will reveal.
The issue of Context
The new functionality is sadly not perfect. As I’ve been playing with the functionality, I have encountered two separate issues connected with the system’s awareness of current context which could cause problems.
Adding a second record
Clicking New in the modal window may not do what you think it will do. It doesn’t as you might expect reopen open a fresh form in the modal window it instead opens it in the main form overriding what was previously there.
Context
The second issue requires a tiny bit of background knowledge, that D365 / powerapps remembers in the background the current entity / entity Type it is dealing with.
Now that has never previously been an issue as there has only ever been 1 core entity on the screen but now you can add a second (or even a third, by using both positions) entity it’s no longer so clear cut and the newly created / saved record will override the information in the background.
The video below shows a quick example of the issue – as the contact record is saved the context is switched from account to contact as pressing F5 in this video shows.
F5 form refresh doesn’t work
As you can see upon saving the contact form in the dialog window the application context switched from the account record you half expect it to be to the contact record that had been closed.
To avoid this and return the context to what the user is probably expecting (the account record) you need to refresh / reload the account form within the success function of the promise.
The code sample below does exactly that. As part of the initialisation of the dialog window we create a separate object containing the account information so that when the dialog is successfully closed we can refresh / reload the account window and the context is restored.
this.resolveCase = function(context){
var id=context.data.entity.getId().replace(/[{}]/g,"");
var ef = {};
ef["pageType"]= "entityrecord";
ef["entityName"] = "contact"; ef["formType"]=2;
ef["cmdbar"]=false;
var createFrom ={};
createFrom ["entityType"]= "account";
createFrom ["id"]= id;
createFrom ["name"] = context.getAttribute("name").getValue();
ef["createFromEntity"]=createFrom;
var es = {};
es["entityName"] = "accounbt";
es["entityId"]=id;
Xrm.Navigation.navigateTo(ef, { target: 2, position: 1, height:{value: 600, unit:"px"}, width: {value: 500, unit:"px"}}).then(
function(){Xrm.Navigation.openForm(es)});
};
Back in August/ September 2019 when Microsoft announced the license changes to Dynamics 365 and the Power Platform as part of the wave 2 2019 release Microsoft also announced changes to the list of restricted entities that require a full Dynamics 365 license.
Annoyingly 5 months later that list of restricted entities has still not been updated which is fine until a customer asks, “Can we use the product entity outside of Dynamics” and you disappear off to answer the question only to discover there is no answer.
So last week we decided to do an experiment and see if it is possible to identify the entities that exist in Dynamics 365 but do not exist in freshly created Common Data Service / Power Platform application environment.
This post is the first of a number of posts listing Dynamics 365 entities that don’t exist in non Dynamics 365 / standard Power Platform environments.
For convenience, we are going to display things on a Solution by Solution basis. So here are the Sales related Entities in the msdynce_Sales solution.
D365 Sales Entities not in available in PowerApps
Entity Name
Entity System Name
Notes
Competitor
Competitor
?
Competitor Address
CompetitorAddress
Helper Entity
Competitor Product
CompetitorProduct
Helper Entity
Competitor Sales Literature
CompetitorSalesLiterature
Helper Entity
Contact Invoices
ContactInvoices
Hidden
Contact Orders
ContactOrders
Hidden
Contact Quotes
ContactQuotes
Hidden
Customer Opportunity Role
CustomerOpportunityRole
Helper Entity
Discount
Discount
?
Discount Type
Discount Type
Helper Entity
Invoice
Invoice
Probably Restricted
Invoice Detail
InvoiceDetail
Probably Restricted
Lead
Lead
Probably Restricted
Lead Competitors
LeadCompetitors
Helper Entity
Lead Product
LeadProduct
Probably Restricted (join on two entities that are both probably restricted)
Opportunity
Opportunity
Probably Restricted
Opportunity Close
Opportunity Close
tied to opportunity
Opportunity Competitors
Opportunity Competitors
tied to opportunity entity
Opportunity Product
OpportunityProduct
Probably Restricted (there is lot of business logic behind here)
Order Close
OrderClose
tied to Sales Order entity
Product Price Level
ProductPriceLevel
Probably restricted (join on two entities that are both probably restricted)
Product Sales Literature
ProductSalesLiterature
?
Quote
Quote
Probably restricted
Quote Close
QuoteClose
tied to Quote entity
Quote Detail
QuoteDetail
Probably restricted (there is lot of business logic behind here)
Sales Literature
SalesLiterature
??
Sales Literature Item
SalesLiteratureItem
??
Sales Order
SalesOrder
Probably Restricted
Sales Order Details
SalesOrderDetail
Probably Restricted (there is lot of business logic behind here)
So that’s the Sales side of things – where there are a number of likely entities due to the business logic that occurs behind them.
Next I will move on to the product side of things before looking at Marketing, then customer service and finally some other interesting bits and pieces.
And yes there should be a sales pitch here but I will leave that until everything else is set up and ready to go.
When Microsoft introduced the WebAPI call limits for Dynamics 365 and the Power Platform back in October last year we started to look for approaches that allow you to reduce the number of calls we made to the webAPI services within the Common Data Service.
Microsoft have actually done a lot of work in this area by using etags within their results and ensuring their client side api (XRM.Webapi utilises it to minimise the number of api calls required) which works wonders on a record level but doesn’t help when you need to run queries to gather results instead of retrieving individual records.
And our Licence management software (License Power) needs to run queries as the standard question is “does this environment / instance have a vaild license for this solution called XYZ?” Which means that every time we needed to answer that question we would need to run a query and that would eat into the 5,000 – 20,000 requests a user is allowed every day.
To fix that the only solution we had to find a way that we could avoid running retrieveMultiple requests and replace them with standard retrieve requests so we disappeared to google and found a suitable solution and we found it by using localStorage within the browser.
Now I could spend a lot of time talking about localStorage and it’s strengths and weaknesses – but that is for another day. What is worth saying is that Dynamics makes full use of it to reduce download times after first installation and you should never store anything secure within it. However that isn’t an issue here as I don’t want to store anything secure I just need to know that the record (license) I want to check exists and what the GUID of the record is.
Using LocalStorage
So as you might have guessed from reading the previous paragraph by using local storage we can store the results of a previous query and use it as the basis of the subsequent query. Which means that in our case, after the initial retrieveMultiple request with the returned GUID stored in localStorage – I can access the GUID from local storage as required and use the caching within Xrm.WebApi.retrieve to retrieve the record in when required at no cost due to Xrm.WebApi’s caching functionality.
So without further ado the sample code.
var item= localStorage.getItem(license);
if (item===null || !item.id ){
// we need to get the record from the system
Xrm.WebApi.retrieveMultipleRecords("hdn_license", "?$select=hdn_licensexml,hdn_expirydate,hdn_licenseid&$filter=hdn_product eq '"+license+"'").then(
function success(result) {
if (result.entities.length>0){
let cache={
id: result.entities[0].hdn_licenseid,
etag: result.entities[0]["@odata.etag"]
};
localStorage.setItem(license, JSON.stringify(cache) );
nextStage(result.entities[0]);
}
else {
// something has gone wrong here.
}
},
function(error) {
console.log("Error: " + error.message);
}
);
}
else {
// this is the retrieve version
Xrm.WebApi.retrieveRecord("hdn_license", item, "?$select=hdn_licensexml,hdn_expirydate,hdn_licenseid").then(
function success(result) {
//ready to call the next stage
if (storedrecord.etag===result["@odata.etag"]){}
else {
let cache={
id: result.hdn_licenseid,
etag: result["@odata.etag"]
};
localStorage.setItem(license, JSON.stringify(cache) );
// you may wish to do other things here if say child records are cached against this parent record
}
nextStage(result.entities[0]);
},
function(error) {
// invalidate the cache as somethings gone wrong
localStorage.removeItem(license);
console.log("Error: " + error.message);
}
);
}
And finally, a quick explanation:-
The code checks for a stored item in the localStorage of the browser. If no stored record exists, the code calls retrievemultiple (API cost involved) retrieves the record and caches the information for the next time it’s required. If a local stored record does exist, we use the information contained within it, to retrieve the record using the stored GUID via a retrieve request. This should be at no cost as between the browser and the logic built into Xrm.Webapi and the Power Platform as a whole, the record will be retrieved from a cache instead of requiring a database call.
About Us
This blog is the journal for information from the CRM Power set of businesses. They consist of:-
Process Power is the consulting side of our business. We offer support on Dynamics 365 CE and Power Platform applications including fixed priced development, cloud readiness and license cost cutting reviews.
CRM Power is or slim downed CRM solution built on the Power Platform for companies who want simple Sales and Customer Service systems without all the complexity that Dynamics 365 imposes. It’s also far cheaper saving $1000 per user per year compared to Dynamics 365 Enterprise Edition.
License Power is our License Management system that allows ISVs and partners to offer hassle free trials of their software and utilise the work of other developers within their own solutions
Recent Comments