olivernn / davis.js

RESTful degradable JavaScript routing using pushState
http://davisjs.com
532 stars 58 forks source link

Route is triggered when location.hash is changed. #56

Closed chrishoage closed 12 years ago

chrishoage commented 12 years ago

I am using location.hash to keep track of where in a list a user is on the page.

The issue I am having is when I update location.hash it triggers the same route again.

Is this expected behavior? If so, is there a way to keep the route from being triggered if location.hash changes?

olivernn commented 12 years ago

That does sound like a bug to me. The hashchange event, which the hash routing uses, only gets fired when the value of location.hash changes. It would only then fire the same route if that route also matched the new value of location.hash.

Could you provide a small example that demonstrates the problem, also what version of Davis are you using and which browsers you are seeing the issue in.

chrishoage commented 12 years ago

I am not using hash routing so I figured this would not have been an issue.

Here is a JSFiddle of the issue: http://jsfiddle.net/UkRUE/1/

This happens both in chrome and in firefox using the latest Davis.js. Clicking run route works as expected, running the route every time it is clicked. If you change the hash before you run the route there is no issue, but once you click run route, it runs the route again every time you update the hash.

chrishoage commented 12 years ago

It looks like changing the hash updates the history stack which in turn fires a pop state http://stackoverflow.com/questions/9948510/hashchange-firing-popstate

How would I go about keeping the route from being re-ran if the only the hash changes?

olivernn commented 12 years ago

I see what you mean now.

After some investigation I'm not sure this is something the library should handle, it is also quite difficult to handle correctly automatically.

Changing the location.hash triggers a popstate event, this event has no state data associated with it, this makes it look like an initial page load popstate event. Because of this Davis is generating a request for whatever the current path is, hence you are getting the existing route re-ran.

I find it weird that the popstate event fires, this event doesn't fire when changing the history stack with pushState or replaceState. Also the popstate event fires before the hashchange event, so it is difficult to know reliably whether the popstate event is a genuine one or one triggered by the location hash changing.

As a work around you could have something that halted the request if it looks like it is being caused by the hash changing, perhaps something like this:

var hashWorkAround = function () {
  var hash = location.hash

  this.before(function (req) {
    return hash === location.hash
  })

  this.after(function (req) {
    hash = location.hash
  })
}

You can then use this in your app like so:

var app = Davis(function () {
  this.use(hashWorkAround)

  // your routes and configuration here
})

So this just checks whether the hash has changed between this and the last request. If it has it assumes it has been done by manually setting the location.hash property and so ignores it. This might not work if you have some links with hashes in them, but hopefully gives you something to work with.

chrishoage commented 12 years ago

I agree, after seeing that location.hash fires popState it seems the library is behaving exactly as it should. The workaround you posted solves my problem.

I'm gonna close this issue, thank you for your help!