From: jdlrobson Date: Thu, 24 Dec 2015 18:05:00 +0000 (-0800) Subject: Introduce mediawiki.router for handling hash fragment navigation X-Git-Tag: 1.31.0-rc.0~7082^2 X-Git-Url: http://git.cyclocoop.org/%22%20.%20generer_url_aide%28?a=commitdiff_plain;h=7bacc11bd7b40a761f42d6634c3ffdd1a8849086;p=lhc%2Fweb%2Fwiklou.git Introduce mediawiki.router for handling hash fragment navigation 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 --- diff --git a/resources/Resources.php b/resources/Resources.php index cb7adbe4d9..1200dfce1c 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -2288,6 +2288,26 @@ return [ ], ], + 'mediawiki.router' => [ + 'scripts' => [ + 'resources/src/mediawiki.router/index.js', + ], + 'targets' => [ 'desktop', 'mobile' ], + 'dependencies' => [ + 'oojs-router', + ], + ], + + 'oojs-router' => [ + 'scripts' => [ + 'resources/lib/oojs-router/oojs-router.js', + ], + 'targets' => [ 'desktop', 'mobile' ], + 'dependencies' => [ + 'oojs', + ], + ], + /* OOjs UI */ // WARNING: OOjs-UI is NOT TESTED with older browsers and is likely to break // if loaded in browsers that don't support ES5 diff --git a/resources/lib/oojs-router/AUTHORS.txt b/resources/lib/oojs-router/AUTHORS.txt new file mode 100644 index 0000000000..5390c84cb2 --- /dev/null +++ b/resources/lib/oojs-router/AUTHORS.txt @@ -0,0 +1 @@ +Jon Robson diff --git a/resources/lib/oojs-router/LICENSE-MIT b/resources/lib/oojs-router/LICENSE-MIT new file mode 100644 index 0000000000..acbe708990 --- /dev/null +++ b/resources/lib/oojs-router/LICENSE-MIT @@ -0,0 +1,20 @@ +Copyright 2011-2016 OOjs Team and other contributors. + +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. diff --git a/resources/lib/oojs-router/oojs-router.js b/resources/lib/oojs-router/oojs-router.js new file mode 100644 index 0000000000..b136923e9a --- /dev/null +++ b/resources/lib/oojs-router/oojs-router.js @@ -0,0 +1,205 @@ +/*! + * 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 ) ); diff --git a/resources/src/mediawiki.router/index.js b/resources/src/mediawiki.router/index.js new file mode 100644 index 0000000000..a49cfeb866 --- /dev/null +++ b/resources/src/mediawiki.router/index.js @@ -0,0 +1,2 @@ +var Router = require( 'oojs-router' ); +module.exports = new Router();