The problem I am having is specifically with D3.js, but I've had similar problems before and always ended up using a hack to get around it.
I have a class which contains an array of objects. There is a function to add new objects from a CSV file. using d3.csv. This function takes a filename and a callback function with one argument (the current parsed line of the csv file). I want to append each parsed line to this.objects, but this is no longer within the scope of the function.
function MyClass(){
this.objects = [];
this.add_objects = function(filename){
d3.csv(filename, function(data){
//Callback fired for each parsed line in csv file
//Now I want to push data to this.objects...
}
}
}
A common way to do this in javascript is with a closure variable in the parent scope:
function MyClass(){
var self = this;
this.objects = [];
this.add_objects = function(filename){
d3.csv(filename, function(data){
//Callback fired for each parsed line in csv file
// you can access self here
self.objects.push(xxx);
});
}
}
or that variable could be more specific:
function MyClass(){
this.objects = [];
var objectArray = this.objects;
this.add_objects = function(filename){
d3.csv(filename, function(data){
//Callback fired for each parsed line in csv file
// you can access objectArray here
objectArray.push(xxx);
});
}
}
or, you can use .bind() (if you don't need to support older IE like IE8) to create a wrapper that will force the setting of this for your callback:
function MyClass(){
this.objects = [];
this.add_objects = function(filename){
d3.csv(filename, function(data){
//Callback fired for each parsed line in csv file
this.objects.push(xxx);
}.bind(this));
}
}
Another approach (but more complicated) is to use bind:
function MyClass(){
this.objects = [];
function handler(data) {
// here this is your binded this.
}
this.add_objects = function(filename){
d3.csv(filename, handler.bind(this));
}
}
Related
I have this code that is called in an ajax callback once the data is fetched:
function onFetchCallback(data) {
onFetchCallback.accumData ??= [];
onFetchCallback.timeLine ??= [];
onFetchCallback.tempValues1 ??= [];
onFetchCallback.tempValues2 ??= [];
onFetchCallback.char;
const hasNulls = data.includes(null);
if (!hasNulls) {
//push values into different arrays
} else {
//push the rest of no nulls if there is any...
}
}
I dont find this clean, bacause I am checking if the arrays that accumulate the data are initialized for every callback call. I think it woull be better to have the callback function initialized, so that the arrays are created, and then call the functions that will store the data in the arrays.
So I did:
function onFetchCallback() {
function init() {
onFetchCallback.accumData ??= [];
onFetchCallback.timeLine ??= [];
onFetchCallback.tempValues1 ??= [];
onFetchCallback.tempValues2 ??= [];
onFetchCallback.char;
}
function store(data) {
const hasNulls = data.includes(null);
if (!hasNulls) {
//push values into different arrays
} else {
//push the rest of no nulls if there is any...
}
}
onFetchCallback.init = init;
onFetchCallback.store = store;
}
So then when I need to use my callback I do:
onFetchCallback();
onFetchCallback.init();
myWhateverFunc(onFetchCallback.store);
Being myWhateverFunc the one calling the callback:
function myWhateverFunc(callback) {
$.ajax({
//whatever
})
.done(function (data) {
callback(data); //CALL
});
}
This works and I find it super javasScriptic so I do it all the time. Meaning the onFetchCallback initialization + other methods call to handle the function members. I do not know js in depth so I would like to know of there are any flaws with this pattern, or if there is any other better/cooler/javaScriptStylish way to do this.
The pattern you're using has a lot of resemblence with the function constructor which is more commonly used in JavaScript.
An implementation of your code in the function constructor pattern would like like this:
function FetchCallback() {
this.accumData = [];
this.timeLine = [];
this.tempValues1 = [];
this.tempValues2 = [];
this.char;
}
FetchCallback.prototype.store = function(data) {
const hasNulls = data.includes(null);
if (!hasNulls) {
// push values into different arrays
} else {
// push the rest of no nulls if there is any...
}
};
It enables you to create an object with properties and methods which are predefined. This removes the hassle of repetition when you need multiple instances of this same object.
To use the constructor you'll need to create a new instance with the new keyword. This will return an object with all the properties and methods set.
const fetchCallback = new FetchCallback();
// Note the .bind() method!
myWhateverFunc(fetchCallback.store.bind(fetchCallback));
Edit
You'll need to specifically set the value of this to the created instance that is stored in fetchCallback. You can do this with the bind() method. This methods explicitly tells that this should refer to a specific object.
The reason to do this is that whenever you pass the store method as the callback to the myWhateverFunc, it loses it's context with the FetchCallback function. You can read more about this in this post
The main difference between this and your code is that here the FetchCallback function will be unaltered, where your function is reassigned every time you call onFetchCallback() and onFetchCallback.init(). The constructor pattern will result in more predictable behavior, albeit that the this keyword has a learning curve.
I'm working on a Flask app where some data gets passed to a template, and I want to make that data available to multiple instances of an object. Here's what it would look like if I just hardcoded the desired data into my .js file:
var Module = function(selector) {
var targetDiv = selector,
targetData = 'lorem ipsum sit dolor',
show = function() {
$('<p>' + targetData + '</p>').appendTo(targetDiv);
};
return {
show: show,
};
};
$(document).ready(function() {
firstModule = new Module($('#first'));
secondModule = new Module($('#second'));
firstModule.show();
secondModule.show();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='first'></div>
<div id='second'></div>
I can't just call a function on an unconstructed Module object, so my next step is to create a static-like ModuleFactory that I can load with data from jinja, and then create Modules from there:
var ModuleFactory = function() {
var targetData = null,
setData = function(data) {
targetData = data;
},
create = function(selector) {
return new Module(selector, data);
};
return {
setData: setData,
create: create,
};
} ();
Then I attempt to call ModuleFactory.setData({{data}}); from a <script> tag in the HTML, and do something like ModuleFactory.create($('#first')).show(); in the .js
But of course because I have to include my .js file before using the ModuleFactory in the HTML, I end up constructing the objects before the factory is initialized.
(Past this point, my workaround attempts stop being relevant to the problem.)
Anyway, what's the correct way of getting data from Jinja to my JS module? There has to be a common pattern or something.
This feels awful, but it works:
Have an object with a runProgram method, which takes the desired data as a parameter, then executes the logic that had previously been inside the document ready function. e.g.
var ProgramRunner = function() {
var runProgram = function(data) {
ModuleFactory.setData(data);
firstModule = ModuleFactory.create($('#first'));
firstModule.show();
};
return {
runProgram: runProgram;
};
};
Then just
<script>
$(document).ready(function() { ProgramRunner.runProgram({{data}}) });
</script>
in the HTML.
(Leaving question open, because I suspect there's a much better way of handling this.)
I'm currently trying to make a HTML/JavaScript Windows 8 modern application in which I want to access a local XML file that is in the installation directory.
After reading many ideas and code snippets around the web, I came up with a convoluted asynchronous method of accessing the file, which works. However, is this the best/correct way to do something as simple as accessing a local XML file?
Additionally, I'd like to be able to have a function load the xml file, and save the XMLDocument object as a "global" variable, so that on button presses and other triggers, the XMLDocument object can be accessed and parsed. This is where all the problems start, since one method is async, and then the variables are undefined, etc....
(function () {
"use strict";
WinJS.UI.Pages.define("/pages/reader/reader.html", {
// This function is called whenever a user navigates to this page. It
// populates the page elements with the app's data.
ready: function (element, options) {
// TODO: Initialize the page here.
var button = document.getElementById("changeText");
button.addEventListener("click", this.buttonClickHandler, false);
var dropdown = document.getElementById("volumeDropdown");
dropdown.addEventListener("change", this.volumeChangeHandler, false);
var loadSettings = new Windows.Data.Xml.Dom.XmlLoadSettings;
loadSettings.prohibitDtd = false;
loadSettings.resolveExternals = false;
//previous attempt, also didn't work:
//this.xmlDoc = null;
//this.loadXMLdoc(this, this.testXML);
//also not working:
this.getXmlAsync().then(function (doc) {
var xmlDoc = doc;
});
//this never works also, xmlDoc always undefined, or an error:
//console.log(xmlDoc);
},
buttonClickHandler: function (eventInfo) {
// doesn't work, xmlDoc undefined or error:
console.log(xmlDoc);
},
volumeChangeHandler: function (eventInfo) {
var e = document.getElementById("volumeDropdown");
// of course doesn't work, since I can't save the XMLDocument object into a variable (works otherwise):
var nodelist2 = xmlDoc.selectNodes('//volume[#name="volumeName"]/chapter/#n'.replace('volumeName', list[0]));
var volumeLength = nodelist2.length;
for (var index = 0; index < volumeLength; index++) {
var option = document.createElement("option");
option.text = index + 1;
option.value = index + 1;
var volumeDropdown = document.getElementById("chapterDropdown");
volumeDropdown.appendChild(option);
}
},
getXmlAsync: function () {
return Windows.ApplicationModel.Package.current.installedLocation.getFolderAsync("books").then(function (externalDtdFolder) {
externalDtdFolder.getFileAsync("book.xml").done(function (file) {
return Windows.Data.Xml.Dom.XmlDocument.loadFromFileAsync(file);
})
})
},
loadXMLdoc: function (obj, callback) {
var loadSettings = new Windows.Data.Xml.Dom.XmlLoadSettings;
loadSettings.prohibitDtd = false;
loadSettings.resolveExternals = false;
Windows.ApplicationModel.Package.current.installedLocation.getFolderAsync("books").then(function (externalDtdFolder) {
externalDtdFolder.getFileAsync("book.xml").done(function (file) {
Windows.Data.Xml.Dom.XmlDocument.loadFromFileAsync(file, loadSettings).then(function (doc) {
var nodelist = doc.selectNodes("//volume/#name");
var list = [];
for (var index = 0; index < nodelist.length; index++) {
list.push(nodelist[index].innerText);
};
for (var index = 0; index < list.length; index++) {
var option = document.createElement("option");
option.text = list[index] + "new!";
option.value = list[index];
var volumeDropdown = document.getElementById("volumeDropdown");
volumeDropdown.appendChild(option);
};
var nodelist2 = doc.selectNodes('//volume[#name="volumeName"]/chapter/#n'.replace('volumeName', list[0]));
var volumeLength = nodelist2.length;
for (var index = 0; index < volumeLength; index++) {
var option = document.createElement("option");
option.text = index + 1;
option.value = index + 1;
var volumeDropdown = document.getElementById("chapterDropdown");
volumeDropdown.appendChild(option);
};
obj.xmlDoc = doc;
callback(obj);
})
})
});
},
initializeXML: function (doc, obj) {
console.log("WE ARE IN INITIALIZEXML NOW")
obj.xmlDoc = doc;
},
testXML: function (obj) {
console.log(obj.xmlDoc);
},
});
})();
In summary with all these complicated methods failing, how should I go about doing something as simple as loading an XML file, and then having it available as an object that can be used by other functions, etc.?
Thanks for your help!
PS:
I'm very new to JavaScript and Windows 8 Modern Apps/ WinAPIs.
Previous experience all in Python and Java (where doing this is trivial!).
There are a couple of things going on here that should help you out.
First, there are three different loading events for a PageControl, corresponding to methods in your page class. The ready method (which is the only one the VS project template includes) gets called only at the end of the process, and is thus somewhat late in the process for doing an async file load. It's more appropriate to do this work within the init method, which is called before any elements have been created on the page. (The processed method is called after WinJS.UI.processAll is complete but before the page has been added to the DOM. ready is called after everything is in the DOM.)
Second, your getXMLAsync method looks fine, but your completed handler is declaring another xmlDoc variable and then throwing it away:
this.getXmlAsync().then(function (doc) {
var xmlDoc = doc; //local variable gets discarded
});
The "var xmlDoc" declares a local variable in the handler, but it's discarded as soon as the handler returns. What you need to do is assign this.xmlDoc = doc, but the trick is then making sure that "this" is the object you want it to be rather than the global context, which is the default for an anonymous function. The pattern that people generally use is as follows:
var that = this;
this.getXmlAsync().then(function (doc) {
that.xmlDoc = doc;
});
Of course, it's only after that anonymous handler gets called that the xmlDoc member will be valid. That is, if you put a console.log at the end of the code above, after the });, the handler won't have been called yet from the async thread, so xmlDoc won't get be valid. If you put it inside the handler immediately after that.xmlDoc = doc, then it should be valid.
This is all just about getting used to how async works. :)
Now to simplify matters for you a little, there is the static method StorageFile.getFileFromApplicationUriAsync which you can use to get directly to in-package file with a single call, rather than navigating folders. With this you can load create the XmlDocument as follows:
getXmlAsync: function () {
return StorageFile.getFileFromApplicationUriAsync("ms-appx:///books/book.xml").then((function (file) {
return Windows.Data.Xml.Dom.XmlDocument.loadFromFileAsync(file);
}).then(function (xmlDoc) {
return xmlDoc;
});
}
Note that the three /// are necessary; ms-appx:/// is a URI scheme that goes to the app package contents.
Also notice how the promises are chained instead of nested. That's typically a better structure, and one that allows a function like this to return a promise that will be fulfilled with the last return value in the chain. This can then be used with the earlier bit of code that assigns that.xmlDoc, and you avoid passing in obj and a callback (promises are intended to avoid such callbacks).
Overall, if you have any other pages in your app to which you'll navigate, you'll really want to load this XML file and create the XmlDocument once for the app, not with the specific page. Otherwise you'd be reloading the file every time you navigate to the page. For this reason, you could choose to do the loading on app startup, not page load, and use WinJS.Namespace.define to create a namespace variable in which you store the xmlDoc. Because that code would load on startup while the splash screen is visible, everything should be ready when the first page comes up. Something to think about.
In any case, given that you're new to this space, I suggest you download my free ebook, Programming Windows Store Apps with HTML, CSS, and JavaScript, 2nd Edition, where Chapter 3 has all the details about app startup, page controls, and promises (after the broader introductions of Chapters 1 and 2 of course).
So the goal of what I'm doing it to store an array of objects inside an object literal for later reference. I am losing context(if that is the right terminology to use here) in a place that is confusing for me. Here is the code:
HuntObject = {
// Data.hunts gives collection
Data: {},
fetchCollec: function(){
var self = this;
var huntObj = new Parse.Query(huntObject);
huntObj.find({
success: function(results){
var hunts = [];
for(i in results){
hunts.push(i);
}
console.log(self);
//Here self references HuntObject
self.Data = hunts;
},
error: function(e){
console.log(e.message);
}
});
console.log(self);// Here self references HuntObject
console.log(self.Data); // empty
So in both my console.log statements the correct context that I want is referenced and in the last log call to self I can even see in the console that the Data object now has an array of objects in it. But than as soon as I try to reference that array I get an empty Object. I tried assigning the array in different ways like self.Data.array = hunts. I also tried to set up Data as a method like this.
Data: (function(){
return {
array: []
}
}());
I think maybe my understanding of how context changes in different situations is pretty weak so It would be nice to solve my original goal but more important I would like to understand context better and why my implementation is failing in this instance?
This isn't a scope or context issue, you're handling that with your self variable.
My guess is that Parse.Query is asynchronous. And so you'd have to do your console.log within the success function; doing it in the code immediately after the call to Parse.Query calls it too soon (before the query completes).
So:
huntObj.find({
success: function (results) {
var hunts = [];
for (i in results) {
hunts.push(i);
}
console.log(self);
//Here self references HuntObject
self.Data = hunts;
// *********** Move these here
console.log(self); // Here self references HuntObje
console.log(self.Data); // Not empty anymore
},
error: function (e) {
console.log(e.message);
}
});
// Any code here runs after you've *started* the query, but
// before it *completes*
I'm trying to organize my ExtJS javascript a little better. I've an ExtJS object like this:
Ext.define('QBase.controller.ControlModelConfigurationController', {
extend: 'Ext.app.Controller',
views: [
'ControlModelConfiguration'
],
init: function() {
console.log('Initialized ControlModelConfigurationController');
this.control({
'#testBtn': {
click: this.loadModel
}
});
},
loadModel: function() {
console.log('Load Model....');
var conn = new Ext.data.Connection;
conn.request({
url: 'partsV10.xml',
callback: function(options, success, response)
{
if (success)
{
alert("AHHH");
var dq = Ext.DomQuery;
var xml = response.responseXML;
var nodes = dq.select('part', xml,parent);
Ext.Array.forEach(nodes,handleNode);
}
}
});
},
handleNode: function(items) {
console.log(item.name);
}
});
The posted code above is not working. Ext.Array.forEach(nodes,handleNode) causes trouble. Instead of using an anonymous function like :
...
Ext.Array.forEach(nodes,function(item) {
console.log(item)});
}
...
I'd like to extract the anonymous function as a named external one. Unfortunately I'm unable to figure out the right syntax to establish a code structure as shown above.
Meanwhile, I figured out, that putting
function handleNode(item) {
{console.log(item)}
}
at the very end of the file works. Is it possible to make the handleNode method an object - "member" of the controller?
Thanks in advance
Chris
handleNode is a member of the containing object. When loadModel is called, this contains the right object, but at the time the callback is invoked, it will not point to the one we are interested in. You can save this to the local variable self, and use it instead.
loadModel: function() {
var self = this
console.log('Load Model....');
var conn = new Ext.data.Connection;
conn.request({
url: 'partsV10.xml',
callback: function(options, success, response)
{
if (success)
{
alert("AHHH");
var dq = Ext.DomQuery;
var xml = response.responseXML;
var nodes = dq.select('part', xml,parent);
Ext.Array.forEach(nodes, self.handleNode);
}
}
});
},
The solution posted by vhallac is not entirely correct. It assumes that handleNode doesn't reference the current object through this variable. Maybe just a typo, but additionally it's not really the ExtJS way...
Whenever ExtJS provides a callback parameter, there is nearly always a scope parameter to set the value of this within the callback function.
loadModel: function() {
console.log('Load Model....');
var conn = new Ext.data.Connection;
conn.request({
url: 'partsV10.xml',
callback: function(options, success, response) {
if (success) {
alert("AHHH");
var dq = Ext.DomQuery;
var xml = response.responseXML;
var nodes = dq.select('part', xml, parent);
Ext.Array.forEach(nodes, this.handleNode, this);
}
},
scope: this
});
},
handleNode: function(node) {
// From within here you might want to call some other method.
// Won't work if you leave out the scope parameter of forEach.
this.subroutine();
}
Just like forEach uses a scope parameter, the request method uses a scope config option. This is ExtJS convention for passing around the scope. You can alternatively create an extra local variable and reference the scope from there, but in the context of ExtJS this style will feel awkward, plus (I'm pretty sure) it's a lot more bug-prone.