I have this following scenario:
I have a JS (parent) class that constructs/creates two other classes. The parent class is attached to window object on the client side (browser). My issue is a call in the following manner:
window.parent.create({data: window.parent.utility.getId()})
in rare occasions, like one in 200 or one in 1000 I get an error that says can't call utility of undefined!
this means that the parent class is undefined! but if it was undefined how did it even make a call to window.parent.create() ?
what could possibly allow parent to be defined on the first call then make it undefined in the inner call?
but if it was undefined how did it even make a call to window.parent.create() ?
The arguments are processed before the function is called (after all, the program needs to know the function's arguments). This is why it's failing on window.parent.utility.getId() instead of window.parent.create(), the .getId() is running first.
I'd bet that if you replaced window.parent.utility.getId() with a string literal you'd see the failure happen on the .create() call instead.
Edit as pointed out by Bergi below technically the program is accessing window.parent.create first, which will return undefined. This value doesn't matter until after the arguments are processed and it tries to call the value as a function. The getId() function fails before this happens, which is why you are seeing this specific error.
Related
Until now I have been using the following construct quite a lot:
spyOn(ClassToSpy, NameOfMethodToSpy);
...
expect(ClassToSpy.NameOfMethodToSpy).toHaveBeenCalledWith(ObjectToCompare);
But now I would like to check the call to an exported object and not an exported function. Is this also possible with karma/jasmin?
To be more specific:
I have a service which gives me a reference to the global native browser window object:
get nativeWindow(): any {
return window;
}
My code under test does the following with this returned window object:
this.winRef.nativeWindow.location.href = clean_uri;
Now I want do be sure that this href property of the location has received the new url. As there was no function involved in this last call I can not use the toHaveBeenCalledWith-construct from karma/jasmine.
I don't know exactly the answer to your question if it is possible to spy on plain exported objects. I guess it is not possible. But you have mentioned that you already have a service in place which wrapps the access to the window object. Therefore I would suggest that you add some getter / setter methods to this service in order to access the object. This way you can use the well known behaviour of spyOn(...) for functions.
Instructions:
make this code work without modifying snippet it in any way.
Use only plain old JavaScript and no third-party libraries.
Write new code that enables the code below to work properly.
Hint: Feel free to extend native objects... even though it's typically a bad practice.
// Start with an object, any object
var myObject = {};
// Register an event on your object using
// an `on` method
myObject.on('myEvent', function(data) {
// Log the data passed to the callback
console.log(data);
});
// Trigger the event using a `trigger` method.
// Include some data when you trigger the event.
myObject.trigger('myEvent', {
company: 'ABC Corp',
location: 'WTC Bangalore, IN',
website: 'http://abc.co'
});
// Register a different event
myObject.on('yourEvent', function() {
console.log('yourEvent fired');
});
// Trigger the new event
myObject.trigger('yourEvent');
// Trigger all existing events using a special
// "star" identifier.
myObject.trigger('*');
// Remove one event by name
myObject.off('myEvent');
// Since we've removed the event, this should
// do nothing
myObject.trigger('myEvent');
// Remove all existing events
myObject.off();
// Since we've removed all events, this should
// do nothing
myObject.trigger('*');
Everything else went well. I'm unable to get "arguments" while implementing myObject.trigger("*"); unable to read arguments object / parameters while implementing "*" and hence throw undefined.
My JSFiddle
Disclaimer
I obviously dont know what school you go to or anything, but please don't fool yourself trying to fool your teachers. With a few simple questions they'll know if you understand the material or not, and if you show up with a good answer but no knowledge to back it up, they will know what's up. I'm not accusing you of this, just a friendly word of advice of someone who has had good connections with his teachers after graduating last year ;)
So, how do we do this? Basically, you will have to add some functionality to the prototype of object, at least if you want this to affect all objects made afterwards. You can always create your own class and add the function to that prototype if you only want that class to have this functionality.
We need 3 functions added to the prototype, on, off and trigger of course. On top of that we add one extra property called events, initially an empty object.
You can look at the raw code for all these in the jsfiddle, I will only go through the structure and logic of the code here.
events will hold all the handlers (functions) associated with each event. When adding an event for the first time, we add a eventName property to the events object, the value for this property is initially an empty array.
on will find (or create) the array linked to eventName in events, and push the function into the array (note we do not call the function at this time, we simply store the reference to the function in the array).
off will iterate the array of eventName, and if it finds the same function (note the ===), remove it from the array.
trigger will iterate the array of eventName and call each function. Note that the function is called with the this keyword in the function set to the object, and with the same parameters as the trigger function was called (except eventName, the first parameter, which is filtered out). Yes that means you can pass as many parameters as you want to trigger(), and they will all be passed to each handler.
I won't go into detail what things like splice, slice, ===, arguments and apply do exactly, I'm sure you can find more and better information about that elsewhere on the world wide interwebs.
There's a lot more you can do for this, like making the events object invisible through some nice uses of scoping, but that wasn't part of the question so I didn't bother with that.
If you have any more questions after looking through this, feel free to ask. I also didn't test it extensively so if you find any bugs, let me know.
EDIT: I didn't read through the comments at first, but I now also added support for the '*' wildcard. Basically the functions now check for the wildcard and will iterate all eventNames on the event object when removing or triggering. You can also remove all functions for an event by not giving a function or by giving the same wildcard, but with an eventName.
EDIT2: had some bugs running the teacher's code, realized I forgot to check for hasOwnProperty while iterating. Look that one up, it's very important when working with prototypes!
I now put in the teacher's code in my jsfiddle, to show you that it works :)
jsfiddle with own code
jsfiddle with teacher code
EDIT3 - about the 'undefined' log.
The teacher's code calls .trigger 5 times, and you should see 4 console logs and as far as I can tell, they are all correct.Let me run through each trigger, and the subsequent console logs.
You add a handler to myEvent, which logs the first parameter
You trigger myEvent, with parameter => The parameter (the object), is
logged.
You add a handler to yourEvent, which logs a hardcoded
string.
You trigger yourEvent, no parameter => The hardcoded string is logged'
You trigger * with no parameter, all handlers run => undefined is logged, since no parameters were given, data in myEvent's handler is undefined. The hardcoded string is also logged
You remove the myEvent handler, trigger myEvent and confirm no functions are called
You remove all event handlers, trigger * and confirm no functions are called from any events.
I honestly don't know what you expected to happen on step 5, since you give no parameter, the data is assigned undefined, that's intended behaviour.
If you want to merge the data given in step 2 so it remains on the object, then instruct so in your handler. (for example, iterate all properties of data and add them to this, then log this). Right now you simply pass it data, it gets logged, and then thrown away. You can also add a parameter in step 5, and then all handlers will receive it (including the yourEvent handlers, but that one doesn't assign nor use it).
document.getElementById("myBtn").addEventListener("click", displayDate);
I created a computed observable rather than having a extra code in the view and now when I run the page I get an error in the console stating:
"Uncaught TypeError: Cannot read property 'concat' of undefined"
Here is what I have so far:
self.email = ko.observable(initialData.userEmail || '');
self.emailMailto = ko.computed(function(){ return 'mailto:'+self.email();});
I know that email observable is returning the appropriate data but I am not sure if it is referring to emailMailto as undefined.
this is my view:
<p><a data-bind="attr:{href:emailMailto},text:email"></a></p>
The code you've posted is not directly related to your problem.
What's happening here is that some code wants to use the .concat() method on a variable.
.concat() is a prototype of Array, which means that the code in question expects an array.
undefined means that, instead of an array, nothing has been passed to the code. This can mean two things:
Either an expected function parameter has not been passed, e.g. the method is declared as function doSomething(withValue) and you are calling it as function doSomething() (without a value).
Or the code tries to process a property of an object which is not set.
As the code causing the error has been working before and you apparently didn't modify it, I would guess that it's the latter case: You're tossing around an object which is missing a property that should be there and should be an array.
I am learning to program in Javascript. I have created a jsfiddle here - http://jsfiddle.net/vvMRX/1/
function turnRed(node,f){
setTimeout(f,2000,[node]);
}
(function pageLoaded(){
turnRed(document.body,function(node){
alert(node);node.style.backgroundColor = '#ff0000';
});
})();
I am trying to use a setTimeout call on a function to change the body background color. I pass document.body as a node. In the callback, I change the node.style.backgroundColor but it does not work. Interestingly enough, using document.body.style.backgroundColor directly works. If I put an alert(node), it correctly identifies it as html bodyelement.
What am I missing here?
Appreciate responses.
Here's the following error that's being thrown in your JS:
Uncaught TypeError: Cannot set property 'backgroundColor' of undefined
The reason for this is because in your setTimeout, you're passing node in an array. However, in your callback, you're accessing the node directly. Two ways of addressing this are:
Update your callback to access the node within the array.
(function pageLoaded(){
turnRed( document.body, function(node){
alert(node);
// Updated code below
node[0].style.backgroundColor = '#ff0000';
});
})();
The other way would be to update your setTimeout and pass node directly.
function turnRed(node,f){
setTimeout(f,2000,node);
}
Here's an updated fiddle: http://jsfiddle.net/vvMRX/3/
You mentioned that using document.body.style.backgroundColor worked - which makes sense - since document.body will point to the element that contains the content. For most pages, this is almost always the <body> element. However, for frameset documents, this would return the outer frame. (w3.org reference)
Finally, regarding the alert - what's up with that, right? You call alert(node), and it displays [object HTMLBodyElement], which means you were passing the correct element, right? (At least, that's what I would think too!)
What's actually happening is that alert is alerting the value of your array.
Here's a fiddle demonstrating that: http://jsfiddle.net/4Lf3J/
You should see three alerts.
In the first alert, I've updated the original alert to call node.constructor. Object.prototype.constructor will return a reference to the object that created the instance (MDN reference).
In this case, we'll see
function Array() { [native code] }
This hopefully will re-enforce the idea that you're passing an array.
The second alert is actually calling alert(document.body.constructor), which is what we EXPECTED to see originally. In this case, we see:
function HTMLBodyElement() { [native code] }
Finally, a third alert shows the values 1,2,3,4,5, which is just an alert of a simple array with those values (again, re-enforcing the idea that alerts will alert the value of an array - which is why you thought the alert was correct).
Hopefully this helps as you continuing learning JavaScript!
In your function turnRed you are passing node in an array. Try this:
function turnRed(node,f){
setTimeout(f,2000,node);
}
I tried this in the fiddle it works.
In this case i guess that you should use document.bgColor property directly
You can obtain more information and code samples here:
http://www.javascripter.net/faq/backgrou.htm
http://www.webcodingtech.com/javascript/change-background-color.php
Fire up your firebug console and try this out.
Compare this:
$('body').data('x',1);
$(thisx).remove();
console.log($('body').data('x'));
to this:
$('body').data('x',1);
$(this.x).remove();
console.log($('body').data('x'));
Notice the difference? If thisx is undefined, it will immediatly throw a reference error. If x is an undefined property of this, jQuery will return the document as it's result set instead. Next jQuery will attempt to remove your document (which it can't), but before doing that it will remove all data attached to any child element of the document. Thus, wiping out your data store.
Note: this can be any element reference or object. You only need to have jQuery attempt to access an undefined property.
(Talk about a pain. It fails silently, and I'm trying to figure out why my data is suddenly missing. I track it down to a special case where an element reference was undefined in a specific situation.)
So on to my questions:
1) Before I submit a bug report, am I analyzing this correctly? Also if someone happens to know that this is a known issue, let me know. I couldn't find it in the bug tracker, but the interface isn't that great (or maybe I have this wrong).
2) Why is there ultimately any difference? I'm guessing thisx is evaluated immediately which causes the exception while this.x is a reference that is passed and evaluated in the called function, right? (where I think the line selector = selector || document; is the culprit.
3) Suggestions for how to handle this? I guess I should be checking that any/every element reference or property of an object (e.g. stored selector strings) is defined before I pass it to jQuery when removing something.
Why is there ultimately any difference?
Both thisx and this.x are evaluated when the function is called. The first one refers to an undefined variable name and this throws a reference error. The second one accesses an undefined property of an object, which results in the value undefined. This is just how javascript behaves in these cases.
Now when JQuery is called in the second case, the call $(this.x) evaluates to $(undefined) which is the same as if you just would have called $(). Since it looks to JQuery as if no argument was provided, it uses a default instead, and in this case the default is document. Then it proceeds trying to delete document, since it was effectively called as $().remove(), in which case this would be expected.
Suggestions for how to handle this?
The difference with the ReferenceError is a fundamental Javascript difference, not much that can be done about that. JQuerys behavior is unfortunate and a consequence of setting defaults by arg = arg||default. One could user arguments.length instead to get the real number of call parameters, but a change like this would surely result in lot's of broken code that relied on the default being used when undefined or 0 is passed, so it's unlikely to happen.
Try typing these into the console too (with no extra variables defined beforehand):
> a
ReferenceError: Can't find variable: a
> b = {}
Object
> b.a
undefined
one is a javascript error, one silently returns undefined (which jQuery will interpret as $() because javascript and jQuery can't tell the functions $() and $(undefined) apart)
this is the way javascript works, bug or feature I leave open to debate, but I don't think that this is jQuery's fault or problem.
edit: why does jQuery have $() defined?
From the docs:
By default, if no context is specified,
$() looks for DOM elements within the
context of the current HTML document.
If you do specify a context, such as a
DOM element or jQuery object, the
expression will be matched against the
contents of that context.
edit: the docs were referring to the context argument passed to $(), not to calling $() without arguments, so it isn't relevant here.
also note that
$().get(0) == $("").get(0)