I understand why JSLint kicks up a warning here, but I don't know how else to write my code so it validates.
Here's a boiled down sample. In the code below, I have an object that I need to attach two event listeners to: one for "complete" and the other for "error". Each points to its own event handler. When either event handler is reached, I want to remove both event handlers. But I will always get a validation error when I try to remove the second event handler's listener from the first event handler.
var myFunction = function(obj) {
var doComplete = function() {
// ...Do something here to handle successful execution, then remove listeners
obj.removeEventListener('complete',doComplete,true);
obj.removeEventListener('error',handleError,true); // INVALID!
};
var handleError = function() {
// ...Do some error handling here and then remove event listener
obj.removeEventListener('complete',doComplete,true);
obj.removeEventListener('error',handleError,true);
};
obj.addEventListener('complete',doComplete,true);
obj.addEventListener('error',handleError,true);
obj.load();
};
Whenever I get one of these warnings, it has always meant I'm doing something wrong, but in this case, I can't see how to get around the problem. Does anyone know what the right way is to do this?
The validation error is:
Lint at line 5 character 41: 'handleError' is not defined. (the web client says Implied global: handleError 5)
You need to rearrange your code slightly.
var myFunction = function(obj) {
var doComplete, handleError;
doComplete = function() {
// ...Do something here to handle successful execution, then remove listeners
obj.removeEventListener('complete',doComplete,true);
obj.removeEventListener('error',handleError,true); // INVALID!
};
handleError = function() {
// ...Do some error handling here and then remove event listener
obj.removeEventListener('complete',doComplete,true);
obj.removeEventListener('error',handleError,true);
};
obj.addEventListener('complete',doComplete,true);
obj.addEventListener('error',handleError,true);
obj.load();
};
JSLint expects to see a declaration of a variable before it's used. This change does so, even though it appears ineffective.
Related
I have an event handler that modifies some global variable based on the click action. How can I test it? For example:
function initEvent() {
function enable() {
var arr = Context.get('arr');
arr.push('aaa');
};
function disable() {
var arr = Context.get('arr');
arr.push('bbb');
};
$('#content').on('click', '#element', function () {
if (this.checked) {
enable();
} else {
disable();
}
});
};
This is the function I am calling after the HTML has been rendered. It calls enable() and disable() functions based on the user action. I want to test these functions and check if they behave correctly. How can I do that?
You want to test your code. You should never test code with console.log or alert. These are great to debug something on the fly, but they are not test tools. These promote manual testing, where you need to manually run the code and see that pass, that's horrible time waste.
You should use Jasmine in this case (you can use other testing frameworks, though Jasmine is super easy) to test your code. You can setup browser tests or headless tests, which is out of the scope of this question, there are tons of tutorials on the subject.
Now, in order to test this code, I assume that the Context has a static method get which returns an array which is on the Context IIFE scope. If the case is different feel free to fiddle around with the specs and make it serve your needs, or alternatively if you get stuck, update this question or ask another one on Stackoverflow.
I have setup Jasmine, with jasmine-fixture to test your code, the jQuery click event behavior. In this plunk you will find everything you need.
I am using the browser to test the code, so I need jasmine's html reporter.
The real tests are in script-spec.js, where I am using Jasmine's API, by describing a spec suite (with describe) and defining each spec with it method.
In beforeEach I prepare the code to run before each spec executes. Essentially here, I create a simple div with #content id and a child input element of type checkbox with #element id. I do this by using the
setFixtures('<div id="content"><input type="checkbox" id="element" /></div>');
Which is a method jasmine-fixture library provides.
Now I can test the code, wiring up the specs:
it("Should return an array with 'aaa' element when #element is checked", function() {
// Arrange
initEvent();
var checkbox = $("#content").find("#element");
// Act
checkbox.click();
// Assert
expect(Context.get('arr').length).toBe(1);
expect(Context.get('arr')).toEqual(['aaa']);
});
I run the initEvent method and get a reference of the checkbox element. In Act section I click the element manually, marking it as checked, which is the normal behavior. In Assert, I test the Context.get('arr') return value.
Again, link to plunk is here.
Hope this helps.
One simple test you can do to test enable, disable and the click handler is to create a function that checks the contents of arr in Context, and call it after each of the functions within the click handler that add something to arr.
The general way to test conditions in your code is with assertions which will throw an error if the condition you pass into them is false. You can use console.assert just for that:
$('#content').on('click', '#element', function() {
if (this.checked) {
enable();
// assert last element in `arr` is the enabled string 'aaa'
console.assert(
Context.get('arr')[Context.get('arr').length - 1] === 'aaa',
'enable() works'
);
} else {
disable();
// assert last element in `arr` is the disabled string 'bbb'
console.assert(
Context.get('arr')[Context.get('arr').length - 1] === 'bbb',
'disable() works'
);
}
});
If any of the tests run after you click your element, you know initEvent assigned the click handler and it works. Then, you just toggle the checked flag to test enable()/disable() as well.
If there are no errors in your browser console, the tests have passed. Otherwise, there will be an error in your console containing the message passed as the second argument to console.assert.
You could even make a helper function to simplify the testing a bit:
function assertLastElementInContextArr(elem, msg) {
var arr = Context.get('arr');
// assert last item in `arr` is equal to `elem`
console.assert(arr[arr.length - 1] === elem, msg);
}
$('#content').on('click', '#element', function() {
if (this.checked) {
enable();
// assert last element in `arr` is the enabled string 'aaa'
assertLastElementInContextArr('aaa', 'enable() works');
} else {
disable();
// assert last element in `arr` is the disabled string 'bbb'
assertLastElementInContextArr('bbb', 'disable() works');
}
});
EDIT based on your comment
But how do I mock the click event? I mean, I want to automatically test all those events, no I have to somehow trigger the click automatically. How do I do that?
If you want to programmatically invoke click events, you can use JS to trigger them in code. Since you're using jQuery, it already comes with a method trigger to do just that.
All you need to do is:
$('#content').trigger('click')
And it will activate your click handler and run the assertions tests from above.
In fact, jQuery even comes with aliased handlers for specific events so you can just do:
$('#content').click();
To automate the testing, you can create a function that will trigger the clicks and set the checked state as well, to test both cases.
function test(checked) {
var elem = $('#content');
elem.prop('checked', checked);
elem.click();
}
Important thing to be careful about is that these events will happen asynchronously so you must do something to manage a proper testing order if you're going to trigger multiple clicks. Otherwise you will set checked to true, trigger the click and then run the second test that will set checked to false before the click events even happen.
For demonstration purposes, here's one way to safely test multiple successive clicks by adding an event handler just for testing and removing it once you're done. One requirement for this to work is to attach the handler after all your other handlers have been attached, to make sure the test handler runs last. Additionally, you can run your assertions here as well to not pollute your code and keep the testing fully separated:
function test(checked, value, msg, done) {
var elem = $('#content');
elem.prop('checked', checked);
// attach a test event handler and trigger the click
elem.on('click', testClick);
elem.click();
// once the click is executed,
// remove the test handler,
// run the assertions and then
// call the callback to signal the test is done
function testClick() {
elem.off('click', runTest);
assertLastElementInContextArr(value, msg);
done();
}
}
// run your code before the tests
initEvent();
// test enable(), then once that's done, test disable()
test(true, 'aaa', 'enable() works', function() {
test(false, 'bbb', 'disable() works', function() {
console.log('All tests finished');
});
});
If you're going to be testing your entire app like this, you'd probably want to use a test framework like QUnit, Mocha, Jasmine which will handle all these async issues for you and give you a nice API to work with.
Just add console.log(<some variable>) or alert(<some variable>) at function calls. e.g.:
function initEvent() {
function enable() {
alert("enable called!");
var arr = Context.get('arr');
arr.push('aaa');
};
function disable() {
alert("disable called!");
var arr = Context.get('arr');
arr.push('bbb');
};
$('#content').on('click', '#element', function () {
alert("click occured!");
if (this.checked) {
enable();
} else {
disable();
}
});
};
Or use your browsers developer tools setting breakpoints at these spots.
This is driving me nuts. I'm getting multiple mouseout events registering even though I'm getting rid of them with removeEventListener(). I've tried all kinds of variations of this. Essentially, once the mouseout event happens, I want to get rid of it because the user will be rolling over different images to see a large preview. It works fine, but the multiple events registered are ticking me off.
this.removeEventListener('mouseout', handler, false);
and nothing. I'm not sure what I'm doing here. I can't seem to get rid of the mouseout events and they just keep on piling.
document.querySelector('.grid').addEventListener('mouseover',function(e) {
if (e.target.tagName==='IMG') {
var myElement=document.createElement('div');
myElement.className='preview';
e.target.parentNode.appendChild(myElement);
var handler = e.target.addEventListener('mouseout', function (d) {
var myNode = d.target.parentNode.querySelector('div.preview');
console.log(d.target.parentNode);
if (myNode) {
myNode.parentNode.removeChild(myNode);
}
this.removeEventListener('mouseout', handler, false);
});
} //
}, false); // addEventListener
addEventListener does not return a value, so handler is undefined and the call to removeEventListener will fail.
Use a named function expression instead:
// give the function a name vvvvvvv
e.target.addEventListener('mouseout', function handler(d) {
// ...
this.removeEventListener('mouseout', handler, false); // use name
}, false); // <- don't forget `false` here, just in case
The name of the function is only accessible inside the function itself.
I have been teaching myself JavaScript over the last Month now, not super consistently since my work has been all over the place, when I get downtime my job is to work on extensions for our sales team.
Right now I don't have a specific issue that i can't solve, but I have a question that makes me think that there is something very different about functions in javascript that I am still missing.
Look at this code, and I will explain what confuses me about it:
function click(e) {
var selection = e.target.id;
}
document.addEventListener('DOMContentLoaded', function () {
var divs = document.querySelectorAll('div');
for (var i = 0; i < divs.length; i++) {
divs[i].addEventListener('click', click);
}
});
So, in this code, I understand what is going on except how the click(e) part. The 'e' is an event object correct? It is not clear to me how that got passed, and how it knows that 'e' means that. I assume I could replace the e with "foo" and it would work still, but exactly what is happening is not clear.
I am pretty sure it has to do with this line of code:
divs[i].addEventListener('click', click);
But I don't understand what is happening behind the scenes to make that happen the way it does.
Another example is this from the message passing at http://developer.chrome.com/extensions/messaging.html:
contentscript.js
================
chrome.extension.sendMessage({greeting: "hello"}, function(response) {
console.log(response.farewell);
});
background.html
===============
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
console.log(response.farewell);
});
});
'response' in this is not clear to me where it is coming from, much like 'e' in the other example. Any help demystifying how this works would be appreciated, I am open to learning, and I haven't found a good explanation about this.
The event object is passed through the function by the browser itself.
In case there is an event and a respective event handler is attached, the browser calls that event handler and passes an event object with some (more or less) relevant information about the event to the event handler.
So with respect to your first example:
First the function click( e ) is defined in a regular way.
Afterwards two event handlers are registered:
for the event DOMContentLoaded
for a click event on multiple <div> elements.
For the first handler an anonymous function is used.
document.addEventListener('DOMContentLoaded', function () {
// do stuff here
});
Here the event object is omitted as it is probably not needed.
In the second case the <div> elements all get the same event handler, namely click(e).
divs[i].addEventListener('click', click);
Here, however, the event object is captured as a parameter by the function as it is needed inside the function body.
In general in JavaScript you don't have to define all parameters either in the function declaration nor in the call of a function. You just define the parameters needed and they are applied in the order given. That's why in the first event handler's definition the parameter for the event object can be omitted without any errors.
The click function is invoked by the browser in response to a click event. The browser passes the appropriate event object as the first argument.
Also, you're correct that e can be anything. You can give the parameter any (legal) name you want.
I'm trying to validate image URLs with Qunit by setting the URL as the src attribute of a test image and checking with the error event handler whether that went well. So far what I have is:
test('image',function() {
var test_image = $('#test-image');
test_image.error(function(e) { // properly triggered
console.log(e);
is_valid = false;
// ok(false,'Issue loading image'); breaks qunit
});
var is_valid = true;
test_image.attr('src','doesntexist');
console.log('checking is_valid'); // occurs before error event handler
if (is_valid) { // therefore always evaluates to the same
ok(true,'Image properly loaded');
} else {
ok(false,'Issue loading image');
}
});
My problem is that although the error event is properly triggered, it seems to occur in an asynchronous fashion and after the evaluation of is_valid (therefore whatever check I make, the result will always be the same). I have tried adding the ok() assertion inside the error event handler, but I'm getting the following error:
Error: ok() assertion outside test context
How can I run an assertion based on the processing performed inside the error event handler?
PS: if I insert a alert('test'); before checking is_valid it works fine (which confirms problem with error handler being asynchronous) but as you can imagine is not acceptable. I tried using setTimeout to delay execution of if statement but it brings the same assertion context error.
By quickly looking through QUnit API, I see that you should use asyncTest function for this. Before setting the src-attribute for your test_image, hook a function to load event. Here's an untested code:
asyncTest('image',function() {
var test_image = $('#test-image');
test_image.error(function(e) {
console.log(e);
ok(false,'Issue loading image');
start();
});
test_image.load(function() {
ok(true,'Image properly loaded');
start();
});
test_image.attr('src','doesntexist');
});
This is my html code
Hit
This is my javascript file
function clickHandler(evt) {
var thisLink = (evt)?evt.target:Window.event.srcElement;
alert(thisLink.innerHTML);
return false;
}
But when i click the Hit Link, it redirects.
you need to pass in the event if you wish to preventDefault.
html:
Hit
script:
function runFunction (evt) {
evt.preventDefault();
evt.stopPropagation();
}
To tie both of the very-correct answers together, what's happened is you've inlined a function where you've written onclick="return runFunction();"
If you look at that, what it's really doing is going like this:
var link = document.getElementById("myLink");
link.onclick = function () { runFunction(); };
See the problem?
My runFunction is being called without any event object passed in, at all.
...which means that var thisLink = (evt) ? is going to return false, which means that it's going to try to run in oldIE-mode.
By writing onclick="runFunction", that's the same as saying:
link.onclick = runFunction;
Which means that when the onclick event happens, runFunction will be called, and in W3C-compliant browsers, it will be sent an event object.
Which is why that solution works.
The best way to avoid a lot of this confusion is to deal with JavaScript from inside of JavaScript, and to deal with HTML inside of HTML, so that you don't have to worry about how strings translate into code.
Now, to get all of this to work, AND prevent redirection, you want to do this:
for W3C browsers (the ones that pass the event parameter):
function runFunction (evt) {
// stops the default-action from happening
// means you need to find another way to fire it, if you want to later
evt.preventDefault();
// stops higher-up elements from hearing about the event
// like if you stop a submit button from "clicking", that doesn't stop the form
// from submitting
evt.stopPropagation();
//the oldIE versions of both of these are
event.cancelBubble = true;
event.returnValue = false;
}
When I plugged your code into chrome, I got this as the error in the console:
Uncaught TypeError: Cannot read property 'srcElement' of undefined
IF the javascript bombs out while processing, it never gets a chance to return at all so the browser tends to disregard what is in the onclick handler after the exception.
Since it bombed out... default behavior of anchor tags, which is to send you off to wherever the href says to go.
Try wrapping the contents of the function in a try/catch block and see what turns up if this kind of thing plagues you.