collect.js - use clean urls with Backbone History / Router
clickify.js intercepts all internal links and passes them to Backbone History/Router
<!-- jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<!-- backbone.js -->
<script src="/path/to/backbone.js"></script>
<!-- clickify.js -->
<script src="https://raw.github.com/gist/3014727/clickify.js"></script>
Hashbang'd links <a href='http://<site>/#!/whatever'>8====></a> suck. They're ugly, bad for search engines and worse, you end up with a real URL and a hashbang'd URL that both reference the same content on the server.
clickify.js simplifies your single-page web application by allowing non-hash'd URLs with Backbone Router. Use normal links throughout your application, clickify.js will intercept them for use in your single-page app.
This leads to a seamless single-page app experience for users with modern browsers, and standard links for those without (and bots). Best of all, you don't have two links for each piece of content (good for bookmarks, sharing, pagerank, etc)
For nodejs/expressjs users, clickify.js allows you to match your backbone and express routes. Perfect if you plan on rendering the initial page request server-side and all subsequent requests via ajax.
clickify.js creates an event handler on all internal (relative url or same root-url) links. Apply it to any jQuery element, or the entire document body. Remember to apply clickify to any new elements that get inserted to the DOM.
Add class="no-click" to any anchor tags clickify should skip.
$(function() { // DOM ready
// Create a router to handle our URL changes
var SimpleRouter = Backbone.Router.extend({
routes: {
'posts/:id': 'post',
'*notFound': 'notFound' // catch-all for undefined routes
},
post: function(id) {
...
},
notFound: function() {
...
}
});
new SimpleRouter;
Backbone.history.start({ pushState: true }); // Start tracking history
$(document.body).clickify(); // Add click handlers to all internal links
});
(function($){
// Declare the rootUrl used for filtering internal links.
var rootUrl = document.location.protocol + '//' + (document.location.hostname || document.location.host) + (document.location.port ? ':' + document.location.port : '') + '/';
// Helper functions
var getFragment = function(url, root) { // Grab the fragment and format it how Backbone expects
var fragment = url;
if (fragment.indexOf(':') !== -1)
fragment = fragment.replace(/.*:\/\/[^\/]+/, '');
if (!fragment.indexOf(root)) fragment = fragment.substr(root.length);
return fragment.replace(/^[#\/]/, '');
}
// jQuery selector for tagging internal links
$.expr[':'].internal = function(obj, index, meta, stack) {
var url = $(obj).attr('href') || '';
return (url.substring(0, rootUrl.length) === rootUrl || url.indexOf(':') === -1); // same domain || relative link
};
$.fn.clickify = function(options) {
options = $.extend({ 'root': '/' }, options); // Set the root option if your single-page app isn't in the site root
var anchorTags = this.find('a:internal:not(.no-click)');
anchorTags.click(function (event) {
var $this = $(this),
url = $this.attr('href');
if (event.which == 2 || event.metaKey)
return true;
// Make the call to Backbone.History
// Backbone will call pushState, update the hash or change window.location depending on what our browser supports
// Regardless, the part you should care about is that your routes are being called.
Backbone.history.navigate(getFragment(url), { trigger: true });
event.preventDefault();
return false;
});
anchorTags.addClass('no-click'); // Mark tags we've already added our event handler to
return this; // chainability
};
})(jQuery);