Hello everyone,
what is your experience with JavaScript in PowerApps? Where is the best place to put my code, in which scenario? My goal is not to have to paste the same code twice.
Example 1: Business Process Flows
Can you store code globally for a BPF? E.g. to show or hide the create button (https://docs.microsoft.com/en-us/power-apps/developer/model-driven-apps/clientapi/reference/formcont... ).
Example 2: columns/form fields
Multi-selection option fields are not supported by business rules. However, you can implement the desired rule with JavaScript. Is there a way to assign this code to the appropriate column so that it is active in every form.
Alternatively, the code of the two examples would have to be stored in every form that works with the BPF or the column.
I look forward to feedback
Nico
Solved! Go to Solution.
Helper Script:
// helper functions
'use strict';
var cdmtHelper = window.cdmtHelper || {};
(function () {
//required for refreshing ESRI map
// refresh web resource
this.refreshIframe = function (executionContext, controlName) {
var formContext = executionContext.getFormContext();
var control = formContext.getControl(controlName);
if (control !== null) {
var iFrame = control.getObject();
if (iFrame !== null) {
iFrame.contentDocument.location.reload(true);
}
}
};
//required for Map.HTML
// get data parameter from query string, e.g. in HTML web resource
this.getDataParameter = function () {
return decodeURIComponent(cdmtHelper.getUrlParameter('data'));
};
//required for Map.HTML
// get parameter from URL
this.getUrlParameter = function (name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
var results = regex.exec(location.search);
return results === null
? ''
: decodeURIComponent(results[1].replace(/\+/g, ' '));
};
//required for Map.HTML
// function to fetch records recursively in batches
this.retrieveMultipleRecordsRecursive = function (
webApi,
entityLogicalName,
options,
maxPageSize
) {
var totalEntities = [];
return new Promise(function (resolve, reject) {
var resolution = function (result) {
// add current batch to the total array
totalEntities = totalEntities.concat(result.entities);
// if more results to be retrieved
if (result.nextLink) {
var nextLinkOptions = "?" + result.nextLink.split("?")[1];
cccHelper
.retrieveMultipleRecordsRecursive(
webApi,
entityLogicalName,
nextLinkOptions,
maxPageSize
)
.then(resolution, reject);
}
// if no more results to be retrieved
else {
resolve({ entities: totalEntities });
}
};
webApi
.retrieveMultipleRecords(
entityLogicalName,
options,
maxPageSize
)
.then(resolution, reject);
});
};
//required for Create Document from Template feature
this.sendHttpRequestPromise = function (url, data, method, headers) {
return new Promise(function (resolve, reject) {
if (method === null || method === undefined) {
method = 'GET';
}
if (headers === null || headers === undefined) {
headers = [['Accept', 'application/json']];
}
var req = new XMLHttpRequest();
req.open(method, url, true);
// set request headers
for (var headerName in headers) {
if (headers.hasOwnProperty(headerName)) {
req.setRequestHeader(headerName, headers[headerName]);
}
}
req.onreadystatechange = function () {
// if complete
if (req.readyState === 4) {
// if 200 OK
if (req.status === 200) {
resolve(req);
}
// if 204 No Content
else if (req.status === 204) {
resolve(req);
}
// otherwise
else {
reject(req);
}
}
};
req.send(data);
});
};
this.executeFlowFromEV = function (varName, recordID, progressMessage) {
return new Promise((resolve, reject) => {
Xrm.Utility.showProgressIndicator(progressMessage);
cdmtHelper.getEnvironmentVariable(varName).then(
function (flowUrl) {
cdmtHelper.executeFlow(flowUrl, recordID).then(
function () {
Xrm.Utility.closeProgressIndicator();
resolve();
},
function () {
Xrm.Utility.closeProgressIndicator();
reject();
}
);
},
function () {
Xrm.Utility.closeProgressIndicator();
reject();
}
);
});
};
this.executeFlow = function (flowUrl, recordID) {
'use strict';
//do something here
return new Promise((resolve, reject) => {
const input = JSON.stringify({
id: recordID.replace('{', '')?.replace('}', ''),
});
const req = new XMLHttpRequest();
req.open('POST', flowUrl, true);
req.setRequestHeader('Content-Type', 'application/json');
req.send(input);
req.onreadystatechange = function () {
'use strict';
if (this.readyState === 4) {
req.onreadystatechange = null;
//if response is 200
if (this.status === 200) {
resolve();
} else {
reject();
}
}
};
});
};
// Returns a promise to retrieve the given environment variable's value
this.getEnvironmentVariable = function (varName, currentValue) {
return new Promise(function (resolve, reject) {
// If value has been retrieved already
if (currentValue !== undefined) {
// Don't bother retrieving again
resolve(currentValue);
} else {
// Retrieve environment variable record
Xrm.WebApi.retrieveMultipleRecords(
'environmentvariabledefinition',
`?$top=1&fetchXml=<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='true'>
<entity name='environmentvariabledefinition'>
<attribute name='defaultvalue' />
<filter type='and'>
<condition attribute='schemaname' operator='eq' value='${varName}' />
</filter>
<link-entity name='environmentvariablevalue' from='environmentvariabledefinitionid' to='environmentvariabledefinitionid' link-type='outer' alias='varvalue'>
<attribute name='value' />
</link-entity>
</entity>
</fetch>`
).then(function (result) {
if (result.entities.length === 0) {
reject(`Environment variable "${varName}" not found.`);
} else {
var environmentVariable = result.entities[0];
if (typeof environmentVariable['varvalue.value'] !== 'undefined') {
resolve(environmentVariable['varvalue.value']);
} else if (
typeof environmentVariable.defaultvalue !== 'undefined'
) {
resolve(environmentVariable.defaultvalue);
} else {
reject(
`Environment variable "${varName}" does not contain a value.`
);
}
}
}, reject);
}
});
};
//function to open a form as a dialog
this.openDialog = function (
FirstSelectedItemId,
SelectedEntityTypeName,
HeightValue,
WidthValue,
PostionValue
) {
const pageInput = {
pageType: 'entityrecord',
entityName: SelectedEntityTypeName, //uses the entiyty type of the record selected in your subgrid
entityId: FirstSelectedItemId, //uses the id of the record you have selected
};
const navigationOptions = {
target: 2, //show on top of current record
height: { value: HeightValue, unit: '%' }, //controls hieght of dialog
width: { value: WidthValue, unit: '%' }, //controls width of dialog
position: PostionValue, //1 = centre of screen, 2 = right of screen
};
Xrm.Navigation.navigateTo(pageInput, navigationOptions);
};
}.call(cdmtHelper));
Form Script:
function cancelSlot(primaryControl, environmentVariableName) {
const formContext = primaryControl;
const recordId = formContext.data.entity
.getId()
.replace('{', '')
.replace('}', '');
cdmtHelper
.executeFlowFromEV(
environmentVariableName,
recordId,
'Slot cancellation in progress...'
)
.then(
function () {
const alertStrings = {
confirmButtonLabel: 'OK',
text: 'Slot Cancellation Successful',
title: 'Information',
};
const alertOptions = { height: 120, width: 260 };
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions).then(
function confirmButtonPressed() {
formContext.data.refresh(true);
},
function dialogClosed() {
formContext.data.refresh(true);
}
);
},
function () {
const alertStrings = {
confirmButtonLabel: 'OK',
text: 'Slot Cancellation Failed',
title: 'Information',
};
const alertOptions = { height: 120, width: 260 };
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions).then(
function confirmButtonPressed() {
formContext.data.refresh(true);
},
function dialogClosed() {
formContext.data.refresh(true);
}
);
}
);
}
Basically the helper script holds my reusable code, the form scipt calls that piece of code, and passes in parameters required for the function to run
I use a helper script to hold my commonly used functions.
For example I have a script that sends a HTTP request, another that shows/hides a section based on a defined criteria, and other functions that I use in multiple places.
I then create a function in whichever relevant location(s) to simply call the function from my helper script and pass in the required inputs
Hopefully this makes sense, if not, let me know and I'll share some screenshots of the helper script, and an example function to call one of the functions from the helper script
Many thanks for your response.
I would be very happy to see more details (screenshots, examples) of your solution.
I will upload an example later today, most likely on my lunch break
Helper Script:
// helper functions
'use strict';
var cdmtHelper = window.cdmtHelper || {};
(function () {
//required for refreshing ESRI map
// refresh web resource
this.refreshIframe = function (executionContext, controlName) {
var formContext = executionContext.getFormContext();
var control = formContext.getControl(controlName);
if (control !== null) {
var iFrame = control.getObject();
if (iFrame !== null) {
iFrame.contentDocument.location.reload(true);
}
}
};
//required for Map.HTML
// get data parameter from query string, e.g. in HTML web resource
this.getDataParameter = function () {
return decodeURIComponent(cdmtHelper.getUrlParameter('data'));
};
//required for Map.HTML
// get parameter from URL
this.getUrlParameter = function (name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
var results = regex.exec(location.search);
return results === null
? ''
: decodeURIComponent(results[1].replace(/\+/g, ' '));
};
//required for Map.HTML
// function to fetch records recursively in batches
this.retrieveMultipleRecordsRecursive = function (
webApi,
entityLogicalName,
options,
maxPageSize
) {
var totalEntities = [];
return new Promise(function (resolve, reject) {
var resolution = function (result) {
// add current batch to the total array
totalEntities = totalEntities.concat(result.entities);
// if more results to be retrieved
if (result.nextLink) {
var nextLinkOptions = "?" + result.nextLink.split("?")[1];
cccHelper
.retrieveMultipleRecordsRecursive(
webApi,
entityLogicalName,
nextLinkOptions,
maxPageSize
)
.then(resolution, reject);
}
// if no more results to be retrieved
else {
resolve({ entities: totalEntities });
}
};
webApi
.retrieveMultipleRecords(
entityLogicalName,
options,
maxPageSize
)
.then(resolution, reject);
});
};
//required for Create Document from Template feature
this.sendHttpRequestPromise = function (url, data, method, headers) {
return new Promise(function (resolve, reject) {
if (method === null || method === undefined) {
method = 'GET';
}
if (headers === null || headers === undefined) {
headers = [['Accept', 'application/json']];
}
var req = new XMLHttpRequest();
req.open(method, url, true);
// set request headers
for (var headerName in headers) {
if (headers.hasOwnProperty(headerName)) {
req.setRequestHeader(headerName, headers[headerName]);
}
}
req.onreadystatechange = function () {
// if complete
if (req.readyState === 4) {
// if 200 OK
if (req.status === 200) {
resolve(req);
}
// if 204 No Content
else if (req.status === 204) {
resolve(req);
}
// otherwise
else {
reject(req);
}
}
};
req.send(data);
});
};
this.executeFlowFromEV = function (varName, recordID, progressMessage) {
return new Promise((resolve, reject) => {
Xrm.Utility.showProgressIndicator(progressMessage);
cdmtHelper.getEnvironmentVariable(varName).then(
function (flowUrl) {
cdmtHelper.executeFlow(flowUrl, recordID).then(
function () {
Xrm.Utility.closeProgressIndicator();
resolve();
},
function () {
Xrm.Utility.closeProgressIndicator();
reject();
}
);
},
function () {
Xrm.Utility.closeProgressIndicator();
reject();
}
);
});
};
this.executeFlow = function (flowUrl, recordID) {
'use strict';
//do something here
return new Promise((resolve, reject) => {
const input = JSON.stringify({
id: recordID.replace('{', '')?.replace('}', ''),
});
const req = new XMLHttpRequest();
req.open('POST', flowUrl, true);
req.setRequestHeader('Content-Type', 'application/json');
req.send(input);
req.onreadystatechange = function () {
'use strict';
if (this.readyState === 4) {
req.onreadystatechange = null;
//if response is 200
if (this.status === 200) {
resolve();
} else {
reject();
}
}
};
});
};
// Returns a promise to retrieve the given environment variable's value
this.getEnvironmentVariable = function (varName, currentValue) {
return new Promise(function (resolve, reject) {
// If value has been retrieved already
if (currentValue !== undefined) {
// Don't bother retrieving again
resolve(currentValue);
} else {
// Retrieve environment variable record
Xrm.WebApi.retrieveMultipleRecords(
'environmentvariabledefinition',
`?$top=1&fetchXml=<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='true'>
<entity name='environmentvariabledefinition'>
<attribute name='defaultvalue' />
<filter type='and'>
<condition attribute='schemaname' operator='eq' value='${varName}' />
</filter>
<link-entity name='environmentvariablevalue' from='environmentvariabledefinitionid' to='environmentvariabledefinitionid' link-type='outer' alias='varvalue'>
<attribute name='value' />
</link-entity>
</entity>
</fetch>`
).then(function (result) {
if (result.entities.length === 0) {
reject(`Environment variable "${varName}" not found.`);
} else {
var environmentVariable = result.entities[0];
if (typeof environmentVariable['varvalue.value'] !== 'undefined') {
resolve(environmentVariable['varvalue.value']);
} else if (
typeof environmentVariable.defaultvalue !== 'undefined'
) {
resolve(environmentVariable.defaultvalue);
} else {
reject(
`Environment variable "${varName}" does not contain a value.`
);
}
}
}, reject);
}
});
};
//function to open a form as a dialog
this.openDialog = function (
FirstSelectedItemId,
SelectedEntityTypeName,
HeightValue,
WidthValue,
PostionValue
) {
const pageInput = {
pageType: 'entityrecord',
entityName: SelectedEntityTypeName, //uses the entiyty type of the record selected in your subgrid
entityId: FirstSelectedItemId, //uses the id of the record you have selected
};
const navigationOptions = {
target: 2, //show on top of current record
height: { value: HeightValue, unit: '%' }, //controls hieght of dialog
width: { value: WidthValue, unit: '%' }, //controls width of dialog
position: PostionValue, //1 = centre of screen, 2 = right of screen
};
Xrm.Navigation.navigateTo(pageInput, navigationOptions);
};
}.call(cdmtHelper));
Form Script:
function cancelSlot(primaryControl, environmentVariableName) {
const formContext = primaryControl;
const recordId = formContext.data.entity
.getId()
.replace('{', '')
.replace('}', '');
cdmtHelper
.executeFlowFromEV(
environmentVariableName,
recordId,
'Slot cancellation in progress...'
)
.then(
function () {
const alertStrings = {
confirmButtonLabel: 'OK',
text: 'Slot Cancellation Successful',
title: 'Information',
};
const alertOptions = { height: 120, width: 260 };
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions).then(
function confirmButtonPressed() {
formContext.data.refresh(true);
},
function dialogClosed() {
formContext.data.refresh(true);
}
);
},
function () {
const alertStrings = {
confirmButtonLabel: 'OK',
text: 'Slot Cancellation Failed',
title: 'Information',
};
const alertOptions = { height: 120, width: 260 };
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions).then(
function confirmButtonPressed() {
formContext.data.refresh(true);
},
function dialogClosed() {
formContext.data.refresh(true);
}
);
}
);
}
Basically the helper script holds my reusable code, the form scipt calls that piece of code, and passes in parameters required for the function to run
How do you make your helper script available globally? I am assuming you are uploading the files as web resources. But that doesn't automatically run the code. Or do you load the helper script in each form individually?
User | Count |
---|---|
254 | |
101 | |
94 | |
47 | |
37 |