for loop not waiting for function to end - microsoft-metro

I have 5 links on a page and i have to check if all are links are working or not. Here is the code
// iterate through each link and check if ti works.
for(var i=0; i < 5; i++) {
var ifLinkWorks = verifyLinkWorks(links[i]);
if(ifLinkWorks){ OK }
else{ error }
}
This is verifyLinkWorks function. It opens a link. After it get opened, it checks if the page is loaded properly
function verifyLinkWorks(link) {
return winjs.Promise(function(complete) {
link.click();
// wait for page to load
return winjs.promise.timeout(4000).then(function () {
// check if page is loaded
var islinkOK = IsPageLoaded();
complete(islinkOK); // i want verifyLinkWorks to return this value
});
});
}
After reaching link.click(), it is not waiting for page to load. Instead it jumps to the if condtion in outer for loop (which makes linkWorks = undefined therefore,gives Error). How to make it wait in the verfifyLinkWorks function.
Thanks in advance...

You'll need to wait for the results of each promise, either all at once, or individually. As the actions are all async in nature, the code can't wait, but it can call a function when it completes all of the work.
Here, I've created an array which will hold each Promise instance. Once the loop has completed, the code waits for all to complete, and then using the array that is passed, checking the result at each index.
// iterate through each link and check if it works.
var verifyPromises = [];
for(var i=0; i < 5; i++) {
verifyPromises.push(verifyLinkWorks(links[i]));
}
WinJS.Promise.join(verifyPromise).done(function(results) {
for(var i=0; i < 5; i++) {
var ifLinkWorks = results[i];
if (ifLinkWorks) { /* OK */ }
else { /* error */ }
}
});
In case the link.click() call fails, I've wrapped it in a try/catch block:
function verifyLinkWorks(link) {
return WinJS.Promise(function(complete, error) {
try {
link.click();
} catch (e) {
complete(false); // or call the error callback ...
}
// wait for page to load, just wait .. no need to return anything
WinJS.Promise.timeout(4000).then(function () {
// check if page is loaded
var islinkOK = IsPageLoaded();
// finally, call the outer promise callback, complete
complete(islinkOK);
});
});
}
If you want to check the validity of a URL, I'd suggest you consider using WinJS.xhr method to perform a HEAD request instead (rfc). With each link variable, you can use a timeout to validate that there's a reasonable response at the URL, without downloading the full page (or switch to a GET and check the response body).
WinJS.Promise.timeout(4000,
WinJS.xhr({
type: 'HEAD',
url: link
}).then(function complete(result) {
var headers = result.getAllResponseHeaders();
}, function error(err) {
if (err['name'] === 'Canceled') {
}
if (err.statusText) {
}
})
);

Ok heres the link to the msdn code sample for win js promise object.
Promise winjs
now going through the code
<button id="start">StartAsync</button>
<div id="result" style="background-color: blue"></div>
<script type="text/javascript">
WinJS.Application.onready = function (ev) {
document.getElementById("start").addEventListener("click", onClicked, false);
};
function onClicked() {
addAsync(3, 4).then(
function complete(res) {
document.getElementById("result").textContent = "Complete";
},
function error(res) {
document.getElementById("result").textContent = "Error";
},
function progress(res) {
document.getElementById("result").textContent = "Progress";
})
}
function addAsync(l, r) {
return new WinJS.Promise(function (comp, err, prog) {
setTimeout(function () {
try {
var sum = l + r;
var i;
for (i = 1; i < 100; i++) {
prog(i);
}
comp(sum);
}
catch (e) {
err(e);
}
}, 1000);
});
}
</script>
you will see the addAsync(3,4).then() function. So all the code is to be kept inside that function in order to have a delayed response . Sorry m using a tab so cannot write it properly.
Also go through link then for winjs promise

Related

SAPUI5 how to provide Busy Indicator for POST call in for loop

I have an issue in displaying BusyIndicator for post call in for loop. Before posting this question i have explored all possible ways and didnt find any solution. Hence im posting here. Please apologize if this is silly question..
For one of the requirement, i have to call ODataModel POST call for 10 times in a for loop.
Here im able to get the results perfectly without any issue. But the problem is to fire all 10 services, it is taking nearly 20 seconds to finish execution of all services.
So i want to display BusyDialog before executing services. Below is my code..
var payload = {
"sectionIndex": 3,
"ratingType": "overall",
"userId": "",
"rating": "76",
"ratingKey": "wf_sect_3_rating"
};
//var busy = new sap.m.BusyDialog();
//busy.open();
for (var i = 0; i < 10; i++) {
//var busy = new sap.m.BusyDialog();
//busy.open();
var that = this;
oModel.create("/URL", payload, {
success: function (oData, oResponse) {
if (oData.results[0].status === "OK") {
that.getView().getModel("employeesModel").refresh(true);
} else {
MessageBox.error("Error : " + oData.results[0].message);
}
},
error: function (e) {
MessageBox.error("Error : " + e);
}
});
busy.close();
}
I also tried with sap.ui.core.BusyIndicator.show(), hide(), creating separate BusyDialog.fragment and calling explicitly in controller. But none of the ways worked.
But when i try to debug, then im not getting any issue. BusyIndicator is loading perfectly if im keeping breakpoint.
Can someone help me to load BusyDialog at the before calling POST call and close BusyDialog once the execution of services finished.
For this scenario I would recommend using Promises
var payload = {
sectionIndex: 3,
ratingType: "overall",
userId: "",
rating: "76",
ratingKey: "wf_sect_3_rating"
};
//var busy = new sap.m.BusyDialog();
//busy.open();
var aPromises = [];
// Open your busy dialog
for (var i = 0; i < 10; i++) {
//var busy = new sap.m.BusyDialog();
//busy.open();
var that = this;
var pCall = new Promise(function(resolve, reject) {
oModel.create("/URL", payload, {
success: function(oData, oResponse) {
resolve();
if (oData.results[0].status === "OK") {
that
.getView()
.getModel("employeesModel")
.refresh(true);
} else {
MessageBox.error("Error : " + oData.results[0].message);
}
},
error: function(e) {
reject();
MessageBox.error("Error : " + e);
}
});
aPromises.push(pCall);
}).bind(this);
Promise.all(aPromises).then(function() {
// Happy case
// Close your busy dialog
}, function() {
// At least one of the promises went wrong
});
//busy.close();
}
Note that once one of the Promises is rejected, it will go into the second function of Promise.all(), so maybe just use resolve()
Please use batch requests instead of single requests in loops.
var sError;
var aErrorMessages = [];
var payload = {
"sectionIndex": 3,
"ratingType": "overall",
"userId": "",
"rating": "76",
"ratingKey": "wf_sect_3_rating"
};
var busy = new sap.m.BusyDialog();
busy.open();
oModel.setUseBatch(true);
oModel.setDeferredGroups(["myBatchRequest"]);
for (var i = 0; i < 10; i++) { //Loop
oModel.create("/URL", payload, { //Create request without sending
groupId: "myBatchRequest",
changeSetId: "myChangeSet",
single: false,
success: function(oData, oResponse) { //Catch error for each request
if (oData.results[0].status !== "OK") {
aErrorMessages.push("Error : " + oData.results[0].message); //store error message for each request
}
}.bind(this),
error: function(e) {
aErrorMessages.push(e);
}
});
}
oModel.submitChanges({ //Send all requests at once
groupId: "myBatchRequest",
success: function(oResponse) {
if (aErrorMessages.length > 0) { //Are there any errors...
for (var i = 0; i < aErrorMessages.length; i++) {
sError += aErrorMessages[i]; //...put it in string
}
MessageBox.error(sError); //Show all errors
busy.close(); //Close Busy Dialog
} else {
this.getView().getModel("employeesModel").refresh(true);
}
}.bind(this),
error: function(oError) {
MessageBox.error(oError);
busy.close(); //Close Busy Dialog
}
});
You can solves it by using oData model method attachRequestCompleted. An easy way would be.
if(i == 10) {
oModel.attachRequestCompleted(function(oEvent) {
busy.close();
});
}
Of course that's not a perfect solution because it could be, that the 9th request is later finished than your 10th etc.
Normally you need to check every request and if every request is completed successfully, the busy dialog get closed. if one or more of them got an error, you can close if all 10 requests are completed you can close the busy dialog and you show an error message or sth.
so normally on every attachRequestCompleted you need to check your loop counter if it's size is 10 and if yes, you can close the dialog.

Waiting for meteor cursor in method

I have a large aggrogate query that required me to pass "allowDiskUse: true" as an option. This would not work with the aggegate as described here:
https://github.com/meteorhacks/meteor-aggregate/issues/11
My meteor method is defined here. When I call the method I need to wait for ondata to complete before anything is returned to the client, but nothing I try allows me to get that data in a safe way up to the front end.
Meteor.methods({
'getSummary': function (dept,startDate,endDate,filterType) {
f = myQuery(startdate,enddate,dayFinalGroup);
f.on("data", Meteor.bindEnvironment(function(row) {
//load an array or something here to return
}));
f.once("end", Meteor.bindEnvironment(function() {
// tidy up, in my case end the stream
}));
//here I'd return the array loaded
},
});
This is my front end.
Meteor.call(
'getSummary',0,Session.get('start_date'),Session.get('end_date'),1,
function(error, result){
if(error){
console.log(error);
} else {
Session.set('sumTotals',result);
}
}
);
Finally Got it. I utilized wrapSync
'getSummary': function (dept,startDate,endDate,filterType) {
console.log(dept);
console.log(startDate);
console.log(endDate);
console.log(filterType);
var startdate = new Date(startDate);
var enddate = new Date(endDate);
var arr = [];
f = myQuery(startdate,enddate,dayFinalGroup);
var fetchCursor = Meteor.wrapAsync(function fetchCursor (cursor, cb) {
cursor.each(function (err, doc) {
if (err) return cb(err);
if (!doc) return cb(null, { done: true }); // no more documents
arr.push(doc);
});
});
var myData = fetchCursor(f);
return arr;

UI5 Messagebox synchron

Hi i implemented a Messagebox in a for loop, but i know the messagebox works asynchron.
I want that the programm wait for every loop to the desicion of the user.
onBook: function(oEvent) {
var that = this;
for (var i = 0; i < Items.length; i++){
function message(innerArg) {
sap.m.MessageBox.confirm(
"Text", {
icon : sap.m.MessageBox.Icon.INFORMATION,
title : "Really",
actions : [ sap.m.MessageBox.Action.YES,
sap.m.MessageBox.Action.NO ],
onClose : function(oAction) {
if (oAction === sap.m.MessageBox.Action.NO) {
delete(i);
}else{
}
}
});
}
message(i);
}
that.do(oEvent);
The programm jumps in the "do" method before a user action is done
Edit:
for (var i = 0; i < Items.length; i++){
(function (innerArg) {
sap.m.MessageBox.confirm(
"Delete?", {
icon : sap.m.MessageBox.Icon.INFORMATION,
title : "Delete",
actions : [ sap.m.MessageBox.Action.YES,
sap.m.MessageBox.Action.NO ],
onClose : function(oAction) {
if (oAction === sap.m.MessageBox.Action.NO) {
delete(innerArg)
}}
});
})(i);
}
that.Save(oEvent);
When the box is open the entries are booked because the programm goes to the save method without wating of user action Whats wrong ?
Ah, the asynchronous-function-in-a-synchronous-loop anti-pattern ;-)
You can try using closures:
for (var i = 0; i < items; i++) {
// use self-executing function here
(function(innerArg) {
sap.m.MessageBox.confirm(
"Text", {
onClose: function(oAction) {
if (oAction === sap.m.MessageBox.Action.NO) {
// here I want to do something
console.log("Value: ", innerArg);
}
}
}
);
})(i);
}
EDIT: Update with Promises
Based on your updated question, I have provided a more-or-less sorta-kinda working example (it may not work flawless, but it should show the design pattern you should follow)
You wrap the message box responses into a Promise resolve, and store these into an array. You then feed that array to Promise.all() in order to proceed with your save functionality
processData: function() {
var promises = [],
self = this;
for (var i = 0; i < items; i++) {
promises.push(this.doMessageboxAction(i));
}
Promise.all(promises).then(function(aData) {
aData.forEach(function(oData) {
self.save(oData);
});
}).catch(function(err) {
console.log(err);
});
}
doMessageboxAction: function(item) {
return new Promise(function(resolve, reject) {
sap.m.MessageBox.confirm(
"Text", {
onClose: function(oAction) {
if (oAction === sap.m.MessageBox.Action.NO) {
//do something
//etc
resolve(item); // or some other variable
}
else {
//do something else
//etc
resolve(item); // or some other variable
}
}
}
);
});
}

With Chrome FileSystem, let the user choose a Directory, load files inside. And save files in that directory without prompting again

I could not found any examples with this scenario so here we go:
I want the user choose a directory, load all files inside it, change them, and save this file overriding it or saving a new file in that same directory without asking where he want to save.
I don't know how to list the files of the directory
I don't know how to save a file in a directory without prompting the filechooser window
I believe it is possible because I see something similar here (last paragraph):
http://www.developer.com/lang/using-the-file-api-outside-the-sandbox-in-chrome-packaged-apps.html
Any answer will be appreciated, Thank you
EDIT: Thanks to Chris Johnsen for giving me this great answer:
var fileHandler = function() {
var _entry = null;
this.open = function(cb) {
chrome.fileSystem.chooseEntry({
type: 'openDirectory'
}, function(dirEntry) {
if (!dirEntry || !dirEntry.isDirectory) {
cb && cb(null);
return;
}
_entry = dirEntry;
listDir(_entry, cb);
});
};
this.save = function(filename, source) {
chrome.fileSystem.getWritableEntry(_entry, function(entry) {
entry.getFile(filename, {
create: true
}, function(entry) {
entry.createWriter(function(writer) {
writer.onwrite = function() {
writer.onwrite = null;
writer.truncate(writer.position);
};
writer.write(new Blob([source], {
type: 'text/javascript'
}));
});
});
});
};
this.saveAs = function(filename, source) {
chrome.fileSystem.chooseEntry({
type: 'openDirectory'
}, function(entry) {
chrome.fileSystem.getWritableEntry(entry, function(entry) {
entry.getFile(filename, {
create: true
}, function(entry) {
entry.createWriter(function(writer) {
writer.onwrite = function() {
writer.onwrite = null;
writer.truncate(writer.position);
};
writer.write(new Blob([source], {
type: 'text/javascript'
}));
});
});
});
});
};
var listDir = function(dirent, cb, listing) {
if (listing === undefined) {
listing = [];
}
var reader = dirent.createReader();
var read_some = reader.readEntries.bind(reader, function(ents) {
if (ents.length === 0) {
return cb && cb(listing);
}
var process_some = function(ents, i) {
for (; i < ents.length; i++) {
listing.push(ents[i]);
if (ents[i].isDirectory) {
return listDir(ents[i], process_some.bind(null, ents, i + 1), listing);
}
}
read_some();
};
process_some(ents, 0);
}, function() {
console.error('error reading directory');
});
read_some();
};
};
Your save method should work fine (mostly, see below) for your second requirement (write to a code-chosen filename without another user prompt), but there are a couple of bugs in open (at least as presented in the question):
Inside the chooseEntry callback, this !== fileHandler because the callback is invoked with a different this (probably the background page’s window object).
You can work around this in several ways:
Use fileHandler instead of this (if you are not using it as any kind of prototype).
Use .bind(this) to bind each of your callback functions to the same context.
Use var self = this; at the top of open and use self.entry (et cetera) in the callbacks.
You may want to call cb for the success case. Maybe you have another way of postponing calls to (e.g.) fileHandler.save (clicking on some element to trigger the save?), but adding something like
⋮
cb && cb(self.entry);
⋮
after self.entry = dirEntry makes it easy to (e.g.) chain open and save:
fileHandler.open(function(ent) {
fileHandler.save('newfile','This is the text\nto save in the (possibly) new file.');
});
There is a latent bug in save: if you ever overwrite an existing file, then you will want to call writer.truncate() (unless you always write more bytes than the file originally held).
⋮
writer.onwrite = function() {
writer.onwrite = null;
writer.truncate(writer.position);
};
writer.write(…);
⋮
It looks like you have a good start on the file listing part. If you want to reference the list of files later, then you might want to save them in your object instead of just logging them; this can get a bit hairy if you want to recurse into subdirectories (and also not assume that readEntries returns everything for its first call).
function list_dir(dirent, cb, listing) {
if (listing === undefined) listing = [];
var reader = dirent.createReader();
var read_some = reader.readEntries.bind(reader, function(ents) {
if (ents.length === 0)
return cb && cb(listing);
process_some(ents, 0);
function process_some(ents, i) {
for(; i < ents.length; i++) {
listing.push(ents[i]);
if (ents[i].isDirectory)
return list_dir(ents[i], process_some.bind(null, ents, i + 1), listing);
}
read_some();
}
}, function() {
console.error('error reading directory');
});
read_some();
}
You could use it in the open callback (assuming you add its success callback) like this:
fileHandler.open(function(ent) {
ent && list_dir(ent, function(listing) {
fileHandler.listing = listing;
console.log('listing', fileHandler.listing.map(function(ent){return ent.fullPath}).join('\n'));
fileHandler.save('a_dir/somefile','This is some data.');
});
});

Add new data from restful api to angularjs scope

I'm trying to create a list with endless scroll in angularjs. For this I need to fetch new data from an api and then append it to the existing results of a scope in angularjs. I have tried several methods, but none of them worked so far.
Currently this is my controller:
userControllers.controller('userListCtrl', ['$scope', 'User',
function($scope, User) {
$scope.users = User.query();
$scope.$watch('users');
$scope.orderProp = 'name';
window.addEventListener('scroll', function(event) {
if (document.body.offsetHeight < window.scrollY +
document.documentElement.clientHeight + 300) {
var promise = user.query();
$scope.users = $scope.users.concat(promise);
}
}, false);
}
]);
And this is my service:
userServices.factory('User', ['$resource',
function($resource) {
return $resource('api/users', {}, {
query: {
method: 'GET',
isArray: true
}
});
}
]);
How do I append new results to the scope instead of replacing the old ones?
I think you may need to use $scope.apply()
When the promise returns, because it isnt
Part of the angular execution loop.
Try something like:
User.query().then(function(){
$scope.apply(function(result){
// concat new users
});
});
The following code did the trick:
$scope.fetch = function() {
// Use User.query().$promise.then(...) to parse the results
User.query().$promise.then(function(result) {
for(var i in result) {
// There is more data in the result than just the users, so check types.
if(result[i] instanceof User) {
// Never concat and set the results, just append them.
$scope.users.push(result[i]);
}
}
});
};
window.addEventListener('scroll', function(event) {
if (document.body.offsetHeight < window.scrollY +
document.documentElement.clientHeight + 300) {
$scope.fetch();
}
}, false);