Javascript Closure technique - javascript

I have an object which creates a slideshow.
I want to have several slideshows on a page
I have an event handler for slideshow element inside it
I want the event handler to know which slideshow object has created an item clicked
-
slideshow=function(){
var g = document.createElement(...);
...
g.onclick = f1;
f1 = function(slideshow_instance, event) {
//EXPLAIN ME HOW TO GET THE slideshow_instance
}
}
var sl1 = new slideshow();
var sl2 = new slideshow();
Clicking on an element slideshow has created should return either
sl1
or
sl2
I explain well?

Short answer, use: this.
Longer answer, what you want is:
slideshow=function(){
/* ... */
var self = this;
f1 = function(event) {
// do stuff with self.
}
}
The reason you need to point to this using self is that event handlers will change the meaning of this when they are called. But at the time the object is created this properly refer to the correct object (an instance of slideshow). The reason we can access the self variable during event callback is because it has been captured by a closure.
Feel free to google or search on stackoverflow any word/terminology from the above description if you need further explanation.

slideshow=function(){
var g = document.createElement(...);
g._objRef = this;
...
g.onclick = f1;
f1 = function(event) {
alert(this._objRef);
}
}
var sl1 = new slideshow();
var sl2 = new slideshow();
A bit of a hack imo, but it should do the trick.

Related

Passing $(this) to a new P5 instance is undefined

In the code below, I'm looping through each "player_visualizer" element and attempting to create a new P5 instance for each element.
If I console.log(context) in the loop I will get the context of that particular element, which is exactly what I need.
$('.player_visualizer').each(function (i) {
context = $(this);
playerVisualizersP5[i] = new p5(playerVisualizer, context);
});
However, The trouble I'm having is passing the context of that particular element to the function that will handle all of the P5 animations.
For example, when I try and pass that context variable to the function below and do console.log(p.context), the context variable is always undefined.
let playerVisualizer = function (p, context) {
p.context = context;
}
I've done a fair amount of research on what I could do about this, but I can't seem to tie it back to my particular situation. I've narrowed down my research to a few resources below.
http://hugoware.net/blog/passing-context-with-javascript
How do I pass the this context to a function?
Any help or guidance is greatly appreciated.
Why do you believe that passing something into the p5 constructor will automatically pass that argument into the playerVisualizer function?
From the P5.js documentation:
One final note: when creating a p5 instance, you can specify a second
argument (HTML element id) which acts the parent for all elements
created by the sketch. For example, let's say you have:
<body>
<div id = "p5sketch">
</div>
<p>Some other HTML</p>
</body>
You can now say:
var myp5 = new p5(s,'p5sketch');
And all elements will be created inside that div.
This means the only valid second argument is a string ID, which gets used by P5.js but isn't passed into the sketch function.
To understand better what's going on, let's look at this example:
var s = function( sketch ) {
sketch.setup = function() {
sketch.createCanvas(200, 200);
};
sketch.draw = function() {
sketch.background(128);
};
};
var myp5 = new p5(s);
In this example sketch, there are a few things to understand:
myp5 is an instance of p5, which contains P5.js functions like setup() and draw() and background().
s is a sketch function, which takes an instance of p5.
sketch is an instance of p5, which s can use to access P5.js functions.
In other words, myp5 and sketch are the same object.
This is useful to you, because if you want to pass data into sketch, you can pass that data into myp5, like this:
var s = function( sketch ) {
sketch.setup = function() {
sketch.createCanvas(200, 200);
};
sketch.draw = function() {
sketch.background(128);
sketch.text(sketch.extraThing, 20, 20);
};
};
var myp5 = new p5(s);
myp5.extraThing = "testing";

Javascript: run object method on DOM object selected through other property

I am very new to javascript.
Here I am failing to run an object method on a DOM element that I selected through another property of the same object. I suspect there is something wrong with my thinking!
Thanks in advance for any piece of help.
var Arrow = function() {
this.current = $('.arrow');
this.previous = null;
this.bend = function() {
// do bend
};
};
var arrow = new Arrow();
arrow.current.bend();
bend() is a method of Arrow, not current. Use arrow.bend() and it will also have access to current using this.current.
arrow.current.bend is not defined.
You have defined:
this.current as the Array of DOM elements.
this.bend as method with a function.
Hence, you can call:
arrow.current >> returns Array of DOMs
arrow.bend() >> executes function bend.
arrow.current.bend() does not exist.
Also, note that arrow.current is an array. You'd first need to get each of the elements:
for (element of arrow.current) { element.bend(); }
However, as said before, element does not have a bend element by default and you have not appended at any point. Only arrow has a bend property.
I hope this guides you on why this does not work.
However, if you want to open a question on what you are trying to achieve, maybe we can help to get it fixed.
You need to call bend() on arrow object. In bend() function, you do what you need to do.
var Arrow = function() {
this.current = $('.arrow');
this.previous = null;
this.bend = function() {
// do bend
current.style = 'bent';
};
};
var arrow = new Arrow();
arrow.bend();
So two things.
You called the right method on the wrong object
arrow.bend(); // not arrow.current.bend()
The second possible problem is with this.current = $('.arrow');. To get the an element from the DOM, you should make sure it's totally loaded. I'd suggest the following
var Arrow = function($arrow) {
this.current = $arrow;
this.previous = null;
};
// To avoid creating the `bend` in every instance of Arrow
Arrow.prototype.bend = function() {
console.log(this.current.attr('id'));
};
$(function () {
// Now it's certain that the DOM is completely loaded
var arrow = new Arrow($('.arrow').first());
arrow.bend();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="arrow" id="toto">arrow<div>

Understanding better Javascript OOP architecture [duplicate]

This question already has answers here:
JavaScript: Class.method vs. Class.prototype.method
(5 answers)
Closed 9 years ago.
As i read through some examples of Angularjs' UI add-on, i've stumbled over some code that showed me that my knowdledge of Javascript is quite improvable:
The following is a class inside of an Angular provider:
function Dialog(opts) {
var self = this, options = this.options = angular.extend({}, defaults, globalOptions, opts);
this._open = false;
this.backdropEl = createElement(options.backdropClass);
if(options.backdropFade){
// ...
}
this.handleLocationChange = function() {
self.close();
};
// more functions
}
Pretty straightforward. But outside of that class, there are prototype functions, e.g the above invoked close()
Dialog.prototype.open = function(templateUrl, controller){
var self = this, options = this.options;
// .. some code
};
Now i do not understand why that function is declared as a prototype, but handleLocationChange inside the class itself.
How do i decide which method to choose?
The full gist can be found here
Consider these 2 cases:
Dialog.prototype.open = function...
Dialog.open = function....
First case - every object created by calling new Dialog() will have this open function
Second case has nothing to do with dialog objects, consider it as static function.
EDIT
found a great answer here : javascript-class-method-vs-class-prototype-method
function open will be shared by all objects create using new Dialog().. and handleLocationChange will be different for different objects.
I think handleLocationChange is called from event triggering object that registers listeners but doesn't register the this context so when it's triggered you can't use this as it refers to handleLocationChange. To overcome this they have chosen to set a closure reference to this (=the self variable) and call other instance functions using self. Basically it's storing a value known at creation but not known when handleLocationChange is executing.
Here is some code showing the problem:
var eventSystem={
events:{},
add:function(eventname,fnCallback){
if(!this.events[eventname]){
this.events[eventname]=[];
}
this.events[eventname].push(fnCallback);
},
trigger:function(eventname){
if(!this.events[eventname]){
return;
}
var i=0;
for(i=0;i<this.events[eventname].length;i++){
this.events[eventname][i]();
}
}
};
var person=function(name){
this.name=name;
};
person.prototype.sayName=function(){
console.log("this is now:",this.toString());
// logs this is now: function (){ console.log("this is now:...
// so this is now the sayName function not the person instance
console.log(this.name);//undefined: sayName doesn't have a name property
}
var jon=new person("jon");
eventSystem.add("sayname",jon.sayName);//add event and listener function
eventSystem.trigger("sayname");//trigger the event
Here is how it's solved setting a closure reference
var eventSystem={
events:{},
add:function(eventname,fnCallback){
if(!this.events[eventname]){
this.events[eventname]=[];
}
this.events[eventname].push(fnCallback);
},
trigger:function(eventname){
if(!this.events[eventname]){
return;
}
var i=0;
for(i=0;i<this.events[eventname].length;i++){
this.events[eventname][i]();
}
}
};
var person=function(name){
var self=this;// set closure ref to this
this.name=name;
this.sayName=function(){
console.log(self.name);//use closure ref to get this
// logs jon
}
};
var jon=new person("jon");
eventSystem.add("sayname",jon.sayName);//add event and listener function
eventSystem.trigger("sayname");//trigger the event
Here is a fix to the event system to take care of the this context:
var eventSystem={
events:{},
add:function(eventname,fnCallback,thisRef){
if(!this.events[eventname]){
this.events[eventname]=[];
}
this.events[eventname].push({
"callback":fnCallback,//store the event handler
"thisRef":thisRef//store the this context
});
},
trigger:function(eventname){
if(!this.events[eventname]){
return;
}
var i=0;
for(i=0;i<this.events[eventname].length;i++){
this.events[eventname][i].callback.call(
this.events[eventname][i].thisRef);
}
}
};
var person=function(name){
this.name=name;
};
person.prototype.sayName=function(){
console.log("this is now:",this);//referring to person instance
// with the name jon
console.log(this.name);//logs jon
console.log(this instanceof person);//true
}
var jon=new person("jon");
eventSystem.add("sayname",jon.sayName,jon);//add extra parameter for this ref
eventSystem.trigger("sayname");//trigger the event
The pattern used above is not an event system (think it's pulisher subscriber) as an event usually get triggered on or invoked from an object (button, input, dialog) but in case of a more event system like implementation it would be easy to get the correct this context since you trigger the event on or from an instance (like myButton or myDialog).
See following code for event system like implementation:
var eventSystem={
add:function(eventname,fnCallback){
if(!this.events[eventname]){
this.events[eventname]=[];
}
this.events[eventname].push(fnCallback);
},
//change in trigger as it's passing the event object now
trigger:function(event){
if(!this.events[event.type]){
return;
}
var i=0;
for(i=0;i<this.events[event.type].length;i++){
this.events[event.type][i](event);
}
},
initES:function(){//set the instance variables needed
this.events=this.events||{};
}
};
function addProtos(o,protos){
for(item in protos){
o.prototype[item]=protos[item];
}
}
var person=function(name){
this.name=name;
this.initES();//needed to initialeze eventsystem
};
// make person capable of storing event handlers
// and triggering them
addProtos(person,eventSystem);
person.prototype.askQuestion=function(){
//asking a question will trigger an "answer" event
this.trigger({type:"answer",target:this});
}
// handler for when jon will fire an answer event
function answerHandler(event){
console.log("answer from:",event.target);
console.log("name of the person:",event.target.name);
}
var jon=new person("jon");
jon.add("answer",answerHandler);//add event listener
jon.askQuestion();//triggers the answer event from within jon
jon.trigger({type:"answer",target:jon});//trigger the event externally
Not sure why Angular choose to "break" prototype by using closures as the examples show there are other alternatives. Maybe someone can explain that who is more familiar with Angular.

Javascript object methods and binding functions

I'm looking for some help because I don't quite think I understand the Javascript scoping rules. What I'm trying to do in the below example is to push a button on a page that then starts listening for keyboard input. Once the keyboard input has started if there is a break in input for two seconds I want to stop capturing the input and pop an alert with the full contents of the input collected to that point. This is an example I made purely for this question.
What I see is that I click the button and start entering input. On each keypress I am alerted to the string collected to that point. After the two second, no-action timeout takes place I see an alert with the contents "undefined". The first alerts listed above come from startLog(). The second alert comes from stopLog(). What am I doing wrong when I call stopLog that it is telling me that this.message is undefined?
function Logger() {
this.message = '';
this.listenTimer;
this.startLog = function() {
this.message = '';
$(document).bind('keypress', {this_obj:this}, function(event) {
event.preventDefault();
var data = event.data;
clearTimeout(data.this_obj.listenTimer);
data.this_obj.message += String.fromCharCode(event.which);
alert(data.this_obj.message);
data.this_obj.listenTimer = setTimeout(data.this_obj.stopLog, 2000);
});
};
this.stopLog = function() {
$(document).unbind("keypress");
alert(this.message);
};
}
var k = new Logger();
$('.logging-button').click(function() {
k.startLog();
});
The issue is this. When you pass an object method as an event handler, it loses its object context; this will refer to the window object.
There are various ways to fix this, but the main issue is that you need to pass setTimeout a closure that will still refer to the correct context:
setTimeout(function() { data.this_obj.stopLog() }, 2000);
On a separate note, you can save yourself some unnecessary code by just using a closure to refer to the object, rather than binding it as event.data:
this.startLog = function() {
this.message = '';
var this_obj = this;
$(document).bind('keypress', function(event) {
event.preventDefault();
clearTimeout(this_obj.listenTimer);
// etc
});
};
var k = new Logger();
$('.logging-button').click(function() {
k.startLog.apply(this);
//Setting context of "this" so that it refers to element even in startLog()
});

Javascript function objects

I edited the question so it would make more sense.
I have a function that needs a couple arguments - let's call it fc(). I am passing that function as an argument through other functions (lets call them fa() and fb()). Each of the functions that fc() passes through add an argument to fc(). How do I pass fc() to each function without having to pass fc()'s arguments separately? Below is how I want it to work.
function fa(fc){
fc.myvar=something
fb(fc)
}
function fb(fc){
fc.myothervar=something
fc()
}
function fc(){
doessomething with myvar and myothervar
}
Below is how I do it now. As I add arguments, it's getting confusing because I have to add them to preceding function(s) as well. fb() and fc() get used elsewhere and I am loosing some flexibility.
function fa(fc){
myvar=something
fb(fc,myvar)
}
function fb(fc,myvar){
myothervar=something
fc(myvar,myothervar)
}
function fc(myvar,myothervar){
doessomething with myvar and myothervar
}
Thanks for your help
Edit 3 - The code
I updated my code using JimmyP's solution. I'd be interested in Jason Bunting's non-hack solution. Remember that each of these functions are also called from other functions and events.
From the HTML page
<input type="text" class="right" dynamicSelect="../selectLists/otherchargetype.aspx,null,calcSalesTax"/>
Set event handlers when section is loaded
function setDynamicSelectElements(oSet) {
/**************************************************************************************
* Sets the event handlers for inputs with dynamic selects
**************************************************************************************/
if (oSet.dynamicSelect) {
var ySelectArgs = oSet.dynamicSelect.split(',');
with (oSet) {
onkeyup = function() { findListItem(this); };
onclick = function() { selectList(ySelectArgs[0], ySelectArgs[1], ySelectArgs[2]) }
}
}
}
onclick event builds list
function selectList(sListName, sQuery, fnFollowing) {
/**************************************************************************************
* Build a dynamic select list and set each of the events for the table elements
**************************************************************************************/
if (fnFollowing) {
fnFollowing = eval(fnFollowing)//sent text function name, eval to a function
configureSelectList.clickEvent = fnFollowing
}
var oDiv = setDiv(sListName, sQuery, 'dynamicSelect', configureSelectList); //create the div in the right place
var oSelected = event.srcElement;
if (oSelected.value) findListItem(oSelected)//highlight the selected item
}
Create the list
function setDiv(sPageName, sQuery, sClassName, fnBeforeAppend) {
/**************************************************************************************
* Creates a div and places a page in it.
**************************************************************************************/
var oSelected = event.srcElement;
var sCursor = oSelected.style.cursor; //remember this for later
var coords = getElementCoords(oSelected);
var iBorder = makeNumeric(getStyle(oSelected, 'border-width'))
var oParent = oSelected.parentNode
if (!oParent.id) oParent.id = sAutoGenIdPrefix + randomNumber()//create an ID
var oDiv = document.getElementById(oParent.id + sWindowIdSuffix)//see if the div already exists
if (!oDiv) {//if not create it and set an id we can use to find it later
oDiv = document.createElement('DIV')
oDiv.id = oParent.id + sWindowIdSuffix//give the child an id so we can reference it later
oSelected.style.cursor = 'wait'//until the thing is loaded
oDiv.className = sClassName
oDiv.style.pixelLeft = coords.x + (iBorder * 2)
oDiv.style.pixelTop = (coords.y + coords.h + (iBorder * 2))
XmlHttpPage(sPageName, oDiv, sQuery)
if (fnBeforeAppend) {
fnBeforeAppend(oDiv)
}
oParent.appendChild(oDiv)
oSelected.style.cursor = ''//until the thing is loaded//once it's loaded, set the cursor back
oDiv.style.cursor = ''
}
return oDiv;
}
Position and size the list
function configureSelectList(oDiv, fnOnClick) {
/**************************************************************************************
* Build a dynamic select list and set each of the events for the table elements
* Created in one place and moved to another so that sizing based on the cell width can
* occur without being affected by stylesheet cascades
**************************************************************************************/
if(!fnOnClick) fnOnClick=configureSelectList.clickEvent
if (!oDiv) oDiv = configureSelectList.Container;
var oTable = getDecendant('TABLE', oDiv)
document.getElementsByTagName('TABLE')[0].rows[0].cells[0].appendChild(oDiv)//append to the doc so we are style free, then move it later
if (oTable) {
for (iRow = 0; iRow < oTable.rows.length; iRow++) {
var oRow = oTable.rows[iRow]
oRow.onmouseover = function() { highlightSelection(this) };
oRow.onmouseout = function() { highlightSelection(this) };
oRow.style.cursor = 'hand';
oRow.onclick = function() { closeSelectList(0); fnOnClick ? fnOnClick() : null };
oRow.cells[0].style.whiteSpace = 'nowrap'
}
} else {
//show some kind of error
}
oDiv.style.width = (oTable.offsetWidth + 20) + "px"; //no horiz scroll bars please
oTable.mouseout = function() { closeSelectList(500) };
if (oDiv.firstChild.offsetHeight < oDiv.offsetHeight) oDiv.style.height = oDiv.firstChild.offsetHeight//make sure the list is not too big for a few of items
}
Okay, so - where to start? :) Here is the partial function to begin with, you will need this (now and in the future, if you spend a lot of time hacking JavaScript):
function partial(func /*, 0..n args */) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
var allArguments = args.concat(Array.prototype.slice.call(arguments));
return func.apply(this, allArguments);
};
}
I see a lot of things about your code that make me cringe, but since I don't have time to really critique it, and you didn't ask for it, I will suggest the following if you want to rid yourself of the hack you are currently using, and a few other things:
The setDynamicSelectElements() function
In this function, you can change this line:
onclick = function() { selectList(ySelectArgs[0], ySelectArgs[1], ySelectArgs[2]) }
To this:
onclick = function() { selectList.apply(null, ySelectArgs); }
The selectList() function
In this function, you can get rid of this code where you are using eval - don't ever use eval unless you have a good reason to do so, it is very risky (go read up on it):
if (fnFollowing) {
fnFollowing = eval(fnFollowing)
configureSelectList.clickEvent = fnFollowing
}
And use this instead:
if(fnFollowing) {
fnFollowing = window[fnFollowing]; //this will find the function in the global scope
}
Then, change this line:
var oDiv = setDiv(sListName, sQuery, 'dynamicSelect', configureSelectList);
To this:
var oDiv = setDiv(sListName, sQuery, 'dynamicSelect', partial(configureSelectListAlternate, fnFollowing));
Now, in that code I provided, I have "configureSelectListAlternate" - that is a function that is the same as "configureSelectList" but has the parameters in the reverse order - if you can reverse the order of the parameters to "configureSelectList" instead, do that, otherwise here is my version:
function configureSelectListAlternate(fnOnClick, oDiv) {
configureSelectList(oDiv, fnOnClick);
}
The configureSelectList() function
In this function, you can eliminate this line:
if(!fnOnClick) fnOnClick=configureSelectList.clickEvent
That isn't needed any longer. Now, I see something I don't understand:
if (!oDiv) oDiv = configureSelectList.Container;
I didn't see you hook that Container property on in any of the other code. Unless you need this line, you should be able to get rid of it.
The setDiv() function can stay the same.
Not too exciting, but you get the idea - your code really could use some cleanup - are you avoiding the use of a library like jQuery or MochiKit for a good reason? It would make your life a lot easier...
A function's properties are not available as variables in the local scope. You must access them as properties. So, within 'fc' you could access 'myvar' in one of two ways:
// #1
arguments.callee.myvar;
// #2
fc.myvar;
Either's fine...
Try inheritance - by passing your whatever object as an argument, you gain access to whatever variables inside, like:
function Obj (iString) { // Base object
this.string = iString;
}
var myObj = new Obj ("text");
function InheritedObj (objInstance) { // Object with Obj vars
this.subObj = objInstance;
}
var myInheritedObj = new InheritedObj (myObj);
var myVar = myInheritedObj.subObj.string;
document.write (myVar);
subObj will take the form of myObj, so you can access the variables inside.
Maybe you are looking for Partial Function Application, or possibly currying?
Here is a quote from a blog post on the difference:
Where partial application takes a function and from it builds a function which takes fewer arguments, currying builds functions which take multiple arguments by composition of functions which each take a single argument.
If possible, it would help us help you if you could simplify your example and/or provide actual JS code instead of pseudocode.

Categories