9

I want to intercept all route changes with Sammy to first check if there is a pending action. I have done this using the sammy.before API and I return false to cancel the route. This keeps the user on the 'page' but it still changes the hash in the browsers address bar and adds the route to the browsers' history. If I cancel the route, I dont want it in the address bar nor history, but instead I expect the address to stay the same.

Currently, to get around this I can either call window.history.back (yuk) to go back to the original spot in the history or sammy.redirect. Both of which are less than ideal.

Is there a way to make sammy truly cancel the route so it stays on the current route/page, leaves the address bar as is, and does not add to the history?

If not, is there another routing library that will do this?

sammy.before(/.*/, function () {
    // Can cancel the route if this returns false
    var response = routeMediator.canLeave();

if (!isRedirecting && !response.val) {
    isRedirecting = true;
    // Keep hash url the same in address bar
    window.history.back();
    //this.redirect('#/SpecificPreviousPage'); 
}
else {
    isRedirecting = false;
}
return response.val;
});
John Papa
  • 21,880
  • 4
  • 61
  • 60

4 Answers4

17

In case someone else hits this, here is where I ended up. I decided to use the context.setLocation feature of sammy to handle resetting the route.

sammy.before(/.*/, function () {
    // Can cancel the route if this returns false
    var
        context = this,
        response = routeMediator.canLeave();

    if (!isRedirecting && !response.val) {
        isRedirecting = true;
        toastr.warning(response.message); // toastr displays the message
        // Keep hash url the same in address bar
        context.app.setLocation(currentHash);
    }
    else {
        isRedirecting = false;
        currentHash = context.app.getLocation();
    }
    return response.val;
});
Hakan Fıstık
  • 16,800
  • 14
  • 110
  • 131
John Papa
  • 21,880
  • 4
  • 61
  • 60
  • Hahaha John, I got here by searching more information about how you achieved this in your SPA series :-) – Luc Morin Oct 02 '12 at 01:27
1

When using the code provided within the question and answer you have to notice that the route you cancelled will also be blocked for all future calls, routeMediator.canLeave will not be evaluated again. Calling a route twice and cancelling it depending on current state is not possible with this.

  • 1
    Hmmm. I'm curious why you say that. I've used this code and its not blocking future calls to canLeave. Can you elaborate? If I have a bug I would like to understand how to fix it. – John Papa Dec 18 '12 at 17:14
  • Sammy documentation for before says: "If any of the callbacks explicitly return false, execution of any further callbacks and the route itself is halted." - this is what I observed with the code above. This only happens when accessing the blocked route consecutively twice. When accessing an other route between everything works fine. – Björn Peter Dec 19 '12 at 08:24
  • 2
    The route is halted, yes. But it does not say future calls to the route are prevented. I'm not seeing this behavior, I can stop it from leaving, and then it still calls it next time. – John Papa Dec 19 '12 at 14:48
0

I could produce the same results as John Papa did when he used SammyJS on the SPA/Knockout course.

I used Crossroads JS as the router, which relies on Hasher JS to listen to URL changes "emitted" by the browser.

Code sample is:

hasher.changed.add(function(hash, oldHash) {
    if (pageViewModel.isDirty()){
        console.log('trying to leave from ' + oldHash + ' to ' + hash);

        hasher.changed.active = false;
        hasher.setHash(oldHash);
        hasher.changed.active = true;

        alert('cannot leave. dirty.');
    }
    else {
        crossroads.parse(hash);
        console.log('hash changed from ' + oldHash + ' to ' + hash);
        }
});
Renato Xavier
  • 123
  • 1
  • 9
0

After revisiting an older project and having a similar situation, I wanted to share another approach, just in case someone else is directed here.

What was needed was essentially a modern "auth guard" pattern for intercepting pages and redirecting based on credentials.

What worked well was using Sammy.around(callback) as defined here: Sammy.js docs: Sammy.Application around(callback)

Then, simply do the following...

(function ($) {
    var app = Sammy("body");

    app.around(checkLoggedIn);

    function canAccess(hash) {
        /* access logic goes here */
        return true;
    }

    // Authentication Guard
    function authGuard(callback) {
        var context = this;
        var currentHash = app.getLocation();
        if (!canAccess(currentHash)) {
            // redirect
            context.redirect("#/login");
        }
        else {
            // execute the route path
            callback();
        }
    };

})(jQuery);