Q.js - how to access a cached element - javascript

Ok, so I'm using Q.js to help with keeping users notified of a potentially long calculation going on asynchronously (using setTimeout with zero delay to repeatedly call a function a set number of times).
However, this does not work:
var status = d3.select('#status');
var t = transfer({
N: 100
});
t.then(function (values) {
status.text('');
column_chart(values);
}, function (error) {
status.text('There was an error calculating the transfer probabilities')
}, function (progress) {
status.text('Calculating ' + Math.round(progress) + '%');
});
For some reason the status element is undefined inside the resolve, reject and progress functions. However if I replace each status.text with d3.select('#status').text then it all works fine. I'm a bit confused as to why I can't used a cached element?

OK, there was a problem with the name of my cached element!
var status = d3.select('#status');
Changing its name fixed the problem
var statusEl = d3.select('#status');
It turns out inside my promise functions status was the raw HTML element which of course had no text method. This is that weird thing where all DOM elements with ID's are added to the global window object...

Related

JS Script Returning Undefined Instead of Expected String

I basically have been making a program that will go through a website and grab certain images. This is not too hard, just a little complex. I was running the JS code below to click onto the correct page, check for the gallery element to load every 100ms, then finally log the entire object as a string. Most works, but when getting the console.log(...); I get undefined as the main return, and I get the string afterwards as a secondary. This will not due because my program can only intercept the main logged value, not any secondaries. Below is my code and an example output. Thank you!
Code:
document.getElementById('tlcItem_47871').click();
var checkExist = setInterval(function() {
if ($('.pgFooterThumblist').length) {
clearInterval(checkExist);
var ele = document.getElementsByClassName('pgFooterThumb');
console.log(JSON.stringify(ele));
}
}, 100);
Ex Output:
/*Primary Output(First)*/<- undefined
/*Secondary Output(Second)*/<- {"0":{},"1":{},"2":{},"3":{},"4":{},"5":{},"6":{},"7":{},"8":{},"9":{},"10":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{},"21":{},"22":{},"23":{},"24":{},"25":{},"26":{},"27":{},"28":{},"29":{},"30":{},"31":{},"32":{},"33":{},"34":{},"35":{},"36":{},"37":{},"38":{},"39":{},"40":{},"41":{},"42":{},"43":{},"44":{},"45":{},"46":{},"47":{},"48":{},"49":{},"50":{},"51":{},"52":{},"53":{},"54":{},"55":{},"56":{},"57":{},"58":{},"59":{},"60":{},"61":{},"62":{},"63":{},"64":{},"65":{},"66":{},"67":{},"68":{},"69":{},"70":{},"71":{},"72":{},"73":{},"74":{},"75":{},"76":{},"77":{},"78":{},"79":{},"80":{},"81":{},"82":{},"83":{},"84":{},"85":{},"86":{},"87":{},"88":{},"89":{},"90":{},"91":{},"92":{},"93":{},"94":{},"95":{},"96":{},"97":{},"98":{},"99":{},"100":{},"101":{},"102":{},"103":{},"104":{},"105":{},"106":{},"107":{},"108":{},"109":{},"110":{},"111":{},"112":{},"113":{},"114":{},"115":{},"116":{},"117":{},"118":{},"119":{},"120":{},"121":{},"122":{},"123":{},"124":{},"125":{},"126":{},"127":{},"128":{},"129":{},"130":{},"131":{},"132":{},"133":{},"134":{},"135":{},"136":{},"137":{},"138":{},"139":{},"140":{},"141":{},"142":{},"143":{},"144":{},"145":{},"146":{},"147":{},"148":{},"149":{},"150":{},"151":{},"152":{},"153":{},"154":{},"155":{},"156":{},"157":{},"158":{},"159":{},"160":{},"161":{},"162":{},"163":{},"164":{},"165":{},"166":{},"167":{},"168":{},"169":{},"170":{},"171":{},"172":{},"173":{},"174":{},"175":{},"176":{},"177":{},"178":{},"179":{},"180":{},"181":{},"182":{},"183":{},"184":{},"185":{},"186":{},"187":{},"188":{},"189":{},"190":{},"191":{},"192":{},"193":{},"194":{},"195":{},"196":{},"197":{},"198":{},"199":{},"200":{},"201":{},"202":{},"203":{},"204":{},"205":{},"206":{},"207":{},"208":{},"209":{},"210":{},"211":{},"212":{},"213":{},"214":{},"215":{},"216":{},"217":{},"218":{},"219":{}}
EDIT: In the workflow that I have I am able to set my JS script in my c++, and then I can run javascript into a loaded webpage. The only issue is that the runJavascript() function will take the first value outputted and use that. No way around it so I need the string to be outputted ASAP.
THIS IS NOT A CORRECT ANSWER. I AM LEAVING IT FOR CONTEXT ONLY.
Assuming that all that is need is to avoid console logging an undefined value, this would suffice:
document.getElementById('tlcItem_47871').click();
var checkExist = setInterval(function() {
if ($('.pgFooterThumblist').length) {
clearInterval(checkExist);
var ele = document.getElementsByClassName('pgFooterThumb');
var loggedOutput = JSON.stringify(ele);
if (loggedOutput) {
console.log(loggedOutput);
}
}
}, 100);
This is assuming that somehow your C code intercepts this console log. Generally in JavaScript if you need a value from a function you need to pass it back to the caller using a return statement, but it seems that perhaps you have a unique case in which this is not required.

Javascript callbacks gets processed faster than others

I'm using the javascript sdk plugin for facebook to create a feed on my webpage.
The problem is that sometimes during load the feed gets unordered, even if i have setup a callback chain.
I think it gets unordered because sometimes the "second" async call gets processed faster than the "first" async call.
This is the first time i've been using callbacks, am i doing it right?
How can i solve the feed gets unordered if some calls finish faster than others?
The code below is only the relevant code and is under working status.
function initFeed(){
FB.api('/{id}/feed', function(response){
var feedArray = response.data;
$.each(feedArray, function(){
var $this = $(this)[0]; //Status Object for single Status in Feed
setStatus($this, processStatus); //processStatus is function defined below
});
});
}
function setStatus(statusObject, callbackProcessStatus){
FB.api("/{personId}?fields=id,link,name,picture",
function (response) {
var html = /* Generates html based from statusObject and response */
callbackProcessStatus(html);
});
}
function processStatus(html){
$('#fb-status-wrapper').append(html);
}
(was uncertain on the title of this post, please edit if you think it is not descriptive enough)
Best regards
This is a somewhat common problem with parallel async calls. The simplest solution requires promises. I recommend the Bluebird promise library, but most will do fine.
var fbApi = function(url){
return new Promise(function(resolve, reject){
FB.api(url, function(resp){ resolve(resp); });
});
}
function setStatus(statusObject){
return fbApi("/{personId}?fields=id,link,name,picture")
.then(function(response){
var html = ...;
return html;
});
}
function getFeedItemPromises(){
return fbApi("/{id}/feed").then(function(response){
return response.data.map(function(item){
});
});
}
Depending on your needs, initFeed could be one of these. The first renders the feed when all items are available, and the second renders it when each item is available, but enforces the order.
function initFeed(){
return Promise.all(getFeedItemPromises())
.then(function(itemsHtml){
// append all of the items at once
$('#fb-status-wrapper').append(itemsHtml.join("\n"));
});
}
Or this which ensures the order, but eagerly appends items to the feed, after all previous items have been added.
function initFeed(){
function renderItem(html){
$('#fb-status-wrapper').append(html);
}
// reduce can be used to chain promises in sequence
return getFeedItemPromises().reduce(function(p, nextPromise){
return p.then(function(){ return nextPromise })
.then(renderItem);
}, Promise.resolve())
}
An alternative would be to create a div for each item which acts as a placeholder, keep those in an array, and fill them in when each resolves. This works especially well if you know the height of the items beforehand, and fade them in when they load. From a UX perspective, this is the best in my opinion.
I would not recommend the above if you don't know the heights of items, as it'll cause headache inducing shifting of items as new ones are inserted.
Indeed you cannot rely on the order in which the requests will finish. The only way to be sure, is to only call the second one if the first one is done. But that will slow down the loading quite a lot.
Another possibility is to remember for each request which one it is, and insert the items in the right order (insert before a 'later' one, even if that one was received earlier).
I think the easiest way to do that, is to make placeholders for the items inside the each loop, so the placeholders are inserted in the right order. When the requests return, you just place the responses in the right placeholder.
It could look somewhat like this. 2 extra lines and a couple of tiny changes. I couldn't test this without the API, but I hope you get the idea.
function initFeed(){
FB.api('/{id}/feed', function(response){
var feedArray = response.data;
$.each(feedArray, function(index){
var $this = $(this)[0]; //Status Object for single Status in Feed
// Make a container per item inside the wrapper.
var $itemContainer = $('<div></div>');
$('#fb-status-wrapper').append($itemContainer);
// Pass the container to the api function.
setStatus($this, processStatus, $itemContainer); //processStatus is function defined below
});
});
}
function setStatus(statusObject, callbackProcessStatus, $container){
FB.api("/{personId}?fields=id,link,name,picture",
function (response) {
var html = /* Generates html based from statusObject and response */
// Pass the item place holder/container to the processing procedure.
callbackProcessStatus(html, $container);
});
}
function processStatus(html, $container){
$container.append(html);
}

Wait for data to be added to a Div

Ok, I have a div:
<div class="moreStuff"> <p>stuff in here matters</p></div>
and some Javascript:
weiredFunction(takes,some,values);
this "weirdFunction()" takes some values, does some non-important stuff and adds/appends it to the div.moreStuff.
Directly after "WeirdFunction()" I have my own function:
stuffIWantToDo(...);
the "stuffIWantToDo()" checks the div.moreStuff and reacts according to the data in it.
the problem is that it takes about 300ms for the "weirdFunction()" to do it's buisness and add it's data to the div .moreStuff but my function "stuffIWantToDo()" exicutes before 300ms; meaning there is nothing in the div .moreStuff when "stuffIWantToDo()" runs.
I want to prevent "stuffIWantToDo()" from running untill "weirdFunction()" appends the data to the div.moreStuff.
try make stuffIWantToDo() as a callback function in weiredFunction()
function weiredFunction(callback) {
// finish the weird thing
// Call the callback
callback('some','stuff','there');
}
function stuffIWantToDo(a, b, c) {
// I'm the callback
alert(a + " " + b + " " + c);
}
weiredFunction(foo);
Check this SO post: Create a custom callback in JavaScript
If you are using some jQuery calls, then we need to see it, as #tymeJV already said, look's like you have some async code involve in weiredFunction function
If not, your code must work synchronously and the only thing you need, is call your stuffIWantToDo functionas a callback, or at the end of the first one.
If none of this make sense to you, then use the old and well know setTimeout javascript function
like
function weiredFunction(takes,some,values){
/* some weired stuff here */
setTimeout(function() {
stuffIWantToDo(...);
}, 300);
}
If i understand you right, you have no possibility to change the weiredFunction. Based on that assumption, you have to write a small interval-based observer that fires your function once the content has changed.
var stuffContent = '';
window.setInterval(function() {
var currentStuffContent = $('.moreStuff').html();
if(currentStuffContent == stuffContent) return;
stuffContent = currentStuffContent;
stuffIWantToDo();
}, 50);
However, it would be the best if you could simply fire stuffIWantToDo within weiredFunction as a callback.

Extracting values from USGS real time water service

There must be something simple I am missing, but alas, I do not know what I do not know. Below is the code I have thus far for trying to get current streamflow conditions from the USGS.
// create site object
function Site(siteCode) {
this.timeSeriesList = [];
this.siteCode = siteCode;
this.downloadData = downloadData;
this.getCfs = getCfs;
// create reference to the local object for use inside the jquery ajax function below
var self = this;
// create timeSeries object
function TimeSeries(siteCode, variableCode) {
this.variableCode = variableCode;
this.observations = [];
}
// create observation object
function TimeSeriesObservation(stage, timeDate) {
this.stage = stage;
this.timeDate = timeDate;
}
// include the capability to download data automatically
function downloadData() {
// construct the url to get data
// TODO: include the capability to change the date range, currently one week (P1W)
var url = "http://waterservices.usgs.gov/nwis/iv/?format=json&sites=" + this.siteCode + "&period=P1W&parameterCd=00060,00065"
// use jquery getJSON to download the data
$.getJSON(url, function (data) {
// timeSeries is a two item list, one for cfs and the other for feet
// iterate these and create an object for each
$(data.value.timeSeries).each(function () {
// create a timeSeries object
var thisTimeSeries = new TimeSeries(
self.siteCode,
// get the variable code, 65 for ft and 60 for cfs
this.variable.variableCode[0].value
);
// for every observation of the type at this site
$(this.values[0].value).each(function () {
// add the observation to the list
thisTimeSeries.observations.push(new TimeSeriesObservation(
// observation stage or level
this.value,
// observation time
this.dateTime
));
});
// add the timeSeries instance to the object list
self.timeSeriesList.push(thisTimeSeries);
});
});
}
// return serialized array of cfs stage values
function getCfs() {
// iterate timeseries objects
$(self.timeSeriesList).each(function () {
// if the variable code is 00060 - cfs
if (this.variableCode === '00060') {
// return serialized array of stages
return JSON.stringify(this.observations);
}
});
}
}
When I simply access the object directly using the command line, I can access individual observations using:
> var watauga = new Site('03479000')
> watauga.downloadData()
> watauga.timeSeriesList[0].observations[0]
I can even access all the reported values with the timestamps using:
> JSON.stringify(watauga.timeSeriesList[0].observations)
Now I am trying to wrap this logic into the getCfs function, with little success. What am I missing?
I don't see anything in the code above that enforces the data being downloaded. Maybe in whatever execution path you're using to call getCfs() you have a wait or a loop that checks for the download to complete prior to calling getCfs(), but if you're simply calling
site.downloadData();
site.getCfs()
you're almost certainly not finished loading when you call site.getCfs().
You'd need to do invoke a callback from within your success handler to notify the caller that the data is downloaded. For example, change the signature of Site.downloadData to
function downloadData(downloadCallback) {
// ...
Add a call to the downloadCallback after you're finished processing the data:
// After the `each` that populates 'thisTimeSeries', but before you exit
// the 'success' handler
if (typeof downloadCallback === 'function') {
downloadCallback();
}
And then your invocation would be something like:
var watauga = new Site('03479000');
var downloadCallback = function() {
watauga.timeSeriesList[0].observations[0];
};
watauga.downloadData(downloadCallback);
That way, you're guaranteed that the data is finished processing before you attempt to access it.
If you're getting an undefined in some other part of your code, of course, then there may be something else wrong. Throw a debugger on it and step through the execution. Just bear in mind that interactive debugging has many of the same problems as interactively calling the script; the script has time to complete its download in the background before you start inspecting the variables, which makes it look like everything's hunky dory, when in fact a non-interactive execution would have different timing.
The real issue, I discovered through just starting over from scratch on this function, is something wrong with my implementation of jQuery.().each(). My second stab at the issue, I successfully used a standard for in loop. Here is the working code.
function getCfs() {
for (var index in this.timeSeriesList) {
if (this.timeSeriesList[index].variableCode === '00060'){
return JSON.stringify(this.timeSeriesList[index].observations);
}
}
}
Also, some of the stuff you are talking about #Palpatim, I definitely will have to look into. Thank you for pointing out these considerations. This looks like a good time to further investigate these promises things.

javascript setTimeout function out of scope

I am trying to call showUpload(); from within two setTimeouts. Neither works. It seems to be out of scope and I'm not sure why. I tried this.showUpload() which didn't work either.
$(document).ready(function(){
var progress_key = $('#progress_key').val();
// this sets up the progress bar
$('#uploadform').submit(function() {
setTimeout("showUpload()",1500);
$("#progressbar").progressbar({ value:0}).fadeIn();
});
// uses ajax to poll the uploadprogress.php page with the id
// deserializes the json string, and computes the percentage (integer)
// update the jQuery progress bar
// sets a timer for the next poll in 750ms
function showUpload() {
$.get("/myid/videos/uploadprogress/" + progress_key, function(data) {
if (!data)
return;
var response;
eval ("response = " + data);
if (!response)
return;
var percentage = Math.floor(100 * parseInt(response['bytes_uploaded']) / parseInt(response['bytes_total']));
$("#progressbar").progressbar({ value:percentage})
});
setTimeout("showUpload()", 750);
}
});
Thank you for your time.
As #Daniel said, this should work:
setTimeout(showUpload, 750);
Please note that the quotes should be removed (this is why it isn't being executed until the timeout runs out). Right now, you are passing a string, which is evaled when the timeout runs out. This eval will happen in a different scope, which is why you are seeing the problem you are seeing.
Instead, passing a reference to the showUpload function to setTimeout will allow your function to be executed later. Keep in mind that when it runs, it will be in a different scope, so you may have other scope issues, like with progress_key. You will need to create a closure around showUpload to capture that parameter.
It looks like you need to remove the parenthesis from showUpload in both your setTimeout calls. Otherwise you will be invoking the showUpload method instead of passing it as a parameter:
setTimeout(showUpload, 750);

Categories

Resources