I'm reading DOM Scripting and have a beginner question about the abstraction below. The original code didn't include "clearTimeout", and "movement" was declared as a global variable. While the code ran fine, it wasn't a smooth animation which is why clearTimeout was added. My question however, is why I can't just test for "movement" and when it fails (on the first mouseover call to moveElement) continue on through the rest of the function? If I keep "movement" as a global variable, rather than make it a property, the code doesn't run at all?
If it helps to see the other JS and HTML, I've plugged the remaining code into jsFiddle.
function moveElement(elementID,final_x,final_y,interval) {
if (!document.getElementById) return false;
if (!document.getElementById(elementID)) return false;
var elem = document.getElementById(elementID);
if (elem.movement) { //Why can't I use "movement"?
clearTimeout(elem.movement);
}
var xpos = parseInt(elem.style.left);
var ypos = parseInt(elem.style.top);
if (xpos == final_x && ypos == final_y) {
return true;
}
if (xpos < final_x) {
xpos++;
}
if (xpos > final_x) {
xpos--;
}
if (ypos < final_y) {
ypos++;
}
if (ypos > final_y) {
ypos--;
}
elem.style.left = xpos + "px";
elem.style.top = ypos + "px";
var repeat = "moveElement('"+elementID+"',"+final_x+","+final_y+","+interval+")";
elem.movement = setTimeout(repeat,interval); //Originally global property
}
A property can never be undefined, only have a undefined as a value. In contrast, a variable may be undefined (not defined) or have undefined as a value.
When you say if (movement), in the case that movement is not defined, it will throw an exception.
When you say if (elem.movement), in teh case that elem.movement is not defined, it will evaluate to false and fail the condition without exceptions.
If you want to use movement as a global variable, you must first declare it before attempting to read from it via:
var movement;
function moveElement(elementID,final_x,final_y,interval) {
...
}
Alternatively, you could try to read the global movement as a property, since all global variables are just properties of the window object (in a browser):
if (window.movement) { // this will never throw an exception
clearTimeout(movement);
}
And finally, you could switch your if statement to one that protects itself against these exceptions via the typeof operator:
if (typeof movement !== 'undefined') {
clearTimeout(movement);
}
Related
I'm running into an error that I'm not sure how to resolve. I have the function update() below that takes in an object and then performs logic on it.
var requestAnimFrame = ( function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 10);
};
})();
function doAnimation(ballObject) {
requestAnimFrame(doAnimation);
update(ballObject);
console.log( getProperty("ball","top") );
console.log( getProperty("ball","left") );
}
function Ball(id) {
this.id = id;
this.xVel = .17;
this.yVel = .13;
this.ts;
}
function update(ball){
// do timestamp calculations
var newBall = ball;
var ts = Date.now();
newBall.ts = newBall.ts || ts;
var time_elapsed = ts - newBall.ts;
newBall.ts = ts;
// get values from ball
var ball_y = parseFloat( getProperty(newBall.id,"top").replace ("px", "") ) || 0;
var ball_x = parseFloat( getProperty(newBall.id,"left").replace("px", "") ) || 0;
// do other things, such as set new location for the ball's top and
// left position and etc.
}
// Code that starts the entire process
(function() {
ball = new Ball("ball");
doAnimation(ball);
})();
As you can see from this exert of my function above, I am trying to assign the property "ts" on the "newBall" object to the value held by the local variable named "ts". However, when I go to do that assignment, the entire value of the "newBall" object becomes the value of the local variable "ts" instead of just the "newBall" object's "ts" property. I'm assuming this is reflective of an error in my syntax, but I really don't have any clue? Does anyone have any ideas why this is happening?
I know that from c++ pointers, you have to de-reference the pointer before you can access the properties of the pointer object. Do I somehow have to do the same thing here?
EDIT: I have included my class declaration before my update() function, as well as the two functions that manage the repeated call to update.
The issue is on your doAnimation function:
function doAnimation(ballObject) {
requestAnimFrame(doAnimation);
update(ballObject);
/* Logging here */
}
When requestAnimationFrame calls your function again, it won't have the ballObject argument passed to it. In fact, it will be passed a high-resolution timestamp, which is what you're getting manually (with less precision) on the update function. For more information on how requestAnimationFrame works, check out the documentation at Mozilla Developer Network.
Going back to the ballObject argument, you need to use some other way to maintain reference to it. Normally in javascript this is achieved through closures:
function doAnimation(ballObject) {
requestAnimFrame(function() {
doAnimation(ballObject);
});
update(ballObject);
/* Logging here */
}
A working version of your example: http://jsbin.com/nebucedo/2/edit?js,output
I have a global variable NS which I can access from the console as such:
NS.some_func();
NS is populated using a method called extendSafe()
some_scope.extendSafe = function (o1, o2) {
var key;
for (key in o2) {
if (o2.hasOwnProperty(key) && o1.hasOwnProperty(key)) {
throw "naming collision: " + key;
}
o1[key] = o2[key];
}
return o1;
};
This is used by setting up a public scope called $P and then copying over to the global scope NS once all the $P methods have been defined.
I want to to it this way so I can verify that I'm not writing over any properties.
This worked well until I tried to save a local variable to $P for later copying to NS. Because the interpreter does not know that $P will be "released" to the window scope, it does not know to keep the local variable active. So I can not use my safeExtend method.
I verified this was the issue by doing a direct copy as such:
NS.local = local;
I can now access NS.local from the console.
However if I copy it over as I wish to do:
$P.local = local;
extendSafe(NS, $P);
The local variable is not available.
How can I safely release it, i.e. using safeExtend()?
Code Snippet
Issue is commented as
// hacked needs a fix
$P.machine = function (obj) {
var pipe,
data_send,
ajax_type,
wait_animation,
set;
wait_animation = document.getElementById('wait_animation');
set = false;
pipe = NS.makePipe(obj);
if ($R.Parsel[pipe.model] === undefined) {
return;
}
time('start');
if ($R.Parsel[pipe.model].hasOwnProperty("pre")) {
pipe = $R.Parsel[pipe.model].pre(pipe);
} else {
return;
}
if (pipe.form_data) {
ajax_type = 'multi';
var form_data = pipe.form_data;
delete pipe.form_data;
form_data.append("pipe", JSON.stringify(pipe));
data_send = form_data;
} else {
ajax_type = 'post';
data_send = 'pipe=' + encodeURIComponent(JSON.stringify(pipe));
}
if (pipe.state === true) {
time('middle');
if (wait_animation) {
set = true;
wait_animation.style.opacity = 1;
}
NS.ajax({
type: ajax_type,
url: NS.Reg.get('path') + NS.Reg.get('path_ajax'),
data: data_send,
callback: function (pipe_string_receive) {
var pass_prefix = pipe_string_receive.slice(0, 3),
times;
if (wait_animation && set) {
wait_animation.style.opacity = 0;
}
if (pass_prefix === '|D|') {
NS.log('|DEBUG| ' + pipe_string_receive.slice(3));
} else if (pass_prefix === '|A|') {
time('middle');
pipe = JSON.parse(pipe_string_receive.slice(3));
if ($R.Parsel[pipe.model].hasOwnProperty("post")) {
pipe = $R.Parsel[pipe.model].post(pipe);
times = time('finish');
pipe.time.pre = times[0];
pipe.time.transit = times[1];
pipe.time.post = times[2];
// works but hacked needs a fix
NS.last = pipe;
// will not exendSafe()
$P.last = pipe;
} else {
return;
}
} else {
throw "<No 'A' or 'D'>" + pipe_string_receive;
}
}
});
}
};
I see you've solved the problem, but I have a feeling that there's something you're misunderstanding about JavaScript:
This worked well until I tried to save a local variable to $P for later copying to NS. Because the interpreter does not know that $P will be "released" to the window scope, it does not know to keep the local variable active. So I can not use my safeExtend method.
I verified this was the issue by doing a direct copy as such:
NS.local = local;
I can now access NS.local from the console.
However if I copy it over as I wish to do:
$P.local = local;
extendSafe(NS, $P);
The local variable is not available.
How can I safely release it, i.e. using safeExtend()?
This doesn't make sense. JavaScript is very good at keeping track of references to objects. If there are any references to an object, it won't garbage collect the object. I have no idea what it could mean to "release an object to the window scope". There isn't really any such concept, just objects and references to them.
I tried looking through your original code, but there's a lot of code there that isn't related to the problem. If you were to simplify it to a minimal test case, I'll bet a simpler solution would become evident.
I do see one issue in your smaller snippet above. You defined your extendSafe() function as some_scope.extendSafe(), but here you're calling it with a plain extendSafe() call and no reference to some_scope. Did it actually call the function? Is this just a typo in the smaller example?
Of course, if you're just happy to have found a solution and want to move on, that's quite understandable! I just have a strong feeling that there's extra code here that you don't need.
I'm trying to understand jQuery classes but it is not going very well.
My goal is to use a class this way (or to learn a better way to do it):
var player = new Player($("playerElement"));
player.InitEvents();
Using other people's examples, this is what I tried:
$.Player = function ($) {
};
$.Player.prototype.InitEvents = function () {
$(this).keypress(function (e) {
var key = e.which;
if (key == 100) {
MoveRight();
}
if (key == 97) {
MoveLeft();
}
});
};
$.Player.prototype.MoveRight = function () {
$(this).css("right", this.playerX += 10);
}
$.Player.prototype.MoveLeft = function () {
$(this).css("right", this.playerX -= 10);
}
$.Player.defaultOptions = {
playerX: 0,
playerY: 0
};
The end goal is to have a character moving on the screen left and right using the keyboard letters A and D.
I have a feeling that I'm doing something very wrong with this "class"
but I'm not sure why.
(sorry for my English)
An important issue is that you have to assign the passed jQuery object/element to a this.element - or another this.propertyName - so you can access it later inside the instance's methods.
You also cannot call MoveRight()/MoveLeft() directly like that because those functions are not defined up in the scope chain, but rather in the prototype of your instance's Constructor, hence you need a reference to the instance itself to call these.
Updated and commented code below:
(function ($) { //an IIFE so safely alias jQuery to $
$.Player = function (element) { //renamed arg for readability
//stores the passed element as a property of the created instance.
//This way we can access it later
this.element = (element instanceof $) ? element : $(element);
//instanceof is an extremely simple method to handle passed jQuery objects,
//DOM elements and selector strings.
//This one doesn't check if the passed element is valid
//nor if a passed selector string matches any elements.
};
//assigning an object literal to the prototype is a shorter syntax
//than assigning one property at a time
$.Player.prototype = {
InitEvents: function () {
//`this` references the instance object inside of an instace's method,
//however `this` is set to reference a DOM element inside jQuery event
//handler functions' scope. So we take advantage of JS's lexical scope
//and assign the `this` reference to another variable that we can access
//inside the jQuery handlers
var that = this;
//I'm using `document` instead of `this` so it will catch arrow keys
//on the whole document and not just when the element is focused.
//Also, Firefox doesn't fire the keypress event for non-printable
//characters so we use a keydown handler
$(document).keydown(function (e) {
var key = e.which;
if (key == 39) {
that.moveRight();
} else if (key == 37) {
that.moveLeft();
}
});
this.element.css({
//either absolute or relative position is necessary
//for the `left` property to have effect
position: 'absolute',
left: $.Player.defaultOptions.playerX
});
},
//renamed your method to start with lowercase, convention is to use
//Capitalized names for instanceables only
moveRight: function () {
this.element.css("left", '+=' + 10);
},
moveLeft: function () {
this.element.css("left", '-=' + 10);
}
};
$.Player.defaultOptions = {
playerX: 0,
playerY: 0
};
}(jQuery));
//so you can use it as:
var player = new $.Player($("#playerElement"));
player.InitEvents();
Fiddle
Also note that JavaScript does not have actual "classes" (at least not until ES6 gets implemented) nor Methods (which by definition are associated exclusively to Classes), but rather Constructors which provide a sweet syntax that resembles classes. Here's an awesome article written by TJ Crowder regarding JS's "fake" methods, it is a little advanced but everyone should be able to learn something new from reading it:
http://blog.niftysnippets.org/2008/03/mythical-methods.html
When you use this inside your Player prototype functions, this points to the current Player object.
But when you use $(this).keypress it requires that this points to an HTML element.
The two simply are incompatible. There is only one this and it points to the current Player object, not to an HTML element.
To fix your problem, you will need to pass the HTML element into the Player object upon its creation or into the relevant function calls.
You can pass the element into the Player object upon construction like this:
$.Player = function ($, element) {
this.element = element;
};
$.Player.prototype.InitEvents = function () {
$(this.element).keypress(function (e) {
var key = e.which;
if (key == 100) {
MoveRight();
}
if (key == 97) {
MoveLeft();
}
});
};
$.Player.prototype.MoveRight = function () {
$(this.element).css("right", this.playerX += 10);
}
$.Player.prototype.MoveLeft = function () {
$(this.element).css("right", this.playerX -= 10);
}
$.Player.defaultOptions = {
playerX: 0,
playerY: 0
};
It looks like "$smth is not a function" is a very common problem with JavaScript, yet after looking through quite a few threads I still cannot understand what is causing it in my case.
I have a custom object, defined as:
function Scorm_API_12() {
var Initialized = false;
function LMSInitialize(param) {
errorCode = "0";
if (param == "") {
if (!Initialized) {
Initialized = true;
errorCode = "0";
return "true";
} else {
errorCode = "101";
}
} else {
errorCode = "201";
}
return "false";
}
// some more functions, omitted.
}
var API = new Scorm_API_12();
Then in a different script I am trying to use this API in the following way:
var API = null;
function ScormProcessInitialize(){
var result;
API = getAPI();
if (API == null){
alert("ERROR - Could not establish a connection with the API.");
return;
}
// and here the dreaded error pops up
result = API.LMSInitialize("");
// more code, omitted
initialized = true;
}
The getAPI() stuff, looks like this:
var findAPITries = 0;
function findAPI(win)
{
// Check to see if the window (win) contains the API
// if the window (win) does not contain the API and
// the window (win) has a parent window and the parent window
// is not the same as the window (win)
while ( (win.API == null) &&
(win.parent != null) &&
(win.parent != win) )
{
// increment the number of findAPITries
findAPITries++;
// Note: 7 is an arbitrary number, but should be more than sufficient
if (findAPITries > 7)
{
alert("Error finding API -- too deeply nested.");
return null;
}
// set the variable that represents the window being
// being searched to be the parent of the current window
// then search for the API again
win = win.parent;
}
return win.API;
}
function getAPI()
{
// start by looking for the API in the current window
var theAPI = findAPI(window);
// if the API is null (could not be found in the current window)
// and the current window has an opener window
if ( (theAPI == null) &&
(window.opener != null) &&
(typeof(window.opener) != "undefined") )
{
// try to find the API in the current window�s opener
theAPI = findAPI(window.opener);
}
// if the API has not been found
if (theAPI == null)
{
// Alert the user that the API Adapter could not be found
alert("Unable to find an API adapter");
}
return theAPI;
}
Now, the API is probably found, because I do not get the "Unable to find..." message, the code proceeds to try to initialize it. But firebug tells me API.LMSInitialize is not a function, and if I try to debug it with alert(Object.getOwnPropertyNames(API));, it gives me a blank alert.
What am I missing?
For more generic advice on debugging this kind of problem MDN have a good article TypeError: "x" is not a function:
It was attempted to call a value like a function, but the value is not
actually a function. Some code expects you to provide a function, but
that didn't happen.
Maybe there is a typo in the function name? Maybe the object you are
calling the method on does not have this function? For example,
JavaScript objects have no map function, but JavaScript Array object
do.
Basically the object (all functions in js are also objects) does not exist where you think it does. This could be for numerous reasons including(not an extensive list):
Missing script library
Typo
The function is within a scope that you currently do not have access to, e.g.:
var x = function(){
var y = function() {
alert('fired y');
}
};
//the global scope can't access y because it is closed over in x and not exposed
//y is not a function err triggered
x.y();
Your object/function does not have the function your calling:
var x = function(){
var y = function() {
alert('fired y');
}
};
//z is not a function error (as above) triggered
x.z();
Your LMSInitialize function is declared inside Scorm_API_12 function. So it can be seen only in Scorm_API_12 function's scope.
If you want to use this function like API.LMSInitialize(""), declare Scorm_API_12 function like this:
function Scorm_API_12() {
var Initialized = false;
this.LMSInitialize = function(param) {
errorCode = "0";
if (param == "") {
if (!Initialized) {
Initialized = true;
errorCode = "0";
return "true";
} else {
errorCode = "101";
}
} else {
errorCode = "201";
}
return "false";
}
// some more functions, omitted.
}
var API = new Scorm_API_12();
I also hit this error. In my case the root cause was async related (during a codebase refactor): An asynchronous function that builds the object to which the "not a function" function belongs was not awaited, and the subsequent attempt to invoke the function throws the error, example below:
const car = carFactory.getCar();
car.drive() //throws TypeError: drive is not a function
The fix was:
const car = await carFactory.getCar();
car.drive()
Posting this incase it helps anyone else facing this error.
In addition to the popular answers above, if you are using a services or helper functions file and doing an export on the functions that you will later import in your project.
Make sure that the function name you are importing matches the exact name of the function being exported from the services, helper, or utils file - and that the function actually exists in the right file! I got stuck on this error and was debugging for a few hours, getting nowhere until I found this out.
Had the same issue on Next.js. On _app.tsx I forgot to wrap the Component with the AuthProvider where I had all the Authentication functions.
In my case after a ton of stackoverflowing I saw what a function thing would go with here... it was merely a silly typo , I forgot to put $ in start of the next line's instruction:
function o_chir(id,nom) {
_id_ochirish = id
_nom= nom
//here it threw that "Uncaught TypeError: nom is not a function"
('#nom').val(s_)
$('#o_chir').modal('show')
}
and PHPStorm didnt give any warning
I received this error when I copied a class object incorrectly using JSON.parse and JSON.stringify() which removed the function like:
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
// Method
calcArea() {
return this.height * this.width;
}
}
const square = new Rectangle(10, 10);
console.log('area of square: ', square.calcArea());
const squareCopy = JSON.parse(JSON.stringify(square));
// Will throw an exception since calcArea() is no longer function
console.log('area of square copy: ', squareCopy.calcArea());
I want watch for the creation of new global variables in Javascript so that, anytime a global variable is created, an event is fired.
I've heard of the watch() function but that is only for watching for specific variable names. I want a catchall.
If you already know which names pollute your global namespace (see Intercepting global variable definition in javascript), you can use this trick to figure out when does it actually happen:
window.__defineSetter__('someGlobalVar', function() {
debugger;
});
Be sure to have your developer tools open when you run this.
Obviously works only if your browser supports __defineSetter__ but that's true for modern browsers. Also, don't forget to remove your debug code after you've finished.
Found here.
I don't know how to make this work "on demand" as soon as a var is created, but I can suggest a polling approach. In a browser window, all global become a member of the global "window" object. (Because technically, "window" is the "global object"). So you could do something like the following:
1) enumerate all the properties on a window
window.proplist = window.proplist || {};
for (propname in window) {
if (propname !== "proplist") {
window.proplist[propname] = true;
}
}
2) Set a timer to periodically "poll" window for new properties
setInterval(onTimer, 1000);
3) Wake up on the timer callback and look for new props
function onTimer() {
if (!window.proplist) {
return;
}
for (propname in window) {
if (!(window.proplist[propname])) {
window.proplist[propname] = true;
onGlobalVarCreated(propname);
}
}
}
Afaik, .watch() is only SpiderMonkey (Firefox).
I played around with a polling function, I finally came up with this:
var mywatch = (function() {
var last = {
count: 0,
elems: {}
};
return function _REP(cb) {
var curr = {
count: 0,
elems: {}
},
diff = {};
for(var prop in window) {
if( window.hasOwnProperty(prop) ) {
curr.elems[prop] = window[prop]; curr.count++;
}
}
if( curr.count > last.count ) {
for(var comp in curr.elems) {
if( !(comp in last.elems) ) {
diff[comp] = curr.elems[comp];
}
}
last.count = curr.count;
last.elems = curr.elems;
if(typeof cb === 'function')
cb.apply(null, [diff]);
}
setTimeout(function() {
_REP(cb);
}, 400);
};
}());
And then use it like:
mywatch(function(diff) {
console.log('NEW GLOBAL(s): ', diff);
});
Be aware that this only handles new globals. But you can easily extend this for the case last.count > curr.count. That would indicate that global variables were deleted.
You cant have an event fired when some script does var v = 10, but as selbie said, you can poll the window object... I meant to suggest the same, but he beat me to it. Here's my other example... you count how many window objects are there, and execute GlobalVarCreated() function:
var number_of_globals = 0; //last known globals count
var interval = window.setInterval(function(){
var new_globals_count = 0; //we count again
for(var i in window) new_globals_count++; //actual counting
if(number_of_globals == 0) number_of_globals = new_globals_count; //first time we initialize old value
else{
var number_of_new_globals = new_globals_count - number_of_globals; //new - old
if(number_of_new_globals > 0){ //if the number is higher then 0 then we have some vars
number_of_globals = new_globals_count;
for(var i = 0; i<number_of_new_globals; i++) GlobalVarCreated(); //if we have 2 new vars we call handler 2 times...
}
}
},300); //each 300ms check is run
//Other functions
function GlobalVarCreated(){}
function StopInterval(){window.clearInterval(interval);}
You can load up that code in Chrome or FF console only change: function GlobalVarCreated(){console.log("NEW VAR CREATED");} and test it:
var a = 10
b = 10
String NEW VAR CREATED is displayed 2 times.