| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- ###
- * Based on github.com/visionmedia/page.js
- * Licensed under the MIT license
- * Copyright 2012 TJ Holowaychuk <tj@vision-media.ca>
- ###
- running = false
- currentState = null
- callbacks = []
- @page = (value, fn) ->
- if typeof value is 'function'
- page '*', value
- else if typeof fn is 'function'
- route = new Route(value)
- callbacks.push route.middleware(fn)
- else if typeof value is 'string'
- page.show(value, fn)
- else
- page.start(value)
- return
- page.start = (options = {}) ->
- unless running
- running = true
- addEventListener 'popstate', onpopstate
- addEventListener 'click', onclick
- page.replace currentPath(), null, null, true
- return
- page.stop = ->
- if running
- running = false
- removeEventListener 'click', onclick
- removeEventListener 'popstate', onpopstate
- return
- page.show = (path, state) ->
- return if path is currentState?.path
- context = new Context(path, state)
- currentState = context.state
- page.dispatch(context)
- context.pushState()
- track()
- context
- page.replace = (path, state, skipDispatch, init) ->
- context = new Context(path, state or currentState)
- context.init = init
- currentState = context.state
- page.dispatch(context) unless skipDispatch
- context.replaceState()
- track() unless init or skipDispatch
- context
- page.dispatch = (context) ->
- i = 0
- next = ->
- fn(context, next) if fn = callbacks[i++]
- return
- next()
- return
- currentPath = ->
- location.pathname + location.search + location.hash
- class Context
- @initialPath: currentPath()
- @sessionId: Date.now()
- @stateId: 0
- @isInitialPopState: (state) ->
- state.path is @initialPath and @stateId is 1
- @isSameSession: (state) ->
- state.sessionId is @sessionId
- constructor: (@path = '/', @state = {}) ->
- @pathname = @path.replace /(?:\?([^#]*))?(?:#(.*))?$/, (_, query, hash) =>
- @query = query
- @hash = hash
- ''
- @state.id ?= @constructor.stateId++
- @state.sessionId ?= @constructor.sessionId
- @state.path = @path
- pushState: ->
- history.pushState @state, '', @path
- return
- replaceState: ->
- history.replaceState @state, '', @path
- return
- class Route
- constructor: (@path, options = {}) ->
- @keys = []
- @regexp = pathtoRegexp @path, @keys
- middleware: (fn) ->
- (context, next) =>
- if @match context.pathname, params = []
- context.params = params
- fn(context, next)
- else
- next()
- return
- match: (path, params) ->
- return unless matchData = @regexp.exec(path)
- for value, i in matchData[1..]
- value = decodeURIComponent value if typeof value is 'string'
- if key = @keys[i]
- params[key.name] = value
- else
- params.push value
- true
- pathtoRegexp = (path, keys) ->
- return path if path instanceof RegExp
- path = "(#{path.join '|'})" if path instanceof Array
- path = path
- .replace /\/\(/g, '(?:/'
- .replace /(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, (_, slash = '', format = '', key, capture, optional) ->
- keys.push name: key, optional: !!optional
- str = if optional then '' else slash
- str += '(?:'
- str += slash if optional
- str += format
- str += capture or if format then '([^/.]+?)' else '([^/]+?)'
- str += ')'
- str += optional if optional
- str
- .replace /([\/.])/g, '\\$1'
- .replace /\*/g, '(.*)'
- new RegExp "^#{path}$"
- onpopstate = (event) ->
- return if not event.state or Context.isInitialPopState(event.state)
- if Context.isSameSession(event.state)
- page.replace(event.state.path, event.state)
- else
- location.reload()
- return
- onclick = (event) ->
- return if event.which isnt 1 or event.metaKey or event.ctrlKey or event.shiftKey or event.defaultPrevented
- link = event.target
- link = link.parentElement while link and link.tagName isnt 'A'
- if link and not link.target and isSameOrigin(link.href)
- event.preventDefault()
- page.show link.pathname + link.search + link.hash
- return
- isSameOrigin = (url) ->
- url.indexOf("#{location.protocol}//#{location.hostname}") is 0
- track = ->
- ga?('send', 'pageview', location.pathname + location.search + location.hash)
- _gauges?.push(['track'])
- return
|