I'm writing some JS for Dynamics 365 which disables (locks) the fields on the selected editable subgrid row.
The method to do this is .setDisabled() (Documentation). I can run the following method which will lock all the fields upon selecting a row:
function onGridRowSelected(context){
context.data.entity.attributes.forEach(function (attr) {
attr.controls.forEach(function (myField) {
myField.setDisabled(foundResponse);
})
});
}
The issue I am having is trying to run the above following a promise. I have the following code which will pass the result of a promise into my disable fields methods:
var gridContext;
function onGridRowSelected(context){
gridContext = context.getFormContext();
//Retrieve the record we want to check the value on
Xrm.WebApi.retrieveMultipleRecords("ms_approvalquery", "?$select=ms_responsetext&$top=1&$orderby=createdon desc")
.then(result => disableOrEnableFields(result));
}
function disableOrEnableFields(result){
//Check if the record found has a ms_responsetext != null
var foundResponse = false
if (result.entities[0].ms_responsetext != null){
foundResponse = true;
}
//Either disable/enable all the row columns depending on the value retrieved from the above
gridContext.data.entity.attributes.forEach(function (attr) {
attr.controls.forEach(function (myField) {
myField.setDisabled(foundResponse);
})
});
}
When stepping through debug, I can see that myField.setDisabled(true); is getting called but nothing is happening. Is this because it's on a separate thread? How do I get back to the main thread with the result of my promise?
Note: Using Async/Await doesn't work either - it gives the same results.
we had similar issues few days back, unfortunately Async/Await/promise call does not respect grid control, you will have to go by old/classic Sync call way and then it shall work. Let me know if this solves your problem.
Related
Let´s say that i have a list with items with different colors. That list can be updated to only list blue items if i add a parameter. How can i verify that the each item is correct?
cy.addParameter('blue'); //Will send graphQL query to fetch the correct items.
cy.get('data li').each((item)=> {
cy.wrap(item).should('have.text', ' blue ');
});
This will fail because the items in the list has not been updated before i have the possibility to check each item. It´s possible to wait for the request to finnish, but as the queries saves after the first run that "check" won´t work the second time.
You must write a recursive promise function and check by yourself the color text, try the following
function checkColor(selector, color, maxWait, alreadyWaited = 0) {
const waitTime = 500;
// cy.get returns a thenebale
return cy.get(selector).each((item)=> {
// it checks the text right now, without unnecessary waitings
if(!item.text() === color) {
return false;
}
return true;
}).then(result => {
if(result) {
// only when the condition passes it returns a resolving promise
return Promise.resolve(true);
}
// waits for a fixed milliseconds amount
cy.wait(waitTime);
alreadyWaited++;
// the promise will be resolved with false if the wait last too much
if(waitTime * alreadyWaited > maxWait) {
return Promise.reject(new Error('Awaiting timeout'))
}
// returns the same function recursively, the next `.then()` will be the checkColor function itself
return checkColor(selector, color, maxWait, alreadyWaited);
});
}
// then, in your test...
cy.addParameter('blue'); //Will send graphQL query to fetch the correct items.
checkColor('data li', 'blue', 10000).then(result => {
cy.get('data li').each((item)=> {
cy.wrap(item).should('have.text', ' blue ');
});
// or
expect(result).to.be(true);
});
I tried to comment a much as possible the code refactoring the code I developed for an accepted similar question.
Let me know if you need some more help 😊
UPDATE
We (me and Tommaso) have written a plugin to help you with this kind of checks, its name is cypress-wait-until.
Please thank the Open Source Saturday community for that, we developed it during one of them Saturdays 😊
Here's a simple solution without using the cypress-wait-until plugin. I think this should suffice for most cases.
// get "array" of elements
cy.get('data li').should(elements => {
elements.each((i, el) => {
expect(el.innerText).to.eq('blue'); // retry `cy.get()` if one isn't blue
});
});
Source: https://docs.cypress.io/guides/references/assertions#Should-callback
Without having seen OP's code run, I think it's not working because cy.get('data li') is never retried. Cypress doesn't retry because .each() doesn't trigger retries on failure.
I have been trying to extract some information from the props this way.
let roleType=this.props.user.data.permissions.map((val)=>{
console.log(val);
});
In the initial stage when the component is getting rendered data has nothing inside it so I get an error that it can't map over which is true.
How do I deal with these cases. If I console log the above props I see that data is initially empty and then it gets filled. However, the web app crashes because of this?
In order to avoid cases when you try to deal with such scenarios, you either intialize the data in the format you expect it to be or you provide conditional checks to it while using
let roleType=this.props.user && this.props.user.data && this.props.user.data.permissions && this.props.user.data.permissions.map((val)=>{
console.log(val);
});
or initialise it like
state = {
user: {
data: {
permissions: []
}
}
}
What other thing that you can do to avoid unexpected undefined scenarios is to use PropType validation
Component.propTypes = {
user: PropTypes.shape({
data: PropTypes.shape({
permissions: PropTypes.Array
})
})
}
One other improvement that you can do over the first method is to write a function that checks for nested data
function checkNested(obj, accessor) {
var args = accessor.split('.')
for (var i = 0; i < args.length; i++) {
if (!obj || !obj.hasOwnProperty(args[i])) {
return false;
}
obj = obj[args[i]];
}
return true;
}
and then you can use it like
let roleType= checkNested(this.props.user, 'data.permissions') && this.props.user.data.permissions.map((val)=>{
console.log(val);
});
P.S. You can also improve the above method to take into consideration
the array index
Simply check to see if the object has the required attributes you're looking for before mapping through it. If it doesn't use an empty array.
let roleType = this.props.user.data ? this.props.user.data.permissions.map((val) => {
console.log(val);
}) : [];
Look like you have to make an API call to get the data, so add that code for fetching the data in componentDidmount once the response came, set the data response in the state. So that component will render again with updated data.
Also, put a condition on the rendering section where going to use the state so that it won't be trying to access any property of a null value.
EDIT: Like other people stated, the question doesn't really tell how you receive the data. My answer is based on you getting the data on load time. This answer doesn't work if you get the data externally or through an async call.
You can wait for the DOM Content to be fully loaded before running the code.
document.addEventListener('DOMContentLoaded', function() {
let roleType=this.props.user.data.permissions.map((val)=>{
console.log(val);
});
}, false);
Another way to do this is by waiting for the page to be fully loaded. This includes images and styling.
window.onload = function(e){
let roleType=this.props.user.data.permissions.map((val)=>{
console.log(val);
});
}
The first one is quicker in running, but you might be better of if you need the images to be loaded before running the code.
Finally it could also be a problem with the order of your code and simply placing your code lower might also fix the problem.
I've got a single screen with multiple dropdowns. Each get populated via a web api call.
I'm in an active directory environment. I'm using the Durandal SPA framework.
At the moment I do the loading of my dropdowns on the activate of my view. EG.
activate: function (data) {
var id = data.id || undefined;
userRole(data.mode || 'usr');
// i added this as i was thinking i dont need to initialize the "static" dropdowns every time
// I'm testing my code without this as we speak.
if (!hasBeenInit) {
lookupInit(id);
hasBeenInit = true;
}
},
}
[A] It seems like randomly, depending on the amount of server load (I'm guessing) my dropdowns wont get populated..
[B] Some of the dropdowns are dependant on the value of another.
I've I've achieved this by making use of the knockout subscribe event. and updating my observable collections.
Javascript
userInfo.BuildingCode.subscribe(function (value) {
vm.userInfo.FloorCode(undefined);
http.get('api/Floor', { id: value }, false)
.done(function (response) {
console.log('api/Floor');
vm.lookups.allFloors(response);
console.log(vm.lookups.allFloors());
});
});
html
<select data-bind="options: lookups.allFloors, optionsText: 'FloorName', optionsValue: 'FloorCode', value: $root.userInfo.FloorCode"></select>
If I had to select the building value for example, the floor dropdown must get new values.
The API call does happens, it seems like the observable array gets updated, but the values don't seem to be reflecting on the view.
Chrome console log
api/Floor main-built.js:14
[Object, Object, Object]
api/Floor main-built.js:14
[Object, Object, Object]
api/Floor main-built.js:14
[Object, Object, Object, Object, Object]
What can i do to ensure my lookups/dropdowns get populated each and every call. As well as their execution order stays as specified.
I've tried something like this, but it just feels wrong. I've also added an async false overload to the durandal http helper, but it didnt seem to work... any ideas? :\
http.get('api/employmenttype')
.done(function (response) {
console.log('api/employmenttype');
vm.lookups.allEmploymentTypes(response);
})
.then(function () {
http.get('api/actionlist')
.done(function (response) {
console.log('api/actionlist');
vm.lookups.allActionListOptions(response);
})
.then(function () { ... } );
UPDATE
I investigated my async call overload. Ended up being the culprit, once i fixed it, my data seemed to load constantly. The only side effect is that none of my server side data sets loads asynchronously. (Which i'm hoping I wont get crucified for).
Assuming that lookupInit is a async call make sure to return the promise. So activate becomes something along the line.
activate: function (data) {
var id = data.id || undefined;
userRole(data.mode || 'usr');
// i added this as i was thinking i dont need to initialize the "static" dropdowns every time
// I'm testing my code without this as we speak.
if (!hasBeenInit) {
hasBeenInit = true;
return lookupInit(id);
}
return true;
},
I'm still digesting the other part(s) of the question, but thought that might get you started already.
Update: The value of vm.userInfo.FloorCode is reset to undefined, but in the select there's no optionsCaption: 'Please select...' defined. It might be that ko won't update the option values because is can't find a corresponded value.
I have a working AutoCompleteExtender implementation.
What I want, is that if I have exited the text box, and the list of items have dissappeared, I want to re-display the list from javascript code without having to write something in the text box again (just redisplay list based on current filter value in text box by click on a button or something). I know how to get the AutoCompleteExtender Behaviour object from code, so all I need is to know the javascript API on that object that enables me to redisplay the list.
I have tried this as suggested in the comments on this answer, but not working:
AutoCompleteEx.showPopup();
I have also tried this as suggested in this answer, but not working:
AutoCompleteEx._onTimerTick(AutoCompleteEx._timer, Sys.EventArgs.Empty);
EDIT:
After some investigation in the back end code used by the AutoComplete, I think maybe the problem is that once shown, it checks on future calls if the value in the search box has changed since last time, and if not it doesn't show it again. I have not found out how to come around this. I have tried different approaches to reset the value, and then set the value again, but with no success.
Enjoy :). That's was an interesting task.
function redisplayAutocompleteExtender() {
var extender = $find("AutoCompleteEx");
var ev = { keyCode: 65, preventDefault: function () { }, stopPropagation: function () { } };
extender._currentPrefix = "";
extender._onKeyDown.call(extender, ev);
}
Or you can set EnableCaching property to true on extender and use script below. This solution allows to avoid additional web service call.
function redisplayAutoComplete() {
var extender = $find("AutoCompleteEx");
var textBox = extender.get_element();
textBox.focus();
var showSuggestions = function(){
extender._update.call(extender, textBox.value, extender._cache[textBox.value], true);
};
setTimeout(showSuggestions, 0);
}
I am using a jQuery method $.getJSON to update data in some cascading drop down lists, in particular, a default value if there is nothing returned for the drop down, e.g. "NONE".
I just want some clarification to how my logic should go.
var hasItems = false;
$.getJSON('ajax/test.json', function(data) {
hasItems = true;
//Remove all items
//Fill drop down with data from JSON
});
if (!hasItems)
{
//Remove all items
//Fill drop down with default value
}
But I don't think this is right. So do I enter into the function whether or not I receive data? I guess I really want to check the data object contains something - to set my boolean hasItems.
You should handle the check right inside the callback function, check the example here.
var hasItems = false;
$.getJSON('ajax/test.json', function(data) {
hasItems = true;
//Remove all items
//Fill drop down with data from JSON
if (!hasItems)
{
//Remove all items
//Fill drop down with default value
}
});
You want to do all checking of returned data inside the callback, otherwise that condition will be called before the callback has been called, resulting in it always being the initial value assigned.
You're dealing with asynchrony, so you need to think of the code you're writing as a timeline:
+ Some code
+ Fire getJSON call
|
| server working
|
+ getJSON call returns and function runs
The code inside the function happens later than the code outside it.
Generally:
// Setup any data you need before the call
$.getJSON(..., function(r) { //or $.ajax() etc
// Handle the response from the server
});
// Code here happens before the getJSON call returns - technically you could also
// put your setup code here, although it would be weird, and probably upset other
// coders.