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

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";

Related

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>

Javascript / Why mycode is not synchronous

I have discover that a custom code is not syncronous as I thought.
I have this pseudo code:
ObjectA = function ()
{
var pointer;
var value =[];
this.set_pointer = function (p) {pointer = p;}
this.return_value = function () {return value[pointer];}
}
ObjectB = function ()
{
var SCOPE = this;
var OBJ = new ObjectA();
....
this.reset = function ()
{ OBJ.set_pointer(0);}
this.draw = function (what)
{
SCOPE.update();
OBJ.set_pointer(from);
OBJ.get_value();
// do somethings with Three.js
// draw some lines and some little pointclouds.
// do some things
// update two text elements
}
}
Main = new ObjectB();
Main.draw(7);
Main.reset();
ObjectA is using arraybuffers, dataviews and typedarrays.
ObjectB is using Three.js to draw some very symple 3D things.
The problem is inside 'draw'.
OBJ.get_value(); is using the pointer value 0 (zero) instead 7.
Abnormally (as I think) main.draw(7) is not executed first and later Main.reset(); It seems that Main.reset() is inmediatelly executed, so I have 0 (zero)
I'm not going to wait any DOM synchronism.
What can be the reason of this bechaviour?. Maybe the Three,js use ? The OOP style I'm using ?
Is there any way to check why is this happen?
Any idea would be appreciated
NOTE: Sorry for use the Three.js tag.
SOLVED
I have found the reason (or I think ) of a NO synchonism
Sometimes 'this' could be pointing to 'window' instead of the self instance of your object.
Sometimes, a bad use of this (when it is 'window') can raise an error you have not taken into account. Then a next line of code can be executed, and sometimes you can have the impression of an incorrect (not syncronous) operation.
So.... review the bad use of 'this'....

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.

Javascript Closure technique

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.

"this" doesn't work when binding functions to events inside a Javascript class

First off, I know I can copy "this" on instantiation, but that doesn't work here.
Basically I'm writing something to track people interacting with Youtube videos.
I got this working fine for one video at a time. But I want it to work on pages with multiple Youtube videos as well, so I converted the code to a class so I can create a new instance of it for each video on the page.
The problem is when trying to bind to the Youtube event listener for state changes. For "non-class" code, it looks like this:
var o = document.getElementById( id );
o.addEventListener("onStateChange", "onPlayerStateChange" );
(onPlayerStateChange being the function I wrote to track state changes in the video)
(I'm also aware that addEventListener won't work with MSIE but I'm not worrying about that yet)
But when I'm inside a class, I have to use "this" to refer to another function in that class. Here's what the code looks like:
this.o = document.getElementById( id );
this.o.addEventListener("onStateChange", "this.onPlayerStateChange" );
When it's written like this, this.onPlayerStateChange is never called. I've tried copying "this" into another variable, e.g. "me", but that doesn't work either. The onPlayerStateChange function is defined within the "this" scope before I do this:
var me = this;
this.o = document.getElementById( id );
this.o.addEventListener("onStateChange", "me.onPlayerStateChange" );
Any insights?
Looking through other similar questions here, all of them are using jQuery, and I think doing it that way might work if I did it that way. But I don't want to use jQuery, because this is going to be deployed on random third party sites. I love jQuery but I don't want it to be a requirement to use this.
You need a global way to access the onPlayerStateChange method of your object. When you assign me as var me = this;, the variable me is only valid inside the object method where it is created. However, the Youtube player API requires a function that is accessible globally, since the actual call is coming from Flash and it has no direct reference to your JavaScript function.
I found a very helpful blog post by James Coglan in which he discussed a nice way to communicate with the Youtube's JavaScript API and manage events for multiple videos.
I have released a JavaScript wrapper library using his ideas at http://github.com/AnuragMishra/YoutubePlayer. Feel free to checkout the code. The underlying idea is simple - store all instances of the player object on the constructor. For example:
function Player(id) {
// id of the placeholder div that gets replaced
// the <object> element in which the flash video resides will
// replace the placeholder div and take over its id
this.id = id;
Player.instances.push(this);
}
Player.instances = [];
When passing a string as a callback, use a string of the form:
"Player.dispatchEvent('playerId')"
When the flash player evals this string, it should return a function. That function is the callback that will ultimately receive the playback event id.
Player.dispatchEvent = function(id) {
var player = ..; // search player object using id in "instances"
return function(eventId) { // this is the callback that Flash talks to
player.notify(eventId);
};
};
When the flash player has loaded the video, the global onYoutubePlayerReady function is called. Inside that method, setup the event handlers for listening to playback events.
function onYouTubePlayerReady(id) {
var player = ..; // find player in "instances"
// replace <id> with player.id
var callback = "YoutubePlayer.dispatchEvent({id})";
callback = callback.replace("{id}", player.id);
player.addEventListener('onStateChange', callback);
}
See a working example here..
You can use a technique called currying to achieve this. For that you need a currying function. Here's one I wrote some time back
/**
* Changes the scope of function "fn" to the "scope" parameter specified or
* if not, defaults to window scope. The scope of the function determines what
* "this" inside "fn" evaluates to, inside the function "fn". Any additional arguments
* specified in this are passed to the underlying "curried" function. If the underlying
* function is already passed some arguments, the optional arguments are appended
* to the argument array of the underlying function. To explain this lets take
* the example below:
*
* You can pass any number of arguments that are passed to the underlying (curried)
* function
* #param {Function} fn The function to curry
* #param {Object} scope The scope to be set inside the curried function, if
* not specified, defaults to window
* #param arguments {...} Any other optional arguments ot be passed to the curried function
*
*/
var curry = function(fn, scope /*, arguments */) {
scope = scope || window;
var actualArgs = arguments;
return function() {
var args = [];
for(var j = 0; j < arguments.length; j++) {
args.push(arguments[j]);
}
for(var i = 2; i < actualArgs.length; i++) {
args.push(actualArgs[i]);
}
return fn.apply(scope, args);
};
};
You can use it to curry other functions and maintain the 'this' scope inside the functions.
Check out this article on currying
this.o.addEventListener("onStateChange", curry(onPlayerStateChange, this));
Edit:
var curriedFunc = curry(onPlayerStateChange, this);
this.o.addEventListener("onStateChange", "curriedFunc");
Edit:
Okay lets say this is your custom class you create:
function MyCustomClass() {
var privateVar = "x"; // some variables;
this.onPlayerStateChange = function() { //instance method on your custom class
// do something important
}
}
On a global level you create an instance of MyCustomClass
var myCustom = new MyCustomClass(); // create a new instance of your custom class
var curriedFunc = curry(myCustom.onplayerStageChange, myCustom); // curry its onplayerstateChange
// now add it to your event handler
o.addEventListener("onStateChange", "curriedFunc");
You should be using the following to attach an event:
this.o.addEventListener("statechange", this.onPlayerStateChange);
For addEventListener, you don't need to add the on prefix.
What I posted above is correct for standard javascript, but because this passes it to the YT flash object, it's expecting onStateChange which is correct.
HTH
EDIT: Try the method in this post to help.
TheCloudlessSky was partly right and Sean was partly right. You can continue to use "onStateChange" as the event name, but don't put this.onPlayerStateChange in quotations - doing so removes the special meaning of this and javascript will look for a function named "this.onPlayerStateChange" rather than looking for a function "onPlayerStateChange" within this object.
this.o.addEventListener("onStateChange", this.onPlayerStateChange);
After looking at the Youtube Api, it looks like the addEventListener only accepts a String for the event handler function. That means there's no clean way to register a unique event handler for each object.
An alternative is to register a global handler for all youtube state changes, and then let that handler pass the state change onto all your objects. Assuming you have an array of "tracker" objects:
function globalOnPlayerStateChange() {
for (tracker in myTrackerObjects) {
tracker.playerStateChange();
}
}
Each tracker object can then figure out by itself whether or not a state change actually occured (using the API's getPlayerState function):
function MyYoutubeTracker() {
this.currentState = ...
// Determine if state changed happened or not
this.playerStateChange = function() {
var newState = this.o.getPlayerState();
if (newState != this.currentState) {
// State has changed
this.currentState = newState;
}
}
// Register global event handler for this youtube object
this.o.addEventListener("onStateChange", "globalOnPlayerStateChange");
}
Ok, I got this all working. It's a bit of an ugly hack but it works. Basically I'm storing each new instance of the class in an array, and I'm passing the array key (1, 2, etc) into the class, so it can refer to itself externally as needed in a few key places.
The places I need the class to refer to itself externally are the string I pass to addEventListener, and within a few setTimeout functions, where "this" apparently loses its context (as far as I can tell anyways, because the only way I could them working was changing "this" to use external references instead.
Here's the full code.
On the page that has Youtube videos, they are injected using swfobject. The _ytmeta object stores the titles for each video. It's optional, but it's the only way to log the title of a video, because Youtube's API does not give it to you. This means you have to know the title up front, but the point is simply that if you want the title to show up in our reports, you have to create this object:
<div id='yt1'></div>
<script src='youtube.js'></script>
<script src='swfobject.js'></script>
<script>
var _ytmeta = {}
_ytmeta.yt1 = { 'title': 'Moonwalking in Walmart' };
var params = { allowScriptAccess: "always" };
swfobject.embedSWF("http://www.youtube.com/v/gE1ZvCnwkYk?enablejsapi=1&playerapiid=yt1", "yt1", "425", "356", "8", null, null, params );
</script>
So we're including the swfobject javascript code, as well as the youtube.js file, which will be hosted on our server and included on the pages you want to track videos.
Here are the contents of youtube.js:
// we're storing each youtube object (video) in an array, and passing the array key into the class, so the class instance can refer to itself externally
// this is necessary for two reasons
// first, the event listener function we pass to Youtube has to be globally accessible, so passing "this.blah" doesn't work
// it has to be passed as a string also, so putting "this" in quotes makes it lose its special meaning
// second, when we create timeout functions, the meaning of "this" inside that function loses its scope, so we have to refer to the class externally from there too.
// _yt is the global youtube array that stores each youtube object. yti is the array key, incremented automatically for each new object created
var _yt = [], _yti = 0;
// this is the function the youtube player calls once it's loaded.
// each time it's called, it creates a new object in the global array, and passes the array key into the class so the class can refer to itself externally
function onYouTubePlayerReady( id ) {
_yti++;
_yt[ _yti ] = new _yta( id, _yti );
}
function _yta( id, i ) {
if( !id || !i ) return;
this.id = id;
this.mytime;
this.scrubTimer;
this.startTimer;
this.last = 'none';
this.scrubbing = false;
this.o = document.getElementById( this.id );
this.o.addEventListener("onStateChange", "_yt["+i+"].onPlayerStateChange" );
this.onPlayerStateChange = function( newState ) {
// some events rely on a timer to determine what action was performed, we clear it on every state change.
if( this.myTime != undefined ) clearTimeout( this.myTime );
// pause - happens when clicking pause, or seeking
// that's why a timeout is used, so if we're seeking, once it starts playing again, we log it as a seek and kill the timer that would have logged the pause
// we're only giving it 2 seconds to start playing again though. that should be enough for most users.
// if we happen to log a pause during the seek - so be it.
if( newState == '2' ) {
this.myTime = setTimeout( function() {
_yt[i].videoLog('pause');
_yt[i].last = 'pause';
_yt[i].scrubbing = false;
}, 2000 );
if( this.scrubbing == false ){
this.last = 'pre-scrub';
this.scrubbing = true;
}
}
// play
else if( newState == '1' ) {
switch( this.last ) {
case 'none':
this.killTimers();
this.startTimer = setInterval( this.startRun, 200 );
break;
case 'pause':
this.myTime = setTimeout( function() {
_yt[i].videoLog('play');
_yt[i].last = 'play';
}, 2000 );
break;
case 'pre-scrub':
this.killTimers();
this.scrubTimer = setInterval( this.scrubRun, 200 );
break;
}
}
// end
else if( newState == '0' ) {
this.last = 'none';
this.videoLog('end');
}
}
// have to use external calls here because these are set as timeouts, which makes "this" change context (apparently)
this.scrubRun = function() {
_yt[i].videoLog('seek');
_yt[i].killTimers();
_yt[i].last = 'scrub';
_yt[i].scrubbing = false;
}
this.startRun = function() {
_yt[i].videoLog('play');
_yt[i].killTimers();
_yt[i].last = 'start';
}
this.killTimers = function() {
if( this.startTimer ) {
clearInterval( this.startTimer );
this.startTimer = null;
}
if( this.scrubTimer ){
clearInterval( this.scrubTimer );
this.scrubTimer = null;
}
}
this.videoLog = function( action ) {
clicky.video( action, this.videoTime(), this.videoURL(), this.videoTitle());
}
this.videoTime = function() {
return Math.round( this.o.getCurrentTime() );
}
this.videoURL = function() {
return this.o.getVideoUrl().split('&')[0]; // remove any extra parameters - we just want the first one, which is the video ID.
}
this.videoTitle = function() {
// titles have to be defined in an external object
if( window['_ytmeta'] ) return window['_ytmeta'][ this.id ].title || '';
}
}
Hopefully, someone in the future will find this helpful, because it was a serious pain in the ass to get it working!
Thank you everyone who posted their ideas here. :)

Categories