In angular we have an enrich method which runs some rest call to enrich a data object, then sets a variable which will cause a hidden details tab to be visible. Something like this overly simplified example:
$scope.enrich = function(team){
angular.forEach(team.members, function(member){
member.getSkills().then(function(skills){
member.skills=skills;
}
});
$scope.enrichFinished=true;
};
I'm getting exceptions in the detail pane which is opened when enrichFinished is true. The exceptions appear to be due to a filter which attempts to filter on member.skill and discovers skill is undefined for the member. I assume the problem is that we open the detail tab as soon as enrichFinished is set, which is before the then clause that sets member.skills=skill; thus we have a datarace where skills's may not yet be set by the time we try to filter on it.
What is the cleanest way to tell angular to wait to run the filter in the detail's tab until after I have actually generated and saved the data I need? The actual enrich method enriches 3-4 different variables within the for loop.
You can set $scope.enrichFinished=true; after all promisses solve, so you can use $q.all like this
$scope.enrich = function(team){
$q.all(team.members.map(
function(member){
return member.getSkills().then(
function(skills){
member.skills=skills;
}
);
}
)
).then(function(){
$scope.enrichFinished=true;
});
};
Related
I am going to do live data streaming on ag-grid datatable, so I used DeltaRowData for gridOptions and added getRowNodeId method as well which return unique value 'id'.
After all, I got a live update result on my grid table within some period I set, but some rows are duplicated so I can notice total count is a bit increased each time it loads updated data. The question title is warning message from browser console, I got bunch of these messages with different id number. Actually it is supposed not to do this from below docs. This is supposed to detect dups and smartly added new ones if not exist. Ofc, there are several ways to get refreshed data live, but I chose this one, since it says it helps to persist grid info like selected rows, current position of scroll on the grid etc. I am using vanilla js, not going to use any frameworks.
How do I make live data updated periodically without changing any current grid stuff? There is no error on the code, so do not try to speak about any bug. Maybe I am wrong with current implementation, Anyway, I want to know the idea or hear any implementation experience on this.
let gridOptions = {
....
deltaRowDataMode: true,
getRowNodeId = (data) => {
return data.id; // return the property you want set as the id.
}
}
fetch(loadUrl).then((res) => {
return res.json()
}).then((data) => {
gridOptions.api.setRowData(data);
})
...
If you get:
duplicated node warning
it means your getRowNodeId() has 1 value for 2 different rows.
here is part from source:
if (this.allNodesMap[node.id]) {
console.warn("ag-grid: duplicate node id '" + node.id + "' detected from getRowNodeId callback, this could cause issues in your grid.");
}
so try to check your data again.
if u 100% sure there is an error not related with your data - cut oof the private data, create a plinkr/stackblitz examples to reproduce your issue and then it would be simpler to check and help you.
My question comes from the selected answer here
<ui-select-choices repeat="hero in getSuperheroes($select.search) | filter: $select.search">
<div ng-bind="hero"></div>
works perfectly fine with:
$scope.getSuperheroes = function(search) {
var newSupes = $scope.superheroes.slice();
if (search && newSupes.indexOf(search) === -1) {
newSupes.unshift(search);
}
return newSupes;
}
However, when I put a breakpoint on $scope.superheroes I see its called as many times as my data. Data is very large. Its fetched from a $http.get() request and I'm handling not loading the data in the textbox all at once.
However, I don't want to call this until I actually start typing anything in the textbox. I tried to call getSuperheroes using
on-select="getSuperheroes($select.search)"
and similarly
refresh="getSuperheroes($select.search)"
but they do not help allowing manual entry of data.
What do I use to call the function and yet get the task done just like how it works in the referenced answer?
I recommend you to do this:
In order to improve performance get rid of repeat="hero in getSuperheroes($select.search) | filter: $select.search", since if your data doesn't change the function getSuperheroes will be triggered (anyway) every time a $digest is executed (no matter the var newSupes changes or not in getSuperheroes) (that's wahat's happening right now!). Instead put (for instance) repeat="hero in myHeroes" and then in your getSuperheroes func do this:
$scope.getSuperheroes = function(search) {
var aux = $scope.superheroes.slice();
if (search && aux.indexOf(search) === -1) {
aux.unshift(search);
}
$scope.myHeroes = aux;
}
Keep the refresh="getSuperheroes($select.search)", this will search the new values by calling getSuperheroes which will update the $scope.myHeroes var and then it will be refreshed in the view.
Add the property refresh-delay="500" (500 is just an example, put whatever fits your needs). This will delay the search that amount of milisecond. This is useful when you do not want start searching immediately. For instance if the user wants to search "Superman" and she/he type "Sup" in less than 500 milisec the search will start with the string "Sup". If you do not set that property the search would be executed 3 times (one for "S" another for "Su" and a third one for "Sup")
PS:
-- If you want to get the ui-select with some values initially, You must call the getSuperheroes func on your controller start.
-- You may need to declare $scope.myHeroes = []; at the beginning of your controller (it depends on your controller implementation).
-- You might want to read more info about $digest on the AngularJS official doc, on SO there are some related posts:
How exactly does the AngularJS Digest Loop work?
Why do i have to call $scope.$digest() here?
Angular $scope.$digest vs $scope.$apply
$apply vs $digest in directive testing
I have a not too big grid (30x20) with numbers in cells. I have to display all, calculate them in different ways (by columns, rows, some cells, etc.) and write values to some cells. This data is also written and read from db table fields. Everything is working, excluding simple (theoretically) mask tools.
In time of e.g. writing data to the field in the table I try to start mask and close it on finish. I used such a “masks” very often but only in this situation I have a problem and can’t solve it.
I prepare this mask the following way:
msk = new Ext.LoadMask(Ext.getBody(), { msg: "data loading ..." });
msk.show();
[writing data loops]
msk.hide();
msk.destroy();
I also tried to use grid obiect in place of Ext.getBody(), but without result.
I found also that the program behaves in a special way – loops which I use to write data to the table field are "omitted" by this mask, and it looks like loops are working in the background (asynchronously).
Would you be so kind as to suggest something?
No, no, no, sorry guys but my description isn’t very precise. It isn’t problem of loading or writing data to the database. Let’s say stores are in the memory but my problem is to calculate something and write into the grid. Just to see this values on the screen. Let me use my example once again:
msk = new Ext.LoadMask(Ext.getBody(), { msg: "data loading ..." });
msk.show();
Ext.each(dataX.getRange(), function (X) {
Ext.each(dataY.getRange(), function (Y) {
…
X.set('aaa', 10);
…
}
msk.hide();
msk.destroy();
And in such a situation this mask isn’t visible or is too fast to see it.
In the mean time I find (I think) a good description of my problem but still can’t find a solution for me. When I use e.g. alert() function I see this mask, when I use delay anyway, mask is too fast. Explanation is the following:
The reason for that is quite simple - JS is single threaded. If you modify DOM (for example by turning mask on) the actual change is made immediately after current execution path is finished. Because you turn mask on in beginning of some time-consuming task, browser waits with DOM changes until it finishes. Because you turn mask off at the end of method, it might not show at all. Solution is simple - invoke store rebuild after some delay.*
I have no idea how is your code looks in general but this is some tip that you could actually use.
First of all loading operations are asynchronously so you need to make that mask show and then somehow destroy when data are loaded.
First of all check if in your store configuration you have autoLoad: false
If yes then we can make next step:
Since Extjs is strongly about MVC design pattern you should have your controller somewhere in your project.
I suppose you are loading your data on afterrender or on button click event so we can make this:
In function for example loadImportantData
loadImportantData: function(){
var controller = this;
var store = controller.getStore('YourStore'); //or Ext.getStore('YourStore'); depends on your configuration in controller
var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Please wait..."});
myMask.show();
store.load({
callback: function (records, operation, success) {
//this callback is fired when your store load all data.
//then hide mask.
myMask.hide();
}
});
}
When data is loaded your mask will disappear.
If you have a reference to the grid, you can simply call grid.setLoading(true) to display a loading mask over the grid at any time.
I would like to test a filtering function in my angularJS app.
In fact when I click on the filter the number of search results displayed on the page should decrease
Here is is my code so far :
element(by.id('foundNumber')).getText().then (function(text){console.log(text); })
element(by.repeater('term in facets.uproctype').row(0)).click ()
element(by.id('foundNumber')).getText().then (function(text){console.log(text); })
And here is my console log :
Using the selenium server at http://localhost:4444/wd/hub
6209
6195
....
I don't know how can I compare theses two values in an expect line as I can't use them inside their function.
Any help?
Thanks
Zied
I believe you would have to nest your then functions to ensure the original value is available.
element(by.id('foundNumber')).getText().then( function(original_text) {
element(by.repeater('term in facets.uproctype').row(0)).click ();
element(by.id('foundNumber')).getText().then( function(new_text){
expect(original_text).not.toBe(new_text);
});
});
This link also might be helpful.
https://code.google.com/p/selenium/wiki/WebDriverJs#Control_Flows
Another way is to schedule the comparison at the protractor control flow so it will be executed after all values are available.
var oldValue,newValue;
element(by.id('foundNumber')).getText().then(function(text){oldValue=text})
element(by.repeater('term in facets.uproctype').row(0)).click()
element(by.id('foundNumber')).getText().then(function(text){newValue=text})
protractor.promise.controlFlow()
.execute(function(){return protractor.promise.fulfilled()},'wait for control flow')
.then(function(){
expect(oldValue).not.toEqual(newValue);
});
There is a nice explanation of flow.execute() in this blog post.
I suspect you can just compare the results of .getText() from different points using expect
var text1 = element(by.id('foundNumber')).getText();
element(by.repeater('term in facets.uproctype').row(0)).click();
var text2 = element(by.id('foundNumber')).getText();
expect(text1).not.toBe(text2);
expect handles unwrapping of the promises passed to it and actually compares resolved values.
I have two jQuery mobile pages (#list and #show). There are several items on the #list page with different IDs. If I click on item no.5, the ID no5 will be stored in localStorage and I will be redirected to page #show
Now the problem:
Storing the ID in localStorage works, but the next page shows me not the item no.5, but it shows me an old item, that was in the localStorage before.
script from page #list
localStorage.setItem("garageID", $(this).attr('id'));
window.location.replace("#show");
I encountered this problem too (and not on a mobile : on Chromium/linux).
As there doesn't seem to be a callback based API, I "fixed" it with a timeout which "prevents" the page to be closed before the setItem action is done :
localStorage.setItem(name, value);
setTimeout(function(){
// change location
}, 50);
A timeout of 0 might be enough but as I didn't find any specification (it's probably in the realm of bugs) and the problem isn't consistently reproduced I didn't take any chance. If you want you might test in a loop :
function setLocalStorageAndLeave(name, value, newLocation){
value = value.toString(); // to prevent infinite loops
localStorage.setItem(name, value);
(function one(){
if (localStorage.getItem(name) === value) {
window.location = newLocation;
} else {
setTimeout(one, 30);
}
})();
}
But I don't see how the fact that localStorage.getItem returns the right value would guarantee it's really written in a permanent way as there's no specification of the interruptable behavior, I don't know if the following part of the spec can be legitimately interpreted as meaning the browser is allowed to forget about dumping on disk when it leaves the page :
This specification does not require that the above methods wait until
the data has been physically written to disk. Only consistency in what
different scripts accessing the same underlying list of key/value
pairs see is required.
In your precise case, a solution might be to simply scroll to the element with that given name to avoid changing page.
Note on the presumed bug :
I didn't find nor fill any bug report as I find it hard to reproduce. In the cases I observed on Chromium/linux it happened with the delete operation.
Disclaimer: This solution isn't official and only tested for demo, not for production.
You can pass data between pages using $.mobile.changePage("target", { data: "anything" });. However, it only works when target is a URL (aka single page model).
Nevertheless, you still can pass data between pages - even if you're using Multi-page model - but you need to retrieve it manually.
When page is changed, it goes through several stages, one of them is pagebeforechange. That event carries two objects event and data. The latter object holds all details related to the page you're moving from and the page you're going to.
Since $.mobile.changePage() would ignore passed parameters on Multi-page model, you need to push your own property into data.options object through $.mobile.changePage("#", { options }) and then retrieve it when pagebeforechange is triggered. This way you won't need localstorage nor will you need callbacks or setTimeout.
Step one:
Pass data upon changing page. Use a unique property in order not to conflict with jQM ones. I have used stuff.
/* jQM <= v1.3.2 */
$.mobile.changePage("#page", { stuff: "id-123" });
/* jQM >= v1.4.0 */
$.mobile.pageContainer.pagecontainer("change", "#page", { stuff: "id-123" });
Step two:
Retrieve data when pagebeforechange is triggered on the page you're moving to, in your case #show.
$(document).on("pagebeforechange", function (event, data) {
/* check if page to be shown is #show */
if (data.toPage[0].id == "show") {
/* retrieve .stuff from data.options object */
var stuff = data.options.stuff;
/* returns id-123 */
console.log(stuff);
}
});
Demo