Add a generic barebones Router class to core to allow registration of routes in a central place and deal with potential clashes in future. See patches making use of the router: - Kartographer extension I456a4582a67e31533d51d5817d0f4af57528c35e - mediawiki.special.preference If7cb76e362464943df20598bd09fd341574756c4 Bug: T114007 Change-Id: I4295db446eac7cf24a3ed89edfa9eefa5cb34b73
205 lines
4.9 KiB
JavaScript
205 lines
4.9 KiB
JavaScript
/*!
|
|
* OOjs Router v0.1.0
|
|
* https://www.mediawiki.org/wiki/OOjs
|
|
*
|
|
* Copyright 2011-2016 OOjs Team and other contributors.
|
|
* Released under the MIT license
|
|
* http://oojs-router.mit-license.org
|
|
*
|
|
* Date: 2016-05-05T19:27:58Z
|
|
*/
|
|
( function ( $ ) {
|
|
|
|
'use strict';
|
|
|
|
/**
|
|
* Does hash match entry.path? If it does apply the
|
|
* callback for the Entry object.
|
|
*
|
|
* @method
|
|
* @private
|
|
* @ignore
|
|
* @param {string} hash string to match
|
|
* @param {Object} entry Entry object
|
|
* @return {boolean} Whether hash matches entry.path
|
|
*/
|
|
function matchRoute( hash, entry ) {
|
|
var match = hash.match( entry.path );
|
|
if ( match ) {
|
|
entry.callback.apply( this, match.slice( 1 ) );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Provides navigation routing and location information
|
|
*
|
|
* @class Router
|
|
* @mixins OO.EventEmitter
|
|
*/
|
|
function Router() {
|
|
var self = this;
|
|
OO.EventEmitter.call( this );
|
|
// use an object instead of an array for routes so that we don't
|
|
// duplicate entries that already exist
|
|
this.routes = {};
|
|
this.enabled = true;
|
|
this.oldHash = this.getPath();
|
|
|
|
$( window ).on( 'popstate', function () {
|
|
self.emit( 'popstate' );
|
|
} );
|
|
|
|
$( window ).on( 'hashchange', function () {
|
|
self.emit( 'hashchange' );
|
|
} );
|
|
|
|
this.on( 'hashchange', function () {
|
|
// ev.originalEvent.newURL is undefined on Android 2.x
|
|
var routeEv;
|
|
|
|
if ( self.enabled ) {
|
|
routeEv = $.Event( 'route', {
|
|
path: self.getPath()
|
|
} );
|
|
self.emit( 'route', routeEv );
|
|
|
|
if ( !routeEv.isDefaultPrevented() ) {
|
|
self.checkRoute();
|
|
} else {
|
|
// if route was prevented, ignore the next hash change and revert the
|
|
// hash to its old value
|
|
self.enabled = false;
|
|
self.navigate( self.oldHash );
|
|
}
|
|
} else {
|
|
self.enabled = true;
|
|
}
|
|
|
|
self.oldHash = self.getPath();
|
|
} );
|
|
}
|
|
OO.mixinClass( Router, OO.EventEmitter );
|
|
|
|
/**
|
|
* Check the current route and run appropriate callback if it matches.
|
|
*
|
|
* @method
|
|
*/
|
|
Router.prototype.checkRoute = function () {
|
|
var hash = this.getPath();
|
|
|
|
$.each( this.routes, function ( id, entry ) {
|
|
return !matchRoute( hash, entry );
|
|
} );
|
|
};
|
|
|
|
/**
|
|
* Bind a specific callback to a hash-based route, e.g.
|
|
*
|
|
* @example
|
|
* route( 'alert', function () { alert( 'something' ); } );
|
|
* route( /hi-(.*)/, function ( name ) { alert( 'Hi ' + name ) } );
|
|
* Note that after defining all available routes it is up to the caller
|
|
* to check the existing route via the checkRoute method.
|
|
*
|
|
* @method
|
|
* @param {Object} path string or RegExp to match.
|
|
* @param {Function} callback Callback to be run when hash changes to one
|
|
* that matches.
|
|
*/
|
|
Router.prototype.route = function ( path, callback ) {
|
|
var entry = {
|
|
path: typeof path === 'string' ?
|
|
new RegExp( '^' + path.replace( /[\\^$*+?.()|[\]{}]/g, '\\$&' ) + '$' )
|
|
: path,
|
|
callback: callback
|
|
};
|
|
this.routes[ entry.path ] = entry;
|
|
};
|
|
|
|
/**
|
|
* Navigate to a specific route.
|
|
*
|
|
* @method
|
|
* @param {string} path string with a route (hash without #).
|
|
*/
|
|
Router.prototype.navigate = function ( path ) {
|
|
var history = window.history;
|
|
// Take advantage of `pushState` when available, to clear the hash and
|
|
// not leave `#` in the history. An entry with `#` in the history has
|
|
// the side-effect of resetting the scroll position when navigating the
|
|
// history.
|
|
if ( path === '' && history && history.pushState ) {
|
|
// To clear the hash we need to cut the hash from the URL.
|
|
path = window.location.href.replace( /#.*$/, '' );
|
|
history.pushState( null, document.title, path );
|
|
this.checkRoute();
|
|
} else {
|
|
window.location.hash = path;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Triggers back on the window
|
|
*/
|
|
Router.prototype.goBack = function () {
|
|
window.history.back();
|
|
};
|
|
|
|
/**
|
|
* Navigate to the previous route. This is a wrapper for window.history.back
|
|
*
|
|
* @method
|
|
* @return {jQuery.Deferred}
|
|
*/
|
|
Router.prototype.back = function () {
|
|
var deferredRequest = $.Deferred(),
|
|
self = this,
|
|
timeoutID;
|
|
|
|
this.once( 'popstate', function () {
|
|
clearTimeout( timeoutID );
|
|
deferredRequest.resolve();
|
|
} );
|
|
|
|
this.goBack();
|
|
|
|
// If for some reason (old browser, bug in IE/windows 8.1, etc) popstate doesn't fire,
|
|
// resolve manually. Since we don't know for sure which browsers besides IE10/11 have
|
|
// this problem, it's better to fall back this way rather than singling out browsers
|
|
// and resolving the deferred request for them individually.
|
|
// See https://connect.microsoft.com/IE/feedback/details/793618/history-back-popstate-not-working-as-expected-in-webview-control
|
|
// Give browser a few ms to update its history.
|
|
timeoutID = setTimeout( function () {
|
|
self.off( 'popstate' );
|
|
deferredRequest.resolve();
|
|
}, 50 );
|
|
|
|
return deferredRequest;
|
|
};
|
|
|
|
/**
|
|
* Get current path (hash).
|
|
*
|
|
* @method
|
|
* @return {string} Current path.
|
|
*/
|
|
Router.prototype.getPath = function () {
|
|
return window.location.hash.slice( 1 );
|
|
};
|
|
|
|
/**
|
|
* Determine if current browser supports onhashchange event
|
|
*
|
|
* @method
|
|
* @return {boolean}
|
|
*/
|
|
Router.prototype.isSupported = function () {
|
|
return 'onhashchange' in window;
|
|
};
|
|
|
|
module.exports = Router;
|
|
|
|
}( jQuery ) );
|