Micro client-side router inspired by the Express router

Related tags

Routing page.js
Overview

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.

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

    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).

    opened by kdonald 26
  • pagejs doesn't run in node

    pagejs doesn't run in node

    I'm writing an isomorphic app and would like to use pagejs as the url router on both sides (browserify/node js for prerendering mostly). It looks for a window object: ReferenceError: window is not defined at Object. (/home/jeroen/projects/high5/node_modules/page/index.js:20:18) at Module._compile (module.js:456:26) at Module._extensions..js (module.js:47

    opened by ghost 24
  • Updating to latest path-to-regexp breaks '*' matching...

    Updating to latest path-to-regexp breaks '*' matching...

    page( '*', function( context, next ) {
      console.log( 'this matched all routes' );
    });
    

    No longer works with latest path-to-regexp, you must specify an explicit regexp:

    page( /.*/, function( context, next ) {
      console.log( 'this will now really match all routes' );
    });
    
    docs help wanted 
    opened by andyburke 19
  • Implement dispatching iteratively.

    Implement dispatching iteratively.

    Hello, page.js maintainers! đź‘‹

    In applications with a large number of middlewares, the recursive implementation of nextEnter and nextExit become a problem, by generating deep call stacks (2x the number of middlewares, as far as I can tell).

    Even where the call stack size isn't directly an issue, the added cruft to the call tree can make debugging and profiling harder.

    Recursive (note the many layers of nextEnter):

    image

    Iterative (note how there's a single layer of nextEnter now):

    image

    There doesn't seem to be a strong reason for these methods to be implemented recursively, so I reimplemented them iteratively.

    All the tests work correctly, but please double-check the logic, and let me know if there's anything wrong with it, the coding style, or my commit in general.

    Thanks for taking a look! 🙂

    opened by sgomes 18
  • page.js not working with history back or forward

    page.js not working with history back or forward

    Hi, I've used page.js in my project for about a week. Sometimes, page.js will not work with history.back(). The issue is that, when I hit the back button(or manually call history.back) on chrome, the url will change, but the callback doesn't get called. (the URL changes but the route isn't triggered again).

    And the history.forward or the forward button on my chrome or any other browser will not trigger the router callback.

    I don't know whether it's a problem in my code, I found someone has the same issue with me, not solved.

    bug hashbang 
    opened by Nomia 18
  • Idea: page.back()

    Idea: page.back()

    page.back() will be the equivalent of running if (there's history to go back to) { history.back() }.

    page.back()
    

    Optionally, you should be able to pass a path to fall back on, in case you're trying to do page.back() on the first page.

    page.back('/dashboard')
    

    To do this, we'll need to track history length on Context#pushState.

    page.length == 0
    page('/another/page')
    page.length == 1
    

    I have this implemented in https://github.com/rstacruz/repage.js — happy to merge the implementation here if you're all happy with the feature and the api. thoughts?

    opened by rstacruz 18
  • bind to page exit ?

    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.

    opened by weepy 17
  • [WIP] Prevent duplicated event bindings for page instances

    [WIP] Prevent duplicated event bindings for page instances

    Fixes #508

    I've removed the this.configure() call from the Page constructor (and the options argument) since it's already called and used at page.start.

    (Maybe just passing the options throught the .create() would be enough, but it wouldn't solve the duped events in case of the .start being also called at some point)

    opened by kaisermann 15
  • Include page.js in npm package

    Include page.js in npm package

    Right now page.js is not included in the npm package, that seems like a mistake.

    If you merge this pull request, could you also be so kind to release a new version to npm.

    opened by sunesimonsen 15
  • Popstate fired on initial page load causes double trigger (Safari 8)

    Popstate fired on initial page load causes double trigger (Safari 8)

    Safari 8 is still firing a popstate event after the page is ready. This causes a second match to occur and triggers the current route to exit and return. Chrome, IE, and Firefox are all OK.

    Should a check to ignore the initial stateless popstate in Safari be added?

    bug 
    opened by iamcarbon 15
  • State of the project

    State of the project

    I note with some worry the increasing number of issues and PRs on this project, and low maintenance activity. What is the status of the project, in terms of activity? Can we expect to see it return to active maintenance in the foreseeable future?

    opened by gertsonderby 14
  • history.state is cleared on initial page load

    history.state is cleared on initial page load

    When page.js is started, it clears the history.state via the following line.

    https://github.com/visionmedia/page.js/blob/4f9991658f9b9e3de9b6059bade93693af24d6bd/index.js#L168

    This is actually problematic for apps that need to access history.state, especially when navigating through the browser's history. The problem is only present on the first load of the page, since that's the only time page.js automatically interacts with the history state. Indirect interaction through ctx.save() from within a route handler isn't really an issue since that would be intentional — and therefore, avoidable. One way of mitigating this problem is to allow page.start() to accept an initial state. That way, if an app needs to retain the history state, history.state can be passed in as the initial state. The new page.start() function would look something like the following:

    Page.prototype.start = function(options, initialState=null) {
      var opts = options || {};
      this.configure(opts);
    
      if (false === opts.dispatch) return;
      this._running = true;
    
      var url;
      if(isLocation) {
        var window = this._window;
        var loc = window.location;
    
        if(this._hashbang && ~loc.hash.indexOf('#!')) {
          url = loc.hash.substr(2) + loc.search;
        } else if (this._hashbang) {
          url = loc.search + loc.hash;
        } else {
          url = loc.pathname + loc.search + loc.hash;
        }
      }
    
      this.replace(url, initialState, true, opts.dispatch);
    };
    

    Which would allow you to do something like this:

    // --snip--
    page.start(opts, history.state);
    

    I decided to open this issue, as a RFC, rather than submitted a PR, because I'd like some feedback on my proposal.

    opened by AshfordN 0
  • Browser back/Forward button with saved warning message

    Browser back/Forward button with saved warning message

    Hi Team,

    I am working on my application and I am stuck onto a solution, where I want to give a warning message on the click of back button of the browser. Whenever there are unsaved changes on the page, and user is navigating away from that page, I want to warn user that there are unsaved changes. So for this I am using below code:

    window.addEventListener('popstate', function (event) { // The popstate event is fired each time when the current history entry changes. if(dirty==true){ var r = confirm("You pressed a Back button! Are you sure?!");

        if (r == true) {
            // Call Back button programmatically as per user confirmation.
            history.back();
            // Uncomment below line to redirect to the previous page instead.
            // window.location = document.referrer // Note: IE11 is not supporting this.
        } else {
    

    event.preventDefault() // Stay on the current page. history.pushState(null, null, window.location.pathname); }

        history.pushState(null, null, window.location.pathname);
    

    } }, false);

    But niether preventDefault() is working nor window.beforeunload nor popstate confirm cancel is giving the desired outcome to come.

    However, I created one POC where page.js is not used, and there on click of browser back button on before unload event is giving me warning pop up.

    Could you please suggest solution for this as in the documentation no such case is catered

    opened by AditiG12 2
  • fix:Omit query and hash part if both apears in path

    fix:Omit query and hash part if both apears in path

    Hi As mentioned in #575 issue if path have both ?foo=bar and # foo the page.js only removes # foo part so i fix this in this commit and now the output is: canonicalPath /foo?hello=there pathname /foo canonicalPath /foo#hash pathname /foo canonicalPath /foo?hello=there#hash pathname /foo

    It's my first time contributing to this repo so I'm very happy if anyone has a comment on this. also i run test and everything works fine

    opened by aliraad79 0
  • Whether pathname includes base depends on whether there is an anchor (#foo)

    Whether pathname includes base depends on whether there is an anchor (#foo)

    I apologize if I missed an existing issue for this, or if this is expected behavior, but it seems surprising to me. #575 is similar but this is specifically around pathname and base URL. Assuming hashbang is false, if there is no hash symbol (anchor) in the document URL, the pathname is based on path so includes the base URL. In Context():

    https://github.com/visionmedia/page.js/blob/4f9991658f9b9e3de9b6059bade93693af24d6bd/page.js#L1090

    However, if there is a hash symbol (anchor), pathname depends on this.path, which does not have the base URL.

    https://github.com/visionmedia/page.js/blob/4f9991658f9b9e3de9b6059bade93693af24d6bd/page.js#L1098

    I came across while tracking a bug in our code. Here is a gist which shows the behavior in isolation.

    https://gist.github.com/evanbattaglia/2b76bf11fe8a18ac4dcef14f2138a2cc

    URL: /index -> pathname /basic/ URL: /index#whoop -> pathname / URL: /basic/about -> pathname /basic/about URL: /basic/about#whoop -> pathname /about

    The documentation states pathname is The pathname void of query string "/login"., which implies it should not have the base URL (/admin in the documentation).

    opened by evanbattaglia 0
  • It would be nice to have a minified version available

    It would be nice to have a minified version available

    Right now we import node_modules/page/page.js into our project but it would be nice if there was a minified version we could import instead. Not a huge deal but it would be nice.

    opened by arethk 0
Releases(v1.11.5)
  • v1.11.5(Sep 27, 2019)

  • v1.11.4(Jan 29, 2019)

  • 1.11.3(Nov 9, 2018)

  • 1.11.2(Nov 5, 2018)

  • 1.11.1(Oct 31, 2018)

  • 1.11.0(Oct 24, 2018)

  • 1.10.2(Sep 24, 2018)

  • v1.10.1(Sep 8, 2018)

    This is a patch release, fixing an issue from when clicking a link from within a page with query parameters, and navigating to the same page (such as a hash). Previously the querystring was not included in the Context object. This fixes that.

    Source code(tar.gz)
    Source code(zip)
  • v1.10.0(Sep 7, 2018)

    This is a minor release, adding the new page.create() API, for creating distinct page objects.

    Calling page.create(options) create a new page function that behaves just like the global page. It contains its own internal state, options, and event handlers. You can target the page to another window, like an iframe or a popup, if you want.

    var newPage = page.create({
      window: someIframe.contentWindow
    });
    

    The main reason for adding this feature was to clean up or testing infrastructure. Numerous things had to be done in the past to make sure each test was cleanly in isolation. That stuff is no longer necessary now that we can just create a new page instance for each test.

    Source code(tar.gz)
    Source code(zip)
  • v1.9.0(Sep 6, 2018)

    This is a minor release, adding a new ES module bundle to the distributed package. This module is available as page.mjs. It contains one export, the default export which is the usual page object.

    <script type="module">
      import page from "//unpkg.com/page/page.mjs";
    
      page('/home', () => {
        showHome();
      });
      page();
    </script>
    
    Source code(tar.gz)
    Source code(zip)
  • 1.8.3(Jan 22, 2018)

  • 1.8.2(Jan 22, 2018)

    This is a patch release fixing an issue that was meant to be solved in 1.8.1. page.js now runs in Node.js again, when there isn't a window environment.

    Source code(tar.gz)
    Source code(zip)
  • 1.8.1(Jan 22, 2018)

  • 1.8.0(Jan 17, 2018)

    This is a minor release, adding one new (minor) feature and a few bug fixes and documentation improvements.

    Controlling other pages

    The new feature of this release is that page.js can now control other pages (windows) besides the main window. This makes it possible to control an iframe's routing. You can use it by passing the window option to start:

    page('/', function(){
      // Whoa, I'm inside an iframe!
    });
    
    page.start({ window: myiFrame.contentWindow });
    

    Note that page is still a singleton, which means you can only have 1 page, so you can't control both an iframe and the main window at the same time.

    This change was made to improve our testing, but in the future we'll make it possible to have multiple page objects so you really will be able to control multiple iframes and the main window all at the same time.

    Better hashbang support

    Hashbang support has never been very good in page.js. But we're slowly improving it! In 1.8.0 we've fixed the problem where the app's full URL would show up in the hashbang upon start. http://example.com/my/page#!/my/page. Gross! No longer happens thanks to #447.

    Pull Requests

    Those are the big things, but here's a list of all of the pull requests merged into 1.8.0:

    Source code(tar.gz)
    Source code(zip)
  • 1.7.3(Jan 15, 2018)

    This is a patch release making an improvement to how page.js works on pages that use the file protocol, such as Electron and nw.js apps.

    Pull Requests

    • Set the page's base to be the current location when used under the file protocol with hashbang routing. #441
    Source code(tar.gz)
    Source code(zip)
  • 1.7.2(Jan 15, 2018)

    Our first release in almost 2 years! This is a long overdue patch release that contains various bug fixes that have taken place over the course of the last couple of years. As releases will become much more often in the future (containing only a few fixes in most cases), I will be listing the closed issues in releases, but because there are 2 years worth it is not practical to do so in this release.

    While you're here, if you haven't checked out page.js in a long time now is a great time. I've recently taken over maintenance and have a plan in place to take this great small library into the future. For now I am concentrating on stabilizing 1.x by fixing as many of the backlog of issues that have built up as I can. Once that is complete we'll start thinking about 2.0.

    If you've submitted a PR here in the past and seen it be ignored, please come back! Your contributions are invaluable, and I promise that as long as I'm maintaining this project I'll do my best to always at least comment on pull requests and try to get them resolved.

    That's all for now! Please report any issues you find in 1.7.2.

    Source code(tar.gz)
    Source code(zip)
  • 1.6.4(Oct 14, 2015)

  • 1.6.3(Apr 19, 2015)

  • 1.6.1(Feb 16, 2015)

  • 1.6.0(Jan 27, 2015)

    1.6.0./ 2015-01-27

    • added page.back feature #157
    • added decodeURLComponents option #187
    • now ctx.params is object like in express #203
    • skip route processing if another one is called #172
    • docs improved
    • tests improved
    Source code(tar.gz)
    Source code(zip)
  • 1.5.0(Nov 28, 2014)

    1.5.0 / 2014-11-29

    • added page.exit(path, callback[, callback...])
    • added page.redirect(url)
    • fix: ignore links with download attribute
    • fix: remove URL encoding before parsing paths
    Source code(tar.gz)
    Source code(zip)
  • 1.4.1(Nov 13, 2014)

  • 1.4.0(Nov 11, 2014)

    1.4.0 / 2014-11-12

    • add hashbang support. Closes #112
    • add page.redirect() method
    • add plugins list to readme
    • add Context#handled option
    • Fix an issue where redirects in dispatch can be overwritten by ctx.save()
    • add support HTML5-History-API polyfill
    • make sameOrigin public
    • update path-to-regexp
    • allow for missing href in anchors.
    • update examples
    Source code(tar.gz)
    Source code(zip)
An Express.js-Style router for the front-end

An Express.js-Style router for the front-end. Code the front-end like the back-end. Same language same framework. frontexpress demo import frontexpres

Camel Aissani 262 Jul 11, 2022
Single Page Application micro framework. Views, routes and controllers in 60 lines of code

SPApp Single Page Application Micro Framework Supports views, controllers and routing in 60 lines of code! Introduction If you heard anything about MV

Andrew 262 Nov 23, 2022
a tiny and isomorphic URL router for JavaScript

Synopsis Director is a router. Routing is the process of determining what code to run when a URL is requested. Motivation A routing library that works

a decoupled application framework 5.6k Dec 28, 2022
A navigation aid (aka, router) for the browser in 850 bytes~!

A navigation aid (aka, router) for the browser in 865 bytes~! Install $ npm install --save navaid Usage const navaid = require('navaid'); // Setup r

Luke Edwards 732 Dec 27, 2022
⚡️The Fullstack React Framework — built on Next.js

The Fullstack React Framework "Zero-API" Data Layer — Built on Next.js — Inspired by Ruby on Rails Read the Documentation “Zero-API” data layer lets y

⚡️Blitz 12.5k Jan 4, 2023
A chainable router designed for Next.js api. inspired and regex based from itty-router

Next.js Api Router A chainable router designed for Next.js api. inspired and regex based from itty-router Features Tiny (~8xx bytes compressed) with z

Aris Riswanto 1 Jan 21, 2022
Micro.publish is an Obsidian plugin to publish notes directly to Micro.blog, written in TypeScript

Micro.publish Micro.publish is a community maintained plugin for Obsidian to publish notes to a Micro.blog blog. Installing This plugin will be availa

Otavio Cordeiro 14 Dec 9, 2022
Fast and minimal JS server-side writer and client-side manager.

unihead Fast and minimal JS <head> server-side writer and client-side manager. Nearly every SSR framework out there relies on server-side components t

Jonas Galvez 24 Sep 4, 2022
Easy server-side and client-side validation for FormData, URLSearchParams and JSON data in your Fresh app 🍋

Fresh Validation ??     Easily validate FormData, URLSearchParams and JSON data in your Fresh app server-side or client-side! Validation Fresh Validat

Steven Yung 20 Dec 23, 2022
Processing Foundation 18.6k Jan 1, 2023
Router JS đź’˝ Simple Router building in JavaScript

Router JS ?? Simple Router building in JavaScript

David Montaño Tamayo 1 Feb 12, 2022
Make drag-and-drop easier using DropPoint. Drag content without having to open side-by-side windows

Make drag-and-drop easier using DropPoint! DropPoint helps you drag content without having to open side-by-side windows Works on Windows, Linux and Ma

Sudev Suresh Sreedevi 391 Dec 29, 2022
This is an application that entered the market with a mobile application in real life. We wrote the backend side with node.js and the mobile side with flutter.

HAUSE TAXI API Get Started Must be installed on your computer Git Node Firebase Database Config You should read this easy documentation Firebase-Fires

Muhammet Çokyaman 4 Nov 4, 2021
This plugin allows side-by-side notetaking with videos. Annotate your notes with timestamps to directly control the video and remember where each note comes from.

Obsidian Timestamp Notes Use Case Hello Obsidian users! Like all of you, I love using Obsidian for taking notes. My usual workflow is a video in my br

null 74 Jan 2, 2023
This Plugin is For Logseq. If you're using wide monitors, you can place journals, linked references, and journal queries side by side.

Logseq Column-Layout Plugin Journals, linked references, and journal queries can be placed side by side if the minimum screen width is "1850px" or mor

YU 14 Dec 14, 2022
A modular front-end framework - inspired by the server-side and Web Components.

The NX framework Home page, Docs NX is a modular front-end framework - built with ES6 and Web Components. The building blocks of NX are the core, the

NX framework 464 Dec 5, 2022
An Express.js-Style router for the front-end

An Express.js-Style router for the front-end. Code the front-end like the back-end. Same language same framework. frontexpress demo import frontexpres

Camel Aissani 262 Jul 11, 2022
Veloce: Starter template that uses Vue 3, Vite, TypeScript, SSR, Pinia, Vue Router, Express and Docker

Veloce Lightning-fast cold server start Instant hot module replacement (HMR) and dev SSR True on-demand compilation Tech Stack Vue 3: UI Rendering lib

Alan Morel 10 Oct 7, 2022
Keep track of book descriptions on the server-side using MongoDB, Express, and Node.js.

Backend Application A database model/backend for a user directory using Javascript MongoDB, Express, and Node.js. In summary, a backend CRUD model to

Rodrigo Bravo 3 Apr 10, 2022
Fastify is a web framework highly focused on providing the best developer experience with the least overhead and a powerful plugin architecture, inspired by Hapi and Express.

Fastify is a web framework highly focused on providing the best developer experience with the least overhead and a powerful plugin architecture, inspired by Hapi and Express.

Jared Hanson 5 Oct 11, 2022