Why can't $(document).ready() be inside API call? - javascript

FYI Trial 1 does not work, but Trial 2 works.
I understand that getJSON is executes asynchronously, but I don't actually understand how it applies to the code I've written.
What can I learn about asynchronous execution from this?
Why do I have to separate the getJSON call into a function separate from ready()?
FOR TRIAL 2:
How can I write this code so I don't have to initialize functions inside of getJSON? If there isn't a way, how can I write this code to be more robust?
/*
//TRIAL 1
$(document).ready(function(){
console.log("loaded");
$.getJSON(url, function(json){
var fahrenheit = true;
getLocation(json);
getTemperature(fahrenheit, json);
$("#unit").on("click", function(){
fahrenheit = !fahrenheit;
getTemperature(fahrenheit, json);
});
getWeather(json);
});
});
//Gets current weather conditions from current_observation
function getWeather(json){
var currWeather = "";
var iconURL = "";
currWeather=json.current_observation.weather;
iconURL=json.current_observation.icon_url;
$("#icon").attr("src", iconURL);
$("#weather").html(currWeather);
};
//Gets current temperature from current_observation
function getTemperature(fahrenheit, json){
var currTemp = 0;
if(fahrenheit){
currTemp+=json.current_observation.temp_f;
currTemp+="&#8457";
} else{
currTemp+=json.current_observation.temp_c;
currTemp+="&#8451";
}
$("#temperature").html(currTemp);
};
//Gets city, state, country, zip, latitude, and longitude from location
function getLocation(json){
var currLocation=["city", "state", "country", "zip", "lat", "lon"];
var locationHTML = "";
currLocation[0] = json.location.city;
currLocation[1] = json.location.state;
currLocation[2] = json.location.country_name;
currLocation[3] = json.location.zip;
currLocation[4] = json.location.lat;
currLocation[5] = json.location.lon;
locationHTML += currLocation[0]+", "+currLocation[1]+", "+currLocation[2]+" " +currLocation[3]+"<br>";
locationHTML += "Latitude: "+currLocation[4]+"<br>Longitude: "+currLocation[5];
$("#location").html(locationHTML);
};
*/
//TRIAL 2
$(document).ready(function(){
console.log("loaded");
dispWeather();
});
function dispWeather(){
console.log("inside dispWeather");
//Retrieve json from weather underground
var url = "https://api.wunderground.com/api/19c5c96f0b140c0f/geolookup/conditions/q/autoip.json";
$.getJSON(url, function(json){
console.log("Got JSON");
console.log(json);
var fahrenheit = true;
getLocation(json);
getTemperature(fahrenheit, json);
$("#unit").on("click", function(){
fahrenheit = !fahrenheit;
getTemperature(fahrenheit, json);
});
getWeather(json);
//Gets current weather conditions from current_observation
function getWeather(json){
var currWeather = "";
var iconURL = "";
currWeather=json.current_observation.weather;
iconURL=json.current_observation.icon_url;
$("#icon").attr("src", iconURL);
$("#weather").html(currWeather);
};
//Gets current temperature from current_observation
function getTemperature(fahrenheit, json){
var currTemp = 0;
if(fahrenheit){
currTemp+=json.current_observation.temp_f;
currTemp+="&#8457";
} else{
currTemp+=json.current_observation.temp_c;
currTemp+="&#8451";
}
$("#temperature").html(currTemp);
};
//Gets city, state, country, zip, latitude, and longitude from location
function getLocation(json){
var currLocation=["city", "state", "country", "zip", "lat", "lon"];
var locationHTML = "";
currLocation[0] = json.location.city;
currLocation[1] = json.location.state;
currLocation[2] = json.location.country_name;
currLocation[3] = json.location.zip;
currLocation[4] = json.location.lat;
currLocation[5] = json.location.lon;
locationHTML += currLocation[0]+", "+currLocation[1]+", "+currLocation[2]+" " +currLocation[3]+"<br>";
locationHTML += "Latitude: "+currLocation[4]+"<br>Longitude: "+currLocation[5];
$("#location").html(locationHTML);
};
})
};

.ready() jQuery Documentation
Specify a function to execute when the DOM is fully loaded.
What can I learn about asynchronous execution from this?
Your learning that you don't know when the document is going to be ready() so we wait until the event completes before beginning execution on our application. You also learned that you have to wait for $.getJSON to fetch json then you process the data.
Why do I have to separate the getJSON call into a function separate from ready()?
As specified above .ready() is waiting for the DOM to be fully loaded, then we start the application. So when we are "ready" lets fetch the weather data. The document is only ready one time when the DOM is fully loaded.
How can I write this code so I don't have to initialize functions inside of getJSON?
Without you being specific, I'm assuming your problem here was with toggling the degrees between celsius and fahrenheit. After we load the weather you can store the data in a variable outside of the scope of the function, this way when you click to change the degrees you can pass in the same data without having to call the api again (although at this point the weather could have changed)
how can I write this code to be more robust?
I've included a JS Bin that alters your code. The biggest problem was bad naming conventions and not keeping things simple. Example getWeather() was not "getting weather" it was setting html from data we got from $.getJSON which was invoked in your ready() instead of breaking it out into another function we could call later on.
For the most part this is how the code reads now, clear function names help quickly see what this code is supposed to do.
$(document).ready(function() {
renderWeather();
});
var state = {
fahrenheit: true,
data: {}
};
function renderWeather() {
getWeatherJSON()
.then(data => {
state.data = data;
setWeatherHTML(data);
setTemperatureHTML(data, state.fahrenheit);
setLocationHTML(data);
})
.catch(err => console.log(err));
}
http://jsbin.com/cekite/edit?js,output
If we wanted to take this a step further we could create a WeatherAPI prototype concealing our html render functions and extend it with a WeatherUndergroudAPI prototype, this way if we ever change our weather service we should only have to implement a format function to marshall the data the way the WeatherAPI expects it to be in.
class WeatherAPI {
constructor(opt) {
...
}
init() {
...
}
get() {
... feteches json from endpoint provided
}
renderWeather() {
this.get()
.then(this.formatter.bind(this))
.then(this.setWeatherData.bind(this))
.then(this.renderWeatherHTML.bind(this))
.then(this.renderTemperatureHTML.bind(this))
.then(this.renderLocationHTML.bind(this))
.catch(err => console.log(err));
}
formatter(data) {
...
}
setWeatherData(data) {
...
}
renderWeatherHTML() {
...
}
renderTemperatureHTML() {
...
}
renderLocationHTML() {
...
}
}
Extending the WeatherAPI is then a matter of passing in a new endpoint to get data from. Or in this case overriding the WeatherAPI get method and returning static data.
class FakeWeatherAPI extends WeatherAPI {
constructor(opt = {}) {
super(opt);
}
get() {
return new Promise((resolve) => {
const data = {
someReallyWeirdKeyForACity: 'San Francisco',
state: 'CA',
country: 'US',
lat: '37.77999878',
lon: '122.41999817',
f: '1000',
c: '25',
icon: 'http://www.nyan.cat/cats/zombie.gif',
weather: 'Acid Rain'
};
resolve(data);
});
}
formatter(data) {
const formattedData = {
city: data.someReallyWeirdKeyForACity,
state: data.state,
country: data.country,
lat: data.lat,
lon: data.lon,
temperature: {
fahrenheit: data.f,
celsius: data.c
},
icon: data.icon,
weather: data.weather
};
return formattedData;
}
}
Our application init code then becomes.
$(document).ready(init);
function init(){
const weatherAwesomeService = new FakeWeatherAPI();
weatherAwesomeService.init();
};
Here is a working jsbin for they above
http://jsbin.com/sicofe/edit?js,output

Related

How to make jQuery constructor properties globally visible

I am trying to get position coordinate variables using the standard Navigator.geolocation property with jquery, so i can use the value later in my code:
$(document).ready(function(){
$.getlocation = function(){
navigator.geolocation.getCurrentPosition($.getPosition,$.error);
}
$.getVariables = function(lat,lon){
this.lat = lat; // i want these to be visible
this.lon = lon;
}
$.getPosition= function(position){
console.log("latitude:" +position.coords.latitude+ " longitude: "+position.coords.longitude);
//this function will be executed once position is determined.
$.getVariables(position.coords.latitude,position.coords.longitude);
}
$.error = function(){alert("error");}
$.getlocation(); // outputs correctly
setTimeout(()=>{console.log(this.lat)},5000); // undefined
});
I expect to get location output but instead i get undefined from console.log(this.lat), i did try this in vanilla javascript and it works fine, here is the javascript code:
function locateMe() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(getPosition, error);
} else {
alert("connection problem");
}
}
let vars = function(lat, lon) {
this.lat = lat;
this.lon = lon;
}
let getPosition = function(position) {
vars(position.coords.latitude, position.coords.loongitude);
}
let error = function(msg) {
console.log("problem");
}
locateMe();
setTimeout(() => { console.log(this.lat); }, 5000); //correct output
I could get this.lat working if I changed getVariables to:
$.getVariables = function(lat,lon){
document.lat = lat;
document.lon = lon;
}
It appears that the two this objects may refer to different things in the two methods.

Node.Js doesn't execute anonymous functions?? -aws lambda -alexa skill

I'm currently working on a Alexa Skill for my Smart Home.
I created a Lambda function and want to make a http request to a server. but it wont execute any of my anon. functions.
A lambda code example:
/* This code has been generated from your interaction model by skillinator.io
/* eslint-disable func-names */
/* eslint quote-props: ["error", "consistent"]*/
// There are three sections, Text Strings, Skill Code, and Helper Function(s).
// You can copy and paste the contents as the code for a new Lambda function, using the alexa-skill-kit-sdk-factskill template.
// This code includes helper functions for compatibility with versions of the SDK prior to 1.0.9, which includes the dialog directives.
// 1. Text strings =====================================================================================================
// Modify these strings and messages to change the behavior of your Lambda function
const request = require("request");
let speechOutput;
let reprompt;
let welcomeOutput = "Hallo xyz!";
let welcomeReprompt = "xy";
// 2. Skill Code =======================================================================================================
"use strict";
const Alexa = require('alexa-sdk');
const APP_ID = "my id"; // TODO replace with your app ID (OPTIONAL).
speechOutput = '';
const handlers = {
'LaunchRequest': function () {
this.emit(':ask', welcomeOutput, welcomeReprompt);
},
'AMAZON.HelpIntent': function () {
speechOutput = 'Placeholder response for AMAZON.HelpIntent.';
reprompt = '';
this.emit(':ask', speechOutput, reprompt);
},
'AMAZON.CancelIntent': function () {
speechOutput = 'Placeholder response for AMAZON.CancelIntent';
this.emit(':tell', speechOutput);
},
'AMAZON.StopIntent': function () {
speechOutput = 'Placeholder response for AMAZON.StopIntent.';
this.emit(':tell', speechOutput);
},
'SessionEndedRequest': function () {
speechOutput = '';
//this.emit(':saveState', true);//uncomment to save attributes to db on session end
this.emit(':tell', speechOutput);
},
'LichtIntent': function () {
//delegate to Alexa to collect all the required slot values
let filledSlots = delegateSlotCollection.call(this);
speechOutput = '';
//any intent slot variables are listed here for convenience
let zimmerSlotRaw = this.event.request.intent.slots.zimmer.value;
console.log(zimmerSlotRaw);
let zimmerSlot = resolveCanonical(this.event.request.intent.slots.zimmer);
console.log(zimmerSlot);
let was_lichtSlotRaw = this.event.request.intent.slots.was_licht.value;
console.log(was_lichtSlotRaw);
let was_lichtSlot = resolveCanonical(this.event.request.intent.slots.was_licht);
console.log(was_lichtSlot);
//THIS IS THE PART WHERE I NEED HELP!!
MakeRequest(function(data){
console.log("asddd");
speechOutput = "This is a place holder response for the intent named LichtIntent, which includes dialogs. This intent has 2 slots, which are zimmer, and was_licht. Anything else?";
var speechOutput = data;
this.emit(':ask', speechOutput, speechOutput);
});
console.log("asdww");
//DOWN TO HERE!!
},
'StromIntent': function () {
//delegate to Alexa to collect all the required slot values
let filledSlots = delegateSlotCollection.call(this);
speechOutput = '';
//any intent slot variables are listed here for convenience
let geraet_stromSlotRaw = this.event.request.intent.slots.geraet_strom.value;
console.log(geraet_stromSlotRaw);
let geraet_stromSlot = resolveCanonical(this.event.request.intent.slots.geraet_strom);
console.log(geraet_stromSlot);
let wasSlotRaw = this.event.request.intent.slots.was.value;
console.log(wasSlotRaw);
let wasSlot = resolveCanonical(this.event.request.intent.slots.was);
console.log(wasSlot);
//Your custom intent handling goes here
speechOutput = "This is a place holder response for the intent named StromIntent, which includes dialogs. This intent has 2 slots, which are geraet_strom, and was. Anything else?";
this.emit(':ask', speechOutput, speechOutput);
},
'FrageIntent': function () {
//delegate to Alexa to collect all the required slot values
let filledSlots = delegateSlotCollection.call(this);
speechOutput = '';
//any intent slot variables are listed here for convenience
let geraetSlotRaw = this.event.request.intent.slots.geraet.value;
console.log(geraetSlotRaw);
let geraetSlot = resolveCanonical(this.event.request.intent.slots.geraet);
console.log(geraetSlot);
let was_frageSlotRaw = this.event.request.intent.slots.was_frage.value;
console.log(was_frageSlotRaw);
let was_frageSlot = resolveCanonical(this.event.request.intent.slots.was_frage);
console.log(was_frageSlot);
//Your custom intent handling goes here
speechOutput = "This is a place holder response for the intent named FrageIntent, which includes dialogs. This intent has 2 slots, which are geraet, and was_frage. Anything else?";
this.emit(':ask', speechOutput, speechOutput);
},
'TuerIntent': function () {
//delegate to Alexa to collect all the required slot values
let filledSlots = delegateSlotCollection.call(this);
speechOutput = '';
//any intent slot variables are listed here for convenience
let zeitSlotRaw = this.event.request.intent.slots.zeit.value;
console.log(zeitSlotRaw);
let zeitSlot = resolveCanonical(this.event.request.intent.slots.zeit);
console.log(zeitSlot);
//Your custom intent handling goes here
speechOutput = "This is a place holder response for the intent named TuerIntent, which includes dialogs. This intent has one slot, which is zeit. Anything else?";
this.emit(':ask', speechOutput, speechOutput);
},
'Unhandled': function () {
speechOutput = "The skill didn't quite understand what you wanted. Do you want to try something else?";
this.emit(':ask', speechOutput, speechOutput);
}
};
exports.handler = (event, context) => {
const alexa = Alexa.handler(event, context);
alexa.appId = APP_ID;
// To enable string internationalization (i18n) features, set a resources object.
//alexa.resources = languageStrings;
alexa.registerHandlers(handlers);
//alexa.dynamoDBTableName = 'DYNAMODB_TABLE_NAME'; //uncomment this line to save attributes to DB
alexa.execute();
};
// END of Intent Handlers {} ========================================================================================
// 3. Helper Function =================================================================================================
//THESE ARE MY HELPER FUNCTIONS
function url(){
console.log("asd");
return " my server ip";
}
function MakeRequest(callback){
console.log("hallo!");
request.get(url(), function(error, response, body){
console.log("****************************************");
console.log(response);
console.log("****************************************");
console.log(error);
console.log("****************************************");
console.log(body);
console.log("****************************************");
callback("erfolg!");
});
console.log("hffggh");
}
//DOWN TO HERE!!
function resolveCanonical(slot){
//this function looks at the entity resolution part of request and returns the slot value if a synonyms is provided
let canonical;
try{
canonical = slot.resolutions.resolutionsPerAuthority[0].values[0].value.name;
}catch(err){
console.log(err.message);
canonical = slot.value;
};
return canonical;
};
function delegateSlotCollection(){
console.log("in delegateSlotCollection");
console.log("current dialogState: "+this.event.request.dialogState);
if (this.event.request.dialogState === "STARTED") {
console.log("in Beginning");
let updatedIntent= null;
// updatedIntent=this.event.request.intent;
//optionally pre-fill slots: update the intent object with slot values for which
//you have defaults, then return Dialog.Delegate with this updated intent
// in the updatedIntent property
//this.emit(":delegate", updatedIntent); //uncomment this is using ASK SDK 1.0.9 or newer
//this code is necessary if using ASK SDK versions prior to 1.0.9
if(this.isOverridden()) {
return;
}
this.handler.response = buildSpeechletResponse({
sessionAttributes: this.attributes,
directives: getDialogDirectives('Dialog.Delegate', updatedIntent, null),
shouldEndSession: false
});
this.emit(':responseReady', updatedIntent);
} else if (this.event.request.dialogState !== "COMPLETED") {
console.log("in not completed");
// return a Dialog.Delegate directive with no updatedIntent property.
//this.emit(":delegate"); //uncomment this is using ASK SDK 1.0.9 or newer
//this code necessary is using ASK SDK versions prior to 1.0.9
if(this.isOverridden()) {
return;
}
this.handler.response = buildSpeechletResponse({
sessionAttributes: this.attributes,
directives: getDialogDirectives('Dialog.Delegate', null, null),
shouldEndSession: false
});
this.emit(':responseReady');
} else {
console.log("in completed");
console.log("returning: "+ JSON.stringify(this.event.request.intent));
// Dialog is now complete and all required slots should be filled,
// so call your normal intent handler.
return this.event.request.intent;
}
}
function randomPhrase(array) {
// the argument is an array [] of words or phrases
let i = 0;
i = Math.floor(Math.random() * array.length);
return(array[i]);
}
function isSlotValid(request, slotName){
let slot = request.intent.slots[slotName];
//console.log("request = "+JSON.stringify(request)); //uncomment if you want to see the request
let slotValue;
//if we have a slot, get the text and store it into speechOutput
if (slot && slot.value) {
//we have a value in the slot
slotValue = slot.value.toLowerCase();
return slotValue;
} else {
//we didn't get a value in the slot.
return false;
}
}
//These functions are here to allow dialog directives to work with SDK versions prior to 1.0.9
//will be removed once Lambda templates are updated with the latest SDK
function createSpeechObject(optionsParam) {
if (optionsParam && optionsParam.type === 'SSML') {
return {
type: optionsParam.type,
ssml: optionsParam['speech']
};
} else {
return {
type: optionsParam.type || 'PlainText',
text: optionsParam['speech'] || optionsParam
};
}
}
function buildSpeechletResponse(options) {
let alexaResponse = {
shouldEndSession: options.shouldEndSession
};
if (options.output) {
alexaResponse.outputSpeech = createSpeechObject(options.output);
}
if (options.reprompt) {
alexaResponse.reprompt = {
outputSpeech: createSpeechObject(options.reprompt)
};
}
if (options.directives) {
alexaResponse.directives = options.directives;
}
if (options.cardTitle && options.cardContent) {
alexaResponse.card = {
type: 'Simple',
title: options.cardTitle,
content: options.cardContent
};
if(options.cardImage && (options.cardImage.smallImageUrl || options.cardImage.largeImageUrl)) {
alexaResponse.card.type = 'Standard';
alexaResponse.card['image'] = {};
delete alexaResponse.card.content;
alexaResponse.card.text = options.cardContent;
if(options.cardImage.smallImageUrl) {
alexaResponse.card.image['smallImageUrl'] = options.cardImage.smallImageUrl;
}
if(options.cardImage.largeImageUrl) {
alexaResponse.card.image['largeImageUrl'] = options.cardImage.largeImageUrl;
}
}
} else if (options.cardType === 'LinkAccount') {
alexaResponse.card = {
type: 'LinkAccount'
};
} else if (options.cardType === 'AskForPermissionsConsent') {
alexaResponse.card = {
type: 'AskForPermissionsConsent',
permissions: options.permissions
};
}
let returnResult = {
version: '1.0',
response: alexaResponse
};
if (options.sessionAttributes) {
returnResult.sessionAttributes = options.sessionAttributes;
}
return returnResult;
}
function getDialogDirectives(dialogType, updatedIntent, slotName) {
let directive = {
type: dialogType
};
if (dialogType === 'Dialog.ElicitSlot') {
directive.slotToElicit = slotName;
} else if (dialogType === 'Dialog.ConfirmSlot') {
directive.slotToConfirm = slotName;
}
if (updatedIntent) {
directive.updatedIntent = updatedIntent;
}
return [directive];
}
I use the "request" module for my http-request, I think you all know this module.
When I test my function, it gives me NO runtime error!
but the anonymous functions from the MakeRequest call and the request.get call wont execute, and I don't know why.
For Example the console output:
Hallo!,
Asd (very funny.. it gets the params for the request.get.. the bug has to be between the second param(the anon function itself?) in the call and the start of the anon function),
hffggh,
asdww
-the console.logs in the anon. functions doesn't show.
I maybe undestand why the MakeRequest funct. doesn't run -> because the callback from it never came. But why does the request.get not work?? I pasted the necessary parts (the MakeRequest and the helper functions) in a normal node projekt, and it worked!!? -facepalm.... im nearly crying..
My Goal: I want to receive a response from the server.. - thats all. But it just wont go into the function where I can access the response object(s).
(Scroll down to the helper functions)
Please help me out guys, I could really hit my head against the wall.
Reguards

How do I get a value back from a custom dojo module?

I'm working through the process of modulization on an app that I have written. This works with spatial location
I'm using an event to query for the user's lat / lon position for use inside the application. My calling snippet is below (button click starts it up)
<script>
require([
'dojo/dom',
'dojo/_base/array',
'demo/testModule',
'esri/SpatialReference',
'esri/geometry/Point'
], function (
dom,
arrayUtils,
testModule,
SpatialReference,
Point
) {
//Here is the button click listener
$('#whereAmIButton').click(function () {
var spatialRef = new esri.SpatialReference({ 'wkid': 4326 });
//variable I want to set to a returned geometry.
var myGeom;
//This runs but I'm missing the boat on the return of a value
testModule.findUserLocPT(spatialRef);
//var myModule = new testModule(); //not a constructor
});
});
</script>
Here is the custom module. It logs the information to the console for the user's location. But I want to return the value for setting the 'myGeom' variable.
define(['dojo/_base/declare','dojo/_base/lang','dojo/dom',
'esri/geometry/Point','esri/SpatialReference'], function (
declare, lang, dom, Point, SpatialReference) {
return {
findUserLocPT: function (spatialRef) {
var geom;
var location_timeout = setTimeout("geolocFail()", 5000);
navigator.geolocation.getCurrentPosition(function (position) {
clearTimeout(location_timeout);
var lat = position.coords.latitude;
var lon = position.coords.longitude;
setTimeout(function () {
geom = new Point(lon, lat, spatialRef);
//console.log writes out the geom but that isnt what I am after
console.log(geom);
//I want to return this value
return geom;
}, 500);
});
function geolocFail() {
console.log("GeoLocation Failure");
}
}
}//end of the return
});
Any help would be welcome. I can by reference back change textual/html values on the document but am not getting things back as a variable.
Andy
Ok, I don't know if this is the 'best' answer but I have one now.
I added a global variable inside the 'test.html' page
<script>
var theGeom; //This is the variable
require([
'dojo/dom',
here is where I am setting the value of this variable for use in the original dojo 'require' code block. This is coming from the 'testModule.js'
setTimeout(function () {
geom = new Point(lon, lat, spatialRef);
theGeom = geom; //Here is the feedback of the value to the global variable.
return myGeom;
}, 500);
$('#whereAmIButton').click(function () {
var spatialRef = new esri.SpatialReference({'wkid':4326});
testModule.findUserLocPT(spatialRef);
setTimeout(function () {
console.log(theGeom); //here is the value set and ready to use
},2000);
});
I'm not sure if this is the best way. If you have something better please let me know.
Andy

Access variable from another JS file

I have two JS files, base_player.js and station.js.
I have some logic in base_player.js that needs access to a value that comes from station.js. While the var is 'global', it is wrapped in a namepace mm.Station. The value I need is a UUID of a selected station:
mm.Station = function($el) {
"use strict";
if (_.isUndefined($el)) {
return;
}
var self = mm.EventEmitter();
var $actions;
self.uuid = $el.attr("data-uuid");
.........
I feed any selected station with songs in batches at a certain point in a queue. This logic all happens in base_player.js.
I want to get the UUID from station.js to use in this GET in base_player.js:
mm.BasePlayer = function ($el) {
........
function feedSelectedStation(uuid) {
if(_.isUndefined(uuid)){
return false;
}
$.get('/feed-station/' + uuid)
.done(function (data) {
console.log('feedSelectedStation');
if(!_.has(data, "list")){ return; }
self.queue = self.queue.concat(data.list);
});
}
.........
I don't write much Javascript so I'm a bit unsure how to achieve this. Any pointers appreciated.

Waiting to initialize untill data is loaded asynchronously

I am trying to design a personal app which loads data asynchronously and then displays a grid according to the windows 8.1 store apps.
i'm running into the issue that my ui is trying to execute before my data is loaded.
my current code:
(function () {
"use strict";
var asyncInProgress = true;
var groupedItems;
var list;
var observable;
var matches = new WinJS.Binding.List();
var matchGroups = new WinJS.Binding.List();
var BattleGrounds = new WinJS.Binding.List();
list = getData();
initGroups(list);
function initGroups(l) {
var groupedItems = list.createGrouped(
function groupKeySelector(item) { return item.group.key; },
function groupDataSelector(item) { return item.group; }
);
}
WinJS.Namespace.define("Data", {
Observable: WinJS.Class.define(function () {
this.dispatch = function () {
this.dispatchEvent("dataReady");
}
}),
getObservable: getObservable,
items: groupedItems,
groups: groupedItems.groups,
getItemReference: getItemReference,
getItemsFromGroup: getItemsFromGroup,
resolveGroupReference: resolveGroupReference,
resolveItemReference: resolveItemReference,
updateData: updateData,
getAsyncStatus: getAsyncStatus
});
WinJS.Class.mix(Data.Observable, WinJS.Utilities.eventMixin);
WinJS.Class.mix(Data.Observable, WinJS.Utilities.createEventProperties("dataReady"));
// Provides support for event listeners.
function getObservable() {
observable = new Data.Observable();
return observable;
}
// Get a reference for an item, using the group key and item title as a
// unique reference to the item that can be easily serialized.
function getItemReference(item) {
return [item.group.key, item.title, item.backgroundImage];
}
// This function returns a WinJS.Binding.List containing only the items
// that belong to the provided group.
function getItemsFromGroup(group) {
return list.createFiltered(function (item) { return item.group.key === group.key; });
}
// Get the unique group corresponding to the provided group key.
function resolveGroupReference(key) {
return groupedItems.groups.getItemFromKey(key).data;
}
// Get a unique item from the provided string array, which should contain a
// group key and an item title.
function resolveItemReference(reference) {
for (var i = 0; i < groupedItems.length; i++) {
var item = groupedItems.getAt(i);
if (item.group.key === reference[0] && item.title === reference[1]) {
return item;
}
}
}
function updateData() {
asyncInProgress = true;
BattleGrounds.splice(0, matches.length);
BattleGrounds._currentKey = 0;
groupedItems = null;
list = getData();
initGroups(list);
}
function getAsyncStatus() {
return asyncInProgress;
}
function getData() {
var darkGray = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXY3B0cPoPAANMAcOba1BlAAAAAElFTkSuQmCC";
var lightGray = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXY7h4+cp/AAhpA3h+ANDKAAAAAElFTkSuQmCC";
var mediumGray = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXY5g8dcZ/AAY/AsAlWFQ+AAAAAElFTkSuQmCC";
var url = 'https://api.guildwars2.com/v1/wvw/matches.json';
acquireSyndication(url).then(function (response) {
// Remove any invalid characters from JSONp response.
var fixedResponse = response.responseText.replace(/\\'/g, "'");
var jsonObj = JSON.parse(fixedResponse);
jsonObj.wvw_matches.forEach(function (battle) {
var anet_id = value.wvw_match_id;
// Create Group
var matchGroup = {
key: anet_id,
title: anet_id
};
matchGroups.push(matchGroup);
// Get Details
acquireSyndication("https://api.guildwars2.com/v1/wvw/match_details.json?match_id=" + anet_id).then(function (json) {
var fixedJson = json.responseText.replace(/\\'/g, "'");
var obj = JSON.parse(fixedJson);
fixedJson.maps.forEach(function (value) {
BattleGrounds.push({
group: matchGroup, key: matchGroup.title, title: value.type,
subtitle: value.type, map: "eb", description: "NA", content: "NA", "type": value.type,
"scores": value.scores, "objectives": value.objectives, "bonuses": value.bonuses, backgroundImage: lightGray
});
});
}, function (error) {
var x = error.getAllResponseHeaders();
var matchGroup = matchGroups[0];
for (var i = 0; i < matchGroups.length; i++) {
flickrPosts.push({
group: matchGroups[i], key: matchGroup.title, title: "Error loading",
subtitle: "Error", backgroundImage: lightGray, published: "N/A", description: "N/A"
});
}
asyncInProgress = false;
observable.dispatch();
});
});
}, function (error) {
var x = error.getAllResponseHeaders();
var matchGroup = matchGroups[0];
for (var i = 0; i < matchGroups.length; i++) {
flickrPosts.push({
group: matchGroups[i], key: matchGroup.title, title: "Error loading",
subtitle: "Error", backgroundImage: lightGray, published: "N/A", description: "N/A"
});
}
asyncInProgress = false;
observable.dispatch();
});
return BattleGrounds;
}
function acquireSyndication(url) {
return WinJS.xhr({
url: url,
headers: { "If-Modified-Since": "Mon, 27 Mar 1972 00:00:00 GMT" }
});
}
})();
This errors out on groups: groupedItems.groups. which says that groups is undefined.
i know this is because the data is still being processed.
How am i going to work around this?
i took a look at the promise object but the entire concept confuses me as i don't know enough about the infrastructure of a windows 8 app.
The core of your problem is in the getData() function - it is not returning your data because it uses asynchronous calls to get the data. The data is not yet available when it returns. It appears that that function makes several asynchronous calls to get data (using acquireSyndication()). When those asynchronous functions finish sometime in the future, you then put that data into matchGroups and then later into BattleGrounds after more calls to acquireSyndication().
What you're doing is quite messy so there isn't a simple fix. Conceptually, you need to process the BattleGrounds data from the completion handler of the asynchronous code and ALL code that uses it must continue from inside that completion handler, not after the getData() call. You cannot call getData() and use it like a synchronous function because it's asynchronous. This requires asynchronous programming techniques.
If you are doing multiple asynchronous calls and trying to carry out some action after all of them have completed (which I think is what you're doing), then you will need to code specifically for that condition too. You can either use promises or you can keep a counter of how many ajax calls there are and in each completion function, you increment the counter and see if this is the last one that just completed and, if so, then you can process all the data and continue executing the rest of your code.
I would also suggest that you don't use promises in one part of a function and then completion callbacks in the very next part. Use one of the other, not a mixture, to keep your code clean.

Categories

Resources