I'm writing modular JavaScript and I have a certain function that does a whole lotta processing, viz. Draw 2 canvases, update a lot of variables and store object references. Now I want to execute another function which uses some of the variables updated above.
Something like this:
Paint canvases - Store image dimensions in variables (and a lot of other stuff)
Use those dimensions to do some math and geometry, update the canvases again! I can't do this math in the first function, as it is a common utility function I use to paint canvas, everywhere in my code.
If I inject a setTimeout in my code for 10 seconds, everything works fine, but without it, the second instruction above does not find the updated variables and hence fails.
Any way to work around this? Meaning, I want to execute the second instruction ONLY after some of the required variables are set. Synchronous execution, I say.
Note: I can't post any code here (or anywhere for that matter) as it is not allowed in my workplace!
For cases like this, I suggest to use jQuery and custom events. Simply post an event when the first function has finished updating the canvas. The second function (and anything else) can listen to these events and do whatever they want.
Pro:
No coupling
Individual parts are easy to test
Extensible
Con:
Needs jQuery or you'll need to extract the event handling code.
You could use getters and setters to watch for you for a given condition.
In the setter you can do some computations, check if some conditions are met
and update if required.
Just to give you an idea :
// updateFunc is the function called whenever a property changes
// and all conditions are met for an update.
// newProp1,2,3 are the new values for prop1,2,3
function MyStorageClass(updateFunc, newProp1, newProp2, newProp3 ) {
this.updateFunc = updateFunc;
this.prop1 = newProp1 ;
this.prop2 = newProp2 ;
this.prop3 = newProp3 ;
}
var MSCProto = MyStorageClass.prototype;
// update is needed if all properties are >0
MSCProto.checkUpdateRequired = function() {
return ( ( this.prop1 > 0 ) && (this.prop2 > 0) && (this.prop3 > 0) )
}
Object.defineProperty(MSCProto, 'prop1', {
get : function() { retrurn this._prop1},
set : function(x) { this._prop1 = x;
// and some other computations if need be
if (this.checkUpdateRequired()) this.updateFunc(); } };
Object.defineProperty(MSCProto, 'prop2', {
get : function() { retrurn this._prop2},
set : function(x) { this._prop2 = x;
// and some other computations if need be
if (this.checkUpdateRequired()) this.updateFunc(); } };
Object.defineProperty(MSCProto, 'prop3', {
get : function() { retrurn this._prop3},
set : function(x) { this._prop3 = x;
// and some other computations if need be
if (this.checkUpdateRequired()) this.updateFunc(); } };
Related
I am extending mxgraph delete control example to add delete like controls to nodes which are generated dynamically in my graph. The source code for the example is available here
The problem is in this part of the code -
// Overridden to add an additional control to the state at creation time
mxCellRendererCreateControl = mxCellRenderer.prototype.createControl;
mxCellRenderer.prototype.createControl = function(state)
{
mxCellRendererCreateControl.apply(this, arguments);
var graph = state.view.graph;
if (graph.getModel().isVertex(state.cell))
{
if (state.deleteControl == null)
mxCellRendererCreateControl.apply inside the overridden call back of createControl seems to work as intended (calls the original function before creating additional controls) with the initial state of the graph on load. But, once I add nodes dynamically to the graph and the callback is invoked by mxgraph's validate/redraw, the control goes into an infinite loop, where 'apply' function basically keeps calling itself (i.e, the callback).
I am a bit clueless because when I debug, the context(this) looks fine, but I can't figure out why instead of invoking the prototype method, it just keeps invoking the overridden function in a loop. What am I doing wrong?
It looks like you are not cloning your original function the right way, please try the following :
Function.prototype.clone = function() {
var that = this;
return function theClone() {
return that.apply(this, arguments);
};
};
Add that new method somewhere in your main code so it will available in the whole application, now you can change your code to :
// Overridden to add an additional control to the state at creation time
let mxCellRendererCreateControl = mxCellRenderer.prototype.createControl.clone();
mxCellRenderer.prototype.createControl = function(state) {
mxCellRendererCreateControl(state);
var graph = state.view.graph;
if (graph.getModel().isVertex(state.cell)) {
if (state.deleteControl == null) {
// ...
}
}
// ...
};
This should work if I understood your problem correctly, if it does not, please change the old function call back to the apply. Otherwise let me know if something different happened after the Function prototype change.
It seems that your overriding code is being called multiple times (adding a simple console.log before your overriding code should be enough to test this)
Try to ensure that the code that overrides the function only gets called once, or validate whether the prototype function is the original or yours.
Here is an example of how you can check if the function is yours or not
if (!mxCellRenderer.prototype.createControl.isOverridenByMe) {
let mxCellRendererCreateControl = mxCellRenderer.prototype.createControl;
mxCellRenderer.prototype.createControl = function(state) { /* ... */ };
mxCellRenderer.prototype.createControl.isOverridenByMe = true;
}
There are other ways, like using a global variable to check if you have overriden the method or not.
If this doesn't fix your issue, please post more about the rest of your code (how is this code being loaded/called would help a lot)
As a desktop developer I am very new to Javascript, so I often run into things that puzzle me about the language. I was working with click events on RaphaelJS shapes, and initially I was setting the state and animation of the object in a private method:
innershape.node.onclick = function () {
if (scope.state === 0) {
_setState(1);
} else {
_setState(0);
}
};
function _setState(state) {
scope.state = state;
if (scope.state === 0) {
innershape.animate({ fill: "#00FF19" }, 500);
} else {
innershape.animate({ fill: "#C05219" }, 500);
}
}
This was functioning as expected. I then decided to add an outside function that would loop through all the objects and de-select (and therefore reverse-animate) all the other shapes. The result may be seen in this jsfiddle: http://jsfiddle.net/txj4zasn/4/
The function is called properly, and the animate() function is apparently executed, but the visible animation never appears, and the color never changes. I suspect that this is something very basic to Javascript that I just don't understand. Can someone explain to me why this is happening?
Its not really very clear what you want to achieve (beyond getting the animation to work), so my initial solution I think isn't good, but I will expand on that.
The problem looks a bit like you are trying to combine two different elements, functional scope and object variables.
A quick solution would be to include...
this.id = 1;
var id = this.id; // so id now a closure to the later function
as updateSelected(id); the id here, is inside another function, so we can't use 'this.id'. But then later you are checking against z[i].id so you need that to be defined also.
jsfiddle
This all feels a bit clunky though, prone to error, and is quite hard to read. So the first question is do you need objects ? You could store information in the "data" part of a Raph element, which already is an object.
Here is an example of how I would write it, I appreciate this may not be suitable as it may be part of a bigger project which needs other elements in an object, but it may give some idea.
function updateSelected( el ) {
if( el.data('innerstate') == 1 ) {
el.animate({ fill: "#00FF19" }, 500);
el.data('innerstate',0)
} else {
el.animate({ fill: "#C05219" }, 500);
el.data('innerstate',1);
}
}
function addElement() {
var innershape = paper.rect(100,100,100, 100);
innershape.attr({fill: "#00FF19" });
innershape.data('innerstate', 0);
innershape.click( function () {
updateSelected( innershape )
} );
};
addElement();
This code I can pretty much read instantly and know how and if it will work.
jsfiddle
jsfiddle showing it combined with more than one element, or jsfiddle thats a bit more compact
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¶meterCd=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.
First, please excuse my bad English. I'm not use to write in English.
I'm using Node.js and i have variables that sometimes get their value from async functions and sometimes by direct assignment (ex:
async(function(data) {
var x= data.something
}; or x = 5;)
the problem is that later on the have shared code which forces me to duplicate the code.
in syncronius scripting i usually do an if.. else statement to seperate the cases and assign. ex:
if(boolivar){
var x = niceFunc();
}
else {
var x = 5;
}
coolFunc(x);
now days im forced to to this:
if(boolivar){
niceFUnc(function(X){
coolFunc(X);
}
}
else{
var x = 5;
coolFunc(X);
}
does someone has an idea how to solve my problem?
I thought about forcing the async function to be sync but:
a. i dont know how
b. it kind of ruins the whole point
I would do it essentially as you have, except that I would abstract the sync/async calls so that it doesn't make any difference to the code that's using it what's really happening behind the scenes (ignore the bad function names; I have no idea what your code does):
function doNiceFunc(boolivar, callback) {
if (boolivar) {
niceFUnc(function(x) {
callback(x);
});
} else {
callback(5);
}
}
Now doNiceFunc appears the same in both cases from the outside:
doNiceFunc(boolivar, function(x) {
coolFunc(x);
});
I've used this exact pattern in a library that retrieved data that was sometimes immediately available and other times had to be retrieved from the network. The users of this library didn't have to care which was the case at any given time, because the call looked the same in both situations.
I tend to use this lib node-sync
var sync = require('sync');
sync(function(){
var result = query.sync(query, params);
// result can be used immediately
})
I have a static class which contains an array of callback functions, I then have a few other classes that are used to interact with this static class...
Here is a simple example of the static class:
var SomeStaticInstance = {};
(function(staticInstance) {
var callbacks = {};
staticInstance.addCallback = function(callback) { callbacks.push(callback); }
staticInstance.callAllCallbacks = function() { /* call them all */ }
}(SomeStaticInstance));
Then here is an example of my other classes which interact with it:
function SomeClassOne() {
this.addCallbackToStaticInstance = function() { SomeStaticInstance.addCallback(this.someCallback); }
this.someCallback = function() { /* Do something */ }
this.activateCallbacks = function() { SomeStaticInstance.callAllCallbacks(); }
}
function SomeClassTwo() {
this.addCallbackToStaticInstance = function() { SomeStaticInstance.addCallback(this.someOtherCallback); }
this.someOtherCallback = function() { /* Do something else */ }
this.activateCallbacks = function() { SomeStaticInstance.callAllCallbacks(); }
}
Now the problem I have is that when I call either class and tell it to activateCallbacks() the classes only activate the callbacks within their own scope, i.e SomeClassOne would call someCallback() but not someOtherCallback() and vice versa, now I am assuming it is something to do with the scope of the closures, however I am not sure how to get the behaviour I am after...
I have tried turning the static class into a regular class and then passing it into the 2 classes via the constructor, but still get the same issue...
So my question is how do I get the classes to raise all the callbacks
-- EDIT --
Here is an example displaying the same issue as I am getting on my actual app, I have put all script code into the page to give a clearer example:
http://www.grofit.co.uk/other/pubsub-test.html
It is a simple app with 2 presenters and 2 views... one view is concerned with adding 2 numbers at the top of the page, the 2nd view is concerned with taking that total and multiplying it and showing a result.
The 3rd party library I am using is PubSubJS, and the first presenter listens for an event to tell it that the one of the boxes has changed and re-totals the top row. The 2nd presenter listens for when the multiply or total at the top changes, then recalculates the bottom one. Now the first presenter recalculates correctly, and the 2nd presenter will correctly recalculate whenever the multiply box changes, HOWEVER! It will NOT recalculate when the total on the top changes, even thought it should receive the notification...
Anyway take a quick look through the source code on the page to see what I mean...
First, I think you want var callbacks = [] (an array instead of an object) since you're using callbacks.push().
I'm not sure I understand your problem. The way your classes are structured, you can achieve what you want by instantiating both classes and calling addCallbackToStaticInstance() on both new objects. E.g.,
var one = new SomeClassOne();
var two = new SomeClassTwo();
one.addCallbackToStaticInstance();
two.addCallbackToStaticInstance();
one.activateCallbacks();
Then, as above, you can call activateCallbacks() from either object.
If you're saying you want to be able to call activateCallback() after instantiating only one of the classes, you really have to rethink your approach. I'd start with moving addCallbackToStaticInstance() and activateCallbacks() into their own class.
This is a very odd way of doing things, but your main problem is that your callbacks object it not part of SomeStaticInstance, it is defined within an anonymous closure. Also your callbacks object {} should be an array [].
try staticInstance.callbacks = []; instead of var callbacks = {};
and
staticInstance.addCallback = function(callback) {
this.callbacks.push(callback);
}