You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
devdocs/assets/javascripts/lib/page.coffee

166 lines
4.1 KiB

11 years ago
###
* 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()
ga?('send', 'pageview', location.pathname + location.search + location.hash)
11 years ago
_gauges?.push(['track'])
11 years ago
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()
ga?('send', 'pageview', location.pathname + location.search + location.hash) unless init
11 years ago
_gauges?.push(['track'])
11 years ago
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
11 years ago
.replace /\/\(/g, '(?:/'
11 years ago
.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
11 years ago
.replace /([\/.])/g, '\\$1'
.replace /\*/g, '(.*)'
11 years ago
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