I'm using the following snippet if jQuery JavaScript to return a hash value at the end of a URL. It works perfectly in FF but the alert on line 4 returns empty in Chrome.
Seems like the window.location.hash.substring(1) line does not work. I have also tried window.location.hash.replace("#", "");
// Map Clicks
$("#tab2.tab_content #map").delayed('click', 500, function() {
state = window.location.hash.substring(1);
alert(state);
jsonLink = 'http://ml.uscm.org/ministries.json?state=' + state + '&active=true&callback=?';
getMapResults();
return false;
});
What's the trick to retrieving a hash value from the URL in Chrome?
The URL is built like this :
http://www.ourdomain.com/thispage.html#IL
Where IL is a two letter abbreviation for a state. I want to get the "IL".
I have a working demo here:
http://richcoy.com/locator/index2.html
Click on the Search by State tab in Chrome then click on a state and you'll see the issue. The browser shows that the url that it wants to go to is built correctly. –
Thanks.
You may want to try this instead:
$(window).on('hashchange', function() {
console.log(window.location.hash.substring(1));
});
The click event triggers before the hashchange event so you can't rely on your map click implement (even if you delayed it).
Supported browsers list for hashchange: http://caniuse.com/hashchange
In case you don't have to use hash, here is a simpler solution:
$("#tab2.tab_content #map a").click(function() {
console.log($(this).attr('href').substring(1));
});
In summary, you shouldn't use any kind of delayed methods.
Isn't the problem quite apparent, or am I being silly? In the following function, you handle the clicks on the map, you listen to click, delay them by 500ms and then run through your function.
$("#tab2.tab_content #map").delayed('click', 500, function() {
state = window.location.hash.substring(1);
alert(state);
jsonLink = 'http://ml.uscm.org/ministries.json?state=' + state + '&active=true&callback=?';
getMapResults();
return false;
});
But at the point where you alert your state, it's empty because you haven't yet set it. Your return false; will also stop the browser from changing the hash.
Try this function, and I bet you'll get something:
$("#tab2.tab_content #map").delayed('click', 500, function() {
setTimeout(function(){
var state = window.location.hash.substring(1);
alert(state);
jsonLink = 'http://ml.uscm.org/ministries.json?state=' + state + '&active=true&callback=?';
getMapResults();
}, 500);
});
A quick way to get it working would be to do the following:
$("#tab2.tab_content #map a").delayed('click', 500, function(e) {
state = $(this).attr('href').replace('#', '');
alert(state);
});
you could then easily change the hash manually by also adding this into the callback:
window.location.hash = state;
but your real issue is that your a (anchors) aren't changing the hash themselves, which makes it seem like there is somewhere in your code that's stopping it.
In Chrome, only can set hash will anchor, eg:
<a href="#exemplo" />
If you do this by setting a hash of another element, try to exchange it for an anchor.
in this link you see the chrome accepts the hash property:
http://www.w3schools.com/jsref/prop_loc_hash.asp
How about you modify your code a bit,
$("#tab2.tab_content #map a").click(function(){
console.log($(this).attr("href"));//this outputs the value to the console
});
this will output #CA for California
i.e.
$("#tab2.tab_content #map a").delayed('click', 500, function() {
//state = window.location.hash.substring(1);
state = $(this).attr("href").substring(1);
alert(state);
jsonLink = 'http://ml.uscm.org/ministries.json?state=' + state + '&active=true&callback=?';
getMapResults();
return false;
});
In your code if you place a breakpoint (ln36 state = window.location.hash.substring(1);) when the event is fired, the window has not yet changed location so the hash does not exist at that point.
When I check in Chrome's inspector, it appears that your anchors uses href from a different namespace, which isn't declared in your svg tag.
<a xlink:href="#SD">
Firefox seems fine with that, but Chrome doesn't seems to guess how to interpret this correctly.
From this link, I've found:
Bind the required namespaces
SVG is a namespaced XML dialect, and as a consequence all the XML
dialects used in your SVG documents must be bound to their namespace
as specified in the Namespaces in XML recommendation. It is sensible
to bind the SVG and XLink namespaces at a minimum, and possibly also
the XML Events namespace.
So, try to add xmlns:xlink="http://www.w3.org/1999/xlink" to your SVG tag.
Edit
As I can see in the image you posted, you declared xmlns:xlink well in your .svg. But in the rendered page by Chrome, there is no such thing!
Here's what I see (Chrome 30) :
<svg height="100%" version="1.1" width="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 165 96" preserveAspectRatio="meet" style="overflow: hidden; position: relative;">
From here, it's beyond my knowledge: can some namespace for a tag be stored somewhere else by the browser? I've searched through its DOM properties, can't find it.
Another clue: you declared xmlns:svg too.
Quoted from the former link:
Be careful not to type xmlns:svg instead of just xmlns when you bind
the SVG namespace. This is an easy mistake to make, but one that can
break everything. Instead of making SVG the default namespace, it
binds it to the namespace prefix 'svg', and this is almost certainly
not what you want to do in an SVG file. A standards compliant browser
will then fail to recognise any tags and attributes that don't have an
explicit namespace prefix (probably most if not all of them) and fail
to render your document as SVG.
Additional doc about xlink
I appreciate all the help and suggestions.
The answer to the question ended up that I needed to make the paths for the URLs absolute, not relative.
So as an example, a line in the JavaScript went from:
"AL": {tooltip: 'Alabama', attr: {fill: '#F9B625', href: '#AL'}},
To:
"AL": {tooltip: 'Alabama', attr: {fill: '#F9B625', href: 'http://richcoy.com/locator/index2.html#AL'}},
Once I changed this for all 50 states a click on the map pulled the data from the jasonp feed correctly and displayed it on the page in Chrome, as well as in Firefox.
var myURL = document.URL; //example http://www.ourdomain.com/thispage.html#IL
var getTheMagicWord = myURL.split("#");
console.log(getTheMagicWord[1]); //logs IL
Related
I attached an event listener to my SVG image in order to perform some code after the image has been loaded. As it sometimes happens, a bug might occur in my SVG generating code and the SVG file won't load. In that case, when user clicks another button, I want it to remove the event listener, hide the unsuccessful result and let the user select another SVG image.
Here's what I have:
// In external .js file, loaded prior to the other code in <head>
function setResultViewBox() {
var objectEl = document.getElementById("resultImage");
var svgDoc = objectEl.contentDocument;
var svg = svgDoc.childNodes[0];
// If there's no SVG tag, remove the listener etc.
// Tested via alert() and console messages that this actually works.
if (svg.tagName !== "svg") {
editBrush();
return;
}
// some other code to set the viewBox
}
// Attached to the main file at the end of <body>
var resultImage = document.getElementById("resultImage");
function resultImageLoaded(event) {
resultImage.removeEventListener("load", resultImageLoaded, false);
setResultViewBox();
hideProgressBar();
}
submitChanges() {
// Compute URI here
resultImage.data = uri;
resultImage.addEventListener("load", resultImageLoaded, false);
hidePreview();
}
function editBrush() {
alert();
resultImage.removeEventListener("load", resultImageLoaded, false);
hideResult();
hideProgressBar();
}
<object id="resultImage" type="image/svg+xml" width="420" height="420" data=""></object>
Make outline
Edit brush
This came to me as a surprise: for once, in Internet Explorer 11, it does exactly what I want it to do; on the other hand, in Opera it doesn't work (the hell must have frozen I guess).
Testing it on an image case I know for sure it won't load, it attempts to set the viewBox of the result image, fails to find the <svg> tag, throws an alert message as it goes to editBrush(), removes the listener and allows me to select another SVG file, which it loads correctly then. That means it creates the listener once again, loads the correct URI, recognizes <svg> tag, sets viewBox, removes the listener in the resultImageLoaded(event) itself and all is good.
In Opera, it attempts to set the viewBox of the result image, fails to find the <svg> tag, throws an alert message as it goes to editBrush() and I suspect now it doesn't actually remove the listener. When I select another SVG image, which it should load correctly now, nothing happens (tried to add another alert to resultImageLoaded(event) and it wasn't triggered).
Things I gave special attention to:
resultImageLoaded(event) isn't an anonymous function and is located above the code that is using it
reference to the function itself in the add/remove listeners
resultImage is stored in one variable and both add/remove listeners are on this one object
I can't see what I'm doing wrong, any help would be most appreciated.
EDIT: In console in Opera, it shows Internal server error (500) on GET request when I try to load the image that should fail loading. IE shows no such thing. Not sure if this can be of any help.
EDIT 2: Alright, I just found out this has probably nothing to do with removeEventListener(). Even when I comment out all lines where I remove event listeners, the behaviour is exactly the same as described in both browsers. Could the problem be in Opera reporting an error and IE ignoring it?
And question for mods: when I find out the original question's topic is no more relevant, but the problem still persists and I'm not even sure what might be causing it, what do I do? Brutally edit the original question or make a new one and leave the original one unanswered?
Try adding event.stopPropagation(); at the end of each eventListener, this will prevent bubbling of the event to your ancestors and it'll stop at your event.target
If that doesn't help, try event.stopImmediatePropagation();
http://www.kirupa.com/html5/handling_events_for_many_elements.htm
// In external .js file, loaded prior to the other code in <head>
function setResultViewBox() {
var objectEl = document.getElementById("resultImage");
var svgDoc = objectEl.contentDocument;
var svg = svgDoc.childNodes[0];
// If there's no SVG tag, remove the listener etc.
// Tested via alert() and console messages that this actually works.
if (svg.tagName !== "svg") {
editBrush();
return;
}
// some other code to set the viewBox
}
// Attached to the main file at the end of <body>
var resultImage = document.getElementById("resultImage");
function resultImageLoaded(event) {
resultImage.removeEventListener("load", resultImageLoaded, false);
setResultViewBox();
hideProgressBar();
event.stopPropagation();
}
submitChanges() {
// Compute URI here
resultImage.data = uri;
resultImage.addEventListener("load", resultImageLoaded, false);
hidePreview();
event.stopPropagation();
}
function editBrush() {
alert();
resultImage.removeEventListener("load", resultImageLoaded, false);
hideResult();
hideProgressBar();
event.stopPropagation();
}
<object id="resultImage" type="image/svg+xml" width="420" height="420" data=""></object>
Make outline
Edit brush
I'm writing a simple text-base game in my free time. It's called TARDIS flight (Doctor Who!) and I'm working on the main menu.
So I'm using a function, addMainMenuListeners, to add all the event listeners with addEventListener, after I set the innerHTML of the main menu.
Everything works fine, until the point where I go back to the main menu from one of the submenus. Then, I found that the buttons don't work anymore.I'm calling addMainMenuListeners after I set the innerHTML, but even though I do it, and I do it in the console, and I check, there is no event.
Code:
In my main javascript file:
function addMainMenuListeners()
{
if($("start")) $("start").addEventListener("click", startGame);
if($("instructions")) $("instructions").addEventListener("click", instructions);
if($("settings")) $("settings").addEventListener("click", settings);
if($("back")) $("back").addEventListener("click", resetMainMenu);
if($("volume")) $("volume").addEventListener("change", function(){saveAndChangeVolume($("volume").value);});
}
function instructions()
{
$("mainmenu").innerHTML = "<h1>Instructions</h1><p>Fly your TARDIS through the time vortex using the console. Make sure that you use the correct materialization codes. Try to keep the time-space coordinates close to the values of the ones given. AND DON'T CREATE PARADOXES, WHATEVER YOU DO!</p><button id='back'>Back</button>";
addMainMenuListeners();
return true;
}
function settings()
{
$("mainmenu").innerHTML = "<h1>Volume</h1><span>1</span><input type='range' id='volume' min='1' max='100' /><span>100</span><br><button id='back'>Back</button>";
addMainMenuListeners();
loadVolume();
return true;
}
function resetMainMenu()
{
$("mainmenu").innerHTML = "<h1>TARDIS Flight</h1><button id='start'>Start!</button><button id='instructions'>Instructions</button><button id='settings'>Settings</button>";
addMainMenuListeners();
return true;
}
And in my HTML file:
<div id="mainmenu">
<h1>TARDIS Flight</h1>
<button id="start">Start!</button>
<button id="instructions">Instructions</button>
<button id="settings">Settings</button>
</div>
If you need any clarification, just ask.
EDIT: Evidentally, nobody got what I meant. I was readding the event listeners after doing the innerHTML, as you can see from the code. I simply cannot see the event being added, the function is firing but not adding the event.
Also, I am using a custom $ function, just a return document.getElementById(id) sort of function.
Check to see if your $() uses any caching. If it caches old references to elements then when innerHTML is set the $("id") will return a reference to an invalid reference.
[edit] The references are more likely valid even though they are no longer visible in the HTML DOM. So modifying the detached elements works but it doesn't do any good since they are detached from the DOM.
Sukima psychic ability's catched the main problem: your custom $ function (to replace document.getElementById) used a caching mechanism.
After some testing (out of personal curiosity) it turned out that as long as you have some reference to an element, the element is still accessible, even after elm.parentNode.removeChild or a full elm.parentNode.innerHTML rewrite (at least, in FF).
In other words: the events WERE added, every time, just to the wrong/old/previous elements instead of the new ones. Otherwise there would also have been errors that the elements didn't exist and thus didn't have an addEventListener method..
See this crude test-fiddle for proof: http://jsfiddle.net/G28Lu/
I toyed around with a $ function (as you haven't posted yours yet) and gave it an 'refresh'-flag to go with the optional cache mechanism:
var $=function(/*cache_enabled*/c){ // getElementById with optional caching & refresh
return c ?( c={}, function(s,r){return (!r && c[s]) || (c[s]=document.getElementById(s));}
):( function(s){return document.getElementById(s);} );
}(true); // or nothing or 0 etc to disable the cache
Note: dynamically created functions are slow (for some reason) so it has 2 functions so the browser can run them optimized (and the other should be cleared by the garbage collector).
To request a fresh just add a flag that evaluates to true as second argument, like: $('elm_id', 1)
Then I modified your addMainMenuListeners function a little to first test for the existence of a fresh getElementById and then add the eventListener via the freshly updated cached reference (so, essentially I changed nothing in the flow of your routine).
formatted to emphasize what changed and how it works
function addMainMenuListeners(){
$( 'start' , 1) && $( 'start' ).addEventListener('click', startGame);
$('instructions', 1) && $('instructions').addEventListener('click', instructions);
$( 'settings' , 1) && $( 'settings' ).addEventListener('click', settings);
$( 'back' , 1) && $( 'back' ).addEventListener('click', resetMainMenu);
$( 'volume' , 1) && $( 'volume' ).addEventListener('change', function(){
saveAndChangeVolume($('volume').value);
});
}
Finally, putting above 2 functions and the rest of your functions/html into this fiddle rendered a working result: http://jsfiddle.net/xHUGu/ !
Note: I had to substitute a dummy function startGame otherwise there would be a fatal error. The missing volume-functions were not critical.
I would like to point out that this is not really the way to go with your interface, there would be a lot of work you could save both yourself and the browser. You might want to look into div's (containing your subsections of html) and toggle them so there is only one visible. Like tabs. (Hint for google-query).
Credit still goes to Sukima ('the force is strong in this one'), but I felt it was a good idea to share the correct explanation to your problem (with proof) and not to waste the work that was done anyway (out of curiosity).
Hope this helps!
Disabling caching on the $ function worked. It was referencing to a destroyed HTML element, and that's why it didn't work. Also, setTimeouts helped for reliability in the case that innerHTML didn't execute in time.
The title of the question expresses what I think is the ultimate question behind my particular case.
My case:
Inside a click handler, I want to make an image visible (a 'loading' animation) right before a busy function starts. Then I want to make it invisible again after the function has completed.
Instead of what I expected I realize that the image never becomes visible. I guess that this is due to the browser waiting for the handler to end, before it can do any redrawing (I am sure there are good performance reasons for that).
The code (also in this fiddle: http://jsfiddle.net/JLmh4/2/)
html:
<img id="kitty" src="http://placekitten.com/50/50" style="display:none">
<div>click to see the cat </div>
js:
$(document).ready(function(){
$('#enlace').click(function(){
var kitty = $('#kitty');
kitty.css('display','block');
// see: http://unixpapa.com/js/sleep.html
function sleepStupidly(usec)
{
var endtime= new Date().getTime() + usec;
while (new Date().getTime() < endtime)
;
}
// simulates bussy proccess, calling some function...
sleepStupidly(4000);
// when this triggers the img style do refresh!
// but not before
alert('now you do see it');
kitty.css('display','none');
});
});
I have added the alert call right after the sleepStupidly function to show that in that moment of rest, the browser does redraw, but not before. I innocently expected it to redraw right after setting the 'display' to 'block';
For the record, I have also tried appending html tags, or swapping css classes, instead of the image showing and hiding in this code. Same result.
After all my research I think that what I would need is the ability to force the browser to redraw and stop every other thing until then.
Is it possible? Is it possible in a crossbrowser way? Some plugin I wasn't able to find maybe...?
I thought that maybe something like 'jquery css callback' (as in this question: In JQuery, Is it possible to get callback function after setting new css rule?) would do the trick ... but that doesn't exist.
I have also tried to separte the showing, function call and hiding in different handlers for the same event ... but nothing. Also adding a setTimeout to delay the execution of the function (as recommended here: Force DOM refresh in JavaScript).
Thanks and I hope it also helps others.
javier
EDIT (after setting my preferred answer):
Just to further explain why I selected the window.setTimeout strategy.
In my real use case I have realized that in order to give the browser time enough to redraw the page, I had to give it about 1000 milliseconds (much more than the 50 for the fiddle example). This I believe is due to a deeper DOM tree (in fact, unnecessarily deep).
The setTimeout let approach lets you do that.
Use JQuery show and hide callbacks (or other way to display something like fadeIn/fadeOut).
http://jsfiddle.net/JLmh4/3/
$(document).ready(function () {
$('#enlace').click(function () {
var kitty = $('#kitty');
// see: http://unixpapa.com/js/sleep.html
function sleepStupidly(usec) {
var endtime = new Date().getTime() + usec;
while (new Date().getTime() < endtime);
}
kitty.show(function () {
// simulates bussy proccess, calling some function...
sleepStupidly(4000);
// when this triggers the img style do refresh!
// but not before
alert('now you do see it');
kitty.hide();
});
});
});
Use window.setTimeout() with some short unnoticeable delay to run slow function:
$(document).ready(function() {
$('#enlace').click(function() {
showImage();
window.setTimeout(function() {
sleepStupidly(4000);
alert('now you do see it');
hideImage();
}, 50);
});
});
Live demo
To force redraw, you can use offsetHeight or getComputedStyle().
var foo = window.getComputedStyle(el, null);
or
var bar = el.offsetHeight;
"el" being a DOM element
I do not know if this works in your case (as I have not tested it), but when manipulating CSS with JavaScript/jQuery it is sometimes necessary to force redrawing of a specific element to make changes take effect.
This is done by simply requesting a CSS property.
In your case, I would try putting a kitty.position().left; before the function call prior to messing with setTimeout.
What worked for me is setting the following:
$(element).css('display','none');
After that you can do whatever you want, and eventually you want to do:
$(element).css('display','block');
I´am trying to get an simple imagebutton to work in Phonegap. I wanna swap image when clicked and forward to location after a short time.
So what i have tried:
function highl(Bildname,BildURL,Link) {
document.images[Bildname].src = BildURL;
window.setTimeout(forward,1000);
function forward() {
window.location = Link;
}
}
in HTML just links like:
<img name="level01" src="level1.png" border="0">
Works well in my Moz, but not in Webkit/phonegap (swap doesen´t work forward is well).
Can anybody help?
edit: also doesen´t work in chrome...
Webkit doesn't support DOM attribute mutation (see issue 8191) marked won't fix.
There might be a link with your issue.
As a workaround, I think you should simply remove the content of the DOM node, and create a new image node instead.
Edit: with code
You need to identify the container.
Also, I set href, so that I javascrpt is disabled, the link can still be followed.
If javascript is enabled, return false tells the browser not to follow the link.
<a href="test.html" onClick="return highl(this, 'level1h.png', 'test.html');">
javascript. I have inlined forward because it was very short, but you don't need to.
function highl(el, imgURL, link) {
var img = new Image();
img.src = imgUrl;
// remove current image. TODO ensure firstChild is not null?
el.removeChild(el.firstChild);
// place new image
el.append(img);
setTimeout(function() {window.location=link;}, 1000);
return false;
}
I have a script of which the part relevant to the question is:
<script type="text/javascript">
function imageOnClick() {
var image = $("#image_id");
$(image).attr("src", "ajax-loader.gif");
$.ajax({
// do staffs
complete: function () {
$(image).attr("src", "default.gif"); // ***
}
});
}
</script>
Everything works fine in Firefox and IE-8. However, in GoogleChrom(21.0.1180.83) the loading image is shown and then the complete is invoked, the source of image is changed, but not displayed. I have checked the image element's src attribute is changed to default.gif as expected but space of image is left blank.
Is this a common problem with a trivial solution? Or I have to rechech the large imageOnClick function?
This fiddle works for me in Chrome 21.0.1180.79.
I'm not sure why you are experiencing problems, but the extra selection of the image object is at least superfluous. var image = $("#image_id"); gives a jQuery object (or an array of them). Doing $(image) later will give you the exact same object.
And you might also want to read up on prop() vs. attr().