Call a method after a callback and an event - javascript

I have a module with four functions that call one after the other. I am trying to follow the Revealing Module Pattern. One of the functions is public, the remaining are private. It goes like this:
publicMethod is called from another module
queryNames is called from publicMethod
execute(parameters, callback?, errback?) is called from queryNames
addNamesList is called as the callback? argument of execute
Several dijit/form/CheckBox's are created and the method querySegments is triggered onChange
querySegments needs to call a method of an object created in publicMethod.
The problem is in step 6, I can't reach the object created in step 1.
I have tried to use dojo hitch to define the callback? argument in step 3, but I can't get it to work. I tried putting this in its first argument, but even then I can't reach the required scope to call addNamesList.
Here is some code to demonstrate this issue.
define([
'dojo/dom',
'dijit/form/CheckBox',
'esri/layers/ArcGISDynamicMapServiceLayer',
'esri/tasks/query',
'esri/tasks/QueryTask',
'dojo/_base/lang'
],
function (
dom,
CheckBox,
ArcGISDynamicMapServiceLayer,
Query, QueryTask,
lang
) {
// ***************
// private methods
// ***************
// fetch names and call addNamesList to put the list in place
var queryNames = function (map, mapLayer) {
// new QueryTask(url, options?)
var queryTask = new QueryTask("url")
var query = new Query()
// execute(parameters, callback?, errback?)
// this callback passes an argument called featureSet
queryTask.execute(query, lang.hitch(map, "addNamesList", mapLayer), function(error) {console.log(error)})
}
// callback function of queryNames
var addNamesList = function (mapLayer, featureSet) {
console.log('addOplist')
var namesCount = featureSet.features.length
for (var i = 0; i <namesCount; i++) {
// work
var cbox = new CheckBox({
id: "cbox_" + i,
value: featureSet.features[i].attributes["someID"],
checked: false,
onChange: function (evt) {
querySegments(this.value, mapLayer)
}
})
cbox.placeAt("someDiv" + i, "first")
}
}
// triggered by the checkbox event
var querySegments = function (name, mapLayer) {
// build the query
var queryStatement = "someID = " + name
var layerDefinitions = [queryStatement]
// call a method of mapLayer
mapLayer.setLayerDefinitions(layerDefinitions)
}
// **************
// public methods
// **************
var publicMethod = function (map) {
var mapLayer = new ArcGISDynamicMapServiceLayer('restURL')
map.addLayer(mapServiceLayer)
queryNames(map, mapLayer)
return mapLayer
}
return {
publicMethod: publicMethod
}
}
)
You can see a more detailed explanation and a working example on this other (and more broad) question that I have put on Code Review.
I am new to JavaScript and I guess I still have a lot of issues with scoping, closures and callbacks.
I will deeply appreciate any input, including how to improve this question.
Edit
With this current implementation (with dojo hitch), no error is thrown. The method addNamesList is not called (nor errback, which I also don't understand why). I think this is because addNamesList is not on map's (hitch first argument) namespace. I tried to put this instead, but it makes no difference.
Before I decided to use hitch, the code looked like this:
var queryNames = function (map, mapLayer) {
...
queryTask.execute(query, addNamesList)
}
var addNamesList = function (featureSet) {
...
...
...
querySegments(this.value, mapLayer)
}
but then I couldn't reach mapLayer inside the method triggered by the check box event. It would throw Uncaught ReferenceError: mapLayer is not defined. That is why I tried to use hitch.

Javascript is asynchronous, so pretty much data coming from db, http requests or whatever is returned via callbacks. Here's what happens in your code:
public method calls queryNames
queryNames call addNamesList of map asynchronously and returns nothing
public method takes back control, meanwhile some stuff is going on with the addNamesList
mapLayer is returned untouched while some stuff is still going on in the background
So, to avoid this, you should return data from public method via callback, so you pass callback as the second parameter to the public method, then to the querySegments. Then, in the success callback of query, when you finally get the result ready, you do:
callback(mapLayer);
So, everything you should do is to pass this callback as deep as needed to the place where you have your mapLayer ready (so you've done with it everything you wanted), and then do a callback(mapLayer);.
This and this would probably explain better.
Best regards, Alexander

Related

How to use Callback functions in Class with JavaScript/jQuery

I have a class I am using for creating CRUD Objects for my site.
It stores the form and table paths for adding, listing, editing and deleting the data, as well as reloading your view with ajax after each edit.
Here is my class definitions:
class CRUDObj{
constructor(containerSel, links) {
this.links = links;
this.containerSel = containerSel;
this.formCallBack = function(){};
}
setActive(obj_id){
$.post(this.links.editURL+'?'+obj_id, {status:"active"}, this.reload);
}
reload(returnData){
this.formCallBack(returnData);
this.formCallBack = function(){};
if($(this.containerSel).length > 0){
ajaxLoad(this.links.listURL, $(this.containerSel));
}
}
}
A basic instance of initializing it:
var contactObj = new CRUDObj('#contacts', {
editURL: '/contact.edit.php',
listURL: '/contacts.list.php',
});
contactObj.formCallBack = function(){
console.log('Custom Reload Callback');
};
The problem appeared when I tried to add the callback, so that I could run a custom function during the refresh.
Running contactObj.setActive(); works properly, and my refresh function is called after the form submits, but when it hits my callback I get:
Uncaught TypeError: this.formCallBack is not a function
Calling it manually contactObj.refresh(); works smoothly.
How can I pass this callback function through better?
The problem is that you're passing method as function, so you loose this context. this will be window object or undefined (if using strict mode):
You need this:
var self = this;
lightboxForm(this.links.editURL+'?'+obj_id, function(x) { self.reload(x) });
or using ES6
lightboxForm(this.links.editURL+'?'+obj_id, x => this.reload(x));
or using bind to return function with given context:
lightboxForm(this.links.editURL+'?'+obj_id, this.reload.bind(this));

attache function to other function in javascript

I have a simple requirement, I need add the same code to hundreds of other JavaScript functions, the code can be executed at the end of the function, is there a handy way of doing it, like attach an function to another function dynamically, I think yes, because JavaScript is so powerful and too powerful, any ideas?
Note, I need dynamically assign new code or function to existing functions without change existing function's code, please give a solid solution, I can do it in hacky way, but no hacky way please!
The first method that comes to mind is simply create another function:
function primaryFunction() {
// ...
utilityMethod();
}
function otherPrimaryFunction() {
// ...
utilityMethod();
}
function utilityMethod() { ... }
Now utilityMethod() gets called from the end of each other primary function.
There's also a method which requires more code refactoring but is better in the long term: classes/prototypes.
Essentially, you have one "constructor" function which takes a number of parameters for the "class" and returns an class-like object:
function constructor(someClassField, anotherField) {
this.aField = someClassField;
this.fieldTwo = anotherField;
return this;
}
Now if you call this and pass some parameters, you get a class out:
var myClass = new constructor("1", "2");
myClass.aField == "1";
myClass.fieldTwo == "2";
So: If you define your utility method as above, then you can use this: for every primary function you instantiate a new instance of the constructor, with the final code looking like this:
function constructor(primaryFunction) {
this.function = primaryFunction;
this.call = function() {
this.function();
utilityMethod();
}
this.call();
return this;
}
function utilityMethod() { ... }
var primaryMethod = new constructor(function() { ... });
The creation of primaryMethod now automatically calls the primary function followed by the utility method, before returning the object so you can re-call both if you want to.

Is there any way to call "benchmark" in the javascript code

I am trying to understand this javascript code, which extends jquery. I know calling extend on the jquery object will add a tablesorter object to the $ object.
I want to clear the cache on a tablesorter object, something like this
$("#myTable").tablesorter();
$("#myTable").buildParserCache(); //so as to clear the cache. I get undefined is not a function.
$("#myTable").tablesorter().buildParserCache() //also get undefined is not a function
Can someone explain why this is not possible and how to clear the cache. I know I am butting up against some JS design patterns and scope rules and I would like to understand them.
(function ($) {
$.extend({
tablesorter: new
function () {
var parsers = [],
widgets = [];
this.defaults = {
...
};
/* debuging utils */
function benchmark(s, d) {
log(s + "," + (new Date().getTime() - d.getTime()) + "ms");
}
function buildParserCache(table, $headers) {
if (table.config.debug) {
var parsersDebug = "";
}
The buildParserCache function is defined inside the function () which starts on the fourth line of that snippet, so it will only be accessible outside the function if it is explicitly exposed - and it looks like it isn't, so that is effectively a private function.
The methods which are exposed appear to be grouped after the /* public methods */ comment.
The this.construct = function function gets further exposed by this:
$.fn.extend({
tablesorter: $.tablesorter.construct
});
so that is the function that ends up being called when $("#myTable").tablesorter() is called.

Javascript order of execution: how to detect object creation

I want to instantiate a new MediaElementPlayer object. When it's successfully created, I want to pass the whole object on to another function (my_object.attachEvents). My code is as follows:
var options = {
success: function () {
//point 2
console.log("passing player object", local_player_instance);
my_main_object.attachEvents(local_player_instance);
}
}
//point 1
console.log('player inited', local_player_instance);
local_player_instance.video = new MediaElementPlayer('#video_player', options);
my_main_object.attachEvents = function(local_player_instance) {
local_player_instance.video.play()
}
In Firefox, the assignment at point one is executed before the line at point 2 calls the attach events method.
Im Chrome, point 2 is evaluate first, and as a result when the play method in the attach events function is called it doesn't exist.
My question is, how do I pass successfully pass the MediaElementPlayer to another function when it is created?
The best way to handle this in a cross browser way is
// here's where you'll store a global reference to the player
var globalMediaElement = null;
var options = {
success: function (domNode, mediaElement) {
globalMediaElement = mediaElement;
doStuff();
// you can also get the the player via jQuery here
$('#video_player').player
}
}
// create MediaElement
new MediaElementPlayer('#video_player', options);
function doStuff() {
globalMediaElement.play();
}

Javascript Class Inheritance

Can anyone tell me why my 'showDiv_boo' is undefined inside the class´s method?
I also can´t access my class´s methods.
Here´s my class 'Blink' class with its properties and methods:
function Blink(div) {
this.div = div
}
Blink.prototype.counter = 0
Blink.prototype.showDiv_boo = true
Blink.prototype.showDiv = function() {
this.div.style.visibility = 'visible'
}
Blink.prototype.hideDiv = function() {
this.div.style.visibility = 'hidden'
}
Blink.prototype.startEngine = function() {
if (this.showDiv_boo) {
this.showDiv()
} else if (!this.showDiv_boo) {
this.hideDiv()
}
this.showDiv_boo = !this.showDiv_boo
this.counter++
}
Blink.prototype.startEffect = function() {
this.idEffect = setInterval(this.startEngine, 1000 / 45)
}
So, if I create:
_blink = new Blink(myDiv);
_blink.startEffect();
You can test... the variable 'showDiv_boo', is undefined inside the method.
Even, if I set the showDiv_boo inside the method to true, it won´t call my class´s methods showDiv or hideDiv.
Anyone?
Thanks :)
The reason why is that startEngine is called from setInterval. The way in which this callback is invoked causes startEngine to have a different value for this than startEffect. You need to save this in order to maintain it in the callback. For example.
Blink.prototype.startEffect = function () {
var self = this;
self.idEffect = setInterval(function () { self.startEngine(); }, 1000 / 45);
};
You need to:
use var self and call the method via self.startEngine()
use an anonymous function to wrap the call in [1] i.e. function(){ self.startEngine(); }
This is because when you just pass this.startEngine or self.startEngine you are just passing the function startEngine without specifying what this is, which in both cases is supplied by the global conext of DOMWindow.
To give an example...
function startEngine() {
...code omitted...
};
Blink.prototype.startEngine = startEngine;
Blink.prototype.start = function() {
setTimeout(startEngine, 0); // obviously wrong, what is this?
setTimeout(Blink.startEngine, 0); // actually the same as line above, although not as obvious
setTimeout(startEngine.bind(this), 0); // works correctly
}
works to add code to the prototype and if used in the anonymous function will work as expected, but if you just use Blink.startEngine as the callback it is exactly the same as using startEngine only the second is more obviously wrong because there's no object it is being called on so you'd expect this to be whatever is supplied by the context.
The other way you could do this without using the anonymous function would be
Blink.startEngine.bind(self)
Which returns a function that will call startEngine with the correct this same as explicitly creating the anonymous function and wrapping the call to self.startEngine()
Heres a link to a fiddle to play around with the differences: http://jsfiddle.net/bonza_labs/MdeTF/
If you do the following, you will find it is defined
var x = new Blink('hello');
x.showDiv_boo
Javascript uses prototypical inheritance. While showDiv_boo may not be explicitly defined within the instance of Blink that you now have, it does exist within the prototype that Blink inherits from. When you try referencing showDiv_boo from within the object, the Javascript engine realizes the object does not own a member by that name and then will check its prototype.
Along with setting a temporal variable to store this, you must call the startEngine() function with that variable:
Blink.prototype.startEffect = function(){
var self = this;
self.idEffect = setInterval(function(){ self.startEngine.call(self); }, 1000/45);
}
Note the .call(self), which basically calls the function with the variable self, so the variable this in startEngine will be the correct one.

Categories