Giter Club home page Giter Club logo

page.js's Introduction

page router logo

Tiny Express-inspired client-side router.

Build Status Coverage Status Gitter

page('/', index)
page('/user/:user', show)
page('/user/:user/edit', edit)
page('/user/:user/album', album)
page('/user/:user/album/sort', sort)
page('*', notfound)
page()

Installation

There are multiple ways to install page.js. With package managers:

$ npm install page # for browserify
$ component install visionmedia/page.js
$ bower install visionmedia/page.js

Or use with a CDN. We support:

Using with global script tags:

<script src="https://unpkg.com/page/page.js"></script>
<script>
  page('/about', function(){
    // Do stuff
  });
</script>

Or with modules, in modern browsers:

<script type="module">
  import page from "//unpkg.com/page/page.mjs";

  page('/home', () => { ... });
</script>

Running examples

To run examples do the following to install dev dependencies and run the example server:

$ git clone git://github.com/visionmedia/page.js
$ cd page.js
$ npm install
$ node examples
$ open http://localhost:4000

Currently we have examples for:

  • basic minimal application showing basic routing
  • notfound similar to basic with single-page 404 support
  • album showing pagination and external links
  • profile simple user profiles
  • query-string shows how you can integrate plugins using the router
  • state illustrates how the history state may be used to cache data
  • server illustrates how to use the dispatch option to server initial content
  • chrome Google Chrome style administration interface
  • transitions Shows off a simple technique for adding transitions between "pages"
  • partials using hogan.js to render mustache partials client side

NOTE: keep in mind these examples do not use jQuery or similar, so portions of the examples may be relatively verbose, though they're not directly related to page.js in any way.

API

page(path, callback[, callback ...])

Defines a route mapping path to the given callback(s). Each callback is invoked with two arguments, context and next. Much like Express invoking next will call the next registered callback with the given path.

page('/', user.list)
page('/user/:id', user.load, user.show)
page('/user/:id/edit', user.load, user.edit)
page('*', notfound)

Under certain conditions, links will be disregarded and will not be dispatched, such as:

  • Links that are not of the same origin
  • Links with the download attribute
  • Links with the target attribute
  • Links with the rel="external" attribute

page(callback)

This is equivalent to page('*', callback) for generic "middleware".

page(path)

Navigate to the given path.

$('.view').click(function(e){
  page('/user/12')
  e.preventDefault()
})

page(fromPath, toPath)

Setup redirect from one path to another.

page.redirect(fromPath, toPath)

Identical to page(fromPath, toPath)

page.redirect(path)

Calling page.redirect with only a string as the first parameter redirects to another route. Waits for the current route to push state and after replaces it with the new one leaving the browser history clean.

page('/default', function(){
  // some logic to decide which route to redirect to
  if(admin) {
    page.redirect('/admin');
  } else {
    page.redirect('/guest');
  }
});

page('/default');

page.show(path)

Identical to page(path) above.

page([options])

Register page's popstate / click bindings. If you're doing selective binding you'll like want to pass { click: false } to specify this yourself. The following options are available:

  • click bind to click events [true]
  • popstate bind to popstate [true]
  • dispatch perform initial dispatch [true]
  • hashbang add #! before urls [false]
  • decodeURLComponents remove URL encoding from path components (query string, pathname, hash) [true]
  • window provide a window to control (by default it will control the main window)

If you wish to load serve initial content from the server you likely will want to set dispatch to false.

page.start([options])

Identical to page([options]) above.

page.stop()

Unbind both the popstate and click handlers.

page.base([path])

Get or set the base path. For example if page.js is operating within /blog/* set the base path to "/blog".

page.strict([enable])

Get or set the strict path matching mode to enable. If enabled /blog will not match "/blog/" and /blog/ will not match "/blog".

page.exit(path, callback[, callback ...])

Defines an exit route mapping path to the given callback(s).

Exit routes are called when a page changes, using the context from the previous change. For example:

page('/sidebar', function(ctx, next) {
  sidebar.open = true
  next()
})

page.exit('/sidebar', function(ctx, next) {
  sidebar.open = false
  next()
})

page.exit(callback)

Equivalent to page.exit('*', callback).

page.create([options])

Create a new page instance with the given options. Options provided are the same as provided in page([options]) above. Use this if you need to control multiple windows (like iframes or popups) in addition to the main window.

var otherPage = page.create({ window: iframe.contentWindow });
otherPage('/', main);

page.clickHandler

This is the click handler used by page to handle routing when a user clicks an anchor like <a href="/user/profile">. This is exported for those who want to disable the click handling behavior with page.start({ click: false }), but still might want to dispatch based on the click handler's logic in some scenarios.

Context

Routes are passed Context objects, these may be used to share state, for example ctx.user =, as well as the history "state" ctx.state that the pushState API provides.

Context#save()

Saves the context using replaceState(). For example this is useful for caching HTML or other resources that were loaded for when a user presses "back".

Context#handled

If true, marks the context as handled to prevent default 404 behaviour. For example this is useful for the routes with interminate quantity of the callbacks.

Context#canonicalPath

Pathname including the "base" (if any) and query string "/admin/login?foo=bar".

Context#path

Pathname and query string "/login?foo=bar".

Context#querystring

Query string void of leading ? such as "foo=bar", defaults to "".

Context#pathname

The pathname void of query string "/login".

Context#state

The pushState state object.

Context#title

The pushState title.

Routing

The router uses the same string-to-regexp conversion that Express does, so things like ":id", ":id?", and "*" work as you might expect.

Another aspect that is much like Express is the ability to pass multiple callbacks. You can use this to your advantage to flatten nested callbacks, or simply to abstract components.

Separating concerns

For example suppose you have a route to edit users, and a route to view users. In both cases you need to load the user. One way to achieve this is with several callbacks as shown here:

page('/user/:user', load, show)
page('/user/:user/edit', load, edit)

Using the * character we can alter this to match all routes prefixed with "/user" to achieve the same result:

page('/user/*', load)
page('/user/:user', show)
page('/user/:user/edit', edit)

Likewise * can be used as catch-alls after all routes acting as a 404 handler, before all routes, in-between and so on. For example:

page('/user/:user', load, show)
page('*', function(){
  $('body').text('Not found!')
})

Default 404 behaviour

By default when a route is not matched, page.js invokes page.stop() to unbind itself, and proceed with redirecting to the location requested. This means you may use page.js with a multi-page application without explicitly binding to certain links.

Working with parameters and contexts

Much like request and response objects are passed around in Express, page.js has a single "Context" object. Using the previous examples of load and show for a user, we can assign arbitrary properties to ctx to maintain state between callbacks.

To build a load function that will load the user for subsequent routes you'll need to access the ":id" passed. You can do this with ctx.params.NAME much like Express:

function load(ctx, next){
  var id = ctx.params.id
}

Then perform some kind of action against the server, assigning the user to ctx.user for other routes to utilize. next() is then invoked to pass control to the following matching route in sequence, if any.

function load(ctx, next){
  var id = ctx.params.id
  $.getJSON('/user/' + id + '.json', function(user){
    ctx.user = user
    next()
  })
}

The "show" function might look something like this, however you may render templates or do anything you want. Note that here next() is not invoked, because this is considered the "end point", and no routes will be matched until another link is clicked or page(path) is called.

function show(ctx){
  $('body')
    .empty()
    .append('<h1>' + ctx.user.name + '<h1>');
}

Finally using them like so:

page('/user/:id', load, show)

NOTE: The value of ctx.params.NAME is decoded via decodeURIComponent(sliceOfUrl). One exception though is the use of the plus sign (+) in the url, e.g. /user/john+doe, which is decoded to a space: ctx.params.id == 'john doe'. Also an encoded plus sign (%2B) is decoded to a space.

Working with state

When working with the pushState API, and page.js you may optionally provide state objects available when the user navigates the history.

For example if you had a photo application and you performed a relatively extensive search to populate a list of images, normally when a user clicks "back" in the browser the route would be invoked and the query would be made yet-again.

An example implementation might look as follows:

function show(ctx){
  $.getJSON('/photos', function(images){
    displayImages(images)
  })
}

You may utilize the history's state object to cache this result, or any other values you wish. This makes it possible to completely omit the query when a user presses back, providing a much nicer experience.

function show(ctx){
  if (ctx.state.images) {
    displayImages(ctx.state.images)
  } else {
    $.getJSON('/photos', function(images){
      ctx.state.images = images
      ctx.save()
      displayImages(images)
    })
  }
}

NOTE: ctx.save() must be used if the state changes after the first tick (xhr, setTimeout, etc), otherwise it is optional and the state will be saved after dispatching.

Matching paths

Here are some examples of what's possible with the string to RegExp conversion.

Match an explicit path:

page('/about', callback)

Match with required parameter accessed via ctx.params.name:

page('/user/:name', callback)

Match with several params, for example /user/tj/edit or /user/tj/view.

page('/user/:name/:operation', callback)

Match with one optional and one required, now /user/tj will match the same route as /user/tj/show etc:

page('/user/:name/:operation?', callback)

Use the wildcard char * to match across segments, available via ctx.params[N] where N is the index of * since you may use several. For example the following will match /user/12/edit, /user/12/albums/2/admin and so on.

page('/user/*', loadUser)

Named wildcard accessed, for example /file/javascripts/jquery.js would provide "/javascripts/jquery.js" as ctx.params.file:

page('/file/:file(.*)', loadUser)

And of course RegExp literals, where the capture groups are available via ctx.params[N] where N is the index of the capture group.

page(/^\/commits\/(\d+)\.\.(\d+)/, loadUser)

Plugins

An example plugin examples/query-string/query.js demonstrates how to make plugins. It will provide a parsed ctx.query object derived from node-querystring.

Usage by using "*" to match any path in order to parse the query-string:

page('*', parse)
page('/', show)
page()

function parse(ctx, next) {
  ctx.query = qs.parse(location.search.slice(1));
  next();
}

function show(ctx) {
  if (Object.keys(ctx.query).length) {
    document
      .querySelector('pre')
      .textContent = JSON.stringify(ctx.query, null, 2);
  }
}

Available plugins

Please submit pull requests to add more to this list.

Running tests

In the console:

$ npm install
$ npm test

In the browser:

$ npm install
$ npm run serve
$ open http://localhost:3000/

Support in IE8+

If you want the router to work in older version of Internet Explorer that don't support pushState, you can use the HTML5-History-API polyfill:

  npm install html5-history-api
How to use a Polyfill together with router (OPTIONAL):

If your web app is located within a nested basepath, you will need to specify the basepath for the HTML5-History-API polyfill. Before calling page.base() use: history.redirect([prefixType], [basepath]) - Translation link if required.

  • prefixType: [string|null] - Substitute the string after the anchor (#) by default "/".
  • basepath: [string|null] - Set the base path. See page.base() by default "/". (Note: Slash after pathname required)

Pull Requests

  • Break commits into a single objective.
  • An objective should be a chunk of code that is related but requires explanation.
  • Commits should be in the form of what-it-is: how-it-does-it and or why-it's-needed or what-it-is for trivial changes
  • Pull requests and commits should be a guide to the code.

Server configuration

In order to load and update any URL managed by page.js, you need to configure your environment to point to your project's main file (index.html, for example) for each non-existent URL. Below you will find examples for most common server scenarios.

Nginx

If using Nginx, add this to the .conf file related to your project (inside the "server" part), and reload your Nginx server:

location / {
    try_files $uri $uri/ /index.html?$args;
}

Apache

If using Apache, create (or add to) the .htaccess file in the root of your public folder, with the code:

Options +FollowSymLinks
RewriteEngine On

RewriteCond %{SCRIPT_FILENAME} !-d
RewriteCond %{SCRIPT_FILENAME} !-f

RewriteRule ^.*$ ./index.html

Node.js - Express

For development and/or production, using Express, you need to use express-history-api-fallback package. An example:

import { join } from 'path';
import express from 'express';
import history from 'express-history-api-fallback';

const app = express();
const root = join(__dirname, '../public');

app.use(express.static(root));
app.use(history('index.html', { root }));

const server = app.listen(process.env.PORT || 3000);

export default server;

Node.js - Browsersync

For development using Browsersync, you need to use history-api-fallback package. An example:

var browserSync = require("browser-sync").create();
var historyApiFallback = require('connect-history-api-fallback');

browserSync.init({
	files: ["*.html", "css/*.css", "js/*.js"],
	server: {
		baseDir: ".",
		middleware: [ historyApiFallback() ]
	},
	port: 3030
});

Integrations

License

(The MIT License)

Copyright (c) 2012 TJ Holowaychuk <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

page.js's People

Contributors

5im-0n avatar a avatar andyburke avatar boreplusplus avatar chevett avatar christianalfoni avatar devote avatar fisch42 avatar flootr avatar gianlucaguarini avatar hheiskanen avatar hugomrdias avatar jonathanong avatar kaisermann avatar kethinov avatar matthewp avatar matthewpblog avatar mclotworthy avatar nwmcsween avatar pahans avatar paulmaly avatar paulocoghi avatar rstacruz avatar samccone avatar seminice avatar tj avatar victorb avatar wesleydrobinson avatar woodlandhunter avatar zoomzhao avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

page.js's Issues

refactor for multiple Page instances

Multiple Page objects could be used with basepaths and operate independently, but the existing API will be left as-is, this would make testing a bit easier since the routes would reset etc...

page.js support for hybrid apps (e.g. phonegap native apps)

I experimented with adding page.js to the little test app here: https://github.com/kdonald/graceworks. It works fine when served to a HTML 5 browser such as Chrome (using something like serve running on localhost:3000, for example). However, the app doesn't work when wrapped in a Apache Cordova (phonegap) project and targeted at the iPhone simulator or an iPhone device. Specifically, it looks like the default ("/") route is never firing so no content ever gets rendered for the user to interact with.

I've found very little docs on the web about IOS's/Phonegap's support for the HTML5 history API. Previously, I was using path.js for navigation with #/fragments and it was working for the most part; however, window.history.back() did not work consistently in a native Phonegap environment and that's why I wanted to try out page.js (specifically, users were unable to navigate back to the #/home or root page of the app ("default route"), while window.history.back did work as expected when navigating from a child page to a parent that was not the root).

What are your thoughts for supporting multi-page hybrid (phonegap) apps? jQuery Mobile appears to maintain its own url history stack and does not rely on HTML 5 by default. I'm intentionally not using JQuery Mobile's navigation support in my app though as I wanted more control (I've built my own page navigator/router abstraction on-top of page js that routes URLs to MVC objects that handle page interactions... would love to see page.js just working when running on a native HTML container like in a Phonegap environment).

AMD support

Any chance to add AMD support? Would be great, thx!

Stop Making Great Software

I have a problem of having to rewrite parts of my apps every time you release something new. Please stop.. thanks

Using a query string in combination with base fails to dispatch to root

The following code demonstrates the problem

page.base('/example')
page.page('/', function() { console.log("example root"); })

page('/example')  // works OK
page('/example?foo=bar')  // does not find the route

I traced it back to this line in Context initialization

this.path = path.replace(base, '') || '/';

Without a query string, path is correctly initialized to "/", but if the base is followed by a query string, the result of replace is not empty.

I managed to work around this in my application by changing pathname initialization in Route.prototype.match to

pathname = ~qsIndex ? path.slice(0, qsIndex) || '/' : path

but I'm not sure if this is the correct way to fix this.

Parameter parsing appears not to work chrome 32.0

I have defined paths as follows:

page.base('/');
page('/managetask/:number', handle1);
page('*', handle2);
page.start();

I make a call of /managetask/0002

what happens is that handle1 never gets called at all and handle2 receives the full path of "/managetask/0002" and there are no paramaters present in ctx.params.

Metabug for tests

This is a meta bug for tests that need to be created at sometime.

pluggable history junk

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

Load params on page enter?

If i have the following code--

page.base('/Actions');
page("#:id", Controller.init);
page("*", Controller.init);
page();

And i load the page like /Actions#/12 it doesnt load 12 into the params-- am i doing someting wrong? I've tried a few different setups to no avail.

I would like to be able to pass the params in via the url.

Thanks!

maintainer

haven't had much time for client-side work, any takers? all I ask is:

  • keep page.js small and focused (potentially leverage components to shrink more)
  • maintain similar code style
  • follow semver ;D

Bootstrap conflict?

I'm finding that page.js stops working if jquery and bootstrap are included in the page. I've tried this with the basic example. It seems to be bootstrap rather than jquery that's causing the problem though it didn't appear there was a namespace conflict.

Support for mapping "vanity" URL paths

In many apps including mine usernames are used to secure "vanity URLs" e.g http://facebook.com/{user_username}, http://facebook.com/{page_username}. A username can be bound to different types of objects (e.g. users, pages) and the type of object associated with a particular username determines what sub-resource are supported (e.g. {user_username}/messages, {page_username}/fans.)

It looks like I can handle this in page.js by defining a * wildcard route and working with the full path string passed to context.params[0]. It all gets pretty low-level from there. I'm wondering if it might be possible to provide better support for this use-case in the library. I don't have any implementation ideas yet myself, but wanted to go ahead and report the use case.

Thanks for a nice library.

sprintf-ish

maybe..:

page('/user/:name/:op', 'tobi', 'edit')

Non-node ajax example

Hi,

Your library looks fantastic and that too at 1200bytes its almost unbelievable!

I was trying to get it to use but could not do so.

Your nodejs based examples work fine. But I could not understand why my examples could not work.

I was simply trying to host a couple of pages say main.html and child.html. And I wanted to click on a link in the page say #/child which should map to a function which will get child.html via ajax (say via $.ajax). Could not make this simple usecase to work (without node.js)

Could you help me via some code snippets?

Use page.js offline without a server

Hello. I am looking to use page.js for a project. I have to have this project as a downloadable file that runs locally from the html file on the user's desktop.

Although this is a client side routing solution all of the examples get caught in an infinite loop when I run them. Is it because page.js relies on pushstate? If so, what are my options for client side routing without a server.... angular.js?

Navigate pages by options

I'd love to have a way to navigate URL strings like this to keep things DRYer.

page('/user/:id', { id: 24 });

Bonus: a fantasticly-free unintended upside to this is you kinda get named routes support:

urls = {
  listUsers: '/user',
  showUser: '/user/:id'
}

page(urls.showUser, app.showUser); // create a route
page(urls.showUser, { id: 24 }); // navigate to a route

Is it possible to intercept change of url requests

There are two common use cases for this:

  1. Some routes require login (or require certain user role)
  2. There might be unsaved date on the current page.

In both cases, a common solution is to intercept requests to change the page, and warn the user that the requested page 1) requires different credentials, or 2) moving to the request page will cause loosing all unsaved data. Depending on the user feedback, one can either continue with the request action and change to the new URL anyway, or abort the navigation.

I know crossroads.js provides callbacks for this, and durandal's router requires usage of objects that support lifecycle (i.e. assumes callbacks have properties for canActivate, canDeactivate, etc.. which are called when jumping from one page to another). I believe sammy.js has similar functionality through its before and after callbacks, and so does Flatiron's director.

What is the right way to deal with this in page.js?

PS: I am looking for a small routing solution that does what is supposed to do an stays out of the way, it seems the only real contenders are page.js and crossroads.js, but crossroads.js hasn't seen a commit in a year, so I was hoping to use page.js. Suggestions for other alternatives are welcome.

bind to page exit ?

Hi -- is it possible to bind to the exit from a page or route ?

In a classic HTML page there's no need since one renders everything at once, but in a single page app, one often needs to unbind or remove certain elements.

easy to get caught in an infinite loop

Had a peak around the code as well as #30 , but couldn't figure it out. Here's an example:

boot.js:

page('/note/:slug', function() {
  note.select();
});

// ...

page();

noteview.js:

NoteView.prototype.select = function(note) {
  // ...
  page('/note/' + note.slug); // INFINITE LOOP
};

I feel like before page redirects it should check if the pathname is the same. It looks like it does that in the unhandled function, but that function isn't getting called from page('/note/' + note.slug).

Doc seems to be missing a few words.

I am definitely missing something...

  1. Is this a node application or a client side browser app?
  2. Is this a meteor client side app or meteor server side app?
  3. What is it that is being loaded, JSON, javascript, html?

Your documentation does not seem to make these things clear. While I can take my novice self and attempt to ferret out this information from the code, it would be nice to have orienting words or two occur in the ReadMe.

TypeError: Arguments to path.join must be strings

After unzipping the ZIP file: https://github.com/visionmedia/page.js/archive/master.zip
and running the three comands

$ npm install
$ node examples
$ open http://localhost:3000

(actually it was port 4000!)

I get the following error:

TypeError: Arguments to path.join must be strings
    at path.js:360:15
    at Array.filter (native)
    at exports.join (path.js:358:36)
    at exports.send (/...../page.js/node_modules/express/node_modules/connect/lib/middleware/static.js:141:20)
    at ServerResponse.res.sendfile (/...../page.js/node_modules/express/lib/response.js:243:3)
    at /...../page.js/examples/index.js:72:7
    at callbacks (/...../page.js/node_modules/express/lib/router/index.js:171:11)
    at param (/...../page.js/node_modules/express/lib/router/index.js:145:11)
    at param (/...../page.js/node_modules/express/lib/router/index.js:142:11)
    at pass (/...../page.js/node_modules/express/lib/router/index.js:152:5)

Examples

Seems to still be an issue with the examples

Redirect from '/' to '/Home' gets reset on Context#save()

Hi.

I have two routes, '/' and '/:title'. In the '/' route I want to redirect to '/Home' but this instantly gets reset back to '/'.

page('/', function() {
  console.log('index!');
  page('/Home');
});
page('/:title', function() {
  console.log('titled page!');
});
page();

When I run this, I think because of https://github.com/visionmedia/page.js/blob/master/index.js#L145, my output is 'index!' and 'titled page!' but I see '/Home' in the URL for a split second and then the URL is '/' again.

Putting the page('/Home'); in a setTimeout 0 'fixes' it, but i'd rather not. I'm making a small wiki app, so I want to redirect to a page named Home instantly. How do I achieve this behaviour?

Anchors are ignored in start()

Is there any reason why #anchors are ignored in the start() method (see)?

I can work around that "problem" by calling start() with { dispatch: false } and dispatch it myself. But it looks like a bug to me. I'd say hash is a legitimate part of the URL, wouldn't you?

click and dom context

TJ I am using page click option to handle click event

my links have data attributes

<a href="/some-path" data-transition="slide-left">my link</a>

So would it make sense to add a reference to the dom element in the state object
so I can use data attributes? Or Should I write my own custom onClick method for that ?

remove ctx

implementation-wise it'll be a little bit hacky but referencing page.path etc and changing the (ctx, next) signature to (next) might be worth it

We need a plan

I think that we need to plan our actions. May be we start from minor release 1.4.0?

I suggest to include in the package this things:

@nwmcsween what do you think about it? Any additions?

"param" handler ร  la express

It would be interesting to have a page.param(paramName, handler); that would behave identically to express' app.param.

Would reduce redundancy in the route handler definitions in some cases.

What do you think?

Support redirection?

Currently, I want to redirect to a default url by

page('/default', function(context, next){
// show but quickly back to /
})
page('/', function(context, next){
// redirect to '/default'
page('/default');
})
page();

I think the problem come from

page.replace = function(path, state, init, dispatch){
var ctx = new Context(path, state);
ctx.init = init;
if (null == dispatch) dispatch = true;
if (dispatch) page.dispatch(ctx);
ctx.save();
return ctx;
};

which do ctx.save(); before page.dispatch

Shall we save ctx first?

Weird behavior when used with RequireJS

Yeah, it is really behaving weird. Every first time I visit my page after restarting server, it returns 404 when loading page.js via RequireJS. But after that, when I hit reload, all goes smooth. No further 404s.

Only for the first time. Has anybody a clue what this could be?

Have "onClick" verify if "event.defaultPrevented" is set.

At this moment, the onClick handler in page.js will call page.show even if event.preventDefault() was called on the link click event beforeHand.

Browsers normally don't push new history states when event.preventDefault() is called on a link click events. I think that page.js should behave the same and wrap transparently the browser history system in that regard.

This is most useful for web-apps and single page apps when some links are just actions that don't really point to new pages. At this moment, a link such as <a href='#'>toggle visibility</a> that is cancelled will still make the router go through its motions and call the route code.

Redirect/refresh loop for unhandled urls

Suppose we have several urls defined for news articles:

page("/news", listNews);
page("/news/:item", showNewsItem);
page("/news/:item/edit", editNewsItem);

If the user then clicks on a link to "/news/draft", a url for which no handler has been defined, page.js will trigger the unhandled handler. This will change window.location and cause a page refresh at which point the link will still be unhandled causing a refresh loop.

Solution:
unhandled should compare ctx.canonicalPath to window.location before triggering a page change.

Caveat:
I'm not sure about the exact logic required here considering potential url fragments and query parameters.

Click event interception doesn't work with custom elements / shadow DOM

I have a custom element which contains links inside its shadow dom. Page.js should intercept clicks to these links and prevent the default action. The problem is e.target is the custom element, as the browser doesn't give the webpage insight into the element's DOM tree.

e.srcElement on the other hand is the anchor element. But that might be a quirk/bug in the custom element polyfill (I'm using the one from polymer).

pushState Error

Unsure of the error, or solution.

Error - Uncaught DataCloneError: An object could not be cloned. main.js:10026
main.js:10026

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

main.js

  indexView = function() {
    console.log('indexView rendered');
    return document.getElementById('index').style.display = 'block';
  };

  aboutView = function() {
    return console.log('aboutView rendered');
  };

  projectsView = function() {
    return console.log('projectsView rendered');
  };

  notfoundView = function() {
    return console.log('404 page rendered');
  };

  page.base('/');

  page('/', index);

  page('/about', aboutView);

  page('/projects', projectsView);

  page('*', notfoundView);

  page();

window.addEventListener !== document.addEventListener // true

Hi,

I'd run into an annoying issue using last versions of Page.js and I think I've found the source of it.

I randomly run this line of code into chrome 26 console:

> window.addEventListener !== document.addEventListener
> true

I'm not sure why, but this makes my application break by leading Page.js into not listening to click events on the document:

  page.start = function(options){
    // ...
    if (false !== options.click) window.addEventListener('click', onclick, false);
    // ...
  };

I'd like to know if this was made intentional in order to know if I should find a workaround or wait for a fix.

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.