public
Last active

Ajaxify a Website with the HTML5 History API using History.js, jQuery and ScrollTo

  • Download Gist
ajaxify-html5.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
// https://gist.github.com/854622
(function(window,undefined){
// Prepare our Variables
var
History = window.History,
$ = window.jQuery,
document = window.document;
 
// Check to see if History.js is enabled for our Browser
if ( !History.enabled ) {
return false;
}
 
// Wait for Document
$(function(){
// Prepare Variables
var
/* Application Specific Variables */
contentSelector = '#content,article:first,.article:first,.post:first',
$content = $(contentSelector).filter(':first'),
contentNode = $content.get(0),
$menu = $('#menu,#nav,nav:first,.nav:first').filter(':first'),
activeClass = 'active selected current youarehere',
activeSelector = '.active,.selected,.current,.youarehere',
menuChildrenSelector = '> li,> ul > li',
/* Application Generic Variables */
$body = $(document.body),
rootUrl = History.getRootUrl(),
scrollOptions = {
duration: 800,
easing:'swing'
};
// Ensure Content
if ( $content.length === 0 ) {
$content = $body;
}
// Internal Helper
$.expr[':'].internal = function(obj, index, meta, stack){
// Prepare
var
$this = $(obj),
url = $this.attr('href')||'',
isInternalLink;
// Check link
isInternalLink = url.substring(0,rootUrl.length) === rootUrl || url.indexOf(':') === -1;
// Ignore or Keep
return isInternalLink;
};
// HTML Helper
var documentHtml = function(html){
// Prepare
var result = String(html)
.replace(/<\!DOCTYPE[^>]*>/i, '')
.replace(/<(html|head|body|title|meta|script)([\s\>])/gi,'<div class="document-$1"$2')
.replace(/<\/(html|head|body|title|meta|script)\>/gi,'</div>')
;
// Return
return result;
};
// Ajaxify Helper
$.fn.ajaxify = function(){
// Prepare
var $this = $(this);
// Ajaxify
$this.find('a:internal:not(.no-ajaxy)').click(function(event){
// Prepare
var
$this = $(this),
url = $this.attr('href'),
title = $this.attr('title')||null;
// Continue as normal for cmd clicks etc
if ( event.which == 2 || event.metaKey ) { return true; }
// Ajaxify this link
History.pushState(null,title,url);
event.preventDefault();
return false;
});
// Chain
return $this;
};
// Ajaxify our Internal Links
$body.ajaxify();
// Hook into State Changes
$(window).bind('statechange',function(){
// Prepare Variables
var
State = History.getState(),
url = State.url,
relativeUrl = url.replace(rootUrl,'');
 
// Set Loading
$body.addClass('loading');
 
// Start Fade Out
// Animating to opacity to 0 still keeps the element's height intact
// Which prevents that annoying pop bang issue when loading in new content
$content.animate({opacity:0},800);
// Ajax Request the Traditional Page
$.ajax({
url: url,
success: function(data, textStatus, jqXHR){
// Prepare
var
$data = $(documentHtml(data)),
$dataBody = $data.find('.document-body:first'),
$dataContent = $dataBody.find(contentSelector).filter(':first'),
$menuChildren, contentHtml, $scripts;
// Fetch the scripts
$scripts = $dataContent.find('.document-script');
if ( $scripts.length ) {
$scripts.detach();
}
 
// Fetch the content
contentHtml = $dataContent.html()||$data.html();
if ( !contentHtml ) {
document.location.href = url;
return false;
}
// Update the menu
$menuChildren = $menu.find(menuChildrenSelector);
$menuChildren.filter(activeSelector).removeClass(activeClass);
$menuChildren = $menuChildren.has('a[href^="'+relativeUrl+'"],a[href^="/'+relativeUrl+'"],a[href^="'+url+'"]');
if ( $menuChildren.length === 1 ) { $menuChildren.addClass(activeClass); }
 
// Update the content
$content.stop(true,true);
$content.html(contentHtml).ajaxify().css('opacity',100).show(); /* you could fade in here if you'd like */
 
// Update the title
document.title = $data.find('.document-title:first').text();
try {
document.getElementsByTagName('title')[0].innerHTML = document.title.replace('<','&lt;').replace('>','&gt;').replace(' & ',' &amp; ');
}
catch ( Exception ) { }
// Add the scripts
$scripts.each(function(){
var $script = $(this), scriptText = $script.html(), scriptNode = document.createElement('script');
scriptNode.appendChild(document.createTextNode(scriptText));
contentNode.appendChild(scriptNode);
});
 
// Complete the change
if ( $body.ScrollTo||false ) { $body.ScrollTo(scrollOptions); } /* http://balupton.com/projects/jquery-scrollto */
$body.removeClass('loading');
// Inform Google Analytics of the change
if ( typeof window.pageTracker !== 'undefined' ) {
window.pageTracker._trackPageview(relativeUrl);
}
 
// Inform ReInvigorate of a state change
if ( typeof window.reinvigorate !== 'undefined' && typeof window.reinvigorate.ajax_track !== 'undefined' ) {
reinvigorate.ajax_track(url);
// ^ we use the full url here as that is what reinvigorate supports
}
},
error: function(jqXHR, textStatus, errorThrown){
document.location.href = url;
return false;
}
}); // end ajax
 
}); // end onStateChange
 
}); // end onDomLoad
 
})(window); // end closure
readme.md
Markdown

This gist will ajaxify your website with the HTML5 History API using History.js and ScrollTo.

Installation

<!-- jQuery --> 
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script> 

<!-- jQuery ScrollTo Plugin -->
<script defer src="http://github.com/balupton/jquery-scrollto/raw/master/scripts/jquery.scrollto.min.js"></script>

<!-- History.js --> 
<script defer src="http://github.com/balupton/history.js/raw/master/scripts/compressed/history.adapter.jquery.js"></script> 
<script defer src="http://github.com/balupton/history.js/raw/master/scripts/compressed/history.js"></script>
<script defer src="http://gist.github.com/raw/854622/ajaxify-html5.js"></script> 

Explanation

What do the installation instructions do?

  1. Load in jQuery
  2. Load in the jQuery ScrollTo Plugin allowing our ajaxify gist to scroll nicely and smoothly to the new loaded in content
  3. Load in the jQuery History.js Adapter
  4. Load in History.js
  5. Load in this gist :-)

What does this gist do?

  1. Check if History.js is enabled for our current browser, if it isn't then skip this gist.

  2. Create a way to detect our page's root url, so we can compare our links against it.

  3. Create a way to convert the ajax repsonse into a format jQuery will understand - as jQuery is only made to handle elements which go inside the body element, not elements made for the head element.

  4. Define our content and menu selectors, these are using when we load in new pages. We use our content selector to find our new content within the response, and replace the existing content on our current page. We use our menu selector to update the active navigation link in our menu when the page changes.

  5. Discover our internal links on our website, and upgrade them so when they are clicked it instead of changing the page to the new page, it will change our page's state to the new page.

  6. When a page state change occurs, we will:

    1. Determine the absolute and relative urls from the new url

    2. Use our content selector to find our current page's content and fade it out

    3. Send off an ajax request to the absolute url

    4. Convert the response into one we can undertand

    5. Extract the response's title and set document.title and the title element to it

    6. Use our menu selector to find our page's menu, then scan for new page's url in the menu, and make that the active menu item and mark other menu items inactive

    7. Finish the current content's fadeout animation

    8. Use our menu selector to find the new page's content, and replace the current content with the new page's content

    9. Fade the new content in

    10. Scroll to the new current content so the user is directed to the right place - rather than them ending up looking at the footer or something instead of your page's content due to the height shift with the content change

    11. Inform Google Analytics and other tracking software about the page change

Using this Gist?

Post your website in the showcase here!

Further Reading

You can now trial this script without actually implementing it on your server by using this bookmark script: https://gist.github.com/919358

I ran into a problem with certain symbols in inline javascript (i.e. ' < ' symbol would become <t&; or whatever). The easiest fix was to change scriptText = $script.html() to scriptText = $script.text() on line 156

FYI

Magnificent work, Benjamin. You simplified the simple! Now it just works with zero work on the server side :D

This says that it will "ajaxify your website", but its specific to some specific website.

The Explanation doesn't actually explain what it does :)

There's a bunch of DOM dependent stuff that seems to suggest that its going to insert into the #content area of a webpage.

So what is going on ? You fetch a URL and then extract this content area out of that and insert that into the current page ?
I assume the content area is some smaller interior part of the page. It really would help if you could add a paragraph to explanation rather than ask us to look analyze the implementation.

other than that it looks like what I need to solve my case.

thanks!

You just have to adjust the selector with your tag containing actual content. Also you need to wrap your ajax responses in the same tag, because ajaxify also executes that selector on Ajax response.

As far as I can see it isn't necessarily intended to return ajax responses, but rather to return the entire page and then extract that DOM id item.

I had to remove the menu stuff as it wasn't applicable to my site.

My comment is just that it would be very helpful to introduce with a paragraph that explains what this is since "ajaxify your website" is quite vague.

Hey @pokonski and @crucialfelix I've updated this gist's readme with an explanation of what it does. Typically I try to educate developers rather than spell it out for them, though I can accept when sometimes I've made that step a bit too hard. So thanks for the feedback I really appreciate :-)

I'm hoping to get some time (or rather, get someone to pay me so I can make time) to update my jQuery Ajaxy project for History.js. Ajaxy still uses the old jQuery History project which is still hashes. Benefit of Ajaxy is that it works with subpages, forms and JSON responses.

great explanation, thanks for that ! its not so much about educating us but just telling us if what we are staring at is at all relevant to whatever problem we are currently working on. is it even going to be worth the 20 minutes to figure out what this thing is ? thanks again

First of all, I really appreciate the work you've done!
But is there an easy way to disable the ajax request for a specific link?

@thomasgg just add a custom class to the links you want to bypass and
add to the link CSS selector something like :not(.no-ajaxify)

@pokonski is right on, @thomasgg here is the relevant code you'll want:

        // Ajaxify Helper
        $.fn.ajaxify = function(){
            // Prepare
            var $this = $(this);

            // Ajaxify
            $this.find('a:internal:not(.no-ajaxy)').click(function(event){

In fact, I'll update the actual gist to include that by default. Done.

Yup, that's it. I had to add :not([data-remote]) for links in Rails 3.0 ;)

Just wondering about the :internal helper... should it be counting anchor tags that have an href of # (or start with #) linke <a href="#"> or <a href="#something">? I know lots of people still use # on links that have javascript only functionality or even simply named anchors.

Thoughts?

@pokonski how do :not([data-remote]) links work in rails??? intrigued

@elidupuis can you provide some sample links, and whether or not those links should or shouldn't be ajaxified. This will let me be 100% sure on what you're asking :-)

I'm thinking that the following links should not be ajaxified:

<a href="#">tied to some misc javascript functionality</a>
<a href="#heading">tied to an element on this page</a>

While the following should be ajaxified as normal:

<a href="page.html#heading">tied to an element on a different page</a>

@balupton, Rails uses his own custom adapter for binding links with data-remote attribute: https://github.com/rails/jquery-ujs . That's why I added :not clause to skip those links.

Interesting script!

I recommend using GitHub Pages for linked resources ... not /raw/...

Read
http://code.lancepollard.com/posts/github-as-a-cdn/

@chrisjacob thanks for the feedback. Updated the links.

The menu update does not work for me, I think I'm doing it wrong, I'd appreciate a bit of help here, this is my html code:

<div id="menu">
    <ul class="sf-menu">
        <li><a href="/"><img src="/images/home.png" /></a></li>
        <li><a href="/link1">Link 1</a>
            <ul>
                <li><a href="/link2">Link 2</a></li>
                <li><a href="/link3">Link 3</a></li>
                <li><a href="/link4">Link 4</a></li>
            </ul>
        </li>
    </ul>
</div>

I need to select the 'a' not the li, I've tried with:

menuChildrenSelector = '> li > a,> ul > li > a',

But it doesn't work, it does not add any class to the 'a' tag... But when I use this:

menuChildrenSelector = '> li,> ul > li',

It adds the class to the li, which in my case is useless as I need the 'a' to have the class not the li.

BTW, awesome plugin, I love it.

Thanks.

@zietbukuel It may be a CSS cascading issue. Try adding the specific class you're looking to target menuChildrenSelector = '.my-li-selector'. The idea is that you customise this gist to your site structure.

Thank you, I managed to make it work with the main links, however, the sub-menus do not show work. I'll try your suggestion.

Hey @balupton this is brilliant.

Thanks for making something that should be very simple, er, very simple.

I have a quick question about re-applying $scripts to new content.

The .document-script class is being applied to all script references in the document just fine, this Gist seems to create a fragment and then populate the body of that fragment with the innards of the script and inject it into the new page. Though this doesn't seem to allow new scripts with common jQuery $(function(){ style instantiations to run (I'm probably dead wrong!).

Do you suggest reattaching document.ready style handlers or self-invoking functions around each script loaded via the .document-script method, or something similar (function(window,undefined){});

Or perhaps something completely different?

Sorry to be slow, I've spent about half an hour with this and honestly think it's the only thing that's not instantly obvious and instantly great. Thanks again :)

EDIT:
My super basic solution was just to include a custom event trigger at e.g. line 145 of this gist. and then monitor that from other scripts.
e.g.

$content.html(contentHtml).ajaxify().css('opacity',100).show().trigger("contentLoaded");

Hi everybody. I am trying to make ajax site. With back forward history using ajax. And maximum what I did is making it but using # in address. There is a small plugin that detects changes after # sign when u press back or forward.

What I want is to have clean address bar like mysite.com/test1/test2 . Without # sign.
And as I understand this script does it. But I'm sorry I'm a littlebit novice in scripting and programing , so I couldn't understand the usage of it. Also I see it's a bit powerfull with lots of functions in it. But I need only basics.

Is there any light version of this script which can only detect if back or forward button was pressed , and fire alert if so. That's all I want. I've done the rest using my own scripting. What I need is just this.
If user is on mysite.com pressing some link for example inside link is "test1" then something must be triggered address bar must be changed to mysite.com/test1 and the rest will does my script.

Also if user is already on mysite.com/test1 pressing "back" button then address must change to mysite.com and then my script must be fired.

Is it easy to make and can you help me with such code , or I must study the whole project ?

Hi all
First, like others, I find this script awesome. Was really struggling with other history scripts but this works great. That said, Im having trouble with forms, my form resubmits the page. Is this correct?

would also be useful to maybe add something into the script to put &ajax=yes or similar onto links so the called page could be stripped out of unneeded things, server side, as long as the url didnt change, this is the part Im struggling to do.

Additionally is it possible to make the script reparse links? I have a shopping basket on my page and it doesnt "see" new things added to it unless the page is refreshed, which ruins the ajax smoothness.

To avoid ajaxing in page (href="#*) links, change
$this.find('a:internal:not(.no-ajaxy)') to $this.find('a:internal:not(.no-ajaxy,[href^="#"])')

This prevented problems I was having with a skip to content link, <a href="#content"...

can you get it so take over form submits without page refreshes?

the problem I have with it not parsing links is links added to the page by additional JavaScript i.e. on the fly. Anything that this plugin adds is hooked correctly.

guys please tell me how to set up simple script to grab href source when pressed, put it into addressbar, and if user presses back fire some action with old source code. I don't need anything else

W dniu 14.10.2011 10:00, chmig29fulcrum pisze:

guys please tell me how to set up simple script to grab href source when pressed, put it into addressbar, and if user presses back fire some action with old source code. I don't need anything else

Erm... this script does exactly that. Just trim the rest of features

sorry for all the question spam >< but I have another (aswell as the forms one, and parsing java added links)
I have been testing this in FF, Chrome, and IE 9. FF and Chrome work great, every part works inc bookmarking. However, IE 9 is causing issues. When using the page it works fine, as it should but when you save a bookmark it saved "http://doman.com/index.asp#details.asp?id=99715" if I try to then view the details page from a saved bookmark, it just loads the index page, ignoring the hash stuff. Obviously FF and Chrome dont have this problem because they dont use the #. To reproduce this :
browse site as you would, save one to favs. Close browser (if you leave it open the links load as they should). Load bookmark. It loads the main page. If you then try the bookmark again, it loads the correct page.

trying to add this script to my page...

missing variable name
[Break On This Error] = $(contentSelector).filter(':first'),

This comes when initializing variables...

@blocosted this is a job for your CMS.
This script just requests the page's content. Nothing more, nothing less.

will chuck another question into the mix, although none of my other ones got answered :s
anyway, aside from my issues above, namely forms, I got this working well, until I decided to do some URL Rewriting. This seems to have broken the history stuff. i.e. /folder/index.asp?value=1 works great, add some url rewriting so it looks like /folder/cool_file_name1.asp and the history stops working, and instead reloads the page, correctly loading the url but not fading in/out the content as it should
Any ideas? the correct content is being sent out based on the URL but for some reason the history has trouble with it and causes a normal page load instead

@knowntobe can you please provide a sample url to reproduce the issue, the version of History.js you are using, the OS and version you are using, and the browser and version you are using. This will allow us to help you out.

I can yes but do you have an address I can email details to? the site Im working on us pass protected currently as its under development.
Browser tried are IE9, FF latest, Chrome Latest. History Im using is ajaxify-html5.js on my site, latest download, and linked to your copy of /jquery.history.js

Further info on the above issue. It appears the history starts, and tries to load cool_file_name1.asp (content area fades out), but then redirects to its actual name of index.asp?value=1 instead.

the cool_name issue seems to be resolved, I think it was because I edited the gist ajax call to include a random number (had caching issues in IE), once I took this out it started to work fine. I havent tested things like details.asp?value=1 being changed to details.asp/value/1 yet. But the main part is working. if I could just get it to work with forms, and links added via ajax (after history.js has loaded a page), I would be all sorted.
Its truely awesome though, love this thing :)

been doing some testing in various versions of IE, and IE 7 seems to have issues with :
scriptNode.appendChild(document.createTextNode(scriptText));
which is line 158 of ajaxify-html5.js
from some googling, I found a stackoverflow post here http://stackoverflow.com/questions/2592092/executing-script-elements-inserted-with-innerhtml
which suggests a workaround. Not got it to work myself yet but thought I would post.


this is my current code, working in all versions of IE now
// Add the scripts
$scripts.each(function(){
var $script = $(this), scriptText = $script.html(), scriptNode = document.createElement('script');
// scriptNode.appendChild(document.createTextNode(scriptText));
// contentNode.appendChild(scriptNode);
try {
// doesn't work on ie...
scriptNode.appendChild(document.createTextNode(scriptText));
contentNode.appendChild(scriptNode);

} catch(e) {
// IE has funky script nodes
scriptNode.text = scriptText;
contentNode.appendChild(scriptNode);
}
});

obviously remove the commented lines, left in for reference

1) I second the previous question about using this to AJAX-ify forms. Any easy way to do that?

2) I'm having issues having other jQuery scripts work for content that is loaded by this script. Can you please give some instructions on how to use the built-in script loading functions?

for my forms I did use Jquery to load the content but the AJAX-ify then doesnt see the new content and so doesnt hook into the links, which means a full page refresh. I assume this is the same as the above issue.

@knowntobe @jetlej The reason for this the that the event hooks are set on the elements that are present on page load and not onto new elements loaded into the DOM. You can get around this by using .live() to bind event handlers that listen for newly added content too. So instead on $('#myElement').click( function(){ // do stuff }); you would use $('#myElement').live('click', function(){ // do stuff });

Thanks Craig! Or even better, us the new .on() function from jQuery 1.7. It's supposed to beat even the browser in element searching. jQuery is awesome.

On Nov 8, 2011, at 7:15 AM, Craig Dennis wrote:

@knowntobe @jetlej The reason for this the that the event hooks are set on the elements that are present on page load and not onto new elements loaded into the DOM. You can get around this by using .live() to bind event handlers that listen for newly added content too. So instead on $('#myElement').click( function(){ // do stuff }); you would use $('#myElement').live('click', function(){ // do stuff });


Reply to this email directly or view it on GitHub:
https://gist.github.com/854622

Just noticed that if I click an ajaxified link, and then click another link before the former has a chance to load AND the latter link loads faster than the former, it will display the latter and then load the former over it when it's finished loading. Where can I place a stop() function to prevent this?

Thanks Craig, just having a look through the ajaxift-html5.js file and found $this.find('a:internal:not(.no-ajaxy,[href^="#"])').click(function(event){ so will edit this and see how it goes

edit

works a treat \o/ so pleased
Just the form issue to try and solve now

@jetlej Yeh 1.7 uses .on() in place of bind, live and delegate so it's a unified event handler. It also adds innerShiv which solved the ajax issue of loading HTML5 content in non-HTML5 browsers. At the time of posting it wasn't hosted on Google's CDN so I didn't want to muddy the waters.

fixed my form problem by writing a Jquery ajax function to send the form search to the content div, then using .live in the Ajaxify file making it parse the newly loaded links. Awesome stuff. Now another issue
I have paged results, for example this is one link+query /index.asp/page/14/offset/10. Some how, the ajax is sending repeated url changes, so it ends up like this :
http://www.domain.com/property/index.asp/page/14/offset/10#index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/14/offset/index.asp/page/16/offset/10
basically each part from index.asp to the end of the querystring is repeated constantly until the browser calls this error :
Line: 1
Error: Access is denied.
jquery.history.js, line 1 character 15681
The code the IE debugger highlights is this :
return b !== !1 && m.busy() ? (m.pushQueue({
scope: m,
callback: m.setHash,
args: arguments,
queue: b
}), !1) : (c = m.escapeHash(a), m.busy(!0), e = m.extractState(a, !0), e && !m.emulated.pushState ? m.pushState(e.data, e.title, e.url, !1) : d.location.hash !== c && (m.bugs.setHash ? (f = m.getPageUrl(), m.pushState(null, null, f + "#" + c, !1)) : d.location.hash = c), m)

This is in IE8 and 9. Chrome and FF seem unaffected. Turning off url rewriting fixes it, or appears to at least. The rewrite rule is :
RewriteRule ^(.?.asp)/([^/])/([^/]*)(/.+)? $1$4?$2=$3 [NC,LP,QSA]
turns index.asp?value=1&field=2 etc into index.asp/value/1/field/1

I know this isnt a fix my site section, just posting in case this is a real overlooked issue. Meanwhile I'll go back to trying to find a fix in case its just me thats broken it.

I've added the class 'document-script' to my scripts being loaded in the ajax content and they're being parsed and re-added but not executed! Anyone know what I'm doing wrong??

I have got a serious issues and unfortunately I dont have the knowledge to solve it myself and would be happy if some1 can take a look at this:

When using a HTML4 browser (in my case its IE8) and I try to navigate directly to an "ajax sub-page", for instance by going to "hxxp://example.com/index.html#sub-page.html" - the AJAX does not fire off and I get to see the content of "index.html" instead of "sub-page.html". The same happens when I reload the page: instead of seeing the content of "sub-page.html", I see the content of "index.html"

I put it online to an testing host: http://test-this.allalla.com

Try to visit http://test-this.allalla.com/index.html#ajax1.html with a HTML4 Browser and you will see the content of "index.html" instead of "ajax1.html".

I didnt change anything in the script, besides the selectors for the content and navigation. Some more infos:

OS: Windows 7
Browser: IE8
History script: html4+html5 jquery.history.js [newest version downloaded from here]

Regarding the conversion of script tags to div.document-script tags, I've come up with a fix. The issue I had was related to tweet and facebook like buttons no longer working when the content was loading with AJAX. I modified the $scripts detachment block as follows:

// Fetch the scripts
$scripts = $dataContent.find('.document-script');
$scripts.each(function () {
    if ($(this).text().match(/(twitter|facebook)/i)) {
        $(this).replaceWith('<script type="text/javascript">' + $(this).text() + '</script>');
    }
});
if ( $scripts.length ) {
    $scripts.detach();
}

@blocosted if you have not solved it already, you need to disable caching on all you ajax partial requests. The back and forward buttons load content that is in the history. That is why you are only seeing the partial. This answer in Stackoverflow shows how to disable the caching: http://stackoverflow.com/questions/985530/best-way-to-disable-client-caching

@loickreitmann
would your edit be workable for googlemaps? Im having issues getting mine to display.

@knowntobe
It might work. Try the following:

// Fetch the scripts
$scripts = $dataContent.find('.document-script');
$scripts.each(function () {
    if ($(this).text().match(/(google)/i)) {
        $(this).replaceWith('<script type="text/javascript">' + $(this).text() + '</script>');
    }
});
if ( $scripts.length ) {
    $scripts.detach();
}

I'm trying to make this gist work for my embeddable ajax widget. Is there any way to modify it in order to make it work with an external root url? In other words I need a way to implement a browsing history of my widget on another website. Pull every url from my website, strip it of the root url, and push the state on the embedding website. Right now I'm not using this gist but I'm using History.js with this code in my widget. This almost works but has a lot of problems like not having the possibility to bookmark the url.

$("a").live("click", function() {
  $.getScript(this.href + ".js");
  var relativeUrl = this.href.replace('mydomain', '');
  History.pushState(null, null, relativeUrl);
  return false;
});

Please tell me if I'm totally off the road, I'm kinda new at this stuff and I apologize in advance. I'm using Rails 3.

Really cool! I tried to implement this in a wordpress theme. When I embed the scripts from the official servers like in the installation described, everything works fine. When I download a copy of the ajaxify-html5.js and link this in my head, the script does not work. How come? I mean it is the same. Sorrs, but I have not so much experience in coding and using scripts.

First of all, awesome gist! I just have a couple questions as I don't have much experience in the client-side scripting portion of web apps. First, is it bad practice to, in this script, load scripts that are in the "head" portion of the site as well? I ask because I have a framework that loads scripts into this portion of the site. Secondly, to have each "page load" call a certain function, I have everything in a "function pageLoad()", and this gets called at the end of the script. But, this doesn't work if I just put "pageLoad()" at the end of the gist [as it relies on scripts loaded through this gist?]. To work around this, I have a setTimeout to a certain time (1ms in my case, which doesn't work particularly well). Is there a better way to do this?

to use an example.
Put ALL your includes into the head of index.php
Assume you have a div with id "content" inside your index page, all your sub pages will be loaded, via ajax, into this div. So all the sub page functions will work as if they were written directly into index.php (since all your includes are in this head).
For your function, you have 2 choices.
1. at the end of each sub page, call your page_load, this will fire once ajax has loaded your subpage into your content div, do this for each sub page
2. in ajaxify-html5.js, roughly line 186, look for "if ( $body.ScrollTo||false )" after this line put your page_load call. This will fire after each ajax call has completed, this is what I do.

you also probably want to find :
$this.find('a:internal:not(.no-ajaxy)').click(function(event){
and change to :
$this.find('a:internal:not(.no-ajaxy,[href^="#"])').live('click', function(event){
so it ignores page anchors, and also works on some ajax related page changes.

note : Im by no means an expect, see my questions above. This is just what I've done, and found works for me, might not be best practice.

Thanks for the quick reply, I'll try it out!

I just tried it out, and say I load the first page, "Home", and then I go to a second page "Users". Home will have one version of pageLoad, and Users will have the other. If I do #2, it still fires the first version of pageLoad (the Home version) instead of the Users version. I tried to do something like window['pageLoad'] = function() {} instead, but haven't had any luck getting it to work

than change the names so its 2 different functions. Its likely calling the
first one twice because the names the same.

On 10 January 2012 01:16, DKFUNG <
reply@reply.github.com

wrote:

I just tried it out, and say I load the first page, "Home", and then I go
to a second page "Users". Home will have one version of pageLoad, and Users
will have the other. If I do #2, it still fires the first version of
pageLoad (the Home version) instead of the Users version. I tried to do
something like window['pageLoad'] = function() {} instead, but haven't had
any luck getting it to work


Reply to this email directly or view it on GitHub:
https://gist.github.com/854622

Hi,
I managed to use this in a wordpress theme. Two things I can't get to work. First of all I wanted to fade in the the new content and replaced the line: $content.html(contentHtml).ajaxify().css('opacity',100).show(); with $content.html(contentHtml).ajaxify().css('opacity',100).fadeIn(800); But this does not work. Another Problem is removing the active class from the current menu item. Wordpress gives the current menu item the class "current-menu-item". So when I load another page the new page is highlighted as it should, but the old page stays highlighted as well. So the class is not removed correctly.

Can anyone help?

@christianslater Try adding the wordpress menu classes to

activeClass = 'active selected current youarehere',
activeSelector = '.active,.selected,.current,.youarehere' 

@craigmdennis Thats what I did

activeClass = 'current-menu-item',
activeSelector = '.current-menu-item' 

The problem is, that the script is changing the class name to "current-menu-item" when I click on it, but does nit remove the class from the recent item. Take a look at this page: www.schauundhorch.de/wordpress/?page_id=10

any idea how to get the changed content fade in?

regards christianslater

@christianslater Well Wordpress uses many different class names to denote the active state and the hierarchy, in your case the class is current_page_item and not current-menu-item so maybe that's the issue. Bare in mind that this class is just for pages and differs for posts and sub pages.

Those having issues with forms - It is possible to have AJAX-based forms, you just need to re-execute the code after the AJAX for the page is complete.

Create a function that houses the code for your AJAX-based form, then call that function in the success callback of the AJAX load used in this Gist. The same goes for if you need to reinitialize plugins upon entering a page.

Hey, I managed to get the current stat change of the menu working. But I can't get the content fade in. Can someone help me?! here's my test page: http://schauundhorch.de/wordpress/

Ah, I got it. I don't know if it's correctly used, but it is fading in the content now!

I changed this line: $content.html(contentHtml).ajaxify().css('opacity',100).show(); to this $content.html(contentHtml).ajaxify().css('opacity',100).hide();

and added this line afterwards: $content.fadeIn(800);

now it's working!

unfortunately the scrollTo function is not working anylonger. I think because I hide the content before I fade in the function can not detect the size of the content an does not scroll to top! Any other idea how to solve this?

regards chris

Instead of .show() and .hide(), perhaps try using .css('visiblility', visible') and .css('visiblity', 'hidden') ?

Thanks! "visibility" did the trick. here's my line of code:

$content.html(contentHtml).ajaxify().css({opacity: 0.0, visibility: "visible"}).animate({opacity: 1.0},800);

this is amazing.

one small thing, shouldn't it be: css('opacity',1)

nice job ! really :)

Hi

Thank you for this great gist. It is amazing

But I have find that doesn't work properly with Safari VersiĆ³n 5.0.2 (6533.18.5) It works perfect on Chrome, Safari and even Internet Explorer 7

The thing I have observed with Safari is that event statechanged is triggered twice. The first one when the user clicks on an internal link (url variable is set to the new url), and a second time with the url variable still set to the older url. Making this to work erratic.

You can take a look at http://testwp1.alfonsosiloniz.es/wp2/home/

Work fine here on Safari 5.1.3... I can't test on 5.0.2 sorry

I've seen it. A petty I have this old Safari version for OSX. Don't know if
this is a bug in such a version or not. Thank you anyway

On Thu, Feb 23, 2012 at 1:43 PM, wedezign <
reply@reply.github.com

wrote:

Work fine here on Safari 5.1.3... I can't test on 5.0.2 sorry


Reply to this email directly or view it on GitHub:
https://gist.github.com/854622

Got this working great, only issue is it's not updating Meta tags, any assistance on how to go about integrating that?

Omar, look at lines 124 and 154 where the scripts are detached and then added. You should be able to follow a similar pattern for tags. Is it really worth bothering though? Isn't that data only for machines, which won't use this method of loading pages anyway?

@scruffian Hmmm, let me try some more tests.... I was originally trying to accomodate the Facebook Like Button.... But I wonder if it's using the current pages meta actually or retrieving it on their end.... Good Point.

@omarvelous I also need to include meta tags for facebook, but facebook won't crawl your site like a user, it picks up the meta tags directly from the URL - you can see what it gets from the URL linter:

http://developers.facebook.com/tools/debug

So I don't think you need to do this to support facebook...

In case anyone would like to be able to also append external scripts using the .document-script class, you can modify the appending of the scripts code to look like this:

//Add the scripts
$scripts.each(function(){
var $script = $(this), scriptText = $script.text(), scriptSrc = $script.attr('src'), scriptNode = document.createElement('script');
if(scriptSrc) {
scriptNode.src = scriptSrc;
contentNode.appendChild(scriptNode);
}
else{
scriptNode.appendChild(document.createTextNode(scriptText));
contentNode.appendChild(scriptNode);
}
});

This whole concept is unusable in IE < 9 because script insertion from reading text on a page is bugged in those browsers. Every method of inserting text into a script node mentioned here has its problems.

If you use this, don't depend on script insertion at all or provide a fallback for older versions of IE if you need code to work everywhere.

I wanted to ask if its possible to add a loading indicator that shows up in between page loads..

Thanks!

@shmelman, you expect too much. This won't magically add indicators. This is your part. At line 106 it adds .loading class for <body>. Just style it appropriately.

Is it possible to prevent this scrolling to the top of the content when the user has clicked the back button? In this case this behaviour is not desirable. Is it possible to tell if the event is a "back" event inside the statechange event listener?

This is how I achieved it. I tried modifying the State object to do it this, but it ended up getting really messy, so instead I just defined a new variable and used the id of the state to store the current scrolling position for that state. It's simple but it should work. Just add this around line 83:

            var Scroll = [];

            //Add scroll position for this state
            Scroll[currentState.id] = $body.scrollTop();

And add this in the statechange event around line 162...

            //get the scroll position from the scroll object, if it exists.
            if (typeof (Scroll[State.id]) !== 'undefined') {
                top = Scroll[State.id];
            }
            $('html, body').animate({scrollTop: top}, 300);

Also, can anyone shed any light on the purpose of the closure around this gist? Thanks...

@scruffian, I believe this is just a precaution to separate the code from the rest of your Javascript.

@ scruffian
Can you explain where exactly you put the code for the scrolling "fix"? I've edited mine a lot so "around line.." is messing things up as I cant find the correct location.

@knowntobe
i have changed the code a bit since that post, so it's probably better to explain what I have done and how it works, so that you understand the concept and can incorporate it into your code. The idea is to extend the History object with a new property - i called it Scroll. After you define the variables I would put:

History.Scroll = {}; // it should be an object not an array as I put above.

Then in the click handler which is assigned to each link you need to get the id of the state of the page that you are currently on (each state has an id associated with it), like this: currentState = History.getState();

You can use this id to save the current scroll position of this state, ready for using later on, like this: History.Scroll[currentState.id] = $body.scrollTop();

Then in the "statechange" event listener, where the scrollTo function is used, you can retrieve the scroll position that you saved earlier in the History.Scroll object, like this: top = History.Scroll[State.id];

Of course this may not exist so you need to wrap it with the if statement to check it exists, otherwise you should fall back to whatever mechanism you were using before to work out where to scroll to.

This all works fine, but it creates one strange behaviour, which is that if you click a link you have been to before, you will be scrolled to the position you were at last time you visited the page, which is unexpected and undesirable (probably). It's easy to resolve this though, you just need to remove the scroll position for any states that are visited via a click, rather than via back or forwards buttons. You can just add this to the click event handler:

            //get the full url for the next page to go to
            nextFullUrl = History.getFullUrl(url),
            //get the id for the state of the next page 
            nextId = History.getIdByUrl(nextFullUrl);
            //if the id exists (it always should, but just in case)
            if (typeof (nextId) !== 'undefined') {
                History.Scroll[nextId] = 0; // i just reset to zero, but actually it's probably better to remove this from the object altogether
            }

Hope this helps. If you have any more questions let me know...

Thanks for the detailed reply, I shall give it a go when I get some time

Meanwhile, is anything having issues with pressing Back before the page fully loads? On a site Im testing this on, image heavy, it doesnt do anything, the url changes but the content doesnt. This doesnt happen 100% of the time, I think it depends how much of the new page has downloaded (unless something else is causing it to "stick"). A fully downloaded page doesnt have issues, where as one thats fresh, and only been opened for a few seconds does.

I've been working with this and have found it to be great, but one problem I'm having is triggering a separate .js again once the content has loaded or when I go back or forward in the history. Essentially, I have a .js that controls the visibility of some objects on the page as well as some of the menu styling, and I need it to fire every time I change pages. Sorry if this is a crazy dumb question.

this was asked by me, and solved further up the list.

On 19 April 2012 12:58, clafleche <
reply@reply.github.com

wrote:

I've been working with this and have found it to be great, but one problem
I'm having is triggering a separate .js again once the content has loaded
or when I go back or forward in the history. Essentially, I have a .js that
controls the visibility of some objects on the page as well as some of the
menu styling, and I need it to fire every time I change pages. Sorry if
this is a crazy dumb question.


Reply to this email directly or view it on GitHub:
https://gist.github.com/854622

@knowntobe Saw that just now... whoops. Sorry. Figured it out, and everything is running well in Safari, Chrome, and Mac FF.

I've encountered another strange problem. In FF 3.6.3 (Win) and IE 9 (and probably older versions too), the ajax link paths are being appended to the root as #content/page1, #content/page2, etc. and all of the text content loads, embedded scripts execute, everything is fine... except that image paths are broken (I'm loading slideshows into some pages). I could go in and give all the images full paths, but I'm hoping there's an easier solution.

Possibly related: in my menu I have a li that is linked to "#" and when clicked opens a hidden ul (and the ajaxify ignores it using the solution provided above). But in FF (Win) and IE it points back to _root/# regardless of what page I'm on, throwing everything into a mess. What's going on?

For those that are using Google Analytics and are having trouble with the above code actually tracking their ajax calls there is a quick fix for this. The following line in the current code is designed for the old analytics tracking code:

if ( typeof window.pageTracker !== 'undefined' ) {
     window.pageTracker._trackPageview(relativeUrl);
}

In order for the tracking to work with the most current tracking code you need to update to:

_gaq.push(['_trackPageview', relativeUrl]);

Thank you for your very useful work, I have a question from a js noob:

How can I modify the body id of each loaded content?

I tried using the following in the body of each page:

document.getElementsByTagName('body')[0].setAttribute('id','green');

('green' is the id I need to apply)
it kinda works but I don't think it's the correct way, also it seems to load the body id AFTER the css file so some styles won't update according to the page body ID.

Thank you

Hi, i got this problem. When i click on an ajaxify link, i need to add the link object into pushState, but the i got a message like: Uncaught TypeError: Converting circular structure to JSON . How can i add the link object and save it as a State object?

Could some please write a real-world practical example - or at least tell me and those after me - with what kind of site structure this demo is supposed to be working?

I used the whole yesterday trying to applying this to my site only to discover at night that I had it all wrong...

I assumed this was using links like /sub/sub/ and when JS visitors enabled would convert them / fetch content dynamically from source at corresponding location folder with index-file.

My site under dev (the violet section) can be found at http://envision.fi/lab/ for some time, and hopefully soon will be published at root :)

Hello,
Thanks for this great plugin.
I'm facing some problems while using it with Twitter's bootstrap.
This plugin seems to add "#" url for internal links which previously were not showing #example-id in the url.
You can see what I'm trying to say in following fiddle:
http://jsfiddle.net/vineonardo/PL2Yv/2/
Just comment out the history pluging, the carousel will start working again.

Can you help me with this? I'm a complete newbie at jquery. Thanks for your time.

The content should change when you click Back, my site does, tested in IE
6-9, FF, and Chrome.

And yes, in todays super fast world you might not want to ajax your site in
such a way, each to their own. I have and I believe my site is faster,
cleaner, and much more like a desktop program than a web site. Thats why I
use this.

On 8 June 2012 20:21, MrSidecar <
reply@reply.github.com

wrote:

Hi,

firstly- great work. Great it works- looking for ajax-solutions without
hash mark was what brought me here.
The thing is, and it might well be only me- I always considered AJAX to be
useful especially because what I load into the browser is NOT a whole new
page, but rather an entry in one or another database, be it MySQL or a
simple HTML file with divs whose content you load to be displayed on your
page. And, if I understood this here correctly (and I assume I have, since
I got it to work, plus, up there was someone mentioning the same), the
whole point in the approach presented here is that what you actually take
your content from is A REAL SITE that could as well be loaded
conventionally. At least, that's the only way I got it working so far. And
this also assures the compatibility with IE, for in that case the normal
whole page is updated. Granted, it works and only changes the portion I
need to change and leaves the rest of the page untouched- but I don't
really see the point in ajaxing a site when I have to code (or copypaste)
the whole shebang into every sub site anyways, ilk
e it was 1995. Then I can just was well update the whole page/browser
window, likely nobody will notice the difference in today' s
fast-connection-world. Or am I missing something? I likely am.
Another thing I have not gotten to cooperate yet is the
reload-then-backbutton-scenario. When I reload a page, the browser updates
the whole page. When I thereafter press back (newest Safari-tested), the
URL changes to the state WHERE I CAME FROM. But the content does NOT
change. If this is mentioned anywhere before, I could not find it and beg
your pardon. If it isn't- what can I do about that?
Best
Sidecar


Reply to this email directly or view it on GitHub:
https://gist.github.com/854622

Hello,
Thanks again for the great plugin.
While working on a single page website, I found a problem that whenever after using internal links like http://example.com/#work, if I open a link which is of an external page like http://example.com/work/article-1, it loads the page nicely, but then the back button doesnt work.
It's supposed to take user back to http://example.com/#work, but instead, only url changes in the address box but the content loaded doesnt change back to original.
I'm sure I'm missing out something essential, if possible, someone kindly enlighten me.
Thanks.

Regards,
Vineonardo

EDIT: Ohkay, I just found out about the class="no-ajaxy" but, in single page websites, if user is on the bottom of the page when I click the external url, the back button should bring user back to that portion of the page, as it happens in normal webpages.
Any comments on this guys?

EDIT #2: @knowntobe: Yeah, I read a few comments. I'm really being very lazy lately :p. I just learned about contentSelector which seems to work the way I want it to work. When opened the new link, and pressed back button, the element specified in contentSelector is shown. In my case it'd be

.

Thanks buddy for your help.

Have you read all the comments? Im not sure how to fix it but there was an
edit posted, something to do with insite hash tags. Probably not related
but posting just in case

On 8 June 2012 21:38, vineonardo <
reply@reply.github.com

wrote:

Hello,
Thanks again for the great plugin.
While working on a single page website, I found a problem that whenever
after using internal links like http://example.com/#work, if I open a
link which is of an external page like http://example.com/work/article-1,
it loads the page nicely, but then the back button doesnt work.
It's supposed to take user back to http://example.com/#work, but instead,
only url changes in the address box but the content loaded doesnt change
back to original.
I'm sure I'm missing out something essential, if possible, someone kindly
enlighten me.
Thanks.

Regards,
Vineonardo


Reply to this email directly or view it on GitHub:
https://gist.github.com/854622

I see the comment above from @roc about $(document).ready() not firing properly, I seem to have the same issues. Is this a known issue with this ajaxify + history setup? Other than that, it's been pretty much flawless.. I'm really happy with it.

So, on my pages that have special commands in $(document).ready() just for the one page.. if I ajaxify browse to the page, I don't get the commands running. But, If I do a control+R on the page to refresh, then they run. In fact I can't seem to get any javascript that I embed into this one particular page to fire if I ajax browse to it. But I'm not getting any type of JS error in the console either, so I'm sort of confused what the problem is.

Is document.ready supposed to work? Or how about if I link to an external JS file on that view, is that supposed to work also?

Thanks!

doc ready only runs on first page load. If you have other java that is
loaded via ajax, look at using Jquerys .on command as this checks for new
content and adds the hooks as it appears.

On 9 July 2012 18:30, Dan B <
reply@reply.github.com

wrote:

I see the comment above from @roc about $(document).ready() not firing
properly, I seem to have the same issues. Is this a known issue with this
ajaxify + history setup? Other than that, it's been pretty much flawless..
I'm really happy with it.

So, on my pages that have special commands in $(document).ready() just for
the one page.. if I ajaxify browse to the page, I don't get the commands
running. But, If I do a control+R on the page to refresh, then they run.
In fact I can't seem to get any javascript that I embed into this one
particular page to fire if I ajax browse to it. But I'm not getting any
type of JS error in the console either, so I'm sort of confused what the
problem is.

Is document.ready supposed to work? Or how about if I link to an external
JS file on that view, is that supposed to work also?

Thanks!


Reply to this email directly or view it on GitHub:
https://gist.github.com/854622

@knowntobe ok, but then I end up with ton of javascript in the main js file, essentially all js is sitewide instead of on a particular view.. I suppose it's not the end of the world, but that's the solution? I can live with that, wanted to make sure I wasn't wasting time if it's a known issue.. which guess it looks like it is. @roc also had a pretty good solution with the "finished loading" callback idea. I just found some good minifying JS scripts.. so i suppose compressing all JS is just as good of an answer: http://yearofmoo.com/2011/04/minify-css-and-js-with-git-hooks/

thats basically what I did, unless you include script blocks as part of the
ajax returned content.
That said, some sections of my large, which is large. I do no-ajaxy the
links so a full reload can be done to add additional JS includes.

On 9 July 2012 18:43, Dan B <
reply@reply.github.com

wrote:

@knowntobe ok, but then I end up with ton of javascript in the main js
file, essentially all js is sitewide instead of on a particular view.. I
suppose it's not the end of the world, but that's the solution? I can live
with that, wanted to make sure I wasn't wasting time if it's a known
issue.. which guess it looks like it is. @roc also had a pretty good
solution with the "finished loading" callback idea. I just found some good
minifying JS scripts.. so i suppose compressing all JS is just as good of
an answer: http://yearofmoo.com/2011/04/minify-css-and-js-with-git-hooks/


Reply to this email directly or view it on GitHub:
https://gist.github.com/854622

Is anyone having issues with google analytics not tracking ajaxy pages? my google code is :
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-xxxxxxxx-x']);
_gaq.push(['_setDomainName', 'Domain.com']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();

and in my ajaxify-html5.js I have :
// Inform Google Analytics of the change
if ( typeof window.pageTracker !== 'undefined' ) {
window.pageTracker._gaq.push(['_trackPageview', relativeUrl]);
}

using googles live view, I can watch myself moving around the site as long as I dont use ajaxy pages. If I do, the urls dont update on the tracking and it looks like Im staying on 1 page.
I installed the debug extension for chrome and get this error
Uncaught ReferenceError: blockreferrer is not defined
(anonymous function)
not sure what it means yet though but its shown on every Ajaxy link clicked.

@knowntobe Take a look at my comment posted ~ 2 months back above. The code in this gist is using the old version of the Google Analytics code. You need to use the new version. My comment shows you what code to replace.

hi
as per my code post Im pretty sure Im using that already, I copy/pasted it
in the day you made that post and double checked it last night when I
realised but if you could double check my post to be sure I would be
grateful.

edit
also I tried _gaq.push(['_trackPageview', relativeUrl]); instead of window.pageTracker._gaq.push(['_trackPageview', relativeUrl]); as I wasnt 100% sure on the intended change, no effect.

Further playing, if I change
if ( typeof window.pageTracker !== 'undefined' ) {
_gaq.push(['_trackPageview', relativeUrl]);
}
to just :
_gaq.push(['_trackPageview', relativeUrl]);

google tracking is updating (using analytics live view) the url the visitor is on, which is good but I dont know the negative effects of this or if something else is causing the block. I dont know enough about it and Im not getting any errors.

edit
@aspiziri, on reflection, your earlier post, did you mean replace all 3 lines with the new one? I Thought you meant just replace the 2nd line (of the 3) which is what I did when you made your post. Now I've replaced all 3 with the new 1, its working. I guess thats what you meant? now I see it like that its obvious, just the "The following line " comment threw me off /facepalm

Thank you, really useful especially your documentHtml function!

Hi,

Thanks for this very efficient script.
I need to get some meta tags from the head section of the page being called using ajax and replace the existing page's meta tags.
These are meta tags for social sharing like og:title, og:url, etc.
Can you please explain how I can get this done.
Let me know if you need any clarification on the issue.

@arfaRed no you don't. refer to previous comments on the matter...

I am submitting a form through a function rather than form submit and appending a table using the returned JSON into #information div. I then use jquery to hide(#myform) and show(#information). I am attempting to use history.js to provide back button capability. When using the back button, I can see that the title returns from /?state1 back to /. However, the content remains the same with #myform hidden and #information showing.

Is there a way to store the view and restore it when using the back button.

FYI solution for others who might have a similar issue...

I disabled the hide()/ show(), deleteded my #information div, and used the following to change the #content div in my javascript that is called to run the form submit:

                var
                History = window.History;
                History.Adapter.bind(window, 'statechange', function() {
                    var State = History.getState(); 
                    $('#content').empty().append(State.data.content);
                });

                /* to store starting state, otherwise it returns to an empty #content*/
                var content = $('#content').html(), State = History.getState();
                History.pushState({content: content}, State.title, State.url);

                                    /*parse returned json and put into table*/
                                                                             table += '</table>';
                                    /*insert table into #content with a new stored state*/
                                    History.pushState({content: table}, "Selection", "?state=2");

However, it now does not work with my jquery mobile which is activated when screen>799. When submitting the form, it fails and console shows "TypeError: c.originalEvent is undefined"

Hi,

Came across the following 2 errors in IE7 when I implemented your script.
1. SCRIPT5007: Unable to get value of the property 'enabled': object is null or undefined
ajaxify-html5.js, line 11 character 2
This error comes when the index page & script is loaded.
2. SCRIPT65535: Unexpected call to method or property access.
ajaxify-html5.js, line 158 character 7
This error comes when I click on a link to load the content.

Please guide me through this.
You have created an awesome script.

Thanks

Sorry the two errors mentioned above are for the following code of lines

  1. if ( !History.enabled ) {
  2. scriptNode.appendChild(document.createTextNode(scriptText));

just like to share something I found while working with this.. I use cakePHP which uses named parameters in a fashion like this - http://site.com/controller/action/param1:val1/param2:val2

i found that with this script, it was failing this check and would not ajaxify any links that had the : in the URL. I confirmed this by checking 2 identical links next to eachother in same place, same classes, and only diff was one had the ":" and one didn't. (i.e. /controller/action/param/2 vs /controller/action/param:2)

$this.find('a:internal:not(.no-ajaxy)').click(function(event){

My only guess is that the "internal" selector must see the ":" in the parameters values and think it's an external link..(i.e. http://) that's the only thing I can figure.. So I changed it to:

$this.find('a:not(.no-ajaxy)').click(function(event){

And now I will just add class no-ajaxy to external links also.. hope this helps someone.

Hi just wondering how i'd load in scripts from the other pages I'm loading in?
I saw the lines: $dataContent.find('.document-script')

just wondering how I'd apply that to script blocks.. i've tried adding a class of document-script to scripts inside my content containers that im loading in but they're still not carried through

Great stuff. Please check (and merge) my gist: https://gist.github.com/4041997
It adds that also all links where any parent has the class "no-ajaxy" are exluded. Makes excluding e.g. navbars entirely more comfortable.

Hi just wondering how to make it work if the page struture is like (index.html, about.html, and bla bla) in same folder, do i have to make some configuration in the gist, sorry for the silly question but i am trying for last 2 days to make it work for static website pure html

Thanks

i use ajaxify-html5.js, and have inline scripts in my html.

<script>
    for( var i = 0; i < 10; i++ ){ }
</script>

resulted in an error...

so i change the code.. (it's a mess though..)

i wonder if anyone have a better solution, i really need the help...

the code (edited):
the followings are inside the $window.bind('statechange',function(){ ...
starting from line 117

    // Ajax Request the Traditional Page
$.ajax({
    url: url,
    success: function(data, textStatus, jqXHR){
        // get inline script
        var body = data.match(/<body[^>]*?>[^]*?<\/body>/img);
        var inlinescript = body[0].match(/<script[^>]*?>[^]*?<\/script>/img);

        // replace inline script with '', 
        data = data.replace(/<script[^>]*?>[^]*?<\/script>/img,'');

        // Prepare
        var
            $data = $(documentHtml(data)),
            $dataBody = $data.find('.document-body:first'),
            $dataContent = $dataBody.find(contentSelector).filter(':first'),
            $menuChildren, contentHtml, $scripts;

        ...

        // Add the scripts
        $scripts.each(function(){
                var $script = $(this), scriptText = $script.text(), 
                scriptNode = document.createElement('script');
                scriptNode.appendChild(document.createTextNode(scriptText));
                contentNode.appendChild(scriptNode);
        });

        // add the inline script
        $(inlinescript).each(function(){
            var scriptText = this.replace(/<script[^]*?>/,'').replace(/<\/script>/,''), 
            scriptNode = document.createElement('script');
            scriptNode.appendChild(document.createTextNode(scriptText));
            contentNode.appendChild(scriptNode);
        });

        ...
    } //end of ajax success function

:)

This a great script! Love it!

I did run into an issue when implemented with the new Google Tag Manager (GTM) with Google Analytics. If using GTM the _gaq.push event still fires but there is no link to an account id. You will see the event fire but it's posting to UA-XXXXX-XX. To quickly work around this, in GTM, I created a rule based on {event} that equals a value of 'pageView'. Then in the ajaxify-html5.js script I changed the following starting at 172:

                // Inform Google Analytics of the change
                if ( typeof window._gaq !== 'undefined' ) {
                    window._gaq.push(['_trackPageview', relativeUrl]);
                }

Change to:

                if ( typeof window._gaq !== 'undefined' ) {
                    dataLayer.push({'event':'pageView'});
                } 

Pageviews are executing normally now. For more info on Google Tag Manager -- check out http://support.google.com/tagmanager/

Great script! I have a problem with my home page though that is loading in the container if i hit the back button. What i want is every page except my home page to load inside the container. Is this possible to exclude a page from loading in the container div?

Is it possible to exclude specific strings like wp-admin.php, wp-login.php, index.php etc? How can i add a function that when i visit the home page (including with browser's back button) will just hide the $content div?

Help anyone i desperately need a solution on this. Please!

should note that this script throws an error if using jquery v1.9.0(latest version)

Error under jquery 1.9.0 is on $data = $(documentHtml(data)), and, after failing to figure out the selector, ends up here:
Sizzle.error = function( msg ) {
throw new Error( "Syntax error, unrecognized expression: " + msg );
};

In the jQuery 1.9 change log there is this: http://jquery.com/upgrade-guide/1.9/#jquery-htmlstring-versus-jquery-selectorstring

In my case, the response returned from documentHtml(data) doesn't start with a < (in my case it is a conditional for modernizr) is rejected by the new jquery selector parser.

changing the var documentHtml = function(html) to return $.parseHtml seems to work for me, though I haven't explored any follow on effects or issues.

// Return
return $.parseHTML(result);

You can also use Davis.js to Ajaxify/hijax you webpage:
see: http://78.47.126.11:3000

As far as I can see; the script makes an ajax call for the browser back-button and previously clicked links. Can you update this gist to show how to ensure that saved states/pages are retrieved from history instead of making a new ajax request?

The fix from genereddick works but ScrollTo is still broken with new versions of jQuery, since $.browser has been deprecated. I've tried including the old code for $.browser but it only seems to be working in Firefox.

Why already loaded javascript is not applied to new content in content div?

Zeokat you saved my day! thanks. Nice piece of code.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.