All-in-one location/hashchange history management library

15,233

Solution 1

I believe Sammy.js ( http://sammyjs.org) (MIT-licenced) has the best focus on what you want to do, with its 2 main pillars being:

  1. Routes
  2. Events

I could quote from the docs but it's pretty straightforward:

  • setup clientside routes that relate to stuff to be done, e.g: update the view through ajax

  • link events to call routes, e.g: call the route above when I click an link. (You would have to make sure e.preventDefault is called in the defined event I believe, since this is an app decision really, so that can't be abstracted away by any library that you're going to use imho)

Some relevant docs

Example for a route: (from http://sammyjs.org/docs/tutorials/json_store_1)

 this.get('#/', function(context) {
    $.ajax({
      url: 'data/items.json',
      dataType: 'json',
      success: function(items) {
        $.each(items, function(i, item) {
          context.log(item.title, '-', item.artist);
        });
      }
    });
  });

Or something like

 this.get('#/', function(context) {
     context.app.swap(''); ///the 'swap' here indicates a cleaning of the view
                              //before partials are loaded, effectively rerendering the entire screen. NOt doing the swap enables you to do infinite-scrolling / appending style, etc. 
     // ...
   });

Of course other clientside MVC-frameworks could be an option too, which take away even more plumbing, but might be overkill in this situation.

a pretty good (and still fairly recent) comparison:

http://codebrief.com/2012/01/the-top-10-javascript-mvc-frameworks-reviewed/ ( I use Spine.js myself ) .

Lastly, I thought it might be useful to include an answer I've written a while ago that goes into detail to the whole best-practice (as I see it) in client-side refreshes, etc. Perhaps you find it useful:

Accessibility and all these JavaScript frameworks

Solution 2

I currently use PathJS in one of my applications. It has been the best decision that i have made. For your particular usecase take a look at HTML5 Example.

The piece of code that that makes the example work (from the source):

<script type="text/javascript">
        // This example makes use of the jQuery library.

        // You can use any methods as actions in PathJS.  You can define them as I do below,
        // assign them to variables, or use anonymous functions.  The choice is yours.
        function notFound(){
            $("#output .content").html("404 Not Found");
            $("#output .content").addClass("error");
        }

        function setPageBackground(){
            $("#output .content").removeClass("error");
        }        

        // Here we define our routes.  You'll notice that I only define three routes, even
        // though there are four links.  Each route has an action assigned to it (via the 
        // `to` method, as well as an `enter` method.  The `enter` method is called before
        // the route is performed, which allows you to do any setup you need (changes classes,
        // performing AJAX calls, adding animations, etc.
        Path.map("/users").to(function(){
            $("#output .content").html("Users");
        }).enter(setPageBackground);

       Path.map("/about").to(function(){
            $("#output .content").html("About");
        }).enter(setPageBackground);

       Path.map("/contact").to(function(){
            $("#output .content").html("Contact");
        }).enter(setPageBackground);

        // The `Path.rescue()` method takes a function as an argument, and will be called when
        // a route is activated that you have not yet defined an action for.  On this example
        // page, you'll notice there is no defined route for the "Unicorns!?" link.  Since no
        // route is defined, it calls this method instead.
        Path.rescue(notFound);

        $(document).ready(function(){
            // This line is used to start the HTML5 PathJS listener.  This will modify the
            // `window.onpopstate` method accordingly, check that HTML5 is supported, and
            // fall back to hashtags if you tell it to.  Calling it with no arguments will
            // cause it to do nothing if HTML5 is not supported
            Path.history.listen();

            // If you would like it to gracefully fallback to Hashtags in the event that HTML5
            // isn't supported, just pass `true` into the method.

            // Path.history.listen(true);

            $("a").click(function(event){
                event.preventDefault();

                // To make use of the HTML5 History API, you need to tell your click events to
                // add to the history stack by calling the `Path.history.pushState` method. This
                // method is analogous to the regular `window.history.pushState` method, but
                // wraps calls to it around the PathJS dispatched.  Conveniently, you'll still have
                // access to any state data you assign to it as if you had manually set it via
                // the standard methods.
                Path.history.pushState({}, "", $(this).attr("href"));
            });
        });
    </script>

PathJS has some of the most wanted features of a routing library:

  • Lightweight
  • Supports the HTML5 History API, the 'onhashchange' method, and graceful degredation
  • Supports root routes, rescue methods, paramaterized routes, optional route components (dynamic routes), and Aspect Oriented Programming
  • Well Tested (tests available in the ./tests directory)
  • Compatible with all major browsers (Tested on Firefox 3.6, Firefox 4.0, Firefox 5.0, Chrome 9, Opera 11, IE7, IE8, IE9)
  • Independant of all third party libraries, but plays nice with all of them

I found the last too points most attractive. You can find them here

I hope you find this useful.

Solution 3

i'd like to suggest a combination of

crossroads.js as a router http://millermedeiros.github.com/crossroads.js/

and hasher for handling browser history and hash urls (w/ plenty of fallback solutions): https://github.com/millermedeiros/hasher/ (based on http://millermedeiros.github.com/js-signals/)

This will still require a few lines of code (to load ajax content etc.), but give you loads and loads of other possibilities when handling a route.

Here's an example using jQuery (none of the above libraries require jQuery, i'm just lazy...)

http://fiddle.jshell.net/Fe5Kz/2/show/light

HTML

<ul id="menu">
    <li>
        <a href="foo">foo</a>            
    </li>
    <li>
        <a href="bar/baz">bar/baz</a>
    </li>
</ul>

<div id="content"></div>

JS

//register routes
crossroads.addRoute('foo', function() {
    $('#content').html('this could be ajax loaded content or whatever');
});

crossroads.addRoute('bar/{baz}', function(baz) {

    //maybe do something with the parameter ...
    //$('#content').load('ajax_url?baz='+baz, function(){
    //    $('#content').html('bar route called with parameter ' + baz);
    //});

    $('#content').html('bar route called with parameter ' + baz);
});


//setup hash handling
function parseHash(newHash, oldHash) {
    crossroads.parse(newHash);
}
hasher.initialized.add(parseHash);
hasher.changed.add(parseHash);
hasher.init();


//add click listener to menu items
$('#menu li a').on('click', function(e) {
    e.preventDefault();
    $('#menu a').removeClass('active');
    $(this).addClass('active');

    hasher.setHash($(this).attr('href'));
});​

Solution 4

Have you looked at the BigShelf sample SPA (Single Page Application) from Microsoft? It sounds like it covers how to achieve most of what you're asking.

It makes use of History.js, a custom wrapper object to easily control navigation called NavHistory and Knockout.js for click handling.

Here's an extremely abbreviated workflow of how this works: first you'll need to initialize a NavHistory object which wraps history.js and registers a callback which executes when there is a push state or hash change:

var nav = new NavHistory({
    params: { page: 1, filter: "all", ... etc ... },
    onNavigate: function (navEntry) {
        // Respond to the incoming sort/page/filter parameters
        // by updating booksDataSource and re-querying the server
    }
});

Next, you'll define one or more Knockout.js view models with commands that can be bound to links buttons, etc:

var ViewModel = function (nav) {
  this.search = function () {
    nav.navigate({ page: 2, filter: '', ... }); // JSON object matching the NavHistory params
  };
}

Finally, in your markup, you'll use Knockout.js to bind your commands to various elements:

<a data-bind="click: search">...</a>

The linked resources are much more detailed in explaining how all of this works. Unfortunately, it's not a single framework like you're seeking, but you'd be surprised how easy it is to get this working.

One more thing, following the BigShelf example, the site I'm building is fully cross-browser compatible, IE6+, Firefox, Safari (mobile and desktop) and Chrome (mobile and desktop).

Solution 5

The AjaxTCR Library seems to cover all bases and contains robust methods that I haven't seen before. It's released under a BSD License (Open Source Initiative).

For example, here are five AjaxTCR.history(); methods:

init(onStateChangeCallback, initState);

addToHistory(id, data, title, url, options);

getAll();

getPosition();

enableBackGuard(message, immediate);

The above addToHistory(); has enough parameters to allow for deep hash-linking in websites.

More eye-candy of .com.cookie(), .storage(), and .template() provides more than enough methods to handle any session data requirements.

The well documented AjaxTCR API webpage has a plethora of information with downloadable doc's to boot!

Status Update:
That website also has an Examples Webpage Section including downloadable .zip files with ready to use Front End(Client) and Back End(Server) project files.

Notably are the following ready-to-use examples:
One-way Cookie
HttpOnly Cookies
History Stealing
History Explorer

There are quite a bit other examples that rounds out the process to use many of their API methods, making any small learning curve faster to complete.

Share:
15,233
Ansel Halliburton
Author by

Ansel Halliburton

Updated on June 06, 2022

Comments

  • Ansel Halliburton
    Ansel Halliburton about 2 years

    First of all, I know there's libraries that provide polyfills for location.pushState/popState (History.js, Hash.js, jQuery hashchange), so please don't just link to those.

    I need a more powerful library to achieve the following in a RIA:

    1. User clicks a link
    2. library is notified and loads context via Ajax (no complete reload!)
    3. All <a> elements are leveraged with a click handler that
      • prevents page reloads in 2. (preventDefault) and
      • calls location.pushState instead / sets location.hash for older browsers
    4. loaded content is inserted in page and replaces current content
    5. Continue with 1.

    Also, previously loaded content should be restored as the user navigates back.

    As an example, klick through Google+ in Internet Explorer <10 and any other browser.

    Is there anything that comes even close? I need support for IE8, FF10, Safari 5 and Chrome 18. Also, it should have a permissive license like MIT or Apache.

  • Dmitry Pashkevich
    Dmitry Pashkevich almost 12 years
    While ExtJS is a great framework, it's 1) an overkill if used just for the issue in question; 2) doesn't yet implement the new History API; 3) Quite primitive, no route framework + quirks like the one you referred (about redundant calls)
  • arttronics
    arttronics almost 12 years
    I've +1 to compensate for whomever did that. Now, the Upvotes are fair. Having said that, it looks like you'll get 1/2 the Bounty for the most Upvotes since the OP failed to manually award the full +500. Cheers!
  • Sujay
    Sujay over 11 years
    Please comment to help me understand why you gave me a downvote
  • Nicolas Le Thierry d'Ennequin
    Nicolas Le Thierry d'Ennequin about 11 years
    I've just finished a project using pathjs and I'm very happy with it. Very easy to use (and make it work with Google Analytics). This is a minimalist library but it would deserved a better documentation.
  • Richard Nalezynski
    Richard Nalezynski over 7 years