History.js and manual refresh of the page - javascript

i'm working with History.js and i'm trying to obtain an url as Google Plus urls.
(function(window, undefined){
var History = window.History; // Note: We are using a capital H instead of a lower h
if ( !History.enabled ) {
// History.js is disabled for this browser.
// This is because we can optionally choose to support HTML4 browsers or not.
return false;
}
History.Adapter.bind(window, 'statechange', function(){ // Note: We are using statechange instead of popstate
var State = History.getState(); // Note: We are using History.getState() instead of event.state
History.log(State.data, State.title, State.url);
console.log(State.data.page);
});
$('.item').live('click', function(e){
e.preventDefault();
var url = $(this).children(1).attr('href');
$(document).remove('#content');
$('#loaded').load('/controlpanel' + url + '.php #content');
History.pushState({page: url + '.php'}, "Prova Pagina", History.getRootUrl() + 'controlpanel' + url); // logs {state:1}, "State 1", "?state=1"
});
})(window);
this script works when I click on the link, but when I manually refresh the page, and I've already clicked on a link and the url becomes http://www.mysite.com/something/page, the browser gives me an 404 Error.
How can I solve it?
I would like to obtain something like: https://plus.google.com/explore

Even though JS allows you to not reload the whole page, the URL should still be accessible on the server side, so that someone accessing directly the link will see something (not to mention graceful degradation).
Also, you have to add a click handler on the anchor links, so that you can prevent the default action (going to the link) and use the pushState method to change the URL. Something like this:
document.body.onclick = function( e ) {
var evt = e || window.event,
target = evt.target || evt.srcElement // Get the target cross-browser
// If the element clicked is a link
if ( target.nodeName === 'A' ) {
// Use the History API to change the URL
History.pushState() // Use the library correctly
// Don't forget to return false so that the link doesn't make you reload the page
return false
}
}
This is definitely not production code (you should check for external links, not bind on the body, etc), but you get the idea.

Related

How to handle back button while changing the browser-URL with HTML5 pushState

I’ve made a one page site. When user clicks on the menu buttons, content is loaded with ajax.
It works fine.
In order to improve SEO and to allow user to copy / past URL of different content, i use
function show_content() {
// change URL in browser bar)
window.history.pushState("", "Content", "/content.php");
// ajax
$content.load("ajax/content.php?id="+id);
}
It works fine. URL changes and the browser doesn’t reload the page
However, when user clicks on back button in browser, the url changes and the content have to be loaded.
I've done this and it works :
window.onpopstate = function(event) {
if (document.location.pathname == '/4-content.php') {
show_content_1();
}
else if (document.location.pathname == '/1-content.php') {
show_content_2();
}
else if (document.location.pathname == '/6-content.php') {
show_content_();
}
};
Do you know if there is a way to improve this code ?
What I did was passing an object literal to pushState() on page load. This way you can always go back to your first created pushState. In my case I had to push twice before I could go back. Pushing a state on page load helped me out.
HTML5 allows you to use data-attributes so for your triggers you can use those to bind HTML data.
I use a try catch because I didn't had time to find a polyfill for older browsers. You might want to check Modernizr if this is needed in your case.
PAGELOAD
try {
window.history.pushState({
url: '',
id: this.content.data("id"), // html data-id
label: this.content.data("label") // html data-label
}, "just content or your label variable", window.location.href);
} catch (e) {
console.log(e);
}
EVENT HANDLERS
An object filled with default information
var obj = {
url: settings.assetsPath, // this came from php
lang: settings.language, // this came from php
historyData: {}
};
Bind the history.pushState() trigger. In my case a delegate since I have dynamic elements on the page.
// click a trigger -> push state
this.root.on("click", ".cssSelector", function (ev) {
var path = [],
urlChunk = document.location.pathname; // to follow your example
// some data-attributes you need? like id or label
// override obj.historyData
obj.historyData.id = $(ev.currentTarget).data("id");
// create a relative path for security reasons
path.push("..", obj.lang, label, urlChunk);
path = path.join("/");
// attempt to push a state
try {
window.history.pushState(obj.historyData, label, path);
this.back.fadeIn();
this.showContent(obj.historyData.id);
} catch (e) {
console.log(e);
}
});
Bind the history.back() event to a custom button, link or something.
I used .preventDefault() since my button is a link.
// click back arrow -> history
this.back.on("click", function (ev) {
ev.preventDefault();
window.history.back();
});
When history pops back -> check for a pushed state unless it was the first attempt
$(window).on("popstate", function (ev) {
var originalState = ev.originalEvent.state || obj.historyData;
if (!originalState) {
// no history, hide the back button or something
this.back.fadeOut();
return;
} else {
// do something
this.showContent(obj.historyData.id);
}
});
Using object literals as a parameter is handy to pass your id's. Then you can use one function showContent(id).
Wherever I've used this it's nothing more than a jQuery object/function, stored inside an IIFE.
Please note I put these scripts together from my implementation combined with some ideas from your initial request. So hopefully this gives you some new ideas ;)

Why greasemonkey doesn't detect some page changes in facebook?

I was trying to do a user.js to /messages page in facebook, but looks like greasemonkey doens't notice when the navigation changes from / to /messages. It also occurs in other internal pages.
First i thought that it was caused by AJAX navigation, but the URL changes (not hash part), so it's normal navigation, right?
This is a test page that I used:
// ==UserScript==
// #name Test
// #namespace none
// #description just an alert when page changes
// #include http*://www.facebook.com/*
// ==/UserScript==
alert(location.href);
How can I correctly detect page changes?
Firefox version: 6.0.2
Greasemonkey version: 0.9.11
For browsers that support it, including Firefox 4+, Facebook takes advantage of the HTML5 History API. This API allows the location to be changed using the history.pushState() method although no navigation actually occurs. Though the page may seem to have changed, all that's happened is a behind-the-scenes ajax call that changes most of the content.
If you wanted to capture this change, you'd have to proxy the pushState() method with your own function:
(function (old) {
window.history.pushState = function () {
old.apply(window.history, arguments);
alert(window.location.href);
}
})(window.history.pushState);
Read more about the History API at https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history.
Another approach is to hook DOMNodeInserted for the page, and to run when the path matches /messages after insertion:
// ==UserScript==
// ...
// #include https://www.facebook.com/*
// ...
// ==/UserScript==
var url = document.location.toString();
function scriptBody(){
if (!url.match(/facebook.com\/messages/)) return;
// ...
// do stuff
// ...
});
scriptBody(); // run on initial page load
document.querySelector('html').addEventListener('DOMNodeInserted', function(ev){
var new_url = document.location.toString();
if (url == new_url) return; // already checked or processed
url = new_url;
scriptBody(); // run when URL changes
});
Note that if you users use the forward/back buttons you may get 'DOMNodeInserted' events for content that is being reinserted to the page that you've already modified with your script, so you'll need to make sure you check whether whatever changes you normally make to the page have already been made, to prevent inserting duplicate controls or whatever.
+1 to #rampion suggestions. I wanted to perform a simple redirect though and looking for elements on page was not very useful as I don't want to redirect user unattendedly.
Anyway, I used this code to install a listener that would redirect where needed upon user clicking on a link with particular href:
if (document.addEventListener ){
document.addEventListener("click", function(event) {
var targetElement = event.target || event.srcElement;
// TODO: support deeper search for parent element with a href attribute
var href = targetElement.getAttribute('href') || targetElement.parentElement.getAttribute('href') ;
if (href && videoURLRe.test(href)) {
var target = "";
if (href.indexOf("/") == 0) {
target = "https://m.facebook.com" + href
} else {
target = href.replace("www.facebook", "m.facebook");
}
window.location.assign(target);
}
}, true);
}
Works pretty neat. I had to figure correct third addEventListener param so that my listener is executed before any others.
For full script look at https://greasyfork.org/en/scripts/8176-switch-to-mobile-version-on-facebook-video-page
I couldn't find any way to reliably detect URL changes regardless of method used by the web site to change that URL. #andy-e approach seems awesome but didn't work for me for some reason. Perhaps I couldn't make script #grant tag properly.

javascript history onpopstate

I am trying to understand HTML5 history object. Here is a simple example which I started off with.
function addDialog(){
document.getElementById('d').style.display ='';
history.pushState({name:"changed"},"","#newURL");
}
window.onpopstate = function(e){
alert(e.state);
}
I have a div with an id d for which display property is none. On clicking a link, I will display the div and change the history so that new url will be loaded.
When I copy paste the new url, popstate event is fired and I get null for e.state.
From what I understand, if I load the new url http://example.com#newURL, e.state should point to the object which I pushed using pushstate.
Please correct me if I am wrong and also I would like to know when e.state gets populated.
As I tested, the e.state only get the pop state that you add to the history when you click in the Back or Forward button. Otherwise, it will give you the null.
You can use parameters to your URL so you can test if the request came from a history call or from an URL in the location bar.
onpopstate = function(event) {
alert('popEvent: ' + event);
if(event.state){
setupPage(event.state);
} else {
setupPage(getQStringParam('zid'));
}
}

Change hash without triggering a hashchange event

I'm using the hash to load content dynamically. To make the back button work I am capturing hash changes. However sometimes I need to change the hash without triggering the hash changed function (eg, when the page was redirected server side and I need to update the hash once the content has returned.)
The best solution I have come up with is to unbind the hashchange event, make the change and then rebind it. However, as this happens asynchronously, I am finding that it rebinds too quickly and still catches the hash change.
My solution at the moment is very poor: Rebinding in a setTimeout. Does anyone have a better idea?
$(window).unbind( 'hashchange', hashChanged);
window.location.hash = "!" + url;
setTimeout(function(){
$(window).bind( 'hashchange', hashChanged);
}, 100);
Edit:
Amir Raminfar's suggestion prompted me to a solution that does not require a timeout.
I added a class variable
_ignoreHashChange = false;
When I want to change the hash silently I do this:
_ignoreHashChange = true;
window.location.hash = "!" + url;
and the hash changed event does this :
function hashChanged(event){
if(_ignoreHashChange === false){
url = window.location.hash.slice(2);
fetchContent(url);
}
_ignoreHashChange = false;
}
You could use history.replaceState and append the hash, to replace the current URI without triggering the hashchange event:
var newHash = 'test';
history.replaceState(null, null, document.location.pathname + '#' + newHash);
JSFiddle example
You can have a function like this:
function updateHash(newHash){
...
oldHash = newHash
}
then in your setTimeOut you need to do
function(){
if(oldHash != currenHash){
updateHash(currenHash);
}
}
So now you can call update hash manually and it won't be triggered by the event. You can also have more parameters in updateHash to do other things.
By the way, have you looked at the jquery history plugin? http://tkyk.github.com/jquery-history-plugin/

MooTools: Attaching event to multiple elements

I've got a jQuery routine I need to convert to MooTools, but I can't get it to work. Here's my jQuery version:
$(".google-analytics-link").click(function () {
var href = $(this).attr("href");
pageTracker._link(href);
location.href = href;
return false;
});
Here's my MooTools translation:
$$(".google-analytics-link").addEvent("click", function () {
var href = this.get("href");
pageTracker._link(href);
location.href = href;
return false;
});
Doesn't seem to work though. I don't understand MooTools selectors. Any help, please?
You don't need to explicitly set the window's location when clicking the link already does it. Currently the code stops the native event, calls a method on the pageTracker object, then redirects to the location of the clicked link.
Google Analytics documentation for the _link method says that
This method works in conjunction with the _setDomainName() and _setAllowLinker() methods to enable cross-domain user tracking. The _link() method passes the cookies from this site to another via URL parameters (HTTP GET). It also changes the document.location and redirects the user to the new URL.
implying that you simply have to stop the click event, and call the _link method which will take care of the rest.
var analyticsLinks = document.getElements('.google-analytics-link');
analyticsLinks.addEvent('click', function(event) {
// stop the page from navigating away
event.stop();
var href = this.get('href');
// let the Analytics API do its work, and then redirect to this link
pageTracker._link(href);
});
$$(".google-analytics-link").each(function (e) {
e.addEvent("click", function () {
var href = this.get("href");
pageTracker._link(href);
location.href = href;
return false;
});
});

Categories