I'm making a web application for a class which contains a jsp file that uses jquery. I want the app to trigger an alert right before submitting. This works some of the time in my real program, and other times the alert is never triggered. I can't seem to peg down an instance where it always works or never works. My error console is silent on the matter, unless I add Firebug breakpoints. Then it gives me
Error: attempt to run compile-and-go script on a cleared scope
Source File: http://code.jquery.com/jquery-1.7.2.min.js
Line: 2
But I have no idea what that means. As far as Firebug goes, I can't understand why the evaulation stops before the alert message.
I tried to make an sscce documenting the problem, but I guess it was too different from my real program, because submit never worked. I'll show some code from my real program. (Sorry about the lack of SSCCE.)
$(document).ready(function() {
$("#questionDisplay").submit(function() {
var correctAnswer = $(".correctAnswer").attr("value");
var answer = "";
if ($(".multipleChoice").length > 0) {
answers = $("input:checked");
for (var obj in answers) {
answer += obj.attr("value") + "#";
}
} else if ($(".fillBlank").length > 0) {
for (var answerNo = 1; $(".answer" + answerNo).length > 0; ++answerNo) {
answer += $(".answer" + answerNo).attr("value") + "#";
}
} else {
answer = $(".answer1").attr("value");
}
if (answer == correctAnswer) {
alert("Yes! Correct!");
} else {
alert("Sorry, incorrect.");
}
});
});
The jsp is a huge mess (we have to use scriplets :( ), but if you'd like to see it just lemme know.
How do I get my submit handler to work every time?
In your example, answers is a jQuery collection of elements. Looping through it using for(var obj in answers) { } is actually looping through the properties of answers, not the elements themselves. Therefore, calling .attr() on a property is not going to work.
In general, if I see my debugger state that jQuery has an error, it's 99.9% of the time me calling a jQuery method on a non-jquery selected object. In this case, I saw the error in Chrome's JavaScript console, and sometimes results may vary with different consoles.
A good practice is to prefix variables that store jQuery elements with $ to indicate that they are jQuery objects. For instance, $answers makes it easier to keep track of what it contains.
Use:
answers.each(function() {
answer += $(this).attr("value") + "#";
});
Instead of this:
for (var obj in answers) {
answer += obj.attr("value") + "#";
}
Related
We have a bunch of forms on our Intranet coded for IE9 that contain code similar to this:
var dept = req.responseXML.selectNodes("//Dept")[0].text;
We just upgraded all of our PCs to IE10 and selectNodes is now obsolete and has been replaced with querySelectorAll, so the corrected bit of code would be:
var dept = req.responseXML.querySelectorAll("Dept")[0].textContent;
All these forms use a shared JS file for sending requests out so I thought if I could define some prototype functions/properties I could fix this incompatibility without touching every form. I made some progress and was able to map selectNodes to querySelectorAll using:
Document.prototype.selectNodes = function(param) {
return this.querySelectorAll(param.replace("//", ""));
}
However I am now running into issues mapping text to textContent. The following doesn't seem to work:
Element.prototype.text = function() {
return this.textContent;
};
If anyone has any adivce I would greatly appreciate it, I really don't want to track all these forms down. Thanks!
It really seems that you ought to fix your code rather than hack the DOM methods in order to avoid fixing your code. Imagine how this builds up over time and your code gets further and further away from programming to a standard DOM and then at some point, you'll probably even have a name collision with something that changes in a browser.
If you wanted to hack the code so that elem.text returns elem.textContent, then because these are properties references, not function calls you need to use a getter like this:
Object.defineProperty(HTMLElement.prototype, "text", {
get: function() {
return this.textContent || this.innerText;
},
set: function(txt) {
if (this.textContent) {
this.textContent = txt;
} else {
this.innerText = txt;
}
}
});
I am trying to convert the following old school snippet to alertify.js 0.3.8:
window.doPrompt = function() {
var str;
do str = prompt("Enter your name");
while (str === "" && (alert("Can't be empty!") || true));
if (str) document.getElementsByTagName("body")[0].innerHTML += ("<pre>Your name is: " + str + "</pre>");
}
JSFiddle of the above
This is what I first tried:
window.doPrompt = function() {
alertify.prompt(
"Enter your name",
function(confirmed, str) {
if (confirmed) {
if (str.length === 0) {
alertify.alert(
"Can't be empty!",
function() { doPrompt(); }
);
} else {
document.getElementsByTagName("body")[0].innerHTML += ("<pre>Your name is: " + str + "</pre>");
}
}
}
);
}
JSFiddle of the above
It is not working as intended. For example, in Firefox 19, if you use the keyboard to submit the prompt empty, the "error" alert will only show for a very short time and will then disappear by itself, which is not how it worked before.
I tried breaking the recursion by inserting calls to window.setTimeout with the timeout set to 0 around each alertify call. It didn't help.
It is okay if you can recommend me an alternate JavaScript library with a similar API that I can use instead (that doesn't have this problem of course).
It doesn't seem to be a problem with alertify.js, but with Firefox. If you search bugs related to transitionend (used in the dialog animation, according to the sources) you'll see some potential candidates for the issue you're experiencing (in particular "transitionend event not fired when there are multiple transitions"). This is consistent with what you're experiencing - if you try to show a dialog while other is still in place (i.e. the previous dialog is still in the middle of a transition) then things break.
I can offer a workaround, though. It's not pretty, but gets the job done. But first, an unrelated problem I spotted in all browsers:
The library seems to become confused if you append stuff to the body; using a "target" div instead solves the issue:
<div id="target"></div>
...
document.getElementById("target").innerHTML += ("<pre>Your name is: " + str + "</pre>");
Both when showing an alert and when re-displaying the prompt, you should use setTimeout as you suggested. However, it's not enough for the timeout to be zero, since the problem here is on the transitionend. Set a value high enough for the previous dialog to finish hiding and the problem is fixed:
setTimeout(function() {
alertify.alert(
"Can't be empty!",
function() {
setTimeout(function() {
doPrompt();
}, 500);
}
);
}, 500);
(if the value is not high enough, it will not only keep broken on Firefox but will also break once-working browsers like Chrome, so keep that in mind)
Working example. Tested successfully in Firefox 19.0, Chrome 25 and Safari 4.0.4.
I'm using the ACE editor for interactive JavaScript editing. When I set the editor to JavaScript mode, ACE automatically determines if the code is valid or not, with an error message and line number highlighted when it's not.
During the change event handler, I want to detect if ACE thinks the code is valid or not before I attempt to eval() it. The only way I thought that I might do it is:
var jsMode = require("ace/mode/javascript").Mode;
var editor = ace.edit('mycode'), edEl = document.querySelector('#mycode');
editor.getSession().setMode(new jsMode);
editor.getSession().on('change',function(){
// bail out if ACE thinks there's an error
if (edEl.querySelector('div.ace_gutter-cell.ace_error')) return;
try{
eval(editor.getSession().getValue());
}catch(e){}
});
However:
Leaning on the presence of an element in the UI with a particular class seems awfully fragile, but more importantly,
The visual update for parsing occurs after the change callback occurs.
Thus, I actually have to wait more than 500ms (the delay before the JavaScript worker kicks in):
editor.getSession().on('change',function(){
setTimeout(function(){
// bail out if ACE thinks there's an error
if (edEl.querySelector('div.ace_gutter-cell.ace_error')) return;
try{
eval(editor.getSession().getValue());
}catch(e){}
},550); // Must be longer than timeout delay in javascript_worker.js
});
Is there a better way, something in an undocumented API for the JS mode, to ask whether there are any errors or not?
The current session fires onChangeAnnotation event when annotations change.
after that the new set of annotations can be retrieved as follows
var annotations = editor.getSession().getAnnotations();
seems to do the trick. It returns a JSON object which has the row as key and an array as value. The value array may have more than one object, depending on whether there are more than one annotation for each row.
the structure is as follows (copied from firebug –for a test script that I wrote)
// annotations would look like
({
82:[
{/*annotation*/
row:82,
column:22,
text:"Use the array literal notation [].",
type:"warning",
lint:{/*raw output from jslint*/}
}
],
rownumber : [ {anotation1}, {annotation2} ],
...
});
so..
editor.getSession().on("changeAnnotation", function(){
var annot = editor.getSession().getAnnotations();
for (var key in annot){
if (annot.hasOwnProperty(key))
console.log("[" + annot[key][0].row + " , " + annot[key][0].column + "] - \t" + annot[key][0].text);
}
});
// thanks http://stackoverflow.com/a/684692/1405348 for annot.hasOwnProperty(key) :)
should give you a list of all annotations in the current Ace edit session, when the annotations change!
Hope this helps!
I found a solution that is probably faster than traversing the DOM. The editor's session has a getAnnotations method you can use. Each annotation has a type that shows whether they are an error or not.
Here is how I set my callback for the on 'change'
function callback() {
var annotation_lists = window.aceEditor.getSession().getAnnotations();
var has_error = false;
// Unfortunately, you get back a list of lists. However, the first list is
// always length one (but not always index 0)
go_through:
for (var l in annotation_lists) {
for (var a in annotation_lists[l]) {
var annotation = annotation_lists[l][a];
console.log(annotation.type);
if (annotation.type === "error") {
has_error = true;
break go_through;
}
}
}
if (!has_error) {
try {
eval(yourCodeFromTextBox);
prevCode = yourCodeFromTextBox;
}
catch (error) {
eval(prevCode);
}
}
}
As far as I know, there are two other types for annotations: "warning" and "info", just in case you'd like to check for those as well.
I kept track of the pervious code that worked in a global (well, outside the scope of the callback function) because often there would be errors in the code but not in the list of annotations. In that case, when eval'ing the errored code, it would be code and eval the older code instead.
Although it seems like two evals would be slower, it seems to me like the performance is no that bad, thus far.
Ace uses JsHint internally (in a worker) and as you can see in the file there is an event emitted:
this.sender.emit("jslint", lint.errors);
You can subscribe to this event, or call the JSHint code yourself (it's pretty short) when needed.
I found you can subscribe worker events in Ace 1.1.7:
For javascript code, subscribe 'jslint' event:
session.setMode('ace/mode/javascript}');
session.on('changeMode', function() {
if (session.$worker) {
session.$worker.on('jslint', function(lint) {
var messages = lint.data, types;
if (!messages.length) return ok();
types = messages.map(function(item) {
return item.type;
});
types.indexOf('error') !== -1 ? ko() : ok();
});
}
});
For JSON code, subscribe 'error' and 'ok' event:
session.setMode('ace/mode/json');
session.on('changeMode', function() {
// session.$worker is available when 'changeMode' event triggered
// You could subscribe worker events here, whatever changes to the
// content will trigger 'error' or 'ok' events.
session.$worker.on('error', ko);
session.$worker.on('ok', ok);
});
In the end, I have decided that this isn't a problem that I particularly need to fix, however it bothers me that I don't understand why it is happening.
Basically, I have some checkboxes, and I only want the users to be able to select a certain number of them. I'm using the code below to achieve that effect.
$j( function () {
$j('input[type=checkbox].vote_item').click( function() {
var numLeft = (+$j('#vote_num').text());
console.log(numLeft);
if ( numLeft == 0 && this.checked ) {
alert('I\'m sorry, you have already voted for the number of items that you are allowed to vote for.');
return false;
} else {
if ( this.checked == true ) {
$j('#vote_num').html(numLeft-1);
} else {
$j('#vote_num').html(numLeft+1);
}
}
});
});
And when I was testing it, I noticed that if I used:
$j('input[type=checkbox]').each( function () {
this.click()
});
The JavaScript reacted as I would expect, however when used with:
$j('input[type=checkbox]').each( function () {
$j(this).click()
});
It would actually make the counter count UP.
I do realize that it isn't the most secure way to keep count using the counter, however I do have server side error-checking that prevents more than the requisite amount from being entered in the database, that being the reason that I have decided that it doesn't actually need fixing.
Edit: The $j is due to the fact that I have to use jQuery in noConflict mode...
$(this) contains a jQuery wrapper (with lots of functions) whereas this is solely the DOM object.
The fact that counter is going up gave me the clue that there is a link between checked attribute, which you are using, and firing the click event manually.
I searched Google for 'jquery checkbox click event raise' and found this link, where author faces the exact same problem and the workaround he used.
http://www.bennadel.com/blog/1525-jQuery-s-Event-Triggering-Order-Of-Default-Behavior-And-triggerHandler-.htm
On a side note, I think you can simplify your code further:
$j('input[type=checkbox].vote_item').click(
function()
{
var maxNumberOfChoices = 5;
//get number of checked checkboxes.
var currentCheckedCount = $j('input[type=checkbox].vote_item :checked');
if(currentCheckedCount > maxNumberOfChoices)
{
//It's useful if you show how many choices user can make. :)
alert('You can only select maximum ' + maxNumberOfChoices + ' checkboxes.');
return false;
}
return true;
});
this.click() calls the browser DOM method click().
$(this).click() calls the jQuery method click(), which does more than just call the browser method: see the implementation of the function trigger for details.
For the purposes of tracking non-HTML documents via google analytics, I need the mentioned algorithm. It should:
not hard-code the domain
ignore the protocol (i.e. http/https)
not worry about the presence/absence of "www" (any absolute links WILL prefix with "www" and all pages WILL be served via "www")
This is complicated by the fact that I need to access it via a function called from the IE-only 'attachEvent'.
UPDATE Sorry, I've worded this question really badly. The real problem is getting this to work via an event, since IE has its own made-up world of event handling. Take the following:
function add_event(obj) {
if (obj.addEventListener)
obj.addEventListener('click', track_file, true);
else if (obj.attachEvent)
obj.attachEvent("on" + 'click', track_file);
}
function track_file(obj) { }
It seems as if the "obj" in track_file is not the same across browsers - how can I refer to what was clicked in IE?
I would like to point out that, if you're on so.com, the following links are URLs within the same domain:
http://test.so.com
http://so.com/index
index
/index
#
/#
https://subdomain.so.com#hash
mail.google.com
mail.google.com/index.php?var=value#index
(it may seem odd, but the last two ones are valid: if you're on http://so.com, the last one would take you to http://so.com/mail.google.com/index.php?var=value, which is perfectly valid)
This doesn't really answer the question but I hope it will guide the rest of the answers. If there's anything else weird enough, feel free to add it.
This sounds like a comedy answer but in all seriousness it would be be advisable that you could also do something like:
$('a.external')
Certainly the regex comparison to your window.location is the programmatic answer.
The method of attachment is not the only way IE and W3 event listeners differ. For IE you must read window.event.srcElement; in W3 it's event.target where event is the parameter passed to the callback function.
If you don't need multiple event handlers on links, old-school DOM 0 event handlers are probably an easier way for you to approach this, allowing you to just us ‘this’ to get the object on any browser.
function bindtolinks() {
for (var i= document.links.length; i-->0;)
document.links.onclick= clicklink;
}
function clicklink() {
if (this.host==window.location.host) {
dosomething();
return true; // I'm an internal link. Follow me.
} else {
dosomethingelse();
return false; // I'm an external link. Don't follow, only do something else.
}
}
I will answer the question in the update, about events in IE:
function track_file(evt)
{
if (evt == undefined)
{
evt = window.event; // For IE
}
// Use evt
}
is the classical way to get consistent event object across browsers.
After that, I would use regexes to normalize the URL, but I am not sure what you look after.
[EDIT] Some real code to put in practice what I wrote above... :-)
function CheckTarget(evt)
{
if (evt == undefined)
{
// For IE
evt = window.event;
//~ event.returnValue = false;
var target = evt.srcElement;
var console = { log: alert };
}
else
{
target = evt.target;
//~ preventDefault();
}
alert(target.hostname + " vs. " + window.location.hostname);
var re = /^https?:\/\/[\w.-]*?([\w-]+\.[a-z]+)\/.*$/;
var strippedURL = window.location.href.match(re);
if (strippedURL == null)
{
// Oops! (?)
alert("Where are we?");
return false;
}
alert(window.location.href + " => " + strippedURL);
var strippedTarget = target.href.match(re);
if (strippedTarget == null)
{
// Oops! (?)
alert("What is it?");
return false;
}
alert(target + " => " + strippedTarget);
if (strippedURL[1] == strippedTarget[1])
{
//~ window.location.href = target.href; // Go there
return true; // Accept the jump
}
return false;
}
That's test code, not production code, obviously!
The lines with //~ comments show the alternative way of preventing the click on link to do the jump. It is, somehow, more efficient because if I use Firebug's console.log, curiously the return false is ineffective.
I used here the behavior "follow link or not", not knowing the real final purpose.
As pointed out in comments, the RE can be simpler by using hostname instead of href... I leave as it because it was already coded and might be useful in other cases.
Some special precautions should be taken in both cases to handle special cases, like localhost, IP addresses, ports...
I got rid of the domain name, before re-reading the question and seeing it wasn't a problem... Well, perhaps it can be useful to somebody else.
Note: I shown a similar solution in a question to decorate links: Editing all external links with javascript
Given a click event and the original target element, this should work for the original question:
if(target.protocol == window.location.protocol && target.host == window.location.host){
}
Browsers nicely convert the link from the various patterns mentioned by #Tom into full links, so the protocol and host values simply need to match your domain.
if( someDomElementWhichIsALink.href.indexOf(window.location) != -1 ) {
// this is targeting your domain
}