6f7fb05a9958bb4514ecea1fe1b71ec9e335a62098640c700f653ae15e4cdd77a786e90cdff5fa29576e1634f9d1ac2ffaef6380028c145eb46b337fbf040b 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. /* @flow */
  2. import type Router from '../index'
  3. import { History } from './base'
  4. import { cleanPath } from '../util/path'
  5. import { START } from '../util/route'
  6. import { setupScroll, handleScroll } from '../util/scroll'
  7. import { pushState, replaceState, supportsPushState } from '../util/push-state'
  8. export class HTML5History extends History {
  9. _startLocation: string
  10. constructor (router: Router, base: ?string) {
  11. super(router, base)
  12. this._startLocation = getLocation(this.base)
  13. }
  14. setupListeners () {
  15. if (this.listeners.length > 0) {
  16. return
  17. }
  18. const router = this.router
  19. const expectScroll = router.options.scrollBehavior
  20. const supportsScroll = supportsPushState && expectScroll
  21. if (supportsScroll) {
  22. this.listeners.push(setupScroll())
  23. }
  24. const handleRoutingEvent = () => {
  25. const current = this.current
  26. // Avoiding first `popstate` event dispatched in some browsers but first
  27. // history route not updated since async guard at the same time.
  28. const location = getLocation(this.base)
  29. if (this.current === START && location === this._startLocation) {
  30. return
  31. }
  32. this.transitionTo(location, route => {
  33. if (supportsScroll) {
  34. handleScroll(router, route, current, true)
  35. }
  36. })
  37. }
  38. window.addEventListener('popstate', handleRoutingEvent)
  39. this.listeners.push(() => {
  40. window.removeEventListener('popstate', handleRoutingEvent)
  41. })
  42. }
  43. go (n: number) {
  44. window.history.go(n)
  45. }
  46. push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  47. const { current: fromRoute } = this
  48. this.transitionTo(location, route => {
  49. pushState(cleanPath(this.base + route.fullPath))
  50. handleScroll(this.router, route, fromRoute, false)
  51. onComplete && onComplete(route)
  52. }, onAbort)
  53. }
  54. replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  55. const { current: fromRoute } = this
  56. this.transitionTo(location, route => {
  57. replaceState(cleanPath(this.base + route.fullPath))
  58. handleScroll(this.router, route, fromRoute, false)
  59. onComplete && onComplete(route)
  60. }, onAbort)
  61. }
  62. ensureURL (push?: boolean) {
  63. if (getLocation(this.base) !== this.current.fullPath) {
  64. const current = cleanPath(this.base + this.current.fullPath)
  65. push ? pushState(current) : replaceState(current)
  66. }
  67. }
  68. getCurrentLocation (): string {
  69. return getLocation(this.base)
  70. }
  71. }
  72. export function getLocation (base: string): string {
  73. let path = window.location.pathname
  74. if (base && path.toLowerCase().indexOf(base.toLowerCase()) === 0) {
  75. path = path.slice(base.length)
  76. }
  77. return (path || '/') + window.location.search + window.location.hash
  78. }