From b0704f3dc1f9a4819ea768b762f66fac18777f90 Mon Sep 17 00:00:00 2001 From: Rob Eisenberg Date: Tue, 13 Oct 2015 03:25:22 -0400 Subject: [PATCH] chore(all): prepare release v0.9.0 --- bower.json | 5 +- config.js | 33 +- dist/amd/aurelia-history-browser.d.ts | 71 ++- dist/amd/aurelia-history-browser.js | 330 ++++++++----- dist/aurelia-history-browser.d.ts | 71 ++- dist/aurelia-history-browser.js | 378 +++++++++------ dist/commonjs/aurelia-history-browser.d.ts | 71 ++- dist/commonjs/aurelia-history-browser.js | 334 ++++++++----- dist/es6/aurelia-history-browser.d.ts | 71 ++- dist/es6/aurelia-history-browser.js | 378 +++++++++------ dist/system/aurelia-history-browser.d.ts | 71 ++- dist/system/aurelia-history-browser.js | 325 ++++++++----- doc/CHANGELOG.md | 52 ++ doc/api.json | 532 +++++++++++++++------ package.json | 30 +- 15 files changed, 1820 insertions(+), 932 deletions(-) diff --git a/bower.json b/bower.json index 861821e..89f6bdc 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "aurelia-history-browser", - "version": "0.8.0", + "version": "0.9.0", "description": "An implementation of the Aurelia history interface based on standard browser hash change and push state mechanisms.", "keywords": [ "aurelia", @@ -17,7 +17,8 @@ "url": "http://github.com/aurelia/history-browser" }, "dependencies": { - "aurelia-history": "^0.7.0", + "aurelia-history": "^0.8.0", + "aurelia-pal": "^0.2.0", "core-js": "zloirock/core-js" } } diff --git a/config.js b/config.js index 1617eb3..1412dbe 100644 --- a/config.js +++ b/config.js @@ -14,18 +14,37 @@ System.config({ }, map: { - "aurelia-history": "github:aurelia/history@0.7.0", - "aurelia-pal": "github:aurelia/pal@0.1.4", + "aurelia-history": "github:aurelia/history@0.8.0", + "aurelia-pal": "github:aurelia/pal@0.2.0", "babel": "npm:babel-core@5.1.13", "babel-runtime": "npm:babel-runtime@5.1.13", - "core-js": "npm:core-js@1.1.3", - "github:jspm/nodelibs-process@0.1.1": { - "process": "npm:process@0.10.1" + "core-js": "npm:core-js@1.2.1", + "github:jspm/nodelibs-assert@0.1.0": { + "assert": "npm:assert@1.3.0" }, - "npm:core-js@1.1.3": { + "github:jspm/nodelibs-process@0.1.2": { + "process": "npm:process@0.11.2" + }, + "github:jspm/nodelibs-util@0.1.0": { + "util": "npm:util@0.10.3" + }, + "npm:assert@1.3.0": { + "util": "npm:util@0.10.3" + }, + "npm:core-js@1.2.1": { "fs": "github:jspm/nodelibs-fs@0.1.2", - "process": "github:jspm/nodelibs-process@0.1.1", + "process": "github:jspm/nodelibs-process@0.1.2", "systemjs-json": "github:systemjs/plugin-json@0.1.0" + }, + "npm:inherits@2.0.1": { + "util": "github:jspm/nodelibs-util@0.1.0" + }, + "npm:process@0.11.2": { + "assert": "github:jspm/nodelibs-assert@0.1.0" + }, + "npm:util@0.10.3": { + "inherits": "npm:inherits@2.0.1", + "process": "github:jspm/nodelibs-process@0.1.2" } } }); diff --git a/dist/amd/aurelia-history-browser.d.ts b/dist/amd/aurelia-history-browser.d.ts index 9de1235..183a630 100644 --- a/dist/amd/aurelia-history-browser.d.ts +++ b/dist/amd/aurelia-history-browser.d.ts @@ -1,18 +1,68 @@ declare module 'aurelia-history-browser' { - import * as core from 'core-js'; + import 'core-js'; + import { DOM, PLATFORM } from 'aurelia-pal'; import { History } from 'aurelia-history'; /** - * An implementation of the basic history api. + * Class responsible for handling interactions that should trigger browser history navigations. */ - export class BrowserHistory extends History { + export class LinkHandler { + + /** + * Activate the instance. + * + * @param history The BrowserHistory instance that navigations should be dispatched to. + */ + activate(history: BrowserHistory): any; /** - * Creates an instance of BrowserHistory. + * Deactivate the instance. Event handlers and other resources should be cleaned up here. */ + deactivate(): any; + } + + /** + * The default LinkHandler implementation. Navigations are triggered by click events on + * anchor elements with relative hrefs when the history instance is using pushstate. + */ + export class DefaultLinkHandler extends LinkHandler { constructor(); - getHash(window?: Window): string; - getFragment(fragment: string, forcePushState?: boolean): string; + activate(history: BrowserHistory): any; + deactivate(): any; + + /** + * Gets the href and a "should handle" recommendation, given an Event. + * + * @param event The Event to inspect for target anchor and href. + */ + static getEventInfo(event: Event): Object; + + /** + * Finds the closest ancestor that's an anchor element. + * + * @param el The element to search upward from. + */ + static findClosestAnchor(el: Element): Element; + + /** + * Gets a value indicating whether or not an anchor targets the current window. + * + * @param target The anchor element whose target should be inspected. + */ + static targetIsThisWindow(target: Element): boolean; + } + + /** + * Configures the plugin by registering BrowserHistory as the implementation of History in the DI container. + */ + export function configure(config: Object): void; + + /** + * An implementation of the basic history API. + */ + export class BrowserHistory extends History { + static inject: any; + constructor(linkHandler: any); /** * Activates the history object. @@ -24,24 +74,17 @@ declare module 'aurelia-history-browser' { * Deactivates the history object. */ deactivate(): void; - checkUrl(): boolean; - loadUrl(fragmentOverride: string): boolean; /** * Causes a history navigation to occur. * @param fragment The history fragment to navigate to. * @param options The set of options that specify how the navigation should occur. */ - navigate(fragment?: string, options?: Object): boolean; + navigate(fragment?: string, undefined?: any): boolean; /** * Causes the history state to navigate back. */ navigateBack(): void; } - - /** - * Configures the plugin by registering BrowserHistory as the implementor of History in the DI container. - */ - export function configure(config: Object): void; } \ No newline at end of file diff --git a/dist/amd/aurelia-history-browser.js b/dist/amd/aurelia-history-browser.js index b41bc13..afed963 100644 --- a/dist/amd/aurelia-history-browser.js +++ b/dist/amd/aurelia-history-browser.js @@ -1,178 +1,220 @@ -define(['exports', 'core-js', 'aurelia-history'], function (exports, _coreJs, _aureliaHistory) { +define(['exports', 'core-js', 'aurelia-pal', 'aurelia-history'], function (exports, _coreJs, _aureliaPal, _aureliaHistory) { 'use strict'; exports.__esModule = true; + + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + exports.configure = configure; + function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + var LinkHandler = (function () { + function LinkHandler() { + _classCallCheck(this, LinkHandler); + } - var routeStripper = /^#?\/*|\s+$/g; + LinkHandler.prototype.activate = function activate(history) {}; - var rootStripper = /^\/+|\/+$/g; + LinkHandler.prototype.deactivate = function deactivate() {}; - var trailingSlash = /\/$/; + return LinkHandler; + })(); - var absoluteUrl = /^([a-z][a-z0-9+\-.]*:)?\/\//i; + exports.LinkHandler = LinkHandler; - function updateHash(location, fragment, replace) { - if (replace) { - var href = location.href.replace(/(javascript:|#).*$/, ''); - location.replace(href + '#' + fragment); - } else { - location.hash = '#' + fragment; - } - } + var DefaultLinkHandler = (function (_LinkHandler) { + _inherits(DefaultLinkHandler, _LinkHandler); - var BrowserHistory = (function (_History) { - _inherits(BrowserHistory, _History); + function DefaultLinkHandler() { + var _this = this; - function BrowserHistory() { - _classCallCheck(this, BrowserHistory); + _classCallCheck(this, DefaultLinkHandler); - _History.call(this); + _LinkHandler.call(this); - this.interval = 50; - this.active = false; - this.previousFragment = ''; - this._checkUrlCallback = this.checkUrl.bind(this); + this.handler = function (e) { + var _DefaultLinkHandler$getEventInfo = DefaultLinkHandler.getEventInfo(e); - if (typeof window !== 'undefined') { - this.location = window.location; - this.history = window.history; - } + var shouldHandleEvent = _DefaultLinkHandler$getEventInfo.shouldHandleEvent; + var href = _DefaultLinkHandler$getEventInfo.href; + + if (shouldHandleEvent) { + e.preventDefault(); + _this.history.navigate(href); + } + }; } - BrowserHistory.prototype.getHash = function getHash(window) { - var match = (window || this).location.href.match(/#(.*)$/); - return match ? match[1] : ''; + DefaultLinkHandler.prototype.activate = function activate(history) { + if (history._hasPushState) { + this.history = history; + _aureliaPal.DOM.addEventListener('click', this.handler, true); + } }; - BrowserHistory.prototype.getFragment = function getFragment(fragment, forcePushState) { - var root = undefined; + DefaultLinkHandler.prototype.deactivate = function deactivate() { + _aureliaPal.DOM.removeEventListener('click', this.handler); + }; - if (!fragment) { - if (this._hasPushState || !this._wantsHashChange || forcePushState) { - fragment = this.location.pathname + this.location.search; - root = this.root.replace(trailingSlash, ''); - if (!fragment.indexOf(root)) { - fragment = fragment.substr(root.length); - } - } else { - fragment = this.getHash(); + DefaultLinkHandler.getEventInfo = function getEventInfo(event) { + var info = { + shouldHandleEvent: false, + href: null, + anchor: null + }; + + var target = DefaultLinkHandler.findClosestAnchor(event.target); + if (!target || !DefaultLinkHandler.targetIsThisWindow(target)) { + return info; + } + + if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) { + return info; + } + + var href = target.getAttribute('href'); + info.anchor = target; + info.href = href; + + var hasModifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey; + var isRelative = href && !(href.charAt(0) === '#' || /^[a-z]+:/i.test(href)); + + info.shouldHandleEvent = !hasModifierKey && isRelative; + return info; + }; + + DefaultLinkHandler.findClosestAnchor = function findClosestAnchor(el) { + while (el) { + if (el.tagName === 'A') { + return el; } + + el = el.parentNode; } + }; - return '/' + fragment.replace(routeStripper, ''); + DefaultLinkHandler.targetIsThisWindow = function targetIsThisWindow(target) { + var targetWindow = target.getAttribute('target'); + var win = _aureliaPal.PLATFORM.global; + + return !targetWindow || targetWindow === win.name || targetWindow === '_self' || targetWindow === 'top' && win === win.top; }; + return DefaultLinkHandler; + })(LinkHandler); + + exports.DefaultLinkHandler = DefaultLinkHandler; + + function configure(config) { + config.singleton(_aureliaHistory.History, BrowserHistory); + + if (!config.container.hasHandler(LinkHandler)) { + config.transient(LinkHandler, DefaultLinkHandler); + } + } + + var BrowserHistory = (function (_History) { + _inherits(BrowserHistory, _History); + + _createClass(BrowserHistory, null, [{ + key: 'inject', + value: [LinkHandler], + enumerable: true + }]); + + function BrowserHistory(linkHandler) { + _classCallCheck(this, BrowserHistory); + + _History.call(this); + + this._isActive = false; + this._checkUrlCallback = this._checkUrl.bind(this); + + this.location = _aureliaPal.PLATFORM.location; + this.history = _aureliaPal.PLATFORM.history; + this.linkHandler = linkHandler; + } + BrowserHistory.prototype.activate = function activate(options) { - if (this.active) { + if (this._isActive) { throw new Error('History has already been activated.'); } - this.active = true; + var wantsPushState = !!options.pushState; + this._isActive = true; this.options = Object.assign({}, { root: '/' }, this.options, options); - this.root = this.options.root; - this._wantsHashChange = this.options.hashChange !== false; - this._wantsPushState = !!this.options.pushState; - this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); - var fragment = this.getFragment(); + this.root = ('/' + this.options.root + '/').replace(rootStripper, '/'); - this.root = ('/' + this.root + '/').replace(rootStripper, '/'); + this._wantsHashChange = this.options.hashChange !== false; + this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); + var eventName = undefined; if (this._hasPushState) { - window.onpopstate = this._checkUrlCallback; - } else if (this._wantsHashChange && 'onhashchange' in window) { - window.addEventListener('hashchange', this._checkUrlCallback); + eventName = 'popstate'; } else if (this._wantsHashChange) { - this._checkUrlTimer = setTimeout(this._checkUrlCallback, this.interval); + eventName = 'hashchange'; } - this.fragment = fragment; + _aureliaPal.PLATFORM.addEventListener(eventName, this._checkUrlCallback); - var loc = this.location; - var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root; + if (this._wantsHashChange && wantsPushState) { + var loc = this.location; + var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root; - if (this._wantsHashChange && this._wantsPushState) { if (!this._hasPushState && !atRoot) { - this.fragment = this.getFragment(null, true); + this.fragment = this._getFragment(null, true); this.location.replace(this.root + this.location.search + '#' + this.fragment); return true; } else if (this._hasPushState && atRoot && loc.hash) { - this.fragment = this.getHash().replace(routeStripper, ''); - this.history.replaceState({}, document.title, this.root + this.fragment + loc.search); + this.fragment = this._getHash().replace(routeStripper, ''); + this.history.replaceState({}, _aureliaPal.DOM.title, this.root + this.fragment + loc.search); } } - if (!this.options.silent) { - return this.loadUrl(); + if (!this.fragment) { + this.fragment = this._getFragment(); } - }; - - BrowserHistory.prototype.deactivate = function deactivate() { - window.onpopstate = null; - window.removeEventListener('hashchange', this._checkUrlCallback); - clearTimeout(this._checkUrlTimer); - this.active = false; - }; - BrowserHistory.prototype.checkUrl = function checkUrl() { - var current = this.getFragment(); + this.linkHandler.activate(this); - if (this._checkUrlTimer) { - clearTimeout(this._checkUrlTimer); - this._checkUrlTimer = setTimeout(this._checkUrlCallback, this.interval); - } - - if (current === this.fragment && this.iframe) { - current = this.getFragment(this.getHash(this.iframe)); - } - - if (current === this.fragment) { - return false; - } - - if (this.iframe) { - this.navigate(current, false); + if (!this.options.silent) { + return this._loadUrl(); } + }; - this.loadUrl(); + BrowserHistory.prototype.deactivate = function deactivate() { + _aureliaPal.PLATFORM.removeEventListener('popstate', this._checkUrlCallback); + _aureliaPal.PLATFORM.removeEventListener('hashchange', this._checkUrlCallback); + this._isActive = false; + this.linkHandler.deactivate(); }; - BrowserHistory.prototype.loadUrl = function loadUrl(fragmentOverride) { - var fragment = this.fragment = this.getFragment(fragmentOverride); + BrowserHistory.prototype.navigate = function navigate(fragment) { + var _ref = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - return this.options.routeHandler ? this.options.routeHandler(fragment) : false; - }; + var _ref$trigger = _ref.trigger; + var trigger = _ref$trigger === undefined ? true : _ref$trigger; + var _ref$replace = _ref.replace; + var replace = _ref$replace === undefined ? false : _ref$replace; - BrowserHistory.prototype.navigate = function navigate(fragment, options) { if (fragment && absoluteUrl.test(fragment)) { - window.location.href = fragment; + this.location.href = fragment; return true; } - if (!this.active) { + if (!this._isActive) { return false; } - if (options === undefined) { - options = { - trigger: true - }; - } else if (typeof options === 'boolean') { - options = { - trigger: options - }; - } + fragment = this._getFragment(fragment || ''); - fragment = this.getFragment(fragment || ''); - - if (this.fragment === fragment) { + if (this.fragment === fragment && !replace) { return false; } @@ -186,30 +228,55 @@ define(['exports', 'core-js', 'aurelia-history'], function (exports, _coreJs, _a if (this._hasPushState) { url = url.replace('//', '/'); - this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url); + this.history[replace ? 'replaceState' : 'pushState']({}, _aureliaPal.DOM.title, url); } else if (this._wantsHashChange) { - updateHash(this.location, fragment, options.replace); + updateHash(this.location, fragment, replace); + } else { + return this.location.assign(url); + } - if (this.iframe && fragment !== this.getFragment(this.getHash(this.iframe))) { - if (!options.replace) { - this.iframe.document.open().close(); - } + if (trigger) { + return this._loadUrl(fragment); + } + }; + + BrowserHistory.prototype.navigateBack = function navigateBack() { + this.history.back(); + }; + + BrowserHistory.prototype._getHash = function _getHash() { + return this.location.hash.substr(1); + }; + + BrowserHistory.prototype._getFragment = function _getFragment(fragment, forcePushState) { + var root = undefined; - updateHash(this.iframe.location, fragment, options.replace); + if (!fragment) { + if (this._hasPushState || !this._wantsHashChange || forcePushState) { + fragment = this.location.pathname + this.location.search; + root = this.root.replace(trailingSlash, ''); + if (!fragment.indexOf(root)) { + fragment = fragment.substr(root.length); } } else { - return this.location.assign(url); - } - - if (options.trigger) { - return this.loadUrl(fragment); + fragment = this._getHash(); + } } - this.previousFragment = fragment; + return '/' + fragment.replace(routeStripper, ''); }; - BrowserHistory.prototype.navigateBack = function navigateBack() { - this.history.back(); + BrowserHistory.prototype._checkUrl = function _checkUrl() { + var current = this._getFragment(); + if (current !== this.fragment) { + this._loadUrl(); + } + }; + + BrowserHistory.prototype._loadUrl = function _loadUrl(fragmentOverride) { + var fragment = this.fragment = this._getFragment(fragmentOverride); + + return this.options.routeHandler ? this.options.routeHandler(fragment) : false; }; return BrowserHistory; @@ -217,7 +284,20 @@ define(['exports', 'core-js', 'aurelia-history'], function (exports, _coreJs, _a exports.BrowserHistory = BrowserHistory; - function configure(config) { - config.singleton(_aureliaHistory.History, BrowserHistory); + var routeStripper = /^#?\/*|\s+$/g; + + var rootStripper = /^\/+|\/+$/g; + + var trailingSlash = /\/$/; + + var absoluteUrl = /^([a-z][a-z0-9+\-.]*:)?\/\//i; + + function updateHash(location, fragment, replace) { + if (replace) { + var href = location.href.replace(/(javascript:|#).*$/, ''); + location.replace(href + '#' + fragment); + } else { + location.hash = '#' + fragment; + } } }); \ No newline at end of file diff --git a/dist/aurelia-history-browser.d.ts b/dist/aurelia-history-browser.d.ts index 9de1235..183a630 100644 --- a/dist/aurelia-history-browser.d.ts +++ b/dist/aurelia-history-browser.d.ts @@ -1,18 +1,68 @@ declare module 'aurelia-history-browser' { - import * as core from 'core-js'; + import 'core-js'; + import { DOM, PLATFORM } from 'aurelia-pal'; import { History } from 'aurelia-history'; /** - * An implementation of the basic history api. + * Class responsible for handling interactions that should trigger browser history navigations. */ - export class BrowserHistory extends History { + export class LinkHandler { + + /** + * Activate the instance. + * + * @param history The BrowserHistory instance that navigations should be dispatched to. + */ + activate(history: BrowserHistory): any; /** - * Creates an instance of BrowserHistory. + * Deactivate the instance. Event handlers and other resources should be cleaned up here. */ + deactivate(): any; + } + + /** + * The default LinkHandler implementation. Navigations are triggered by click events on + * anchor elements with relative hrefs when the history instance is using pushstate. + */ + export class DefaultLinkHandler extends LinkHandler { constructor(); - getHash(window?: Window): string; - getFragment(fragment: string, forcePushState?: boolean): string; + activate(history: BrowserHistory): any; + deactivate(): any; + + /** + * Gets the href and a "should handle" recommendation, given an Event. + * + * @param event The Event to inspect for target anchor and href. + */ + static getEventInfo(event: Event): Object; + + /** + * Finds the closest ancestor that's an anchor element. + * + * @param el The element to search upward from. + */ + static findClosestAnchor(el: Element): Element; + + /** + * Gets a value indicating whether or not an anchor targets the current window. + * + * @param target The anchor element whose target should be inspected. + */ + static targetIsThisWindow(target: Element): boolean; + } + + /** + * Configures the plugin by registering BrowserHistory as the implementation of History in the DI container. + */ + export function configure(config: Object): void; + + /** + * An implementation of the basic history API. + */ + export class BrowserHistory extends History { + static inject: any; + constructor(linkHandler: any); /** * Activates the history object. @@ -24,24 +74,17 @@ declare module 'aurelia-history-browser' { * Deactivates the history object. */ deactivate(): void; - checkUrl(): boolean; - loadUrl(fragmentOverride: string): boolean; /** * Causes a history navigation to occur. * @param fragment The history fragment to navigate to. * @param options The set of options that specify how the navigation should occur. */ - navigate(fragment?: string, options?: Object): boolean; + navigate(fragment?: string, undefined?: any): boolean; /** * Causes the history state to navigate back. */ navigateBack(): void; } - - /** - * Configures the plugin by registering BrowserHistory as the implementor of History in the DI container. - */ - export function configure(config: Object): void; } \ No newline at end of file diff --git a/dist/aurelia-history-browser.js b/dist/aurelia-history-browser.js index a0b818e..c638d86 100644 --- a/dist/aurelia-history-browser.js +++ b/dist/aurelia-history-browser.js @@ -1,74 +1,142 @@ -import * as core from 'core-js'; -import { History } from 'aurelia-history'; +import 'core-js'; +import {DOM,PLATFORM} from 'aurelia-pal'; +import {History} from 'aurelia-history'; -// Cached regex for stripping a leading hash/slash and trailing space. -let routeStripper = /^#?\/*|\s+$/g; - -// Cached regex for stripping leading and trailing slashes. -let rootStripper = /^\/+|\/+$/g; - -// Cached regex for removing a trailing slash. -let trailingSlash = /\/$/; - -// Cached regex for detecting if a URL is absolute, -// i.e., starts with a scheme or is scheme-relative. -// See http://www.ietf.org/rfc/rfc2396.txt section 3.1 for valid scheme format -const absoluteUrl = /^([a-z][a-z0-9+\-.]*:)?\/\//i; +/** + * Class responsible for handling interactions that should trigger browser history navigations. + */ +export class LinkHandler { + /** + * Activate the instance. + * + * @param history The BrowserHistory instance that navigations should be dispatched to. + */ + activate(history: BrowserHistory) {} -// Update the hash location, either replacing the current entry, or adding -// a new one to the browser history. -function updateHash(location, fragment, replace) { - if (replace) { - let href = location.href.replace(/(javascript:|#).*$/, ''); - location.replace(href + '#' + fragment); - } else { - // Some browsers require that `hash` contains a leading #. - location.hash = '#' + fragment; - } + /** + * Deactivate the instance. Event handlers and other resources should be cleaned up here. + */ + deactivate() {} } /** - * An implementation of the basic history api. + * The default LinkHandler implementation. Navigations are triggered by click events on + * anchor elements with relative hrefs when the history instance is using pushstate. */ -export class BrowserHistory extends History { - /** - * Creates an instance of BrowserHistory. - */ +export class DefaultLinkHandler extends LinkHandler { constructor() { super(); - this.interval = 50; - this.active = false; - this.previousFragment = ''; - this._checkUrlCallback = this.checkUrl.bind(this); + this.handler = (e) => { + let {shouldHandleEvent, href} = DefaultLinkHandler.getEventInfo(e); - if (typeof window !== 'undefined') { - this.location = window.location; - this.history = window.history; + if (shouldHandleEvent) { + e.preventDefault(); + this.history.navigate(href); + } + }; + } + + activate(history: BrowserHistory) { + if (history._hasPushState) { + this.history = history; + DOM.addEventListener('click', this.handler, true); } } - getHash(window?: Window): string { - let match = (window || this).location.href.match(/#(.*)$/); - return match ? match[1] : ''; + deactivate() { + DOM.removeEventListener('click', this.handler); } - getFragment(fragment: string, forcePushState?: boolean): string { - let root; + /** + * Gets the href and a "should handle" recommendation, given an Event. + * + * @param event The Event to inspect for target anchor and href. + */ + static getEventInfo(event: Event): Object { + let info = { + shouldHandleEvent: false, + href: null, + anchor: null + }; + + let target = DefaultLinkHandler.findClosestAnchor(event.target); + if (!target || !DefaultLinkHandler.targetIsThisWindow(target)) { + return info; + } - if (!fragment) { - if (this._hasPushState || !this._wantsHashChange || forcePushState) { - fragment = this.location.pathname + this.location.search; - root = this.root.replace(trailingSlash, ''); - if (!fragment.indexOf(root)) { - fragment = fragment.substr(root.length); - } - } else { - fragment = this.getHash(); + if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) { + return info; + } + + let href = target.getAttribute('href'); + info.anchor = target; + info.href = href; + + let hasModifierKey = (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey); + let isRelative = href && !(href.charAt(0) === '#' || (/^[a-z]+:/i).test(href)); + + info.shouldHandleEvent = !hasModifierKey && isRelative; + return info; + } + + /** + * Finds the closest ancestor that's an anchor element. + * + * @param el The element to search upward from. + */ + static findClosestAnchor(el: Element): Element { + while (el) { + if (el.tagName === 'A') { + return el; } + + el = el.parentNode; } + } - return '/' + fragment.replace(routeStripper, ''); + /** + * Gets a value indicating whether or not an anchor targets the current window. + * + * @param target The anchor element whose target should be inspected. + */ + static targetIsThisWindow(target: Element): boolean { + let targetWindow = target.getAttribute('target'); + let win = PLATFORM.global; + + return !targetWindow || + targetWindow === win.name || + targetWindow === '_self' || + (targetWindow === 'top' && win === win.top); + } +} + +/** + * Configures the plugin by registering BrowserHistory as the implementation of History in the DI container. + */ +export function configure(config: Object): void { + config.singleton(History, BrowserHistory); + + if (!config.container.hasHandler(LinkHandler)) { + config.transient(LinkHandler, DefaultLinkHandler); + } +} + +/** + * An implementation of the basic history API. + */ +export class BrowserHistory extends History { + static inject = [LinkHandler]; + + constructor(linkHandler) { + super(); + + this._isActive = false; + this._checkUrlCallback = this._checkUrl.bind(this); + + this.location = PLATFORM.location; + this.history = PLATFORM.history; + this.linkHandler = linkHandler; } /** @@ -76,48 +144,42 @@ export class BrowserHistory extends History { * @param options The set of options to activate history with. */ activate(options?: Object): boolean { - if (this.active) { + if (this._isActive) { throw new Error('History has already been activated.'); } - this.active = true; + let wantsPushState = !!options.pushState; - // Figure out the initial configuration. Do we need an iframe? - // Is pushState desired ... is it available? + this._isActive = true; this.options = Object.assign({}, { root: '/' }, this.options, options); - this.root = this.options.root; - this._wantsHashChange = this.options.hashChange !== false; - this._wantsPushState = !!this.options.pushState; - this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); - - let fragment = this.getFragment(); // Normalize root to always include a leading and trailing slash. - this.root = ('/' + this.root + '/').replace(rootStripper, '/'); + this.root = ('/' + this.options.root + '/').replace(rootStripper, '/'); + + this._wantsHashChange = this.options.hashChange !== false; + this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); - // Depending on whether we're using pushState or hashes, and whether - // 'onhashchange' is supported, determine how we check the URL state. + // Determine how we check the URL state. + let eventName; if (this._hasPushState) { - window.onpopstate = this._checkUrlCallback; - } else if (this._wantsHashChange && ('onhashchange' in window)) { - window.addEventListener('hashchange', this._checkUrlCallback); + eventName = 'popstate'; } else if (this._wantsHashChange) { - this._checkUrlTimer = setTimeout(this._checkUrlCallback, this.interval); + eventName = 'hashchange'; } + PLATFORM.addEventListener(eventName, this._checkUrlCallback); + // Determine if we need to change the base url, for a pushState link // opened by a non-pushState browser. - this.fragment = fragment; + if (this._wantsHashChange && wantsPushState) { + // Transition from hashChange to pushState or vice versa if both are requested. + let loc = this.location; + let atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root; - let loc = this.location; - let atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root; - - // Transition from hashChange to pushState or vice versa if both are requested. - if (this._wantsHashChange && this._wantsPushState) { // If we've started off with a route from a `pushState`-enabled // browser, but we're currently in a browser that doesn't support it... if (!this._hasPushState && !atRoot) { - this.fragment = this.getFragment(null, true); + this.fragment = this._getFragment(null, true); this.location.replace(this.root + this.location.search + '#' + this.fragment); // Return immediately as browser will do redirect to new url return true; @@ -125,13 +187,19 @@ export class BrowserHistory extends History { // Or if we've started out with a hash-based route, but we're currently // in a browser where it could be `pushState`-based instead... } else if (this._hasPushState && atRoot && loc.hash) { - this.fragment = this.getHash().replace(routeStripper, ''); - this.history.replaceState({}, document.title, this.root + this.fragment + loc.search); + this.fragment = this._getHash().replace(routeStripper, ''); + this.history.replaceState({}, DOM.title, this.root + this.fragment + loc.search); } } + if (!this.fragment) { + this.fragment = this._getFragment(); + } + + this.linkHandler.activate(this); + if (!this.options.silent) { - return this.loadUrl(); + return this._loadUrl(); } } @@ -139,41 +207,10 @@ export class BrowserHistory extends History { * Deactivates the history object. */ deactivate(): void { - window.onpopstate = null; - window.removeEventListener('hashchange', this._checkUrlCallback); - clearTimeout(this._checkUrlTimer); - this.active = false; - } - - checkUrl(): boolean { - let current = this.getFragment(); - - if (this._checkUrlTimer) { - clearTimeout(this._checkUrlTimer); - this._checkUrlTimer = setTimeout(this._checkUrlCallback, this.interval); - } - - if (current === this.fragment && this.iframe) { - current = this.getFragment(this.getHash(this.iframe)); - } - - if (current === this.fragment) { - return false; - } - - if (this.iframe) { - this.navigate(current, false); - } - - this.loadUrl(); - } - - loadUrl(fragmentOverride: string): boolean { - let fragment = this.fragment = this.getFragment(fragmentOverride); - - return this.options.routeHandler ? - this.options.routeHandler(fragment) : - false; + PLATFORM.removeEventListener('popstate', this._checkUrlCallback); + PLATFORM.removeEventListener('hashchange', this._checkUrlCallback); + this._isActive = false; + this.linkHandler.deactivate(); } /** @@ -181,29 +218,19 @@ export class BrowserHistory extends History { * @param fragment The history fragment to navigate to. * @param options The set of options that specify how the navigation should occur. */ - navigate(fragment?: string, options?: Object): boolean { + navigate(fragment?: string, {trigger = true, replace = false} = {}): boolean { if (fragment && absoluteUrl.test(fragment)) { - window.location.href = fragment; + this.location.href = fragment; return true; } - if (!this.active) { + if (!this._isActive) { return false; } - if (options === undefined) { - options = { - trigger: true - }; - } else if (typeof options === 'boolean') { - options = { - trigger: options - }; - } - - fragment = this.getFragment(fragment || ''); + fragment = this._getFragment(fragment || ''); - if (this.fragment === fragment) { + if (this.fragment === fragment && !replace) { return false; } @@ -219,35 +246,20 @@ export class BrowserHistory extends History { // If pushState is available, we use it to set the fragment as a real URL. if (this._hasPushState) { url = url.replace('//', '/'); - this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url); - + this.history[replace ? 'replaceState' : 'pushState']({}, DOM.title, url); + } else if (this._wantsHashChange) { // If hash changes haven't been explicitly disabled, update the hash // fragment to store history. - } else if (this._wantsHashChange) { - updateHash(this.location, fragment, options.replace); - - if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) { - // Opening and closing the iframe tricks IE7 and earlier to push a - // history entry on hash-tag change. When replace is true, we don't - // want history. - if (!options.replace) { - this.iframe.document.open().close(); - } - - updateHash(this.iframe.location, fragment, options.replace); - } - + updateHash(this.location, fragment, replace); + } else { // If you've told us that you explicitly don't want fallback hashchange- // based history, then `navigate` becomes a page refresh. - } else { return this.location.assign(url); } - if (options.trigger) { - return this.loadUrl(fragment); + if (trigger) { + return this._loadUrl(fragment); } - - this.previousFragment = fragment; } /** @@ -256,11 +268,67 @@ export class BrowserHistory extends History { navigateBack(): void { this.history.back(); } + + _getHash(): string { + return this.location.hash.substr(1); + } + + _getFragment(fragment: string, forcePushState?: boolean): string { + let root; + + if (!fragment) { + if (this._hasPushState || !this._wantsHashChange || forcePushState) { + fragment = this.location.pathname + this.location.search; + root = this.root.replace(trailingSlash, ''); + if (!fragment.indexOf(root)) { + fragment = fragment.substr(root.length); + } + } else { + fragment = this._getHash(); + } + } + + return '/' + fragment.replace(routeStripper, ''); + } + + _checkUrl(): boolean { + let current = this._getFragment(); + if (current !== this.fragment) { + this._loadUrl(); + } + } + + _loadUrl(fragmentOverride: string): boolean { + let fragment = this.fragment = this._getFragment(fragmentOverride); + + return this.options.routeHandler ? + this.options.routeHandler(fragment) : + false; + } } -/** - * Configures the plugin by registering BrowserHistory as the implementor of History in the DI container. - */ -export function configure(config: Object): void { - config.singleton(History, BrowserHistory); +// Cached regex for stripping a leading hash/slash and trailing space. +const routeStripper = /^#?\/*|\s+$/g; + +// Cached regex for stripping leading and trailing slashes. +const rootStripper = /^\/+|\/+$/g; + +// Cached regex for removing a trailing slash. +const trailingSlash = /\/$/; + +// Cached regex for detecting if a URL is absolute, +// i.e., starts with a scheme or is scheme-relative. +// See http://www.ietf.org/rfc/rfc2396.txt section 3.1 for valid scheme format +const absoluteUrl = /^([a-z][a-z0-9+\-.]*:)?\/\//i; + +// Update the hash location, either replacing the current entry, or adding +// a new one to the browser history. +function updateHash(location, fragment, replace) { + if (replace) { + let href = location.href.replace(/(javascript:|#).*$/, ''); + location.replace(href + '#' + fragment); + } else { + // Some browsers require that `hash` contains a leading #. + location.hash = '#' + fragment; + } } diff --git a/dist/commonjs/aurelia-history-browser.d.ts b/dist/commonjs/aurelia-history-browser.d.ts index 9de1235..183a630 100644 --- a/dist/commonjs/aurelia-history-browser.d.ts +++ b/dist/commonjs/aurelia-history-browser.d.ts @@ -1,18 +1,68 @@ declare module 'aurelia-history-browser' { - import * as core from 'core-js'; + import 'core-js'; + import { DOM, PLATFORM } from 'aurelia-pal'; import { History } from 'aurelia-history'; /** - * An implementation of the basic history api. + * Class responsible for handling interactions that should trigger browser history navigations. */ - export class BrowserHistory extends History { + export class LinkHandler { + + /** + * Activate the instance. + * + * @param history The BrowserHistory instance that navigations should be dispatched to. + */ + activate(history: BrowserHistory): any; /** - * Creates an instance of BrowserHistory. + * Deactivate the instance. Event handlers and other resources should be cleaned up here. */ + deactivate(): any; + } + + /** + * The default LinkHandler implementation. Navigations are triggered by click events on + * anchor elements with relative hrefs when the history instance is using pushstate. + */ + export class DefaultLinkHandler extends LinkHandler { constructor(); - getHash(window?: Window): string; - getFragment(fragment: string, forcePushState?: boolean): string; + activate(history: BrowserHistory): any; + deactivate(): any; + + /** + * Gets the href and a "should handle" recommendation, given an Event. + * + * @param event The Event to inspect for target anchor and href. + */ + static getEventInfo(event: Event): Object; + + /** + * Finds the closest ancestor that's an anchor element. + * + * @param el The element to search upward from. + */ + static findClosestAnchor(el: Element): Element; + + /** + * Gets a value indicating whether or not an anchor targets the current window. + * + * @param target The anchor element whose target should be inspected. + */ + static targetIsThisWindow(target: Element): boolean; + } + + /** + * Configures the plugin by registering BrowserHistory as the implementation of History in the DI container. + */ + export function configure(config: Object): void; + + /** + * An implementation of the basic history API. + */ + export class BrowserHistory extends History { + static inject: any; + constructor(linkHandler: any); /** * Activates the history object. @@ -24,24 +74,17 @@ declare module 'aurelia-history-browser' { * Deactivates the history object. */ deactivate(): void; - checkUrl(): boolean; - loadUrl(fragmentOverride: string): boolean; /** * Causes a history navigation to occur. * @param fragment The history fragment to navigate to. * @param options The set of options that specify how the navigation should occur. */ - navigate(fragment?: string, options?: Object): boolean; + navigate(fragment?: string, undefined?: any): boolean; /** * Causes the history state to navigate back. */ navigateBack(): void; } - - /** - * Configures the plugin by registering BrowserHistory as the implementor of History in the DI container. - */ - export function configure(config: Object): void; } \ No newline at end of file diff --git a/dist/commonjs/aurelia-history-browser.js b/dist/commonjs/aurelia-history-browser.js index f7d6c4f..be51e58 100644 --- a/dist/commonjs/aurelia-history-browser.js +++ b/dist/commonjs/aurelia-history-browser.js @@ -1,185 +1,225 @@ 'use strict'; exports.__esModule = true; -exports.configure = configure; -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } +exports.configure = configure; function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } -var _coreJs = require('core-js'); +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +require('core-js'); -var core = _interopRequireWildcard(_coreJs); +var _aureliaPal = require('aurelia-pal'); var _aureliaHistory = require('aurelia-history'); -var routeStripper = /^#?\/*|\s+$/g; +var LinkHandler = (function () { + function LinkHandler() { + _classCallCheck(this, LinkHandler); + } -var rootStripper = /^\/+|\/+$/g; + LinkHandler.prototype.activate = function activate(history) {}; -var trailingSlash = /\/$/; + LinkHandler.prototype.deactivate = function deactivate() {}; -var absoluteUrl = /^([a-z][a-z0-9+\-.]*:)?\/\//i; + return LinkHandler; +})(); -function updateHash(location, fragment, replace) { - if (replace) { - var href = location.href.replace(/(javascript:|#).*$/, ''); - location.replace(href + '#' + fragment); - } else { - location.hash = '#' + fragment; - } -} +exports.LinkHandler = LinkHandler; -var BrowserHistory = (function (_History) { - _inherits(BrowserHistory, _History); +var DefaultLinkHandler = (function (_LinkHandler) { + _inherits(DefaultLinkHandler, _LinkHandler); - function BrowserHistory() { - _classCallCheck(this, BrowserHistory); + function DefaultLinkHandler() { + var _this = this; - _History.call(this); + _classCallCheck(this, DefaultLinkHandler); - this.interval = 50; - this.active = false; - this.previousFragment = ''; - this._checkUrlCallback = this.checkUrl.bind(this); + _LinkHandler.call(this); - if (typeof window !== 'undefined') { - this.location = window.location; - this.history = window.history; - } + this.handler = function (e) { + var _DefaultLinkHandler$getEventInfo = DefaultLinkHandler.getEventInfo(e); + + var shouldHandleEvent = _DefaultLinkHandler$getEventInfo.shouldHandleEvent; + var href = _DefaultLinkHandler$getEventInfo.href; + + if (shouldHandleEvent) { + e.preventDefault(); + _this.history.navigate(href); + } + }; } - BrowserHistory.prototype.getHash = function getHash(window) { - var match = (window || this).location.href.match(/#(.*)$/); - return match ? match[1] : ''; + DefaultLinkHandler.prototype.activate = function activate(history) { + if (history._hasPushState) { + this.history = history; + _aureliaPal.DOM.addEventListener('click', this.handler, true); + } }; - BrowserHistory.prototype.getFragment = function getFragment(fragment, forcePushState) { - var root = undefined; + DefaultLinkHandler.prototype.deactivate = function deactivate() { + _aureliaPal.DOM.removeEventListener('click', this.handler); + }; - if (!fragment) { - if (this._hasPushState || !this._wantsHashChange || forcePushState) { - fragment = this.location.pathname + this.location.search; - root = this.root.replace(trailingSlash, ''); - if (!fragment.indexOf(root)) { - fragment = fragment.substr(root.length); - } - } else { - fragment = this.getHash(); + DefaultLinkHandler.getEventInfo = function getEventInfo(event) { + var info = { + shouldHandleEvent: false, + href: null, + anchor: null + }; + + var target = DefaultLinkHandler.findClosestAnchor(event.target); + if (!target || !DefaultLinkHandler.targetIsThisWindow(target)) { + return info; + } + + if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) { + return info; + } + + var href = target.getAttribute('href'); + info.anchor = target; + info.href = href; + + var hasModifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey; + var isRelative = href && !(href.charAt(0) === '#' || /^[a-z]+:/i.test(href)); + + info.shouldHandleEvent = !hasModifierKey && isRelative; + return info; + }; + + DefaultLinkHandler.findClosestAnchor = function findClosestAnchor(el) { + while (el) { + if (el.tagName === 'A') { + return el; } + + el = el.parentNode; } + }; - return '/' + fragment.replace(routeStripper, ''); + DefaultLinkHandler.targetIsThisWindow = function targetIsThisWindow(target) { + var targetWindow = target.getAttribute('target'); + var win = _aureliaPal.PLATFORM.global; + + return !targetWindow || targetWindow === win.name || targetWindow === '_self' || targetWindow === 'top' && win === win.top; }; + return DefaultLinkHandler; +})(LinkHandler); + +exports.DefaultLinkHandler = DefaultLinkHandler; + +function configure(config) { + config.singleton(_aureliaHistory.History, BrowserHistory); + + if (!config.container.hasHandler(LinkHandler)) { + config.transient(LinkHandler, DefaultLinkHandler); + } +} + +var BrowserHistory = (function (_History) { + _inherits(BrowserHistory, _History); + + _createClass(BrowserHistory, null, [{ + key: 'inject', + value: [LinkHandler], + enumerable: true + }]); + + function BrowserHistory(linkHandler) { + _classCallCheck(this, BrowserHistory); + + _History.call(this); + + this._isActive = false; + this._checkUrlCallback = this._checkUrl.bind(this); + + this.location = _aureliaPal.PLATFORM.location; + this.history = _aureliaPal.PLATFORM.history; + this.linkHandler = linkHandler; + } + BrowserHistory.prototype.activate = function activate(options) { - if (this.active) { + if (this._isActive) { throw new Error('History has already been activated.'); } - this.active = true; + var wantsPushState = !!options.pushState; + this._isActive = true; this.options = Object.assign({}, { root: '/' }, this.options, options); - this.root = this.options.root; - this._wantsHashChange = this.options.hashChange !== false; - this._wantsPushState = !!this.options.pushState; - this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); - var fragment = this.getFragment(); + this.root = ('/' + this.options.root + '/').replace(rootStripper, '/'); - this.root = ('/' + this.root + '/').replace(rootStripper, '/'); + this._wantsHashChange = this.options.hashChange !== false; + this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); + var eventName = undefined; if (this._hasPushState) { - window.onpopstate = this._checkUrlCallback; - } else if (this._wantsHashChange && 'onhashchange' in window) { - window.addEventListener('hashchange', this._checkUrlCallback); + eventName = 'popstate'; } else if (this._wantsHashChange) { - this._checkUrlTimer = setTimeout(this._checkUrlCallback, this.interval); + eventName = 'hashchange'; } - this.fragment = fragment; + _aureliaPal.PLATFORM.addEventListener(eventName, this._checkUrlCallback); - var loc = this.location; - var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root; + if (this._wantsHashChange && wantsPushState) { + var loc = this.location; + var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root; - if (this._wantsHashChange && this._wantsPushState) { if (!this._hasPushState && !atRoot) { - this.fragment = this.getFragment(null, true); + this.fragment = this._getFragment(null, true); this.location.replace(this.root + this.location.search + '#' + this.fragment); return true; } else if (this._hasPushState && atRoot && loc.hash) { - this.fragment = this.getHash().replace(routeStripper, ''); - this.history.replaceState({}, document.title, this.root + this.fragment + loc.search); + this.fragment = this._getHash().replace(routeStripper, ''); + this.history.replaceState({}, _aureliaPal.DOM.title, this.root + this.fragment + loc.search); } } - if (!this.options.silent) { - return this.loadUrl(); - } - }; - - BrowserHistory.prototype.deactivate = function deactivate() { - window.onpopstate = null; - window.removeEventListener('hashchange', this._checkUrlCallback); - clearTimeout(this._checkUrlTimer); - this.active = false; - }; - - BrowserHistory.prototype.checkUrl = function checkUrl() { - var current = this.getFragment(); - - if (this._checkUrlTimer) { - clearTimeout(this._checkUrlTimer); - this._checkUrlTimer = setTimeout(this._checkUrlCallback, this.interval); - } - - if (current === this.fragment && this.iframe) { - current = this.getFragment(this.getHash(this.iframe)); + if (!this.fragment) { + this.fragment = this._getFragment(); } - if (current === this.fragment) { - return false; - } + this.linkHandler.activate(this); - if (this.iframe) { - this.navigate(current, false); + if (!this.options.silent) { + return this._loadUrl(); } + }; - this.loadUrl(); + BrowserHistory.prototype.deactivate = function deactivate() { + _aureliaPal.PLATFORM.removeEventListener('popstate', this._checkUrlCallback); + _aureliaPal.PLATFORM.removeEventListener('hashchange', this._checkUrlCallback); + this._isActive = false; + this.linkHandler.deactivate(); }; - BrowserHistory.prototype.loadUrl = function loadUrl(fragmentOverride) { - var fragment = this.fragment = this.getFragment(fragmentOverride); + BrowserHistory.prototype.navigate = function navigate(fragment) { + var _ref = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - return this.options.routeHandler ? this.options.routeHandler(fragment) : false; - }; + var _ref$trigger = _ref.trigger; + var trigger = _ref$trigger === undefined ? true : _ref$trigger; + var _ref$replace = _ref.replace; + var replace = _ref$replace === undefined ? false : _ref$replace; - BrowserHistory.prototype.navigate = function navigate(fragment, options) { if (fragment && absoluteUrl.test(fragment)) { - window.location.href = fragment; + this.location.href = fragment; return true; } - if (!this.active) { + if (!this._isActive) { return false; } - if (options === undefined) { - options = { - trigger: true - }; - } else if (typeof options === 'boolean') { - options = { - trigger: options - }; - } + fragment = this._getFragment(fragment || ''); - fragment = this.getFragment(fragment || ''); - - if (this.fragment === fragment) { + if (this.fragment === fragment && !replace) { return false; } @@ -193,30 +233,55 @@ var BrowserHistory = (function (_History) { if (this._hasPushState) { url = url.replace('//', '/'); - this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url); + this.history[replace ? 'replaceState' : 'pushState']({}, _aureliaPal.DOM.title, url); } else if (this._wantsHashChange) { - updateHash(this.location, fragment, options.replace); + updateHash(this.location, fragment, replace); + } else { + return this.location.assign(url); + } - if (this.iframe && fragment !== this.getFragment(this.getHash(this.iframe))) { - if (!options.replace) { - this.iframe.document.open().close(); - } + if (trigger) { + return this._loadUrl(fragment); + } + }; + + BrowserHistory.prototype.navigateBack = function navigateBack() { + this.history.back(); + }; + + BrowserHistory.prototype._getHash = function _getHash() { + return this.location.hash.substr(1); + }; + + BrowserHistory.prototype._getFragment = function _getFragment(fragment, forcePushState) { + var root = undefined; - updateHash(this.iframe.location, fragment, options.replace); + if (!fragment) { + if (this._hasPushState || !this._wantsHashChange || forcePushState) { + fragment = this.location.pathname + this.location.search; + root = this.root.replace(trailingSlash, ''); + if (!fragment.indexOf(root)) { + fragment = fragment.substr(root.length); } } else { - return this.location.assign(url); - } - - if (options.trigger) { - return this.loadUrl(fragment); + fragment = this._getHash(); + } } - this.previousFragment = fragment; + return '/' + fragment.replace(routeStripper, ''); }; - BrowserHistory.prototype.navigateBack = function navigateBack() { - this.history.back(); + BrowserHistory.prototype._checkUrl = function _checkUrl() { + var current = this._getFragment(); + if (current !== this.fragment) { + this._loadUrl(); + } + }; + + BrowserHistory.prototype._loadUrl = function _loadUrl(fragmentOverride) { + var fragment = this.fragment = this._getFragment(fragmentOverride); + + return this.options.routeHandler ? this.options.routeHandler(fragment) : false; }; return BrowserHistory; @@ -224,6 +289,19 @@ var BrowserHistory = (function (_History) { exports.BrowserHistory = BrowserHistory; -function configure(config) { - config.singleton(_aureliaHistory.History, BrowserHistory); +var routeStripper = /^#?\/*|\s+$/g; + +var rootStripper = /^\/+|\/+$/g; + +var trailingSlash = /\/$/; + +var absoluteUrl = /^([a-z][a-z0-9+\-.]*:)?\/\//i; + +function updateHash(location, fragment, replace) { + if (replace) { + var href = location.href.replace(/(javascript:|#).*$/, ''); + location.replace(href + '#' + fragment); + } else { + location.hash = '#' + fragment; + } } \ No newline at end of file diff --git a/dist/es6/aurelia-history-browser.d.ts b/dist/es6/aurelia-history-browser.d.ts index 9de1235..183a630 100644 --- a/dist/es6/aurelia-history-browser.d.ts +++ b/dist/es6/aurelia-history-browser.d.ts @@ -1,18 +1,68 @@ declare module 'aurelia-history-browser' { - import * as core from 'core-js'; + import 'core-js'; + import { DOM, PLATFORM } from 'aurelia-pal'; import { History } from 'aurelia-history'; /** - * An implementation of the basic history api. + * Class responsible for handling interactions that should trigger browser history navigations. */ - export class BrowserHistory extends History { + export class LinkHandler { + + /** + * Activate the instance. + * + * @param history The BrowserHistory instance that navigations should be dispatched to. + */ + activate(history: BrowserHistory): any; /** - * Creates an instance of BrowserHistory. + * Deactivate the instance. Event handlers and other resources should be cleaned up here. */ + deactivate(): any; + } + + /** + * The default LinkHandler implementation. Navigations are triggered by click events on + * anchor elements with relative hrefs when the history instance is using pushstate. + */ + export class DefaultLinkHandler extends LinkHandler { constructor(); - getHash(window?: Window): string; - getFragment(fragment: string, forcePushState?: boolean): string; + activate(history: BrowserHistory): any; + deactivate(): any; + + /** + * Gets the href and a "should handle" recommendation, given an Event. + * + * @param event The Event to inspect for target anchor and href. + */ + static getEventInfo(event: Event): Object; + + /** + * Finds the closest ancestor that's an anchor element. + * + * @param el The element to search upward from. + */ + static findClosestAnchor(el: Element): Element; + + /** + * Gets a value indicating whether or not an anchor targets the current window. + * + * @param target The anchor element whose target should be inspected. + */ + static targetIsThisWindow(target: Element): boolean; + } + + /** + * Configures the plugin by registering BrowserHistory as the implementation of History in the DI container. + */ + export function configure(config: Object): void; + + /** + * An implementation of the basic history API. + */ + export class BrowserHistory extends History { + static inject: any; + constructor(linkHandler: any); /** * Activates the history object. @@ -24,24 +74,17 @@ declare module 'aurelia-history-browser' { * Deactivates the history object. */ deactivate(): void; - checkUrl(): boolean; - loadUrl(fragmentOverride: string): boolean; /** * Causes a history navigation to occur. * @param fragment The history fragment to navigate to. * @param options The set of options that specify how the navigation should occur. */ - navigate(fragment?: string, options?: Object): boolean; + navigate(fragment?: string, undefined?: any): boolean; /** * Causes the history state to navigate back. */ navigateBack(): void; } - - /** - * Configures the plugin by registering BrowserHistory as the implementor of History in the DI container. - */ - export function configure(config: Object): void; } \ No newline at end of file diff --git a/dist/es6/aurelia-history-browser.js b/dist/es6/aurelia-history-browser.js index a0b818e..c638d86 100644 --- a/dist/es6/aurelia-history-browser.js +++ b/dist/es6/aurelia-history-browser.js @@ -1,74 +1,142 @@ -import * as core from 'core-js'; -import { History } from 'aurelia-history'; +import 'core-js'; +import {DOM,PLATFORM} from 'aurelia-pal'; +import {History} from 'aurelia-history'; -// Cached regex for stripping a leading hash/slash and trailing space. -let routeStripper = /^#?\/*|\s+$/g; - -// Cached regex for stripping leading and trailing slashes. -let rootStripper = /^\/+|\/+$/g; - -// Cached regex for removing a trailing slash. -let trailingSlash = /\/$/; - -// Cached regex for detecting if a URL is absolute, -// i.e., starts with a scheme or is scheme-relative. -// See http://www.ietf.org/rfc/rfc2396.txt section 3.1 for valid scheme format -const absoluteUrl = /^([a-z][a-z0-9+\-.]*:)?\/\//i; +/** + * Class responsible for handling interactions that should trigger browser history navigations. + */ +export class LinkHandler { + /** + * Activate the instance. + * + * @param history The BrowserHistory instance that navigations should be dispatched to. + */ + activate(history: BrowserHistory) {} -// Update the hash location, either replacing the current entry, or adding -// a new one to the browser history. -function updateHash(location, fragment, replace) { - if (replace) { - let href = location.href.replace(/(javascript:|#).*$/, ''); - location.replace(href + '#' + fragment); - } else { - // Some browsers require that `hash` contains a leading #. - location.hash = '#' + fragment; - } + /** + * Deactivate the instance. Event handlers and other resources should be cleaned up here. + */ + deactivate() {} } /** - * An implementation of the basic history api. + * The default LinkHandler implementation. Navigations are triggered by click events on + * anchor elements with relative hrefs when the history instance is using pushstate. */ -export class BrowserHistory extends History { - /** - * Creates an instance of BrowserHistory. - */ +export class DefaultLinkHandler extends LinkHandler { constructor() { super(); - this.interval = 50; - this.active = false; - this.previousFragment = ''; - this._checkUrlCallback = this.checkUrl.bind(this); + this.handler = (e) => { + let {shouldHandleEvent, href} = DefaultLinkHandler.getEventInfo(e); - if (typeof window !== 'undefined') { - this.location = window.location; - this.history = window.history; + if (shouldHandleEvent) { + e.preventDefault(); + this.history.navigate(href); + } + }; + } + + activate(history: BrowserHistory) { + if (history._hasPushState) { + this.history = history; + DOM.addEventListener('click', this.handler, true); } } - getHash(window?: Window): string { - let match = (window || this).location.href.match(/#(.*)$/); - return match ? match[1] : ''; + deactivate() { + DOM.removeEventListener('click', this.handler); } - getFragment(fragment: string, forcePushState?: boolean): string { - let root; + /** + * Gets the href and a "should handle" recommendation, given an Event. + * + * @param event The Event to inspect for target anchor and href. + */ + static getEventInfo(event: Event): Object { + let info = { + shouldHandleEvent: false, + href: null, + anchor: null + }; + + let target = DefaultLinkHandler.findClosestAnchor(event.target); + if (!target || !DefaultLinkHandler.targetIsThisWindow(target)) { + return info; + } - if (!fragment) { - if (this._hasPushState || !this._wantsHashChange || forcePushState) { - fragment = this.location.pathname + this.location.search; - root = this.root.replace(trailingSlash, ''); - if (!fragment.indexOf(root)) { - fragment = fragment.substr(root.length); - } - } else { - fragment = this.getHash(); + if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) { + return info; + } + + let href = target.getAttribute('href'); + info.anchor = target; + info.href = href; + + let hasModifierKey = (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey); + let isRelative = href && !(href.charAt(0) === '#' || (/^[a-z]+:/i).test(href)); + + info.shouldHandleEvent = !hasModifierKey && isRelative; + return info; + } + + /** + * Finds the closest ancestor that's an anchor element. + * + * @param el The element to search upward from. + */ + static findClosestAnchor(el: Element): Element { + while (el) { + if (el.tagName === 'A') { + return el; } + + el = el.parentNode; } + } - return '/' + fragment.replace(routeStripper, ''); + /** + * Gets a value indicating whether or not an anchor targets the current window. + * + * @param target The anchor element whose target should be inspected. + */ + static targetIsThisWindow(target: Element): boolean { + let targetWindow = target.getAttribute('target'); + let win = PLATFORM.global; + + return !targetWindow || + targetWindow === win.name || + targetWindow === '_self' || + (targetWindow === 'top' && win === win.top); + } +} + +/** + * Configures the plugin by registering BrowserHistory as the implementation of History in the DI container. + */ +export function configure(config: Object): void { + config.singleton(History, BrowserHistory); + + if (!config.container.hasHandler(LinkHandler)) { + config.transient(LinkHandler, DefaultLinkHandler); + } +} + +/** + * An implementation of the basic history API. + */ +export class BrowserHistory extends History { + static inject = [LinkHandler]; + + constructor(linkHandler) { + super(); + + this._isActive = false; + this._checkUrlCallback = this._checkUrl.bind(this); + + this.location = PLATFORM.location; + this.history = PLATFORM.history; + this.linkHandler = linkHandler; } /** @@ -76,48 +144,42 @@ export class BrowserHistory extends History { * @param options The set of options to activate history with. */ activate(options?: Object): boolean { - if (this.active) { + if (this._isActive) { throw new Error('History has already been activated.'); } - this.active = true; + let wantsPushState = !!options.pushState; - // Figure out the initial configuration. Do we need an iframe? - // Is pushState desired ... is it available? + this._isActive = true; this.options = Object.assign({}, { root: '/' }, this.options, options); - this.root = this.options.root; - this._wantsHashChange = this.options.hashChange !== false; - this._wantsPushState = !!this.options.pushState; - this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); - - let fragment = this.getFragment(); // Normalize root to always include a leading and trailing slash. - this.root = ('/' + this.root + '/').replace(rootStripper, '/'); + this.root = ('/' + this.options.root + '/').replace(rootStripper, '/'); + + this._wantsHashChange = this.options.hashChange !== false; + this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); - // Depending on whether we're using pushState or hashes, and whether - // 'onhashchange' is supported, determine how we check the URL state. + // Determine how we check the URL state. + let eventName; if (this._hasPushState) { - window.onpopstate = this._checkUrlCallback; - } else if (this._wantsHashChange && ('onhashchange' in window)) { - window.addEventListener('hashchange', this._checkUrlCallback); + eventName = 'popstate'; } else if (this._wantsHashChange) { - this._checkUrlTimer = setTimeout(this._checkUrlCallback, this.interval); + eventName = 'hashchange'; } + PLATFORM.addEventListener(eventName, this._checkUrlCallback); + // Determine if we need to change the base url, for a pushState link // opened by a non-pushState browser. - this.fragment = fragment; + if (this._wantsHashChange && wantsPushState) { + // Transition from hashChange to pushState or vice versa if both are requested. + let loc = this.location; + let atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root; - let loc = this.location; - let atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root; - - // Transition from hashChange to pushState or vice versa if both are requested. - if (this._wantsHashChange && this._wantsPushState) { // If we've started off with a route from a `pushState`-enabled // browser, but we're currently in a browser that doesn't support it... if (!this._hasPushState && !atRoot) { - this.fragment = this.getFragment(null, true); + this.fragment = this._getFragment(null, true); this.location.replace(this.root + this.location.search + '#' + this.fragment); // Return immediately as browser will do redirect to new url return true; @@ -125,13 +187,19 @@ export class BrowserHistory extends History { // Or if we've started out with a hash-based route, but we're currently // in a browser where it could be `pushState`-based instead... } else if (this._hasPushState && atRoot && loc.hash) { - this.fragment = this.getHash().replace(routeStripper, ''); - this.history.replaceState({}, document.title, this.root + this.fragment + loc.search); + this.fragment = this._getHash().replace(routeStripper, ''); + this.history.replaceState({}, DOM.title, this.root + this.fragment + loc.search); } } + if (!this.fragment) { + this.fragment = this._getFragment(); + } + + this.linkHandler.activate(this); + if (!this.options.silent) { - return this.loadUrl(); + return this._loadUrl(); } } @@ -139,41 +207,10 @@ export class BrowserHistory extends History { * Deactivates the history object. */ deactivate(): void { - window.onpopstate = null; - window.removeEventListener('hashchange', this._checkUrlCallback); - clearTimeout(this._checkUrlTimer); - this.active = false; - } - - checkUrl(): boolean { - let current = this.getFragment(); - - if (this._checkUrlTimer) { - clearTimeout(this._checkUrlTimer); - this._checkUrlTimer = setTimeout(this._checkUrlCallback, this.interval); - } - - if (current === this.fragment && this.iframe) { - current = this.getFragment(this.getHash(this.iframe)); - } - - if (current === this.fragment) { - return false; - } - - if (this.iframe) { - this.navigate(current, false); - } - - this.loadUrl(); - } - - loadUrl(fragmentOverride: string): boolean { - let fragment = this.fragment = this.getFragment(fragmentOverride); - - return this.options.routeHandler ? - this.options.routeHandler(fragment) : - false; + PLATFORM.removeEventListener('popstate', this._checkUrlCallback); + PLATFORM.removeEventListener('hashchange', this._checkUrlCallback); + this._isActive = false; + this.linkHandler.deactivate(); } /** @@ -181,29 +218,19 @@ export class BrowserHistory extends History { * @param fragment The history fragment to navigate to. * @param options The set of options that specify how the navigation should occur. */ - navigate(fragment?: string, options?: Object): boolean { + navigate(fragment?: string, {trigger = true, replace = false} = {}): boolean { if (fragment && absoluteUrl.test(fragment)) { - window.location.href = fragment; + this.location.href = fragment; return true; } - if (!this.active) { + if (!this._isActive) { return false; } - if (options === undefined) { - options = { - trigger: true - }; - } else if (typeof options === 'boolean') { - options = { - trigger: options - }; - } - - fragment = this.getFragment(fragment || ''); + fragment = this._getFragment(fragment || ''); - if (this.fragment === fragment) { + if (this.fragment === fragment && !replace) { return false; } @@ -219,35 +246,20 @@ export class BrowserHistory extends History { // If pushState is available, we use it to set the fragment as a real URL. if (this._hasPushState) { url = url.replace('//', '/'); - this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url); - + this.history[replace ? 'replaceState' : 'pushState']({}, DOM.title, url); + } else if (this._wantsHashChange) { // If hash changes haven't been explicitly disabled, update the hash // fragment to store history. - } else if (this._wantsHashChange) { - updateHash(this.location, fragment, options.replace); - - if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) { - // Opening and closing the iframe tricks IE7 and earlier to push a - // history entry on hash-tag change. When replace is true, we don't - // want history. - if (!options.replace) { - this.iframe.document.open().close(); - } - - updateHash(this.iframe.location, fragment, options.replace); - } - + updateHash(this.location, fragment, replace); + } else { // If you've told us that you explicitly don't want fallback hashchange- // based history, then `navigate` becomes a page refresh. - } else { return this.location.assign(url); } - if (options.trigger) { - return this.loadUrl(fragment); + if (trigger) { + return this._loadUrl(fragment); } - - this.previousFragment = fragment; } /** @@ -256,11 +268,67 @@ export class BrowserHistory extends History { navigateBack(): void { this.history.back(); } + + _getHash(): string { + return this.location.hash.substr(1); + } + + _getFragment(fragment: string, forcePushState?: boolean): string { + let root; + + if (!fragment) { + if (this._hasPushState || !this._wantsHashChange || forcePushState) { + fragment = this.location.pathname + this.location.search; + root = this.root.replace(trailingSlash, ''); + if (!fragment.indexOf(root)) { + fragment = fragment.substr(root.length); + } + } else { + fragment = this._getHash(); + } + } + + return '/' + fragment.replace(routeStripper, ''); + } + + _checkUrl(): boolean { + let current = this._getFragment(); + if (current !== this.fragment) { + this._loadUrl(); + } + } + + _loadUrl(fragmentOverride: string): boolean { + let fragment = this.fragment = this._getFragment(fragmentOverride); + + return this.options.routeHandler ? + this.options.routeHandler(fragment) : + false; + } } -/** - * Configures the plugin by registering BrowserHistory as the implementor of History in the DI container. - */ -export function configure(config: Object): void { - config.singleton(History, BrowserHistory); +// Cached regex for stripping a leading hash/slash and trailing space. +const routeStripper = /^#?\/*|\s+$/g; + +// Cached regex for stripping leading and trailing slashes. +const rootStripper = /^\/+|\/+$/g; + +// Cached regex for removing a trailing slash. +const trailingSlash = /\/$/; + +// Cached regex for detecting if a URL is absolute, +// i.e., starts with a scheme or is scheme-relative. +// See http://www.ietf.org/rfc/rfc2396.txt section 3.1 for valid scheme format +const absoluteUrl = /^([a-z][a-z0-9+\-.]*:)?\/\//i; + +// Update the hash location, either replacing the current entry, or adding +// a new one to the browser history. +function updateHash(location, fragment, replace) { + if (replace) { + let href = location.href.replace(/(javascript:|#).*$/, ''); + location.replace(href + '#' + fragment); + } else { + // Some browsers require that `hash` contains a leading #. + location.hash = '#' + fragment; + } } diff --git a/dist/system/aurelia-history-browser.d.ts b/dist/system/aurelia-history-browser.d.ts index 9de1235..183a630 100644 --- a/dist/system/aurelia-history-browser.d.ts +++ b/dist/system/aurelia-history-browser.d.ts @@ -1,18 +1,68 @@ declare module 'aurelia-history-browser' { - import * as core from 'core-js'; + import 'core-js'; + import { DOM, PLATFORM } from 'aurelia-pal'; import { History } from 'aurelia-history'; /** - * An implementation of the basic history api. + * Class responsible for handling interactions that should trigger browser history navigations. */ - export class BrowserHistory extends History { + export class LinkHandler { + + /** + * Activate the instance. + * + * @param history The BrowserHistory instance that navigations should be dispatched to. + */ + activate(history: BrowserHistory): any; /** - * Creates an instance of BrowserHistory. + * Deactivate the instance. Event handlers and other resources should be cleaned up here. */ + deactivate(): any; + } + + /** + * The default LinkHandler implementation. Navigations are triggered by click events on + * anchor elements with relative hrefs when the history instance is using pushstate. + */ + export class DefaultLinkHandler extends LinkHandler { constructor(); - getHash(window?: Window): string; - getFragment(fragment: string, forcePushState?: boolean): string; + activate(history: BrowserHistory): any; + deactivate(): any; + + /** + * Gets the href and a "should handle" recommendation, given an Event. + * + * @param event The Event to inspect for target anchor and href. + */ + static getEventInfo(event: Event): Object; + + /** + * Finds the closest ancestor that's an anchor element. + * + * @param el The element to search upward from. + */ + static findClosestAnchor(el: Element): Element; + + /** + * Gets a value indicating whether or not an anchor targets the current window. + * + * @param target The anchor element whose target should be inspected. + */ + static targetIsThisWindow(target: Element): boolean; + } + + /** + * Configures the plugin by registering BrowserHistory as the implementation of History in the DI container. + */ + export function configure(config: Object): void; + + /** + * An implementation of the basic history API. + */ + export class BrowserHistory extends History { + static inject: any; + constructor(linkHandler: any); /** * Activates the history object. @@ -24,24 +74,17 @@ declare module 'aurelia-history-browser' { * Deactivates the history object. */ deactivate(): void; - checkUrl(): boolean; - loadUrl(fragmentOverride: string): boolean; /** * Causes a history navigation to occur. * @param fragment The history fragment to navigate to. * @param options The set of options that specify how the navigation should occur. */ - navigate(fragment?: string, options?: Object): boolean; + navigate(fragment?: string, undefined?: any): boolean; /** * Causes the history state to navigate back. */ navigateBack(): void; } - - /** - * Configures the plugin by registering BrowserHistory as the implementor of History in the DI container. - */ - export function configure(config: Object): void; } \ No newline at end of file diff --git a/dist/system/aurelia-history-browser.js b/dist/system/aurelia-history-browser.js index 52ba8e7..8f0f1fa 100644 --- a/dist/system/aurelia-history-browser.js +++ b/dist/system/aurelia-history-browser.js @@ -1,13 +1,23 @@ -System.register(['core-js', 'aurelia-history'], function (_export) { +System.register(['core-js', 'aurelia-pal', 'aurelia-history'], function (_export) { 'use strict'; - var core, History, routeStripper, rootStripper, trailingSlash, absoluteUrl, BrowserHistory; + var DOM, PLATFORM, History, LinkHandler, DefaultLinkHandler, BrowserHistory, routeStripper, rootStripper, trailingSlash, absoluteUrl; + + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); _export('configure', configure); + function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } - function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + function configure(config) { + config.singleton(History, BrowserHistory); + + if (!config.container.hasHandler(LinkHandler)) { + config.transient(LinkHandler, DefaultLinkHandler); + } + } function updateHash(location, fragment, replace) { if (replace) { @@ -17,171 +27,210 @@ System.register(['core-js', 'aurelia-history'], function (_export) { location.hash = '#' + fragment; } } - - function configure(config) { - config.singleton(History, BrowserHistory); - } - return { - setters: [function (_coreJs) { - core = _coreJs; + setters: [function (_coreJs) {}, function (_aureliaPal) { + DOM = _aureliaPal.DOM; + PLATFORM = _aureliaPal.PLATFORM; }, function (_aureliaHistory) { History = _aureliaHistory.History; }], execute: function () { - routeStripper = /^#?\/*|\s+$/g; - rootStripper = /^\/+|\/+$/g; - trailingSlash = /\/$/; - absoluteUrl = /^([a-z][a-z0-9+\-.]*:)?\/\//i; + LinkHandler = (function () { + function LinkHandler() { + _classCallCheck(this, LinkHandler); + } - BrowserHistory = (function (_History) { - _inherits(BrowserHistory, _History); + LinkHandler.prototype.activate = function activate(history) {}; - function BrowserHistory() { - _classCallCheck(this, BrowserHistory); + LinkHandler.prototype.deactivate = function deactivate() {}; - _History.call(this); + return LinkHandler; + })(); - this.interval = 50; - this.active = false; - this.previousFragment = ''; - this._checkUrlCallback = this.checkUrl.bind(this); + _export('LinkHandler', LinkHandler); - if (typeof window !== 'undefined') { - this.location = window.location; - this.history = window.history; - } + DefaultLinkHandler = (function (_LinkHandler) { + _inherits(DefaultLinkHandler, _LinkHandler); + + function DefaultLinkHandler() { + var _this = this; + + _classCallCheck(this, DefaultLinkHandler); + + _LinkHandler.call(this); + + this.handler = function (e) { + var _DefaultLinkHandler$getEventInfo = DefaultLinkHandler.getEventInfo(e); + + var shouldHandleEvent = _DefaultLinkHandler$getEventInfo.shouldHandleEvent; + var href = _DefaultLinkHandler$getEventInfo.href; + + if (shouldHandleEvent) { + e.preventDefault(); + _this.history.navigate(href); + } + }; } - BrowserHistory.prototype.getHash = function getHash(window) { - var match = (window || this).location.href.match(/#(.*)$/); - return match ? match[1] : ''; + DefaultLinkHandler.prototype.activate = function activate(history) { + if (history._hasPushState) { + this.history = history; + DOM.addEventListener('click', this.handler, true); + } }; - BrowserHistory.prototype.getFragment = function getFragment(fragment, forcePushState) { - var root = undefined; + DefaultLinkHandler.prototype.deactivate = function deactivate() { + DOM.removeEventListener('click', this.handler); + }; - if (!fragment) { - if (this._hasPushState || !this._wantsHashChange || forcePushState) { - fragment = this.location.pathname + this.location.search; - root = this.root.replace(trailingSlash, ''); - if (!fragment.indexOf(root)) { - fragment = fragment.substr(root.length); - } - } else { - fragment = this.getHash(); + DefaultLinkHandler.getEventInfo = function getEventInfo(event) { + var info = { + shouldHandleEvent: false, + href: null, + anchor: null + }; + + var target = DefaultLinkHandler.findClosestAnchor(event.target); + if (!target || !DefaultLinkHandler.targetIsThisWindow(target)) { + return info; + } + + if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) { + return info; + } + + var href = target.getAttribute('href'); + info.anchor = target; + info.href = href; + + var hasModifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey; + var isRelative = href && !(href.charAt(0) === '#' || /^[a-z]+:/i.test(href)); + + info.shouldHandleEvent = !hasModifierKey && isRelative; + return info; + }; + + DefaultLinkHandler.findClosestAnchor = function findClosestAnchor(el) { + while (el) { + if (el.tagName === 'A') { + return el; } + + el = el.parentNode; } + }; - return '/' + fragment.replace(routeStripper, ''); + DefaultLinkHandler.targetIsThisWindow = function targetIsThisWindow(target) { + var targetWindow = target.getAttribute('target'); + var win = PLATFORM.global; + + return !targetWindow || targetWindow === win.name || targetWindow === '_self' || targetWindow === 'top' && win === win.top; }; + return DefaultLinkHandler; + })(LinkHandler); + + _export('DefaultLinkHandler', DefaultLinkHandler); + + BrowserHistory = (function (_History) { + _inherits(BrowserHistory, _History); + + _createClass(BrowserHistory, null, [{ + key: 'inject', + value: [LinkHandler], + enumerable: true + }]); + + function BrowserHistory(linkHandler) { + _classCallCheck(this, BrowserHistory); + + _History.call(this); + + this._isActive = false; + this._checkUrlCallback = this._checkUrl.bind(this); + + this.location = PLATFORM.location; + this.history = PLATFORM.history; + this.linkHandler = linkHandler; + } + BrowserHistory.prototype.activate = function activate(options) { - if (this.active) { + if (this._isActive) { throw new Error('History has already been activated.'); } - this.active = true; + var wantsPushState = !!options.pushState; + this._isActive = true; this.options = Object.assign({}, { root: '/' }, this.options, options); - this.root = this.options.root; - this._wantsHashChange = this.options.hashChange !== false; - this._wantsPushState = !!this.options.pushState; - this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); - var fragment = this.getFragment(); + this.root = ('/' + this.options.root + '/').replace(rootStripper, '/'); - this.root = ('/' + this.root + '/').replace(rootStripper, '/'); + this._wantsHashChange = this.options.hashChange !== false; + this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); + var eventName = undefined; if (this._hasPushState) { - window.onpopstate = this._checkUrlCallback; - } else if (this._wantsHashChange && 'onhashchange' in window) { - window.addEventListener('hashchange', this._checkUrlCallback); + eventName = 'popstate'; } else if (this._wantsHashChange) { - this._checkUrlTimer = setTimeout(this._checkUrlCallback, this.interval); + eventName = 'hashchange'; } - this.fragment = fragment; + PLATFORM.addEventListener(eventName, this._checkUrlCallback); - var loc = this.location; - var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root; + if (this._wantsHashChange && wantsPushState) { + var loc = this.location; + var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root; - if (this._wantsHashChange && this._wantsPushState) { if (!this._hasPushState && !atRoot) { - this.fragment = this.getFragment(null, true); + this.fragment = this._getFragment(null, true); this.location.replace(this.root + this.location.search + '#' + this.fragment); return true; } else if (this._hasPushState && atRoot && loc.hash) { - this.fragment = this.getHash().replace(routeStripper, ''); - this.history.replaceState({}, document.title, this.root + this.fragment + loc.search); + this.fragment = this._getHash().replace(routeStripper, ''); + this.history.replaceState({}, DOM.title, this.root + this.fragment + loc.search); } } - if (!this.options.silent) { - return this.loadUrl(); + if (!this.fragment) { + this.fragment = this._getFragment(); } - }; - BrowserHistory.prototype.deactivate = function deactivate() { - window.onpopstate = null; - window.removeEventListener('hashchange', this._checkUrlCallback); - clearTimeout(this._checkUrlTimer); - this.active = false; - }; + this.linkHandler.activate(this); - BrowserHistory.prototype.checkUrl = function checkUrl() { - var current = this.getFragment(); - - if (this._checkUrlTimer) { - clearTimeout(this._checkUrlTimer); - this._checkUrlTimer = setTimeout(this._checkUrlCallback, this.interval); - } - - if (current === this.fragment && this.iframe) { - current = this.getFragment(this.getHash(this.iframe)); - } - - if (current === this.fragment) { - return false; - } - - if (this.iframe) { - this.navigate(current, false); + if (!this.options.silent) { + return this._loadUrl(); } + }; - this.loadUrl(); + BrowserHistory.prototype.deactivate = function deactivate() { + PLATFORM.removeEventListener('popstate', this._checkUrlCallback); + PLATFORM.removeEventListener('hashchange', this._checkUrlCallback); + this._isActive = false; + this.linkHandler.deactivate(); }; - BrowserHistory.prototype.loadUrl = function loadUrl(fragmentOverride) { - var fragment = this.fragment = this.getFragment(fragmentOverride); + BrowserHistory.prototype.navigate = function navigate(fragment) { + var _ref = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - return this.options.routeHandler ? this.options.routeHandler(fragment) : false; - }; + var _ref$trigger = _ref.trigger; + var trigger = _ref$trigger === undefined ? true : _ref$trigger; + var _ref$replace = _ref.replace; + var replace = _ref$replace === undefined ? false : _ref$replace; - BrowserHistory.prototype.navigate = function navigate(fragment, options) { if (fragment && absoluteUrl.test(fragment)) { - window.location.href = fragment; + this.location.href = fragment; return true; } - if (!this.active) { + if (!this._isActive) { return false; } - if (options === undefined) { - options = { - trigger: true - }; - } else if (typeof options === 'boolean') { - options = { - trigger: options - }; - } + fragment = this._getFragment(fragment || ''); - fragment = this.getFragment(fragment || ''); - - if (this.fragment === fragment) { + if (this.fragment === fragment && !replace) { return false; } @@ -195,36 +244,66 @@ System.register(['core-js', 'aurelia-history'], function (_export) { if (this._hasPushState) { url = url.replace('//', '/'); - this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url); + this.history[replace ? 'replaceState' : 'pushState']({}, DOM.title, url); } else if (this._wantsHashChange) { - updateHash(this.location, fragment, options.replace); + updateHash(this.location, fragment, replace); + } else { + return this.location.assign(url); + } + + if (trigger) { + return this._loadUrl(fragment); + } + }; + + BrowserHistory.prototype.navigateBack = function navigateBack() { + this.history.back(); + }; + + BrowserHistory.prototype._getHash = function _getHash() { + return this.location.hash.substr(1); + }; - if (this.iframe && fragment !== this.getFragment(this.getHash(this.iframe))) { - if (!options.replace) { - this.iframe.document.open().close(); - } + BrowserHistory.prototype._getFragment = function _getFragment(fragment, forcePushState) { + var root = undefined; - updateHash(this.iframe.location, fragment, options.replace); + if (!fragment) { + if (this._hasPushState || !this._wantsHashChange || forcePushState) { + fragment = this.location.pathname + this.location.search; + root = this.root.replace(trailingSlash, ''); + if (!fragment.indexOf(root)) { + fragment = fragment.substr(root.length); } } else { - return this.location.assign(url); - } - - if (options.trigger) { - return this.loadUrl(fragment); + fragment = this._getHash(); + } } - this.previousFragment = fragment; + return '/' + fragment.replace(routeStripper, ''); }; - BrowserHistory.prototype.navigateBack = function navigateBack() { - this.history.back(); + BrowserHistory.prototype._checkUrl = function _checkUrl() { + var current = this._getFragment(); + if (current !== this.fragment) { + this._loadUrl(); + } + }; + + BrowserHistory.prototype._loadUrl = function _loadUrl(fragmentOverride) { + var fragment = this.fragment = this._getFragment(fragmentOverride); + + return this.options.routeHandler ? this.options.routeHandler(fragment) : false; }; return BrowserHistory; })(History); _export('BrowserHistory', BrowserHistory); + + routeStripper = /^#?\/*|\s+$/g; + rootStripper = /^\/+|\/+$/g; + trailingSlash = /\/$/; + absoluteUrl = /^([a-z][a-z0-9+\-.]*:)?\/\//i; } }; }); \ No newline at end of file diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index 41712f2..b6c1c77 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -1,3 +1,55 @@ +## 0.9.0 (2015-10-13) + + +#### Bug Fixes + +* **all:** + * update to latest plugin api ([85418be2](http://github.com/aurelia/history-browser/commit/85418be278701aa3dd9c4947c2ed92d7ce1a54ef)) + * update compiler ([6be59964](http://github.com/aurelia/history-browser/commit/6be5996499d6a4668bfe58ba316462d612b61766)) +* **bower:** correct semver ranges ([9c7f33f5](http://github.com/aurelia/history-browser/commit/9c7f33f59c82075dcd505afbc8936e613685ab30)) +* **build:** + * update linting, testing and tools ([9ad076d6](http://github.com/aurelia/history-browser/commit/9ad076d6ded8b0ca607b742f69a00c76f3d3946e)) + * add missing bower bump ([5b4cc6f3](http://github.com/aurelia/history-browser/commit/5b4cc6f3ee303221f27efa2e4470c2f9acd9bb0e)) +* **history:** + * Improved previous pushState security fix by only fixing up double slashes in fra ([9d7565a1](http://github.com/aurelia/history-browser/commit/9d7565a16fe1765eff7d677c22321643c91b3890)) + * fixed regression issue which added double slashes to the pushState fragment ([57983341](http://github.com/aurelia/history-browser/commit/579833415f55372def76ccbf5fbbd1ebd939ae7b)) + * normalize fragments with a leading slash ([66998d87](http://github.com/aurelia/history-browser/commit/66998d873140c32fc2f78ce21b19356f96d8eba2)) + * fix incorrect falsey check in getFragment ([34cb8224](http://github.com/aurelia/history-browser/commit/34cb8224e24cb1761042668bb96d6c5d42e0c7cf)) +* **history-browser:** + * verify that window is defined in ctor ([78449060](http://github.com/aurelia/history-browser/commit/7844906099178c73faa65e4ee8db122cd7df3d4f)) + * Use correct import for core-js We were previously using `import core from core-j ([45d51c8c](http://github.com/aurelia/history-browser/commit/45d51c8cb525b410731300d1b70945b0f3ad4146)) + * support scheme-relative URLs in BrowserHistory.navigate() ([16fcfdd7](http://github.com/aurelia/history-browser/commit/16fcfdd71656e0ed72d98ca6bd4e2cb6a6654517)) +* **index:** update to latest configuration api ([a3681cfb](http://github.com/aurelia/history-browser/commit/a3681cfb52f58b9a8dffa37911c1eed5b15f81cc)) +* **normalize-fragment:** fix getFragment() to return normalized values ([5cb9bf30](http://github.com/aurelia/history-browser/commit/5cb9bf30689558d12a9ffb05088e2206fac4777f)) +* **package:** + * update aurelia tools and dts generator ([ee59abbc](http://github.com/aurelia/history-browser/commit/ee59abbc9465107644fef1ea6f3748c9f9e32859)) + * change jspm directories ([2b161b23](http://github.com/aurelia/history-browser/commit/2b161b23a0a1cac8b1a9e6eb5d69ee54fe148675)) + * update dependencies ([d56c60df](http://github.com/aurelia/history-browser/commit/d56c60dfa51cf129575cc0f31d9d78e06642029e)) + * update dependencies ([0f9bc51a](http://github.com/aurelia/history-browser/commit/0f9bc51aa77e2441b6ad6c2454bdb79601e35c14)) + * update Aurelia dependencies ([770590c2](http://github.com/aurelia/history-browser/commit/770590c23eb7c391e914bc40653f883de34cc8df)) + * add es6-shim dependency for Object.assign ([517b7e8e](http://github.com/aurelia/history-browser/commit/517b7e8ee94ba18ce662af21a7fdd594d9ed8adb)) + * update dependencies to latest ([cdd1c232](http://github.com/aurelia/history-browser/commit/cdd1c23295b821aa7f68b94f5d5d3a95a1b7e129)) + + +#### Features + +* **all:** leverage the PAL ([01cd524c](http://github.com/aurelia/history-browser/commit/01cd524c09016e18a46e41a572bbb0ff902bc502)) +* **build:** update compiler and switch to register module format ([ff2a572b](http://github.com/aurelia/history-browser/commit/ff2a572b4858ce16eea17b66ded4912b89919d85)) +* **docs:** generate api.json from .d.ts file ([80fd2e30](http://github.com/aurelia/history-browser/commit/80fd2e30a68c9bf4200be831887c49de766a6939)) +* **history:** enable usage as plugin ([f73c967b](http://github.com/aurelia/history-browser/commit/f73c967b64d75da43d31ad8b7305c495ebdf08a8)) +* **history-browser:** + * move link handler from router and allow custom implementations ([f0129d89](http://github.com/aurelia/history-browser/commit/f0129d8959219cae16e729b15d4da52aac504bff)) + * adjust default navigate options ([0f17175d](http://github.com/aurelia/history-browser/commit/0f17175d0591559e102ea8ecabf51b0f61c8506e)) + * allow replace option to trigger a navigation to the same fragment ([1eab2945](http://github.com/aurelia/history-browser/commit/1eab2945bcdcc74eb366ea57acce160a8e0b4553), closes [#201](http://github.com/aurelia/history-browser/issues/201)) + + +#### Breaking Changes + +* trigger now defaults to true when other options are specified, and options must be passed as an object; a boolean is no longer supported as a shortcut for trigger. + + ([0f17175d](http://github.com/aurelia/history-browser/commit/0f17175d0591559e102ea8ecabf51b0f61c8506e)) + + ## 0.8.0 (2015-09-04) diff --git a/doc/api.json b/doc/api.json index faeeeb2..7247c72 100644 --- a/doc/api.json +++ b/doc/api.json @@ -6,7 +6,7 @@ "flags": {}, "children": [ { - "id": 3, + "id": 26, "name": "BrowserHistory", "kind": 128, "kindString": "Class", @@ -14,41 +14,62 @@ "isExported": true }, "comment": { - "shortText": "An implementation of the basic history api." + "shortText": "An implementation of the basic history API." }, "children": [ { - "id": 4, + "id": 28, "name": "constructor", "kind": 512, "kindString": "Constructor", "flags": { "isExported": true }, - "comment": { - "shortText": "Creates an instance of BrowserHistory." - }, "signatures": [ { - "id": 5, + "id": 29, "name": "new BrowserHistory", "kind": 16384, "kindString": "Constructor signature", "flags": {}, - "comment": { - "shortText": "Creates an instance of BrowserHistory." - }, + "parameters": [ + { + "id": 30, + "name": "linkHandler", + "kind": 32768, + "kindString": "Parameter", + "flags": {}, + "type": { + "type": "instrinct", + "name": "any" + } + } + ], "type": { "type": "reference", "name": "BrowserHistory", - "id": 3, + "id": 26, "moduleName": "\"aurelia-history-browser\"" } } ] }, { - "id": 13, + "id": 27, + "name": "inject", + "kind": 1024, + "kindString": "Property", + "flags": { + "isStatic": true, + "isExported": true + }, + "type": { + "type": "instrinct", + "name": "any" + } + }, + { + "id": 31, "name": "activate", "kind": 2048, "kindString": "Method", @@ -57,7 +78,7 @@ }, "signatures": [ { - "id": 14, + "id": 32, "name": "activate", "kind": 4096, "kindString": "Call signature", @@ -67,7 +88,7 @@ }, "parameters": [ { - "id": 15, + "id": 33, "name": "options", "kind": 32768, "kindString": "Parameter", @@ -90,40 +111,18 @@ "overwrites": { "type": "reference", "name": "History.activate", - "id": 41 + "id": 54 } } ], "overwrites": { "type": "reference", "name": "History.activate", - "id": 41 + "id": 54 } }, { - "id": 18, - "name": "checkUrl", - "kind": 2048, - "kindString": "Method", - "flags": { - "isExported": true - }, - "signatures": [ - { - "id": 19, - "name": "checkUrl", - "kind": 4096, - "kindString": "Call signature", - "flags": {}, - "type": { - "type": "instrinct", - "name": "boolean" - } - } - ] - }, - { - "id": 16, + "id": 34, "name": "deactivate", "kind": 2048, "kindString": "Method", @@ -132,7 +131,7 @@ }, "signatures": [ { - "id": 17, + "id": 35, "name": "deactivate", "kind": 4096, "kindString": "Call signature", @@ -147,19 +146,19 @@ "overwrites": { "type": "reference", "name": "History.deactivate", - "id": 44 + "id": 57 } } ], "overwrites": { "type": "reference", "name": "History.deactivate", - "id": 44 + "id": 57 } }, { - "id": 9, - "name": "getFragment", + "id": 36, + "name": "navigate", "kind": 2048, "kindString": "Method", "flags": { @@ -167,26 +166,34 @@ }, "signatures": [ { - "id": 10, - "name": "getFragment", + "id": 37, + "name": "navigate", "kind": 4096, "kindString": "Call signature", "flags": {}, + "comment": { + "shortText": "Causes a history navigation to occur." + }, "parameters": [ { - "id": 11, + "id": 38, "name": "fragment", "kind": 32768, "kindString": "Parameter", - "flags": {}, + "flags": { + "isOptional": true + }, + "comment": { + "text": "The history fragment to navigate to." + }, "type": { "type": "instrinct", "name": "string" } }, { - "id": 12, - "name": "forcePushState", + "id": 39, + "name": "undefined", "kind": 32768, "kindString": "Parameter", "flags": { @@ -194,20 +201,136 @@ }, "type": { "type": "instrinct", - "name": "boolean" + "name": "any" } } ], "type": { "type": "instrinct", - "name": "string" + "name": "boolean" + }, + "overwrites": { + "type": "reference", + "name": "History.navigate", + "id": 59 + } + } + ], + "overwrites": { + "type": "reference", + "name": "History.navigate", + "id": 59 + } + }, + { + "id": 40, + "name": "navigateBack", + "kind": 2048, + "kindString": "Method", + "flags": { + "isExported": true + }, + "signatures": [ + { + "id": 41, + "name": "navigateBack", + "kind": 4096, + "kindString": "Call signature", + "flags": {}, + "comment": { + "shortText": "Causes the history state to navigate back." + }, + "type": { + "type": "instrinct", + "name": "void" + }, + "overwrites": { + "type": "reference", + "name": "History.navigateBack", + "id": 63 } } + ], + "overwrites": { + "type": "reference", + "name": "History.navigateBack", + "id": 63 + } + } + ], + "groups": [ + { + "title": "Constructors", + "kind": 512, + "children": [ + 28 + ] + }, + { + "title": "Properties", + "kind": 1024, + "children": [ + 27 ] }, { - "id": 6, - "name": "getHash", + "title": "Methods", + "kind": 2048, + "children": [ + 31, + 34, + 36, + 40 + ] + } + ], + "extendedTypes": [ + { + "type": "reference", + "name": "History", + "id": 53 + } + ] + }, + { + "id": 9, + "name": "DefaultLinkHandler", + "kind": 128, + "kindString": "Class", + "flags": { + "isExported": true + }, + "comment": { + "shortText": "The default LinkHandler implementation. Navigations are triggered by click events on\nanchor elements with relative hrefs when the history instance is using pushstate." + }, + "children": [ + { + "id": 10, + "name": "constructor", + "kind": 512, + "kindString": "Constructor", + "flags": { + "isExported": true + }, + "signatures": [ + { + "id": 11, + "name": "new DefaultLinkHandler", + "kind": 16384, + "kindString": "Constructor signature", + "flags": {}, + "type": { + "type": "reference", + "name": "DefaultLinkHandler", + "id": 9, + "moduleName": "\"aurelia-history-browser\"" + } + } + ] + }, + { + "id": 12, + "name": "activate", "kind": 2048, "kindString": "Method", "flags": { @@ -215,140 +338,288 @@ }, "signatures": [ { - "id": 7, - "name": "getHash", + "id": 13, + "name": "activate", "kind": 4096, "kindString": "Call signature", "flags": {}, "parameters": [ { - "id": 8, - "name": "window", + "id": 14, + "name": "history", "kind": 32768, "kindString": "Parameter", - "flags": { - "isOptional": true - }, + "flags": {}, "type": { "type": "reference", - "name": "Window" + "name": "BrowserHistory", + "id": 26, + "moduleName": "\"aurelia-history-browser\"" } } ], "type": { "type": "instrinct", - "name": "string" + "name": "any" + }, + "overwrites": { + "type": "reference", + "name": "LinkHandler.activate", + "id": 4 } } - ] + ], + "overwrites": { + "type": "reference", + "name": "LinkHandler.activate", + "id": 4 + } + }, + { + "id": 15, + "name": "deactivate", + "kind": 2048, + "kindString": "Method", + "flags": { + "isExported": true + }, + "signatures": [ + { + "id": 16, + "name": "deactivate", + "kind": 4096, + "kindString": "Call signature", + "flags": {}, + "type": { + "type": "instrinct", + "name": "any" + }, + "overwrites": { + "type": "reference", + "name": "LinkHandler.deactivate", + "id": 7 + } + } + ], + "overwrites": { + "type": "reference", + "name": "LinkHandler.deactivate", + "id": 7 + } }, { "id": 20, - "name": "loadUrl", + "name": "findClosestAnchor", "kind": 2048, "kindString": "Method", "flags": { + "isStatic": true, "isExported": true }, "signatures": [ { "id": 21, - "name": "loadUrl", + "name": "findClosestAnchor", "kind": 4096, "kindString": "Call signature", "flags": {}, + "comment": { + "shortText": "Finds the closest ancestor that's an anchor element." + }, "parameters": [ { "id": 22, - "name": "fragmentOverride", + "name": "el", "kind": 32768, "kindString": "Parameter", "flags": {}, + "comment": { + "text": "The element to search upward from.\n" + }, "type": { - "type": "instrinct", - "name": "string" + "type": "reference", + "name": "Element" } } ], "type": { - "type": "instrinct", - "name": "boolean" + "type": "reference", + "name": "Element" + } + } + ] + }, + { + "id": 17, + "name": "getEventInfo", + "kind": 2048, + "kindString": "Method", + "flags": { + "isStatic": true, + "isExported": true + }, + "signatures": [ + { + "id": 18, + "name": "getEventInfo", + "kind": 4096, + "kindString": "Call signature", + "flags": {}, + "comment": { + "shortText": "Gets the href and a \"should handle\" recommendation, given an Event." + }, + "parameters": [ + { + "id": 19, + "name": "event", + "kind": 32768, + "kindString": "Parameter", + "flags": {}, + "comment": { + "text": "The Event to inspect for target anchor and href.\n" + }, + "type": { + "type": "reference", + "name": "Event" + } + } + ], + "type": { + "type": "reference", + "name": "Object" } } ] }, { "id": 23, - "name": "navigate", + "name": "targetIsThisWindow", "kind": 2048, "kindString": "Method", "flags": { + "isStatic": true, "isExported": true }, "signatures": [ { "id": 24, - "name": "navigate", + "name": "targetIsThisWindow", "kind": 4096, "kindString": "Call signature", "flags": {}, "comment": { - "shortText": "Causes a history navigation to occur." + "shortText": "Gets a value indicating whether or not an anchor targets the current window." }, "parameters": [ { "id": 25, - "name": "fragment", + "name": "target", "kind": 32768, "kindString": "Parameter", - "flags": { - "isOptional": true - }, + "flags": {}, "comment": { - "text": "The history fragment to navigate to." + "text": "The anchor element whose target should be inspected.\n" }, "type": { - "type": "instrinct", - "name": "string" + "type": "reference", + "name": "Element" } - }, + } + ], + "type": { + "type": "instrinct", + "name": "boolean" + } + } + ] + } + ], + "groups": [ + { + "title": "Constructors", + "kind": 512, + "children": [ + 10 + ] + }, + { + "title": "Methods", + "kind": 2048, + "children": [ + 12, + 15, + 20, + 17, + 23 + ] + } + ], + "extendedTypes": [ + { + "type": "reference", + "name": "LinkHandler", + "id": 3 + } + ] + }, + { + "id": 3, + "name": "LinkHandler", + "kind": 128, + "kindString": "Class", + "flags": { + "isExported": true + }, + "comment": { + "shortText": "Class responsible for handling interactions that should trigger browser history navigations." + }, + "children": [ + { + "id": 4, + "name": "activate", + "kind": 2048, + "kindString": "Method", + "flags": { + "isExported": true + }, + "signatures": [ + { + "id": 5, + "name": "activate", + "kind": 4096, + "kindString": "Call signature", + "flags": {}, + "comment": { + "shortText": "Activate the instance." + }, + "parameters": [ { - "id": 26, - "name": "options", + "id": 6, + "name": "history", "kind": 32768, "kindString": "Parameter", - "flags": { - "isOptional": true - }, + "flags": {}, "comment": { - "text": "The set of options that specify how the navigation should occur.\n" + "text": "The BrowserHistory instance that navigations should be dispatched to.\n" }, "type": { "type": "reference", - "name": "Object" + "name": "BrowserHistory", + "id": 26, + "moduleName": "\"aurelia-history-browser\"" } } ], "type": { "type": "instrinct", - "name": "boolean" - }, - "overwrites": { - "type": "reference", - "name": "History.navigate", - "id": 46 + "name": "any" } } - ], - "overwrites": { - "type": "reference", - "name": "History.navigate", - "id": 46 - } + ] }, { - "id": 27, - "name": "navigateBack", + "id": 7, + "name": "deactivate", "kind": 2048, "kindString": "Method", "flags": { @@ -356,65 +627,42 @@ }, "signatures": [ { - "id": 28, - "name": "navigateBack", + "id": 8, + "name": "deactivate", "kind": 4096, "kindString": "Call signature", "flags": {}, "comment": { - "shortText": "Causes the history state to navigate back." + "shortText": "Deactivate the instance. Event handlers and other resources should be cleaned up here." }, "type": { "type": "instrinct", - "name": "void" - }, - "overwrites": { - "type": "reference", - "name": "History.navigateBack", - "id": 50 + "name": "any" } } - ], - "overwrites": { - "type": "reference", - "name": "History.navigateBack", - "id": 50 - } + ] } ], "groups": [ - { - "title": "Constructors", - "kind": 512, - "children": [ - 4 - ] - }, { "title": "Methods", "kind": 2048, "children": [ - 13, - 18, - 16, - 9, - 6, - 20, - 23, - 27 + 4, + 7 ] } ], - "extendedTypes": [ + "extendedBy": [ { "type": "reference", - "name": "History", - "id": 40 + "name": "DefaultLinkHandler", + "id": 9 } ] }, { - "id": 29, + "id": 42, "name": "configure", "kind": 64, "kindString": "Function", @@ -423,17 +671,17 @@ }, "signatures": [ { - "id": 30, + "id": 43, "name": "configure", "kind": 4096, "kindString": "Call signature", "flags": {}, "comment": { - "shortText": "Configures the plugin by registering BrowserHistory as the implementor of History in the DI container." + "shortText": "Configures the plugin by registering BrowserHistory as the implementation of History in the DI container." }, "parameters": [ { - "id": 31, + "id": 44, "name": "config", "kind": 32768, "kindString": "Parameter", @@ -457,6 +705,8 @@ "title": "Classes", "kind": 128, "children": [ + 26, + 9, 3 ] }, @@ -464,7 +714,7 @@ "title": "Functions", "kind": 64, "children": [ - 29 + 42 ] } ] diff --git a/package.json b/package.json index d7deb6b..a7a8603 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aurelia-history-browser", - "version": "0.8.0", + "version": "0.9.0", "description": "An implementation of the Aurelia history interface based on standard browser hash change and push state mechanisms.", "keywords": [ "aurelia", @@ -25,8 +25,8 @@ "lib": "dist/amd" }, "dependencies": { - "aurelia-history": "github:aurelia/history@^0.7.0", - "aurelia-pal": "github:aurelia/pal@^0.1.4", + "aurelia-history": "github:aurelia/history@^0.8.0", + "aurelia-pal": "github:aurelia/pal@^0.2.0", "core-js": "npm:core-js@^0.9.5" }, "devDependencies": { @@ -66,30 +66,8 @@ "yargs": "^2.1.1" }, "aurelia": { - "usedBy": [ - "aurelia-bootstrapper" - ], "documentation": { - "links": [ - { - "rel": "license", - "mediaType": "text/plain", - "title": "The MIT License (MIT)", - "href": "LICENSE" - }, - { - "rel": "describedby", - "mediaType": "application/aurelia-doc+json", - "title": "API", - "href": "doc/api.json" - }, - { - "rel": "version-history", - "mediaType": "text/markdown", - "title": "Change Log", - "href": "doc/CHANGELOG.md" - } - ] + "articles": [] } } }