d3.js return value of function always undefined - d3.js

I am calling a function from my index.html file. The function is defined in a javascript file which i have referred to in the html. However the return value is always undefined. When i debug i could see the value in the return string.
Follwing is the code in index.html
<script type="text/javascript">
function readQueryStringparam(name)
{
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regexS = "[\\?&]" + name + "=([^&#]*)";
var regex = new RegExp(regexS);
var results = regex.exec(window.location.href);
if (results == null)
return "";
else
return results[1];
}
function getDiDataUrlPrefix()
{
diDataUrlPrefix = diGlobal.instanceInfo.getDiDataUrlPrefix();
//alert(diDataUrlPrefix);
sbu = readQueryStringparam('sbu');
appid = readQueryStringparam('appid');
if (sbu.length > 0)
{
sbu = sbu.trim();
CreateChart(diDataUrlPrefix,sbu,0,appid);
}
else if (appid.length > 0)
{
sbu = GetSBUForApplication(appid);
CreateChart(diDataUrlPrefix,0,0,appid);
}
}
</script>
I get the value for the parameters supplied in the url as well as diDataUrlPrefix.
Following is the code in the javascript file:
function GetSBUForApplication(appid)
{
setTimeout(function() { }, 10000);
var string;
var file = diDataUrlPrefix + "/oss/csvs/Consolidated_RAG.csv";
d3.text(file, function(datasetText)
{
parsedCSVapp = d3.csv.parseRows(datasetText);
if (appid >0)
{
parsedCSVapp = parsedCSVapp.filter(function(row)
{
//alert(parsedCSVapp);
return row[0] == appid
})//parsed fileter ends here
returnstring = parsedCSVapp[0][4];
}
})
return returnstring;
}
However the value of sbu is always undefined.However i can see the values in parsedCSVapp. The csv file looks like this:
Application_Id,Application Name,Status,Name,Business Unit
200039,DEALING,RED,Marc Begun,Financial&Risk
200070,NGTX,RED,Marc Begun,Financial&Risk
200097,WORLD-CHECK,RED,Graham Fisher,Financial&Risk
200009,BOARDLINK,RED,Jennifer Simon,Financial&Risk
200088,THOMSON ONE,RED,Jonathan Weinberg,Financial&Risk
200037,DATASTREAM,RED,Ian Brocklehurst,Financial&Risk
200044,EIKON,RED,Olivier Martin,Financial&Risk
200011,COLLABORATION,RED,Frank Tarsillo,Financial&Risk

d3.text (and d3.csv, d3.json and similar) make asynchronous calls. That is, when you run the code, the call is made and execution resumes without waiting for the call to return.
The second argument to those functions is a function that gets executed when the call returns -- the callback.
This function will not be executed at the same time as you run d3.text, but later. You cannot determine at what time exactly it will be run. Any code that you want to call as a result of one of those calls needs to be run as part of the callback function, or called from there.

Related

getting XMP File does not have a constructor error through ExtendScript

I am using In Design CC 2019, on my Mac OS. When I am trying to get XMP data for my .indd (InDesign document) using ExtendScript.
I am currently getting the error like this:
XMPFile Does not have a constructor.
Below is my script.
// load XMP Library
function loadXMPLibrary(){
if ( ExternalObject.AdobeXMPScript){
try{ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript');}
catch (e){alert('Unable to load the AdobeXMPScript library!'); return false;}
}
return true;
}
var myFile= app.activeDocument.fullName;
// check library and file
if(loadXMPLibrary() && myFile != null){
xmpFile = new XMPFile(myFile.fsName, XMPConst.FILE_INDESIGN, XMPConst.OPEN_FOR_UPDATE);
var myXmp = xmpFile.getXMP();
}
if(myXmp){
$.writeln ('sucess')
}
There's an issue with your codes logic, you need to make the following change:
Add the Logical NOT operator (i.e. !) to the condition specified for your if statement in the body of your loadXMPLibrary function.
function loadXMPLibrary(){
if (!ExternalObject.AdobeXMPScript) { // <--- Change to this
// ^
try {ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript');}
catch (e){alert('Unable to load the AdobeXMPScript library!'); return false;}
}
return true;
}
You need to add this because currently your if statement checks whether the condition is truthy, i.e. it checks whether ExternalObject.AdobeXMPScript is true. This will always remain false, until the AdobeXMPScript library has been loaded, therefore you're code that actually loads the library never gets executed.
Revised script:
For clarity here is the complete revised script:
// load XMP Library
function loadXMPLibrary() {
if (!ExternalObject.AdobeXMPScript) {
try{ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript');}
catch (e){alert('Unable to load the AdobeXMPScript library!'); return false;}
}
return true;
}
var myFile= app.activeDocument.fullName;
// check library and file
if (loadXMPLibrary() && myFile !== null) {
xmpFile = new XMPFile(myFile.fsName, XMPConst.FILE_INDESIGN, XMPConst.OPEN_FOR_UPDATE);
var myXmp = xmpFile.getXMP();
}
if (myXmp){
$.writeln ('success')
}

Does Jasmine have an after-advice spy?

When spying on a method, we can either callThrough (use original implementation) or callFake (use a custom implementation).
What I want is a behaviour similar to callThrough but inspect/modify its return value before returning it to the caller.
So I can do something like this:
spyOn(foo, "fetch").and.afterCall(function(result) {
expect(result).toBeDefined();
result.bar = "baz";
return result;
});
Right now the simplest way is doing something like this:
var original = foo.fetch;
foo.fetch = function() {
var result = original.apply(this, arguments);
expect(result).toBeDefined();
result.bar = "baz";
return result;
}
Which is somewhat annoying because now I have to manually restore the spy instead of having the framework automatically does it for me.
Does Jasmine have an after-advice spy?
Generally: no.
You could extend the SpyStrategy object with such a function though:
this.callThroughAndModify = function(resultModifier) {
var result;
plan = function() {
result = originalFn.apply(this, arguments);
return resultModifier(result);
};
return getSpy();
};
You've to clone the above SpyStrategy file and insert that method.
Usage:
var obj = {
fn: function(a) { return a * 2; }
};
spyOn(obj, "fn").and.callThroughAndModify(function(result) {
console.log("Original result: ", result);
return 1;
});
expect(obj.fn(2)).toBe(1);
Drawbacks:
You've to replace the whole SpyStrategy.js
You've to load that script before Jasmine initializes the original SpyStrategy at boot

How to retry failures with $q.all

I have some code that saves data using Breeze and reports progress over multiple saves that is working reasonably well.
However, sometimes a save will timeout, and I'd like to retry it once automatically. (Currently the user is shown an error and has to retry manually)
I am struggling to find an appropriate way to do this, but I am confused by promises, so I'd appreciate some help.
Here is my code:
//I'm using Breeze, but because the save takes so long, I
//want to break the changes down into chunks and report progress
//as each chunk is saved....
var surveys = EntityQuery
.from('PropertySurveys')
.using(manager)
.executeLocally();
var promises = [];
var fails = [];
var so = new SaveOptions({ allowConcurrentSaves: false});
var count = 0;
//...so I iterate through the surveys, creating a promise for each survey...
for (var i = 0, len = surveys.length; i < len; i++) {
var query = EntityQuery.from('AnsweredQuestions')
.where('PropertySurveyID', '==', surveys[i].ID)
.expand('ActualAnswers');
var graph = manager.getEntityGraph(query)
var changes = graph.filter(function (entity) {
return !entity.entityAspect.entityState.isUnchanged();
});
if (changes.length > 0) {
promises.push(manager
.saveChanges(changes, so)
.then(function () {
//reporting progress
count++;
logger.info('Uploaded ' + count + ' of ' + promises.length);
},
function () {
//could I retry the fail here?
fails.push(changes);
}
));
}
}
//....then I use $q.all to execute the promises
return $q.all(promises).then(function () {
if (fails.length > 0) {
//could I retry the fails here?
saveFail();
}
else {
saveSuccess();
}
});
Edit
To clarify why I have been attempting this:
I have an http interceptor that sets a timeout on all http requests. When a request times out, the timeout is adjusted upwards, the user is displayed an error message, telling them they can retry with a longer wait if they wish.
Sending all the changes in one http request is looking like it could take several minutes, so I decided to break the changes down into several http requests, reporting progress as each request succeeds.
Now, some requests in the batch might timeout and some might not.
Then I had the bright idea that I would set a low timeout for the http request to start with and automatically increase it. But the batch is sent asynchronously with the same timeout setting and the time is adjusted for each failure. That is no good.
To solve this I wanted to move the timeout adjustment after the batch completes, then also retry all requests.
To be honest I'm not so sure an automatic timeout adjustment and retry is such a great idea in the first place. And even if it was, it would probably be better in a situation where http requests were made one after another - which I've also been looking at: https://stackoverflow.com/a/25730751/150342
Orchestrating retries downstream of $q.all() is possible but would be very messy indeed. It's far simpler to perform retries before aggregating the promises.
You could exploit closures and retry-counters but it's cleaner to build a catch chain :
function retry(fn, n) {
/*
* Description: perform an arbitrary asynchronous function,
* and, on error, retry up to n times.
* Returns: promise
*/
var p = fn(); // first try
for(var i=0; i<n; i++) {
p = p.catch(function(error) {
// possibly log error here to make it observable
return fn(); // retry
});
}
return p;
}
Now, amend your for loop :
use Function.prototype.bind() to define each save as a function with bound-in parameters.
pass that function to retry().
push the promise returned by retry().then(...) onto the promises array.
var query, graph, changes, saveFn;
for (var i = 0, len = surveys.length; i < len; i++) {
query = ...; // as before
graph = ...; // as before
changes = ...; // as before
if (changes.length > 0) {
saveFn = manager.saveChanges.bind(manager, changes, so); // this is what needs to be tried/retried
promises.push(retry(saveFn, 1).then(function() {
// as before
}, function () {
// as before
}));
}
}
return $q.all(promises)... // as before
EDIT
It's not clear why you might want to retry downsteam of $q.all(). If it's a matter of introducing some delay before retrying, the simplest way would be to do within the pattern above.
However, if retrying downstream of $q.all() is a firm requirement, here's a cleanish recursive solution that allows any number of retries, with minimal need for outer vars :
var surveys = //as before
var limit = 2;
function save(changes) {
return manager.saveChanges(changes, so).then(function () {
return true; // true signifies success
}, function (error) {
logger.error('Save Failed');
return changes; // retry (subject to limit)
});
}
function saveChanges(changes_array, tries) {
tries = tries || 0;
if(tries >= limit) {
throw new Error('After ' + tries + ' tries, ' + changes_array.length + ' changes objects were still unsaved.');
}
if(changes_array.length > 0) {
logger.info('Starting try number ' + (tries+1) + ' comprising ' + changes_array.length + ' changes objects');
return $q.all(changes_array.map(save)).then(function(results) {
var successes = results.filter(function() { return item === true; };
var failures = results.filter(function() { return item !== true; }
logger.info('Uploaded ' + successes.length + ' of ' + changes_array.length);
return saveChanges(failures), tries + 1); // recursive call.
});
} else {
return $q(); // return a resolved promise
}
}
//using reduce to populate an array of changes
//the second parameter passed to the reduce method is the initial value
//for memo - in this case an empty array
var changes_array = surveys.reduce(function (memo, survey) {
//memo is the return value from the previous call to the function
var query = EntityQuery.from('AnsweredQuestions')
.where('PropertySurveyID', '==', survey.ID)
.expand('ActualAnswers');
var graph = manager.getEntityGraph(query)
var changes = graph.filter(function (entity) {
return !entity.entityAspect.entityState.isUnchanged();
});
if (changes.length > 0) {
memo.push(changes)
}
return memo;
}, []);
return saveChanges(changes_array).then(saveSuccess, saveFail);
Progress reporting is slightly different here. With a little more thought it could be made more like in your own answer.
This is a very rough idea of how to solve it.
var promises = [];
var LIMIT = 3 // 3 tris per promise.
data.forEach(function(chunk) {
promises.push(tryOrFail({
data: chunk,
retries: 0
}));
});
function tryOrFail(data) {
if (data.tries === LIMIT) return $q.reject();
++data.tries;
return processChunk(data.chunk)
.catch(function() {
//Some error handling here
++data.tries;
return tryOrFail(data);
});
}
$q.all(promises) //...
Two useful answers here, but having worked through this I have concluded that immediate retries is not really going to work for me.
I want to wait for the first batch to complete, then if the failures are because of timeouts, increase the timeout allowance, before retrying failures.
So I took Juan Stiza's example and modified it to do what I want. i.e. retry failures with $q.all
My code now looks like this:
var surveys = //as before
var successes = 0;
var retries = 0;
var failedChanges = [];
//The saveChanges also keeps a track of retries, successes and fails
//it resolves first time through, and rejects second time
//it might be better written as two functions - a save and a retry
function saveChanges(data) {
if (data.retrying) {
retries++;
logger.info('Retrying ' + retries + ' of ' + failedChanges.length);
}
return manager
.saveChanges(data.changes, so)
.then(function () {
successes++;
logger.info('Uploaded ' + successes + ' of ' + promises.length);
},
function (error) {
if (!data.retrying) {
//store the changes and resolve the promise
//so that saveChanges can be called again after the call to $q.all
failedChanges.push(data.changes);
return; //resolved
}
logger.error('Retry Failed');
return $q.reject();
});
}
//using map instead of a for loop to call saveChanges
//and store the returned promises in an array
var promises = surveys.map(function (survey) {
var changes = //as before
return saveChanges({ changes: changes, retrying: false });
});
logger.info('Starting data upload');
return $q.all(promises).then(function () {
if (failedChanges.length > 0) {
var retries = failedChanges.map(function (data) {
return saveChanges({ changes: data, retrying: true });
});
return $q.all(retries).then(saveSuccess, saveFail);
}
else {
saveSuccess();
}
});

CasperJS: Iterating through URL's

I'm pretty new to CasperJS, but isn't there a way to open a URL and execute CasperJS commands in for loops? For example, this code doesn't work as I expected it to:
casper.then(function() {
var counter = 2013;
for (i = counter; i < 2014; i++) {
var file_name = "./Draws/wimbledon_draw_" + counter + ".json";
// getting some local json files
var json = require(file_name);
var first_round = json["1"];
for (var key in first_round) {
var name = first_round[key].player_1.replace(/\s+/g, '-');
var normal_url = "http://www.atpworldtour.com/Tennis/Players/" + name;
// the casper command below only executes AFTER the for loop is done
casper.thenOpen(normal_url, function() {
this.echo(normal_url);
});
}
}
});
Instead of Casper is calling thenOpen on each new URL per iteration, it gets only called AFTER the for loop executes. Casper thenOpen then gets called with the last value normal_url is set to. Is there no Casper command to have it work each iteration within the for loop?
Follow up: How do we make casper thenOpen return a value on the current iteration of the for loop?
Say for example, I needed a return value on that thenOpen (maybe if the HTTP status is 404 I need to evaluate another URL so I want to return false). Is this possible to do?
Editing casper.thenOpen call above:
var status;
// thenOpen() only executes after the console.log statement directly below
casper.thenOpen(normal_url, function() {
status = this.status(false)['currentHTTPStatus'];
if (status == 200) {
return true;
} else {
return false;
}
});
console.log(status); // This prints UNDEFINED the same number of times as iterations.
If you need to get context then use the example here:
https://groups.google.com/forum/#!topic/casperjs/n_zXlxiPMtk
I used the IIFE (immediately-invoked-function-expression) option.
Eg:
for(var i in links) {
var link = links[i];
(function(index) {
var link = links[index]
var filename = link.replace(/#/, '');
filename = filename.replace(/\//g, '-') + '.png';
casper.echo('Attempting to capture: '+link);
casper.thenOpen(vars.domain + link).waitForSelector('.title h1', function () {
this.capture(filename);
});
})(i);
}
links could be an array of objects and therefore your index is a reference to a group of properties if need be...
var links = [{'page':'some-page.html', 'filename':'page-page.png'}, {...}]
As Fanch and Darren Cook stated, you could use an IIFE to fix the url value inside of the thenOpen step.
An alternative would be to use getCurrentUrl to check the url. So change the line
this.echo(normal_url);
to
this.echo(this.getCurrentUrl());
The problem is that normal_url references the last value that was set but not the current value because it is executed later. This does not happen with casper.thenOpen(normal_url, function(){...});, because the current reference is passed to the function. You just see the wrong url, but the correct url is actually opened.
Regarding your updated question:
All then* and wait* functions in the casperjs API are step functions. The function that you pass into them will be scheduled and executed later (triggered by casper.run()). You shouldn't use variables outside of steps. Just add further steps inside of the thenOpen call. They will be scheduled in the correct order. Also you cannot return anything from thenOpen.
var somethingDone = false;
var status;
casper.thenOpen(normal_url, function() {
status = this.status(false)['currentHTTPStatus'];
if (status != 200) {
this.thenOpen(alternativeURL, function(){
// do something
somethingDone = true;
});
}
});
casper.then(function(){
console.log("status: " + status);
if (somethingDone) {
// something has been done
somethingDone = false;
}
});
In this example this.thenOpen will be scheduled after casper.thenOpen and somethingDone will be true inside casper.then because it comes after it.
There are some things that you need to fix:
You don't use your counter i: you probably mean "./Draws/wimbledon_draw_" + i + ".json" not "./Draws/wimbledon_draw_" + counter + ".json"
You cannot require a JSON string. Interestingly, you can require a JSON file. I still would use fs.read to read the file and parse the JSON inside it (JSON.parse).
Regarding your question...
You didn't schedule any commands. Just add steps (then* or wait*) behind or inside of thenOpen.

Nothing happens with my event listener on click in javascript

I have these functions :
createTreeItem: function (num, val)
{
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var i = document.createElementNS(XUL_NS, "treeitem");
var r = document.createElementNS(XUL_NS, "treerow");
var c1 = document.createElementNS(XUL_NS, 'treecell');
var c2 = document.createElementNS(XUL_NS, 'treecell');
var c3 = document.createElementNS(XUL_NS, 'treecell');
i.setAttribute("container", true);
i.setAttribute("open", true);
c1.setAttribute("label", num);
c2.setAttribute("label", val);
c3.setAttribute("value", false);
r.appendChild(c1);
r.appendChild(c2);
r.appendChild(c3);
i.appendChild(r);
i.addEventListener("click", test, false);
return i;
}
test: function ()
{
alert("zero");
}
func: function (liste)
{
try
{
root = document.getElementById("treeRoot");
var current;
for(o in liste)
{
current = createTreeItem(liste[o].id, liste[o].nom_scenario);
root.appendChild(current);
}
}
catch(e)
{
alert(e);
}
}
I am creating elements in a tree and I would like to add event listeners on each element created. The problem is that nothing happens.
In the code, Liste is the response of a json request. It contains all the elements I want to create in my xul file.
I'm not super familiar with this syntax, but my bet is that the test function isn't being 'hoisted' because of how it's being defined. try moving the 'test' function above the 'createTreeItem' function or just defining test like so:
function test() {
...
}
That way when it gets evaluated it will be 'hoisted' to the top so that when you try to add it as the action for the click event, it'll be defined. Not 100% sure this is correct but if I had to bet...

Resources