Skip to content
This repository

pluggable history junk #5

Closed
visionmedia opened this Issue · 35 comments

15 participants

TJ Holowaychuk weepy Tom Nightingale Wilson Page Massimiliano Mirra Ross Hadden Thomas Blobaum Johannes Ewald Christian Tellnes Adrian Lanning Ian Storm Taylor Tane Piper andi-neck Jonathan Ong Shuvalov Anton
TJ Holowaychuk
Owner

for old shitty browsers. I dont want any of that in this lib itself but we should facilitate it

weepy
weepy commented

+2

Tom Nightingale

If this means supporting https://github.com/balupton/history.js or similar, then +3 :)

Wilson Page

+1

Massimiliano Mirra
bard commented

+1 Only reason stopping me from migrating from pagejs at this point...

TJ Holowaychuk
Owner

are there any straight-up pushState polyfills out there? then that would be really easy haha

Tom Nightingale

Yeah, I looked into dropping history.js in there but the "pushState and replaceState will trigger onpopstate" feature (https://github.com/balupton/history.js/wiki/The-State-of-the-HTML5-History-API) makes things a little difficult.

Wilson Page

Does this fork of History.js fix the popstate issues? https://github.com/balupton/history.js/pull/137

TJ Holowaychuk
Owner

it would be nice if it was just a polyfill so you could plunk it in with page.js and "just work"

Ross Hadden

If page.js supported listening to hash changes then we could solve this without needing a polyfill.

page('/#something'); does change location.hash, but page('/#something', function(){}); does not (seem to) work to bind to such a path.

I think if page.js supported this then it would be up to the developers themselves to implement fallbacks. For instance the following would be an ideal way to handle a fallback for me (Note: This functionality does not currently exist):

var load = function(){};

page('/:page', load);
page('/#:page', load);

With this (which is again left up to the developer) you can leverage pushstate when supported and fallback to hash changes when they are not, using Modernizr or something to figure that out.

Thoughts? If there is positive feedback I will attempt to implement it.

TJ Holowaychuk
Owner

I'd like to avoid bloat for legacy solutions, the only other alternative really if there's no drop-in polyfill is to expose a little bit more api to make it pluggable vs using push/popstate directly

Ross Hadden

I know what you mean. To my knowledge no such polyfill exists. History.js takes on way too much to do what we want it to here.

Which parts of the internal API are you thinking would give us more slack here?

TJ Holowaychuk
Owner

it would be nice if we had a little thing like page.use(page.pushstate) as the default, and then people can plug in their own page.use(hashstuff) etc

Ross Hadden

+1. Very expressive (see what I did there?).

TJ Holowaychuk
Owner

ahahaha

weepy
weepy commented

+1 - that would be clever ^_^

Thomas Blobaum

It would be really useful if i all i had to do to support hashRouting was write links like <a href='#/dashboard'>dashboard</a> and then call page.use(page.hashRoutes) and still use the api for defining and navigating to different pages exactly the same as it is now.

page('/dashboard', function () { /* ... */ })
page('/dashboard')

In the case where the history api is supported a link like <a href='#/dashboard'></a> would just strip the hash and navigate to /dashboard, and calls to page('/dashboard') would be unmodified.

And, in the case that the history api is not supported (and page.use(page.hashRoutes) has been called) the url would be overwritten to #/dashboard for page('/dashboard'), and not modified for <a href='#/dashboard'></a>

Ross Hadden

In my opinion page() should just be able to accept routes with # in them, like in my example above. This would mean developers would have to worry about how they implement hash fallbacks, and thus it wouldn't add any overhead to developers who don't use such routes (which page.use(page.hashwhatever) above solves too).

The reason I think this is a good idea is because it's not just for fallbacks. You could have some links utilize pushstate and some utilize hashes, because they really can serve different purposes. For instance Wikipedia utilizes ID's/hashes all over the place which makes the table of contents useful. Such a situation is a great use of hashes, where pushstate might not make sense. Yet as far as the hash fallback discussion goes, this would allow us to handle hashes in the same way we handle everything else. At that point it would be trivial to check if the browser supports pushstate and modify link href's accordingly.

Discuss.

Thomas Blobaum

This would mean developers would have to worry about how they implement hash fallbacks

Given your suggestion, what would be the most simple way to handle hashRoute fallbacks?

You could have some links utilize pushstate and some utilize hashes, because they really can serve different purposes.

This is probably the best case to be made for not having a transparent fallback option -- but what websites have you actually seen that make good use of both pushState and hash routes at the same time? Wikipedia makes use of hashes to navigate to id attributes which is a technique as old as html and not the same as integrating hash routes with js (http://www.w3.org/TR/1998/REC-html40-19980424/struct/links.html#h-12.2.3)

Ross Hadden

I guess what I am getting at is I do not want the fallback to mandate how I write my link paths. If page.use(page.hashIsTheCoolestThingEver) could transparently translate links pointing to "/something/cool" to "#/something/cool" based on the browser's presence of window.history.pushState, that would be extremely ideal. I would consider that the best route to take (pun).

Thomas Blobaum

@rosshadden I do feel the same way, but having page.js rewrite links in the dom is just not going to be reliable, or remotely possible. The only alternative is to specify hrefs with #hashes so that the browsers without the history api dont take any action. If you dont want to bother with a hashRoute fallback, then you can choose not to use links like that (which should have the slashes after the hash btw, oops). None of the proposed changes would have any effect on the existing usage of page.js, it would just shim the hash routing to function with the page.js api.

If we are wanting a hash routes fallback that works with a elements in the dom then it seems like that is the simplest of working solutions

Johannes Ewald
jhnns commented

+1 I'm also looking forward to use page.js together with history.js.

You could introduce a page.history property and than use this object instead of accessing it globally. When page.start() is being called and page.history is undefined, than you could fallback to the browser's history.

Something like

page.history = History; // page.js is now using history.js
page.start();
Ross Hadden

History.js is not just a polyfill. Unless page.js were utilizing it all throughout the core code it would not make sense to include or reference it just for this purpose. Doing so would significantly bloat page.js for the worse.

TJ Holowaychuk
Owner

yeah I really dont want history.js in there

Johannes Ewald
jhnns commented

Ok. Basically I agree on your decision to exclude legacy code from page.js. But if I'm not able to use page.js together with a library that takes care of legacy browsers than page.js is imho not ready for production.

TJ Holowaychuk
Owner

that's why we need to make it extensible, but definitely not clutter up what we have with legacy code

Christian Tellnes

I wrote some code a while back that switches between push state and iframe / hash history. Last time I tested it it works in IE6+. Maybe something can be used from there?

https://github.com/tellnes/HashHistory/blob/master/HashHistory.js

Johannes Ewald
jhnns commented

Maybe I got it wrong but I think page.js would be ready to use with history.js if page.js wouldn't access the history-object globally. I'm thinking of using it this way:

page.history = History;
page({
    popstate: false // disable popstate handler since we're taking care of this
});

// taken from the readme of history.js
History.Adapter.bind(window, 'statechange', function () {
    var state = History.getState();

    // taken from https://github.com/visionmedia/page.js/blob/master/lib/page.js#L339
    if (state) {
      var path = state.path;
      page.replace(path, state);
    }
});

Please correct me if I'm wrong. But as long as page.js is accessing the history-object globally I can't do anything about it.

Changing page.js to make this work won't bloat it. It would just require 2 additional lines of code :)

TJ Holowaychuk
Owner

yup we just need to add an API for adapters

Adrian Lanning

History.js + monkey-patching lets page.js work in IE9.

Maybe post this on the front-page to help those interested in IE9 support?

  if (History && 
      (!window.history || !window.history.replaceState)) {

    // Monkey-patch page.js with HTML4 history polyfill

    page.Context.prototype.pushState = function () {
      History.pushState(this.state, this.title, this.canonicalPath);
    };

    page.Context.prototype.save = function () {
      History.replaceState(this.state, this.title, this.canonicalPath);
    };

  }
Johannes Ewald

thx for sharing :+1:

Ian Storm Taylor

+1 for allowing routes with hashes in them. Not even going for legacy browsers here, I just don't want to have to manage a server with a '*'. For a couple really simple projects (like http://socrates.io) I'd rather just serve everything from index.html.

I'd like to just do page('/#/:id', ... or page('/#/logout', ....

Edit: That being said, I understand it's probably a pretty small use case?

Tane Piper

Any movement on this @visionmedia ? I for one see a use in the current project I have, that is the app is a single page app being served up as static content that has two routes in it (/professional and /academic) which then have further steps in them.

The issue is that I have:

page('/:route', Router.welcome);

But of course, actually going to http://foo.com/academic - the page doesn't actually exist (because it's technically http://foo.com/index.html).

I'd like to be able to link directly to http://foo.com/#academic and have it detect this and page.js to kick in and fall back to sending the user to http://foo.com/academic and then continue from there. It would allow the further steps to be deep linked too (http://foo.com/#academic/step-2).

The only way I see getting around this would be to have two copies of the same app in subfolders, or set up some redirect at the server level (which I have no control over as this is a client project).

The use case is allows for truly static-serves page applications that can handle the logic internally without having to rely on anything on the server side doing any redirects.

Ian Storm Taylor

@tanepiper Realistically you don't want to be redirecting them to the URL without the hash, because then you're breaking all of the URLs in the location bar, so no one can copy-paste things. (Unless you run a server.) You'd want to make hashes the only way your app does URLs.

andi-neck

devote/HTML5-History-API seems to be a decent pushState polyfill.

Load history.js before page.js.

You can use history.pushStatein the code as expected. It falls back to changing the hash of the url if pushState is not supported in the browser: page("/new/path") leads to http://localhost:3000/old/path?old=query#/new/path.

I tested it in firefox 3.5 which does not have pushState.

Jonathan Ong
Collaborator

gonna support the History polyfill here: #109

if you guys ahve any other suggestions for more browser support let me know, but generally i don't want to include it in this lib unless it's some sort of "adapter"

@ianstormtaylor i think a way to do that would be to specifically route with #s. i.e. page('/#/page/:id'). i'm not sure if that's totally possible though with path-to-regexp, but i think that's a valid use case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.