How can I complete function in init before call ready method. My code:
WinJS.Namespace.define("Data", {
source: ""
});
var page = WinJS.UI.Pages.define("/html/page.html", {
init: function (element, options) {
createDataSoucre();
},
ready: function () {
document.getElementById("result").innerHTML = Data.source;
}
});
function createDataSoucre() {
//blah blah (calculate thousands of calculations)
Data.source = result;
}
When I run, page doesn't render "result" tag. I try use promises but it doesn't work for me:
init: function (element, options) {
return new WinJS.Promise.as(createDataSoucre());
}
Thanks for your time.
I tried your code in a simple test project as follows:
(function () {
"use strict";
WinJS.Namespace.define("Data", {
source: ""
});
function createDataSource() {
Data.source = "<ul><li>Item 1</li><li>Item2</li><li>Item3</li></ul>";
}
WinJS.UI.Pages.define("/pages/home/home.html", {
init: function (element, options) {
createDataSource();
},
ready: function (element, options) {
document.getElementById("result").innerHTML = Data.source;
}
});
})();
Everything works as expected, with the bullet list appearing on the page.
However, I believe you're asking how to make your createDataSource function asynchronous in itself, so that it can do your "thousands of calculations" off the UI thread and produce a promise that the init function can return. This way, the page loading process will wait upon the completion of those calculations.
What's needed here is to use new WinJS.Promise rather than WinJS.Promise.as. The as method just wraps a value in a promise so that the value is send to any completed handler you attach, but doesn't create an async function automatically. That's something you have to do.
Let me provide an example from Appendix A of my free ebook, Programming Windows Store Apps with HTML, CSS, and JavaScript, Second Edition, where Chapter 3 and Appendix A go into all the details about promises. Here's a function that does a long series of calculations using setImmediate to break up the work on the UI thread. (You could also use web workers to put the work on another thread, or use a WinRT component--but I'll leave it to my book to talk about those subjects, which is in Chapter 18 in the section "Implementing Asynchronous Methods").
function calculateIntegerSum(max, step) {
//The WinJS.Promise constructor's argument is a function that receives
//dispatchers for completed, error, and progress cases.
return new WinJS.Promise(function (completeDispatch, errorDispatch, progressDispatch) {
var sum = 0;
function iterate(args) {
for (var i = args.start; i < args.end; i++) {
sum += i;
};
if (i >= max) {
//Complete--dispatch results to completed handlers
Data.source = "Sum is <em>" + sum + "</em>";
completeDispatch(sum);
} else {
//Dispatch intermediate results to progress handlers
progressDispatch(sum);
setImmediate(iterate, { start: args.end, end: Math.min(args.end + step, max) });
}
}
setImmediate(iterate, { start: 0, end: Math.min(step, max) });
});
}
You can do something similar for your own process. The key here is that when your process is complete, you have to call the completeDispatch function that's given to your initializer. In the code above I'm also putting the result into Data.source.
With such a method, your createDataSource function can look like this:
function createDataSource() {
return calculateIntegerSum(100000, 2);
}
Because calculateIntegerSum returns a promise and is implemented to be async, createDataSource will return a promise that you can return from init:
init: function (element, options) {
return createDataSource();
},
I tried this out in a project and it works just fine, with the page loading waiting upon the calculations to complete.
Related
One little problem which JSONStore.add(data).then().fail()
The function initialiserBD() runs and returns success. The function remplireBD() doesn't return success. Surely, it is the function WL.JSONStore.get().add().then().fail()
Object "errorObject" send error :-50 PERSISTENT_STORE_NOT_OPEN
function wlCommonInit() {
initialiserBD();
remplireBD();
}
function initialiserBD() {
var collectionName="Personnes" ;
var collections = {};
collections[collectionName]= {};
collections[collectionName].searchFields={nom :'string'};
WL.JSONStore.init(collections).then(function(){})
.fail(function(errorObject) {
alert(errorObject.tostring());
});
}
function remplireBD(){
var data = {
nom :'Bill Gates'
};
var collectionName = 'Personnes';
WL.JSONStore.get(collectionName).add(data).then(function () {})
.fail(function (errorObject) {
alert(errorObject.toString());
});
}
I think your problem is two-fold...
You initialize the collection both before init and "after" init (var collectionName="Personnes" ;)
JavaScript is async, and you're calling initialiserBD and remplireBD one after the other instead of calling remplireBD in the success callback of initialiserBD, which could lead to trying to .get() before init() completed...
I have this script for countdown I wanted it to start as soon as the page loads (ideally i wanted it to run continuously since it is repeating in intervals).
when I call any function such as [startCountdown()] or [ countdown.start($('#countdown_clock').val());] I keep getting the same error [Uncaught ReferenceError: countdown is not defined]
here is the whole function
window.onload = function mainCountdown() {
var countdown = Tock({
countdown: true,
interval: 250,
callback: function () {
// console.log(countdown.lap() / 1000);
$('#countdown_clock').val(countdown.msToTime(countdown.lap()));
// countdown.start($('#countdown_clock').val());
},
complete: function () {
// console.log('end');
// alert("Time's up!");
repeatCountdown();
console.log('alarm');
}
});
$('#startCountdown').on('click', function () {
countdown.start($('#countdown_clock').val());
});
$('#pauseCountdown').on('click', function () {
countdown.pause();
});
$('#stopCountdown').on('click', function () {
countdown.stop();
});
$('#resetCountdown').on('click', function () {
countdown.stop();
$('#countdown_clock').val('00:10');
});
function repeatCountdown() {
countdown.stop();
$('#countdown_clock').val('00:10');
countdown.start($('#countdown_clock').val());
}
function startCountdown(){
countdown.start($('#countdown_clock').val());
}
}
How can I start the countdown without any button events.
Thank you in advance
You seem to have a few syntax issues that might be causing the problem. First, I'd call the window load event like this:
$(window).load(function () {
// functions, etc here
});
Then, inside the window load event, you can create your timer like this:
var countdown = new Tock(...
You were missing the 'var' and 'new' keywords. You might want to revisit the Tock documentation to make sure everything else was correct, too.
I currently have an issue with a file read in a Windows 8/WinRT application. I have a simple navigation style app, several pages have access to the same data and I have a data.js file that defines a namespace (Data) with a number of members. One part of the application saves items to a txt file stored in the applications local data folder. But on some of the other pages I need to read this in or check for the existence of an item within the list of previously saved items. To do this I added another method into the data.js file. The trouble is, when I call this method to check for the existence of an item, it doesn't return the value straight away due to the async nature, but the rest of code in the page specific js file still seems to execute before it jumps back into the parsing. This means that the logic to check for an item doesn't seem to work. I have a feeling it's down to my use of either .done or .then but my code is as follows:
DATA.JS
var doesItemExist= function(item_id){
var appFolder = Windows.Storage.ApplicationData.current.localFolder;
//note I've tried this with and without the first "return" statement
return appFolder.getFileAsync(dataFile).then(function (file) {
Windows.Storage.FileIO.readTextAsync(file).done(function (text) {
try {
var json = JSON.parse(text);
if (json) {
for (var i = 0; i < json.items.length; i++) {
var temp_item = json.items[i];
if (temp_item.id === item_id) {
return true;
break;
}
}
} else {
return false;
}
} catch (e) {
return false;
console.log(e);
}
}, function (e) { return false;console.log(e); });
}, function (e) { // error handling
return false;
console.log(e);
});
}
WinJS.Namespace.define("Data", {
doesItemExist: doesItemExist
}); //all of the above is wrapped in a self executing function
Then on Page.js I have the following:
var add = document.getElementById('add');
if (Data.doesItemExist(selected_item.id)) {
add.style.display = 'block';
} else {
add.style.display = 'none';
}
All the variables here are assigned and debugging doesn't produce any errors, control just appears to go back to the if/else statement after it hits the getFileAsync but before it even goes through the for loop. But subsequently it does go in to the for loop but after the if statement has finished. I'm guessing this is down to the async nature of it all, but I'm not sure how to get around it. Any ideas?
thanks
A Promise should work here.
I created a new Navigation app, and added a Data.js file containing the following code:
(function () {
var appData = Windows.Storage.ApplicationData;
function doesItemExist(item_id) {
return new WinJS.Promise(
function (completed, error, progress) {
var exists = false;
appData.current.localFolder.createFileAsync("data.txt", Windows.Storage.CreationCollisionOption.openIfExists).then(
function (file) {
Windows.Storage.FileIO.readTextAsync(file).then(
function (fileContents) {
if (fileContents) {
if (fileContents = "foo!") {
completed(true);
}
else {
completed(false);
}
}
else {
completed(false);
}
}
);
},
function (e) {
error(e);
}
);
}
);
}
WinJS.Namespace.define("Data", {
doesItemExist: doesItemExist
});
})();
Note that I've simplified the code for retrieving and parsing the file, since that's not really relevant to the problem. The important part is that once you've determined whether the item exists, you call completed(exists) which triggers the .then or .done of the Promise you're returning. Note that you'd call error(e) if an exception occurs, as I'm doing if there's an exception from the call to createFileAsync (I use this call rather than getFileAsync when I want to be able to either create a file if it does not exist, or return the existing file if it does, using the openIfExists option).
Then, in Home.js, I added the following code to the ready handler:
var itemExists;
var itemExistsPromise = Data.doesItemExist(42);
itemExistsPromise = itemExistsPromise.then(function (exists) {
itemExists = exists;
var content = document.getElementById("content");
content.innerText = "ItemExists is " + itemExists;
});
itemExistsPromise.done(function () {
var a = 42;
});
var b = 0;
The code above sets the variable itemExistsPromise to the returned promise from the function in Data.js, and then uses an anonymous function in the .then function of the Promise to set the variable itemExists to the Boolean value returned from the doesItemExist Promise, and grabs the <p> tag from Home.html (I added an id so I could get to it from code) and sets its text to indicate whether the item exists or not). Because I'm calling .then rather than .done, the call returns another promise, which is passed into the itemExistsPromise variable.
Next, I call itemExistsPromise.done to do any work that has to wait until after the work performed in the .then above it.
If you set a breakpoint on the lines "var a = 42" and "var b = 0" (only included for the purpose of setting breakpoints) as well as on the line "itemExists = exists", you should find that this gives you the control you need over when the various parts are executed.
Hope that helps!
Is there a methodology to test (potential) interleaving of asynchronous functions with vows?
For example:
// Topic portion
var user = new User('jacob')
user.set('email,'foo#bar.com')
user.save() // a
user.set('email',derp#cherp.com')
user.save() // b
user.refresh(this.callback) // Reload from database
// Callback
assert.equals(user.email,'derp#cherp.com')
There is could be a race condition between the two saves. When writing my tests I want to ensure that my API is ensuring that b finishes last (and that we have the correct final value for the email). With the way that's it written, the test will pass coincidentally some of the time.
Heres the example from the vows docs:
The nested contexts act as nested callbacks and pass the return arguments to the next context.
Docs: http://vowsjs.org/
{ topic: function () {
fs.stat('~/FILE', this.callback);
},
'after a successful `fs.stat`': {
topic: function (stat) {
fs.open('~/FILE', "r", stat.mode, this.callback);
},
'after a successful `fs.open`': {
topic: function (fd, stat) {
fs.read(fd, stat.size, 0, "utf8", this.callback);
},
'we can `fs.read` to get the file contents': function (data) {
assert.isString (data);
}
}
}
}
Here's my code. See the line that is commented out. When the element id (which is a span) is hardcoded, it works. When the id is created by concatenating the variables passed into stateChanged, it does not work. Am I not allowed to pass variables in to stateChanged? What's wrong?
function multiplePassportPoints(id, counter)
{
xmlhttp=GetXmlHttpObject();
if (xmlhttp==null)
{
alert ("Browser does not support HTTP Request");
return;
}
var url="addmorepoints.php";
url=url+"?id="+id+"&c="+counter;
url=url+"&sid="+Math.random();
xmlhttp.onreadystatechange=stateChanged(id,counter);
xmlhttp.open("GET",url,true);
xmlhttp.send(null);
}
function stateChanged(id, counter)
{
if (xmlhttp.readyState==4)
{
//THIS WORKS (assuming id is 99 and counter is 5:
//document.getElementById("99_5").innerHTML += xmlhttp.responseText;
//BUT I NEED IT TO WORK LIKE THIS:
document.getElementById(studentID+"_"+counter).innerHTML += xmlhttp.responseText;
}
}
Thanks!
You can change the code to this
xmlhttp.onreadystatechange = function () {
stateChanged(id,counter);
};
<script type="text/javascript">
var i=1;
function validate(str)
{
xmlHttp=GetXmlHttpObject()
var url="checkvalidate.php"
url=url+"?User9="+str
xmlHttp.onreadystatechange=stateChanged19
xmlHttp.open("GET",url,true)
xmlHttp.send(null)
}
function stateChanged19()
{
if (xmlHttp.readyState==4 || xmlHttp.readyState=="complete")
{
document.getElementById("valid"+i)
.innerHTML=xmlHttp.responseText
i=i+1;
} }
</script>
A better method which can be called multiple times before the previous call finishes. Notice that if you call multiplePassportPoints twice your previous value of xmlhttp will get overwritten. There are two outcomes:
1- everything works fine when no concurrency occurs (very high possibility),
2- first call never happens (very low possibility but it will happen from time to time and will be very hard to spot and reproduce)
But the following code uses local variable and might (not tested) be safe to call again and again.
function multiplePassportPoints(id, counter) {
var xmlhttp=GetXmlHttpObject();
if (xmlhttp==null)
{
alert ("Browser does not support HTTP Request");
return;
}
var url="addmorepoints.php";
url=url+"?id="+id+"&c="+counter;
url=url+"&sid="+Math.random();
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState==4) {
stateChanged(id, data, xmlhttp.responseText);
}
};
xmlhttp.open("GET",url,true);
xmlhttp.send(null);
}
function stateChanged(id, counter,text)
{
document.getElementById(id+"_"+counter).innerHTML += text;
}