Making 'scroll-sneak' work - javascript

I've been trying to make the javascript code 'scroll-sneak' (http://mrcoles.com/blog/scroll-sneak-maintain-position-between-page-loads/) work for a few weeks now. This code stops the 'page jump' (to the top) when an anchor link is clicked, and does so without disabling the functionality of that link. I'd like to have the page not move back to the top when a navigation link in a table row below is clicked. It works on the developer's demo page, but is none too well documented. Any takers for tacking this?
<tr id="tabs">
<th>Information</th>
<th>Research</th>
<th>Sources</th>
<th>Institution</th>
<th>Contact</th>
</tr>
<script>
(function() {
var sneaky = new ScrollSneak(location.hostname), tabs =
document.getElementById('tabs').getElementsByTagName('th'), i = 0, len = tabs.length;
for (; i < len; i++) {
tabs[i].onclick = sneaky.sneak;
}
document.getElementById('next').onclick = sneaky.sneak;
})();
</script>
UPDATE
In terms of my original question (and given the various problems and bugs described in the Comments below, the accepted answer proving too unpredictable in behaviour), I managed to figure out a simple solution, as below, that works in IE6, FF3, QZ6 and Webkit 537.21.
(function() {
var sneaky = new ScrollSneak(location.hostname);
document.getElementById('tabs').onclick = sneaky.sneak;
})();

One more edit:
If the last image is still giving you trouble, you can make this change and see if it helps:
Replace this:
$(active).show();
$(active).siblings().hide();
With this:
$("#gallery-interior li").hide(0, function(){
$("#gallery-interior " + active).show();
});
Previous
Here, I combined the script from the other answer and the scroll-sneak for the tabs. I tested both in FF3 and verified that they work properly, this is the complete JavaScript:
var sneaky = new ScrollSneak("scrolltrack");
$(document).ready(function() {
var urlHash = window.location.hash;
if(urlHash) {
$(".thumbs a[href='" + urlHash + "'] img").addClass("dimmed");
} else {
$(".thumbs a:first-child img").addClass("dimmed");
}
$("#tabs th a").on("click", function(e) {
sneaky.sneak();
});
$(".thumbs a").on("click", function(e) {
changeImage($(this).attr("href"), e);
});
$("#gallery-interior area").on("click", function(e) {
changeImage($(this).attr("href"), e);
});
});
function changeImage(active, e) {
var e = window.event || e;
preventNav(active, e);
$(".thumbs a img").removeClass("dimmed");
$(".thumbs a[href='" + active + "'] img").addClass("dimmed");
$(active).show();
$(active).siblings().hide();
}
function preventNav(hash, e) {
if (e) {
if (typeof e.preventDefault != 'undefined') {
e.preventDefault();
} else {
e.returnValue = false;
}
}
var node = $(hash);
node.attr("id", "");
document.location.hash = hash;
node.attr("id", hash.replace("#", ""));
}
Previous
Here you go, this script is adapted to the tabs. In the solution I posted for the gallery (Click anchor link and keep page position / prevent page-jump to top?), the page jump happens because hash links don't actually reload the page and scrolling happens by default and has to be undone. This won't have the same jumping effect for normal links, but I left the ability to capture hash links as well in case you need it. It would be best if this were placed at the bottom of the HTML of your site, just before the closing body tag.
var sneaky = new ScrollSneak("tabs", false);
var capture = true;
$(document).ready(function() {
var urlHash = window.location.hash;
$("#tabs th a").on("click", function(e) {
sneaky.sneak();
capture = true;
});
capture = false;
});
window.onscroll = function () {
if (capture) sneaky.scroll(), capture = false; // This part is only needed for hash links
};

Related

Click anchor link and keep page position / prevent page-jump to top?

I've seen variants of this question asked on SO previously, but these all concern anchors that point nowhere (i.e., href="#") or are placeholders for some javascript. My situation is different in that my anchors DO point somewhere, e.g.,
<a href="#one"><img src="../images/details_thumb.jpg">
More specifically, they interact with a css gallery that advances the main image each time a thumbnail or the image itself is clicked, as below. What I would like to do is have this occur without the default 'target = top' behaviour of href.
<div id="gallery">
<ul id="gallery-interior">
<li id="one"><img src="../images/details.jpg" usemap="#gallerymap" ><map name="gallerymap">
<area shape="circ" coords="429,157,30" href="#two"></map></li>
</ul>
</div>
<div class=' thumbs'>
<a href="#one"><img src="../images/details.jpg">
</div>
Whilst I have seen many javascript solutions to this, they all deactivate the link functionality in so doing.
I should say that while I'm happy to have a js / jquery solution, I am not looking to change my html / css navigation for javascript / jquery -- I wish navigation to remain operative for those who use Noscript.
Can anyone help -- with a demonstrably working solution?
N.B. I've tried 'scroll-sneak' (http://mrcoles.com/blog/scroll-sneak-maintain-position-between-page-loads/) but it has zero documentation and is notoriously difficult to get working.
UPDATED the js that captures the url hash to switch thumbnail opacity (to match gallery navigation) can be seen working, but for the scroll-sneak element, at http://www.ddsol.net/soDavePage/page/testFixed.htm
Scroll Sneak
Here's a method that implements scroll sneak. There's a slight flickering in Firefox 3 due to the small delay in the browser firing the scroll event, but it works as requested.
var sneaky = new ScrollSneak("gallery", false);
var capture = true;
$(document).ready(function() {
var urlHash = window.location.hash;
$(".thumbs a").on("click", function(e) {
sneaky.sneak();
capture = true;
dimThumbs($(this).attr("href"));
});
$("#gallery-interior area").on("click", function(e) {
sneaky.sneak();
capture = true;
dimThumbs($(this).attr("href"));
});
if(urlHash) {
dimThumbs(urlHash);
} else {
$(".thumbs a:first-child img").addClass("dimmed"); // Dim first thumbnail
}
capture = false;
});
window.onscroll = function () {
if (capture) sneaky.scroll(), capture = false;
};
window.onbeforeunload = function () {
sneaky.sneak();
};
function dimThumbs(active) {
$(".thumbs a img").removeClass("dimmed");
$(".thumbs a[href='" + active + "'] img").addClass("dimmed");
}
Another Update
It's become a mission now. This is a slight modification of the second version; I think that considering the only problem was that the image wasn't showing, this will likely address that.
$(document).ready(function() {
var urlHash = window.location.hash;
if(urlHash) {
setTimeout(function() { $("body").scrollTop(0) }, 1); // Scroll to top.
changeImage(urlHash);
} else {
$(".thumbs a:first-child img").addClass("dimmed"); // Dim first thumbnail
}
$(".thumbs a").on("click", function(e) {
changeImage($(this).attr("href"), e);
});
$("#gallery-interior area").on("click", function(e) {
changeImage($(this).attr("href"), e);
});
});
function changeImage(active, e) {
var e = window.event || e;
preventNav(active, e);
$(".thumbs a img").removeClass("dimmed");
$(".thumbs a[href='" + active + "'] img").addClass("dimmed");
$("#gallery-interior li").hide(0, function(){
$("#gallery-interior " + active).show();
});
}
function preventNav(hash, e) {
if (e) {
if (typeof e.preventDefault != 'undefined') {
e.preventDefault();
} else {
e.returnValue = false;
}
}
var node = $(hash);
node.attr("id", "");
document.location.hash = hash;
node.attr("id", hash.replace("#", ""));
}
Second Update
This should operate exactly as you wanted, just replace the script you currently have. It flips between the gallery images, dims the correct thumbnail, prevents page navigation and stops the page from jumping to the element while still changing the document hash. Here's a fiddle to demo http://jsfiddle.net/C2B2M/; it's missing the images, I didn't want to hot-link to your site.
$(document).ready(function() {
var urlHash = window.location.hash;
if(urlHash) {
setTimeout(function() { $("body").scrollTop(0) }, 1); // Scroll to top.
changeImage(urlHash);
} else {
$(".thumbs a:first-child img").addClass("dimmed"); // Dim first thumbnail
}
$(".thumbs a").on("click", function(e) {
changeImage($(this).attr("href"), e);
});
$("#gallery-interior area").on("click", function(e) {
changeImage($(this).attr("href"), e);
});
});
function changeImage(active, e) {
preventNav(active, e);
$(".thumbs a img").removeClass('dimmed'); // Wipe out all dims
$(".thumbs a[href='" + active + "'] img").addClass("dimmed"); // Dim the current thumbnail
$("#gallery-interior li").hide(); // Hide all gallery images
$("#gallery-interior " + active).show(); // Show current image
}
function preventNav(hash, e) {
if (e) e.preventDefault();
var node = $(hash);
node.attr("id", "");
document.location.hash = hash;
node.attr("id", hash.replace("#", ""));
}
Update
Here's another attempt and also a fiddle for you to try it out. http://jsfiddle.net/EPsLV/4/
$(function () {
$("a").on("click", function(e){
var hash = $(this).attr("href");
if (hash.match(/^#\w+/g)) {
e.preventDefault();
var node = $(hash);
node.attr("id", "");
document.location.hash = hash;
node.attr("id", hash.replace("#", ""));
}
});
});
This snippet captures any hash links and removes the id of the destination element long enough to change the page hash and then resumes the event propagation. I think that may be what your gallery is using to change the images, this script shouldn't stop the event itself from bubbling.
Previous
This should work for you
$("div.thumbs a").on("click", function(e){
if ($(this).attr("href").match(/^#/))
e.preventDefault();
});

Change div content and hash url

I made a fully functional Ajax Content Replacement script. The problem is that it adds forwards like /#about or /#work or /#contact to the adress but when I reload the site, the main page will be show. Why? How is it possible that when i type in the adress the right subpage will be show?
Someone told me that the problem is that I added the file manually when I use popstate. So I want a solution without popstate. I am not a Javascript expert but I would like to learn it. Because popstate but this is very circuitous.
window.location.hash = $(this).attr('href');
My .html files are in stored in /data/. The strange thing is that it finds the file but when I try to find it manually,the page show the main page or when I refresh the site with F5 the main page will be show,too.
Can you help me and show me how it works. We can use my code to find the error. Thanks a lot.
Here is the Websitelink : Demo Link
function refreshContent() {
var targetPage = 'home';
var hashMatch = /^#(.+)/.exec(location.hash);
// if a target page is provided in the location hash
if (hashMatch) {
targetPage = hashMatch[1];
}
$('#allcontent').load('data/' + targetPage + '.html');
}
$(document).ready(function(){
refreshContent();
window.addEventListener('hashchange', refreshContent, false);
$('.hovers').click(function() {
var page = $(this).attr('href');
$('#allcontent').fadeOut('slow', function() {
$(this).animate({ scrollTop: 0 }, 0);
$(this).hide().load('data/' + page +'.html').fadeIn('normal');
});
});
});
$('.hovers').click(function() {
window.location.hash = $(this).attr('href');
$.get('data/'+this.href, function(data) {
$('#allcontent').slideTo(data)
})
return false
})
You should load the initial page based on location.hash (if provided) on page load:
function refreshContent() {
var targetPage = 'home';
var hashMatch = /^#!\/(.+)/.exec(location.hash);
// if a target page is provided in the location hash
if (hashMatch) {
targetPage = hashMatch[1];
}
$('#allcontent').load('data/' + targetPage + '.html');
}
$(document).ready(function(){
refreshContent();
...
You can make back and forward work by listening to the Window.onhashchange event:
window.addEventListener('hashchange', refreshContent, false);
Do note that this doesn't work in Internet Explore 7 or lower.
Edit:
Okay, try this:
var $contentLinks = null;
var contentLoaded = false;
function refreshContent() {
var targetPage = 'home';
var hashMatch = /^#(.+)/.exec(location.hash);
var $content = $('#allcontent');
// if a target page is provided in the location hash
if (hashMatch) {
targetPage = hashMatch[1];
}
// remove currently active links
$contentLinks.find('.active').removeClass('active');
// find new active link
var $activeLink = $contentLinks.siblings('[href="' + targetPage + '"]').find('.navpoint');
// add active class to active link
$activeLink.addClass('active');
// update document title based on the text of the new active link
window.document.title = $activeLink.length ? $activeLink.text() + ' | Celebrate You' : 'Celebrate You';
// only perform animations are the content has loaded
if (contentLoaded) {
$content
.fadeOut('slow')
.animate({ scrollTop: 0 }, 0)
;
}
// after the content animations are done, load the content
$content.queue(function() {
$content.load('data/' + targetPage + '.html', function() {
$content.dequeue();
});
});
if (contentLoaded) {
$content.fadeIn();
}
contentLoaded = true;
}
$(document).ready(function() {
$contentLinks = $('.hovers');
refreshContent();
window.addEventListener('hashchange', refreshContent, false);
$contentLinks.click(function(e) {
e.preventDefault();
window.location.hash = '!/' + $(this).attr('href');
});
});

If var not set always results in true, even if its set

Not sure how to formulate this but here it goes.
I am checking if a var exists (content), if it doesnt i set it.
Problem is next click, it still behaves as if there is no var content. But why??
Here my code:
$("#nav a").click(function(event) {
event.preventDefault();
var href = $(this).attr("href");
var load = href + " .content";
if (!content)
{
var content = $('<div>').load(load);
$(".content").append(content);
}
else
{
var position = content.offset();
$(document).scrollTop(position);
}
});
It never results to else, so always a click is made the whole load and append function repeats.
Basically how can I record that content for this particular link has been loaded once, so the else function should be performed next time?
Also, what is wrong with my if(!content) statement? Is it because of scope?
In Javascript functions determine the scope of an object. You need to place content in the global scope. Currently it is created within the anonymous function assigned to the click event handler, so when the function is executed again content is out of scope causing it to return false.
var content;
$("#nav a").click(function(event) {
event.preventDefault();
var href = $(this).attr("href");
var load = href + " .content";
if (!content)
{
content = $('<div>').load(load);
$(".content").append(content);
}
else
{
var position = content.offset();
$(document).scrollTop(position);
}
});
Try to make the var content as a global variable rather than a local one, like you are doing right now. That's why the if (!content) result as true always, like:
var content;
$("#nav a").click(function (event) {
event.preventDefault();
var href = $(this).attr("href");
var load = href + " .content";
if (!content) {
content = $('<div>').load(load);
$(".content").append(content);
} else {
$(document).scrollTop(content.offset());
}
});
Just to show what happens, when value of content is not set at first and then set again:
var content;
console.log(content); // undefined
console.log(!content); // true
content = 'text';
console.log(content); // text
console.log(!content); // false
Thanks to everyone for answering the first question about the checking if var exists.
I ended up ditching this whole concept it turned out the
one()
function is what I needed all along. In order to only execute a function once and another function on all following clicks.
Here it is:
$(document).ready(function() {
//Ajaxify Navi
$("#nav a").one("click", function(event) {
event.preventDefault();
var href = $(this).attr("href");
var load = href + " .content";
var content = $('<div>').load(load);
$(".content").append(content);
$(this).click(function(event) {
event.preventDefault();
var position = content.offset().top;
$(document).scrollTop(position);
$("body").append(position);
});
});
});
What this is is the following:
1st click on a button loads content via ajax and appends it, second click on the same button only scrolls to said content.

jquery ajax navigation within ajax navigation

I'm using this code for my main site navigation which loads each page via ajax and has fallback.
$(function() {
var newHash = '',
$contentWrap = $("#content-wrap");
$("nav").on("click", "a", function() {
window.location.hash = $(this).attr("href");
return false;
});
$(window).on('hashchange', function() {
newHash = window.location.hash.substring(1);
$contentWrap.load(newHash + " #content");
});
$(window).trigger('hashchange');
});​
this works fine but when i load in the content from another page for example about.html i am also loading in some more buttons for navigation within #content-wrap.
so #content-wrap now contains a data box and some more buttons for navigation. when i click on the new navigation it needs to load new data in the data box.
first i tried just pretty much copying the script above but with new anchors however i get a conflict.
i figure i need some sort of if statement, i have looked into something like if (function !== undefined) but cannot figure out what to do.
I'm not sure how well i have explained myself, i'm confused explaining it but basically i want to combine the code above with basically the same code below without a conflict.
$(function() {
var newHash = '',
$contentWrap = $("#content-wrap"),
$aboutWrap = $("#a-wrap");
$("#content-wrap").on("click", "a", function() {
window.location.hash = $(this).attr("href");
return false;
});
$(window).on('hashchange', function() {
newHash = window.location.hash.substring(1);
$aboutWrap.load(newHash + " #a-content");
});
$(window).trigger('hashchange');
});​
Update: kind of works a bit but changed my plan
$(function() {
var newHash = '',
$nav = $("nav a"),
$boxBtn = '',
$aboutWrap = '',
$contentWrap = $("#content-wrap");
$("nav").on("click", "a", function() {
$(this).addClass("nav-click");
window.location.hash = $(this).attr("href");
return false;
});
$contentWrap.on("click", "a", function() {
$(this).addClass("btn-click");
window.location.hash = $(this).attr("href");
return false;
});
$(window).on('hashchange', function() {
var $aboutWrap = $("#a-wrap"),
$boxBtn = $("div.btn a");
newHash = window.location.hash.substring(1);
if ($nav.hasClass("nav-click")){
$contentWrap.load(newHash + " #content");
$nav.removeClass("nav-click");
};
if ($boxBtn.hasClass("btn-click")){
$aboutWrap.load(newHash + " #a-content");
$boxBtn.removeClass("btn-click");
};
});
$(window).trigger('hashchange');
}); /*/end*/
I had a similar problem, basically in most cases the problem is with conflicting element ID's. In the DOM you can use an ID only once. You can workaround that by using classNames and ID's for only unique elements like wrappers.

setInterval with other jQuery events - Too many recursions

I'm trying to build a Javascript listener for a small page that uses AJAX to load content based on the anchor in the URL. Looking online, I found and modified a script that uses setInterval() to do this and so far it works fine. However, I have other jQuery elements in the $(document).ready() for special effects for the menus and content. If I use setInterval() no other jQuery effects work. I finagled a way to get it work by including the jQuery effects in the loop for setInterval() like so:
$(document).ready(function() {
var pageScripts = function() {
pageEffects();
pageURL();
}
window.setInterval(pageScripts, 500);
});
var currentAnchor = null;
function pageEffects() {
// Popup Menus
$(".bannerMenu").hover(function() {
$(this).find("ul.bannerSubmenu").slideDown(300).show;
}, function() {
$(this).find("ul.bannerSubmenu").slideUp(400);
});
$(".panel").hover(function() {
$(this).find(".panelContent").fadeIn(200);
}, function() {
$(this).find(".panelContent").fadeOut(300);
});
// REL Links Control
$("a[rel='_blank']").click(function() {
this.target = "_blank";
});
$("a[rel='share']").click(function(event) {
var share_url = $(this).attr("href");
window.open(share_url, "Share", "width=768, height=450");
event.preventDefault();
});
}
function pageURL() {
if (currentAnchor != document.location.hash) {
currentAnchor = document.location.hash;
if (!currentAnchor) {
query = "section=home";
} else {
var splits = currentAnchor.substring(1).split("&");
var section = splits[0];
delete splits[0];
var params = splits.join("&");
var query = "section=" + section + params;
}
$.get("loader.php", query, function(data) {
$("#load").fadeIn("fast");
$("#content").fadeOut(100).html(data).fadeIn(500);
$("#load").fadeOut("fast");
});
}
}
This works fine for a while but after a few minutes of the page being loaded, it drags to a near stop in IE and Firefox. I checked the FF Error Console and it comes back with an error "Too many Recursions." Chrome seems to not care and the page continues to run more or less normally despite the amount of time it's been open.
It would seem to me that the pageEffects() call is causing the issue with the recursion, however, any attempts to move it out of the loop breaks them and they cease to work as soon as setInterval makes it first loop.
Any help on this would be greatly appreciated!
I am guessing that the pageEffects need added to the pageURL content.
At the very least this should be more efficient and prevent duplicate handlers
$(document).ready(function() {
pageEffects($('body'));
(function(){
pageURL();
window.setTimeout(arguments.callee, 500);
})();
});
var currentAnchor = null;
function pageEffects(parent) {
// Popup Menus
parent.find(".bannerMenu").each(function() {
$(this).unbind('mouseenter mouseleave');
var proxy = {
subMenu: $(this).find("ul.bannerSubmenu"),
handlerIn: function() {
this.subMenu.slideDown(300).show();
},
handlerOut: function() {
this.subMenu.slideUp(400).hide();
}
};
$(this).hover(proxy.handlerIn, proxy.handlerOut);
});
parent.find(".panel").each(function() {
$(this).unbind('mouseenter mouseleave');
var proxy = {
content: panel.find(".panelContent"),
handlerIn: function() {
this.content.fadeIn(200).show();
},
handlerOut: function() {
this.content.slideUp(400).hide();
}
};
$(this).hover(proxy.handlerIn, proxy.handlerOut);
});
// REL Links Control
parent.find("a[rel='_blank']").each(function() {
$(this).target = "_blank";
});
parent.find("a[rel='share']").click(function(event) {
var share_url = $(this).attr("href");
window.open(share_url, "Share", "width=768, height=450");
event.preventDefault();
});
}
function pageURL() {
if (currentAnchor != document.location.hash) {
currentAnchor = document.location.hash;
if (!currentAnchor) {
query = "section=home";
} else {
var splits = currentAnchor.substring(1).split("&");
var section = splits[0];
delete splits[0];
var params = splits.join("&");
var query = "section=" + section + params;
}
var content = $("#content");
$.get("loader.php", query, function(data) {
$("#load").fadeIn("fast");
content.fadeOut(100).html(data).fadeIn(500);
$("#load").fadeOut("fast");
});
pageEffects(content);
}
}
Thanks for the suggestions. I tried a few of them and they still did not lead to the desirable effects. After some cautious testing, I found out what was happening. With jQuery (and presumably Javascript as a whole), whenever an AJAX callback is made, the elements brought in through the callback are not binded to what was originally binded in the document, they must be rebinded. You can either do this by recalling all the jQuery events on a successful callback or by using the .live() event in jQuery's library. I opted for .live() and it works like a charm now and no more recursive errors :D.
$(document).ready(function() {
// Popup Menus
$(".bannerMenu").live("hover", function(event) {
if (event.type == "mouseover") {
$(this).find("ul.bannerSubmenu").slideDown(300);
} else {
$(this).find("ul.bannerSubmenu").slideUp(400);
}
});
// Rollover Content
$(".panel").live("hover", function(event) {
if (event.type == "mouseover") {
$(this).find(".panelContent").fadeIn(200);
} else {
$(this).find(".panelContent").fadeOut(300);
}
});
// HREF Events
$("a[rel='_blank']").live("click", function(event) {
var target = $(this).attr("href");
window.open(target, "_blank");
event.preventDefault();
});
$("a[rel='share']").live("click", function(event) {
var share_url = $(this).attr("href");
window.open(share_url, "Share", "width=768, height=450");
event.preventDefault();
});
setInterval("checkAnchor()", 500);
});
var currentAnchor = null;
function checkAnchor() {
if (currentAnchor != document.location.hash) {
currentAnchor = document.location.hash;
if (!currentAnchor) {
query = "section=home";
} else {
var splits = currentAnchor.substring(1).split("&");
var section = splits[0];
delete splits[0];
var params = splits.join("&");
var query = "section=" + section + params;
}
$.get("loader.php", query, function(data) {
$("#load").fadeIn(200);
$("#content").fadeOut(200).html(data).fadeIn(200);
$("#load").fadeOut(200);
});
}
}
Anywho, the page works as intended even in IE (which I rarely check for compatibility). Hopefully, some other newb will learn from my mistakes :p.

Categories