agt 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.md +7 -0
- data/README.md +5 -0
- data/app/assets/javascripts/agt/config.coffee +9 -0
- data/app/assets/javascripts/agt/dom.coffee +12 -0
- data/app/assets/javascripts/agt/function.coffee +317 -0
- data/app/assets/javascripts/agt/geom/circle.coffee +338 -0
- data/app/assets/javascripts/agt/geom/cubic_bezier.coffee +37 -0
- data/app/assets/javascripts/agt/geom/diamond.coffee +241 -0
- data/app/assets/javascripts/agt/geom/ellipsis.coffee +141 -0
- data/app/assets/javascripts/agt/geom/linear_spline.coffee +56 -0
- data/app/assets/javascripts/agt/geom/matrix.coffee +240 -0
- data/app/assets/javascripts/agt/geom/mixins/geometry.coffee +171 -0
- data/app/assets/javascripts/agt/geom/mixins/intersections.coffee +150 -0
- data/app/assets/javascripts/agt/geom/mixins/path.coffee +145 -0
- data/app/assets/javascripts/agt/geom/mixins/proxyable.coffee +9 -0
- data/app/assets/javascripts/agt/geom/mixins/spline.coffee +329 -0
- data/app/assets/javascripts/agt/geom/mixins/surface.coffee +55 -0
- data/app/assets/javascripts/agt/geom/mixins/triangulable.coffee +112 -0
- data/app/assets/javascripts/agt/geom/point.coffee +454 -0
- data/app/assets/javascripts/agt/geom/polygon.coffee +119 -0
- data/app/assets/javascripts/agt/geom/quad_bezier.coffee +43 -0
- data/app/assets/javascripts/agt/geom/quint_bezier.coffee +42 -0
- data/app/assets/javascripts/agt/geom/rectangle.coffee +599 -0
- data/app/assets/javascripts/agt/geom/spiral.coffee +90 -0
- data/app/assets/javascripts/agt/geom/transformation_proxy.coffee +43 -0
- data/app/assets/javascripts/agt/geom/triangle.coffee +442 -0
- data/app/assets/javascripts/agt/impulse.coffee +105 -0
- data/app/assets/javascripts/agt/index.coffee +56 -0
- data/app/assets/javascripts/agt/inflector/inflection.coffee +21 -0
- data/app/assets/javascripts/agt/inflector/inflections.coffee +75 -0
- data/app/assets/javascripts/agt/inflector/inflector.coffee +235 -0
- data/app/assets/javascripts/agt/inheritance.coffee +132 -0
- data/app/assets/javascripts/agt/math.coffee +45 -0
- data/app/assets/javascripts/agt/mixins/activable.coffee +31 -0
- data/app/assets/javascripts/agt/mixins/aliasable.coffee +25 -0
- data/app/assets/javascripts/agt/mixins/alternate_case.coffee +72 -0
- data/app/assets/javascripts/agt/mixins/cloneable.coffee +71 -0
- data/app/assets/javascripts/agt/mixins/delegation.coffee +90 -0
- data/app/assets/javascripts/agt/mixins/disposable.coffee +7 -0
- data/app/assets/javascripts/agt/mixins/equatable.coffee +37 -0
- data/app/assets/javascripts/agt/mixins/formattable.coffee +52 -0
- data/app/assets/javascripts/agt/mixins/globalizable.coffee +175 -0
- data/app/assets/javascripts/agt/mixins/has_ancestors.coffee +47 -0
- data/app/assets/javascripts/agt/mixins/has_collection.coffee +107 -0
- data/app/assets/javascripts/agt/mixins/has_nested_collection.coffee +51 -0
- data/app/assets/javascripts/agt/mixins/memoizable.coffee +64 -0
- data/app/assets/javascripts/agt/mixins/parameterizable.coffee +101 -0
- data/app/assets/javascripts/agt/mixins/poolable.coffee +62 -0
- data/app/assets/javascripts/agt/mixins/sourcable.coffee +45 -0
- data/app/assets/javascripts/agt/mixins/state_machine.coffee +47 -0
- data/app/assets/javascripts/agt/net/router.coffee +165 -0
- data/app/assets/javascripts/agt/object.coffee +9 -0
- data/app/assets/javascripts/agt/particles/actions/base_action.coffee +7 -0
- data/app/assets/javascripts/agt/particles/actions/die_on_surface.coffee +14 -0
- data/app/assets/javascripts/agt/particles/actions/force.coffee +11 -0
- data/app/assets/javascripts/agt/particles/actions/friction.coffee +14 -0
- data/app/assets/javascripts/agt/particles/actions/live.coffee +9 -0
- data/app/assets/javascripts/agt/particles/actions/macro_action.coffee +13 -0
- data/app/assets/javascripts/agt/particles/actions/move.coffee +11 -0
- data/app/assets/javascripts/agt/particles/actions/null_action.coffee +9 -0
- data/app/assets/javascripts/agt/particles/counters/by_rate.coffee +17 -0
- data/app/assets/javascripts/agt/particles/counters/fixed.coffee +9 -0
- data/app/assets/javascripts/agt/particles/counters/null_counter.coffee +9 -0
- data/app/assets/javascripts/agt/particles/emission.coffee +35 -0
- data/app/assets/javascripts/agt/particles/emitters/null_emitter.coffee +7 -0
- data/app/assets/javascripts/agt/particles/emitters/path.coffee +10 -0
- data/app/assets/javascripts/agt/particles/emitters/ponctual.coffee +9 -0
- data/app/assets/javascripts/agt/particles/emitters/surface.coffee +10 -0
- data/app/assets/javascripts/agt/particles/initializers/explosion.coffee +19 -0
- data/app/assets/javascripts/agt/particles/initializers/life.coffee +16 -0
- data/app/assets/javascripts/agt/particles/initializers/macro_initializer.coffee +10 -0
- data/app/assets/javascripts/agt/particles/initializers/null_initializer.coffee +7 -0
- data/app/assets/javascripts/agt/particles/initializers/particle_sub_system.coffee +12 -0
- data/app/assets/javascripts/agt/particles/initializers/stream.coffee +20 -0
- data/app/assets/javascripts/agt/particles/mixins/randomizable.coffee +10 -0
- data/app/assets/javascripts/agt/particles/particle.coffee +32 -0
- data/app/assets/javascripts/agt/particles/sub_system.coffee +11 -0
- data/app/assets/javascripts/agt/particles/system.coffee +103 -0
- data/app/assets/javascripts/agt/particles/timers/instant.coffee +11 -0
- data/app/assets/javascripts/agt/particles/timers/limited.coffee +19 -0
- data/app/assets/javascripts/agt/particles/timers/null_timer.coffee +11 -0
- data/app/assets/javascripts/agt/particles/timers/unlimited.coffee +9 -0
- data/app/assets/javascripts/agt/particles/timers/until_death.coffee +11 -0
- data/app/assets/javascripts/agt/promise.coffee +214 -0
- data/app/assets/javascripts/agt/random/random.coffee +100 -0
- data/app/assets/javascripts/agt/random/seeds/lagged_fibonnacci.coffee +53 -0
- data/app/assets/javascripts/agt/random/seeds/linear.coffee +16 -0
- data/app/assets/javascripts/agt/random/seeds/linear_congruential.coffee +23 -0
- data/app/assets/javascripts/agt/random/seeds/math_random.coffee +9 -0
- data/app/assets/javascripts/agt/random/seeds/mersenne_twister.coffee +42 -0
- data/app/assets/javascripts/agt/random/seeds/no_random.coffee +12 -0
- data/app/assets/javascripts/agt/random/seeds/paul_houle.coffee +19 -0
- data/app/assets/javascripts/agt/signal.coffee +272 -0
- data/app/assets/javascripts/agt/sprites/animation.coffee +13 -0
- data/app/assets/javascripts/agt/sprites/sprite.coffee +30 -0
- data/app/assets/javascripts/agt/widgets/hash.coffee +36 -0
- data/app/assets/javascripts/agt/widgets/widgets.coffee +194 -0
- data/app/assets/javascripts/agt/widgets/widgets/checked_input.coffee +7 -0
- data/app/assets/javascripts/agt/widgets/widgets/focus_bubbling.coffee +15 -0
- data/lib/agt.rb +8 -0
- data/lib/agt/version.rb +5 -0
- metadata +173 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
namespace('agt.mixins')
|
2
|
+
# Public: A `Memoizable` object can store data resulting of heavy methods
|
3
|
+
# in order to speed up further call to that method.
|
4
|
+
#
|
5
|
+
# The invalidation of the memoized data is defined using a `memoizationKey`.
|
6
|
+
# That key should be generated based on the data that may induce changes
|
7
|
+
# in the functions's results.
|
8
|
+
#
|
9
|
+
# ```coffeescript
|
10
|
+
# class Dummy
|
11
|
+
# @include agt.mixins.Memoizable
|
12
|
+
#
|
13
|
+
# constructor: (@p1, @p2) ->
|
14
|
+
# # ...
|
15
|
+
#
|
16
|
+
# heavyMethod: (arg) ->
|
17
|
+
# key = "heavyMethod-#{arg}"
|
18
|
+
# return @memoFor key if @memoized key
|
19
|
+
#
|
20
|
+
# # do costly computation
|
21
|
+
# @memoize key, result
|
22
|
+
#
|
23
|
+
# memoizationKey: -> "#{p1};#{p2}"
|
24
|
+
# ```
|
25
|
+
class agt.mixins.Memoizable
|
26
|
+
# Public: Returns `true` if data are available for the given `prop`.
|
27
|
+
#
|
28
|
+
# When the current state of the object don't match the stored
|
29
|
+
# memoization key, the whole data stored in the memo are cleared.
|
30
|
+
#
|
31
|
+
# prop - The {String} name of a property.
|
32
|
+
#
|
33
|
+
# Retuns a {Boolean} of whether the value of the propery is memoized or not.
|
34
|
+
memoized: (prop) ->
|
35
|
+
if @memoizationKey() is @__memoizationKey__
|
36
|
+
@__memo__?[ prop ]?
|
37
|
+
else
|
38
|
+
@__memo__ = {}
|
39
|
+
false
|
40
|
+
|
41
|
+
# Public: Returns the memoized data for the given `prop`.
|
42
|
+
#
|
43
|
+
# prop - The {String} name of a property.
|
44
|
+
#
|
45
|
+
# Returns the memoized data for the given prop
|
46
|
+
memoFor: (prop) -> @__memo__[ prop ]
|
47
|
+
|
48
|
+
# Public: Register a memo in the current object for the given `prop`.
|
49
|
+
# The memoization key is updated with the current state of the
|
50
|
+
# object.
|
51
|
+
memoize: (prop, value) ->
|
52
|
+
@__memo__ ||= {}
|
53
|
+
@__memoizationKey__ = @memoizationKey()
|
54
|
+
@__memo__[ prop ] = value
|
55
|
+
|
56
|
+
# Public Abstract: Generates the memoization key for this
|
57
|
+
# instance's state.
|
58
|
+
#
|
59
|
+
# By default the memoization key of an object is the return of its `toString`
|
60
|
+
# method. **You SHOULD redefine the memoization key generation in the class
|
61
|
+
# including the `Memoizable` mixin.**
|
62
|
+
#
|
63
|
+
# Returns a {String} that identify the state of the current instance.
|
64
|
+
memoizationKey: -> @toString()
|
@@ -0,0 +1,101 @@
|
|
1
|
+
namespace('agt.mixins')
|
2
|
+
# Public: A `Parameterizable` object provides a class and an instance
|
3
|
+
# method to convert an arbitrary {Object} or a series of values into
|
4
|
+
# an instance of the class receiving the mixin.
|
5
|
+
#
|
6
|
+
# ```coffeescript
|
7
|
+
# class Dummy
|
8
|
+
# @include agt.mixins.Parameterizable('dummyFrom', {
|
9
|
+
# x: 0
|
10
|
+
# y: 0
|
11
|
+
# name: 'Untitled'
|
12
|
+
# })
|
13
|
+
#
|
14
|
+
# constructor: (@x, @y, @name) ->
|
15
|
+
#
|
16
|
+
# instance = new Dummy
|
17
|
+
#
|
18
|
+
# Dummy.instanceFrom(1,2,'foo')
|
19
|
+
# instance.instanceFrom(1,2,'foo')
|
20
|
+
# ```
|
21
|
+
#
|
22
|
+
# The methods can be used either with an object or with a list
|
23
|
+
# of values whose order is the order of the `parameters` object keys:
|
24
|
+
#
|
25
|
+
# ``` coffeescript
|
26
|
+
# Dummy.instanceFrom(1,2,'Foo') # [Dummy(x=1,y=2,name='Foo')]
|
27
|
+
# Dummy.instanceFrom(x: 1, y: 2, name: 'Foo') # [Dummy(x=1,y=2,name='Foo')]
|
28
|
+
# ```
|
29
|
+
#
|
30
|
+
# The last `instanceFrom` argument controls can be a {Boolean} of whether
|
31
|
+
# the method raises an exception when the creation arguments types mismatch:
|
32
|
+
#
|
33
|
+
# ``` coffeescript
|
34
|
+
# Dummy.instanceFrom(1,'foo','bar' true) # value for y doesn't match type number
|
35
|
+
# Dummy.instanceFrom(x: 1, y: 'foo', name: 'bar', true) # value for y doesn't match type number
|
36
|
+
# ```
|
37
|
+
#
|
38
|
+
# The only edge case is that if you have yourself defined a {Boolean} as last
|
39
|
+
# argument you'll always have to pass the `strict` argument when calling the
|
40
|
+
# methods.
|
41
|
+
#
|
42
|
+
# The `allowPartial` argument, when set to `true`, will prevent the method
|
43
|
+
# to fallback on the default values provided in the `parameters` object
|
44
|
+
# leaving the class constructor to deal with it.
|
45
|
+
#
|
46
|
+
# method - The {String} name of the method to create.
|
47
|
+
# parameters - An {Object} describing the arguments name, type and order.
|
48
|
+
# allowPartial - A {Boolean} of
|
49
|
+
agt.mixins.Parameterizable = (method, parameters, allowPartial=false) ->
|
50
|
+
|
51
|
+
# Public: The concrete mixin as returned by the
|
52
|
+
# [Parameterizable](../files/mixins/parameterizable.coffee.html) generator.
|
53
|
+
class ConcreteParameterizable
|
54
|
+
|
55
|
+
# Internal:
|
56
|
+
@included: (klass) ->
|
57
|
+
f = (args..., strict)->
|
58
|
+
(args.push(strict); strict = false) if typeof strict isnt 'boolean'
|
59
|
+
output = {}
|
60
|
+
|
61
|
+
o = arguments[ 0 ]
|
62
|
+
n = 0
|
63
|
+
firstArgumentIsObject = o? and typeof o is 'object'
|
64
|
+
|
65
|
+
for k,v of parameters
|
66
|
+
value = if firstArgumentIsObject then o[ k ] else arguments[ n++ ]
|
67
|
+
parameterType = typeof v
|
68
|
+
|
69
|
+
if strict
|
70
|
+
if typeof value is parameterType
|
71
|
+
output[ k ] = value
|
72
|
+
else
|
73
|
+
if parameterType is 'number'
|
74
|
+
value = parseFloat value
|
75
|
+
|
76
|
+
if isNaN value
|
77
|
+
throw new Error "value for #{ k } doesn't match type #{ parameterType}"
|
78
|
+
else
|
79
|
+
output[k] = value
|
80
|
+
else
|
81
|
+
throw new Error "value for #{ k } doesn't match type #{ parameterType}"
|
82
|
+
|
83
|
+
else
|
84
|
+
if value?
|
85
|
+
if parameterType is 'number'
|
86
|
+
value = parseFloat value
|
87
|
+
|
88
|
+
if isNaN value
|
89
|
+
output[ k ] = v unless allowPartial
|
90
|
+
else
|
91
|
+
output[ k ] = value
|
92
|
+
else
|
93
|
+
output[ k ] = value
|
94
|
+
else
|
95
|
+
output[ k ] = v unless allowPartial
|
96
|
+
|
97
|
+
|
98
|
+
output
|
99
|
+
|
100
|
+
klass[method] = f
|
101
|
+
klass::[method] = f
|
@@ -0,0 +1,62 @@
|
|
1
|
+
namespace('agt.mixins')
|
2
|
+
# Public: A `Poolable` class has the ability to manage a pool of instances
|
3
|
+
# and prevent the further creation of instances as long as unused ones
|
4
|
+
# are still present.
|
5
|
+
class agt.mixins.Poolable
|
6
|
+
|
7
|
+
# Internal: The two objects stores are created in the extended hook to avoid
|
8
|
+
# that all the class extending `Poolable` shares the same instances.
|
9
|
+
@extended: (klass) ->
|
10
|
+
klass.usedInstances = []
|
11
|
+
klass.unusedInstances = []
|
12
|
+
|
13
|
+
# Public: Resets all the instances pools.
|
14
|
+
@resetPools: ->
|
15
|
+
@usedInstances = []
|
16
|
+
@unusedInstances = []
|
17
|
+
|
18
|
+
# Public: The `get` method returns an instance of the class.
|
19
|
+
# If the class defines an `init` method, it will be called with the
|
20
|
+
# passed-in `options` {Object}.
|
21
|
+
#
|
22
|
+
# options - The option {Object} to use to setup the created instance.
|
23
|
+
#
|
24
|
+
# Returns an instance of the current class.
|
25
|
+
@get: (options={}) ->
|
26
|
+
# Either retrieve or create the instance.
|
27
|
+
if @unusedInstances.length > 0
|
28
|
+
instance = @unusedInstances.shift()
|
29
|
+
else
|
30
|
+
instance = new this
|
31
|
+
|
32
|
+
# Stores the instance in the used pool.
|
33
|
+
@usedInstances.push instance
|
34
|
+
|
35
|
+
# Init the instance and return it.
|
36
|
+
instance.init(options)
|
37
|
+
instance
|
38
|
+
|
39
|
+
# Public: The `release` method takes an instance and move
|
40
|
+
# it from the the used pool to the unused pool.
|
41
|
+
#
|
42
|
+
# instance - The instance of the current class.
|
43
|
+
@release: (instance) ->
|
44
|
+
return unless instance in @usedInstances
|
45
|
+
|
46
|
+
# The instance is removed from the used instances pool.
|
47
|
+
index = @usedInstances.indexOf(instance)
|
48
|
+
@usedInstances.splice(index, 1)
|
49
|
+
|
50
|
+
# And then moved to the unused instances one.
|
51
|
+
@unusedInstances.push instance
|
52
|
+
|
53
|
+
# Public: Default `init` implementation, just copy all the options
|
54
|
+
# in the instance.
|
55
|
+
#
|
56
|
+
# options - The setup {Object} for this instance.
|
57
|
+
init: (options={}) -> @[ k ] = v for k,v of options
|
58
|
+
|
59
|
+
# Public: Default `dispose` implementation, call the `release` method
|
60
|
+
# on the instance constructor. A proper implementation should
|
61
|
+
# take care of removing/cleaning all the instance properties.
|
62
|
+
dispose: -> @constructor.release(this)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
namespace('agt.mixins')
|
2
|
+
# Public: A `Sourcable` object is an object that can return the source code
|
3
|
+
# to re-create it by code.
|
4
|
+
#
|
5
|
+
# ```coffeescript
|
6
|
+
# class Dummy
|
7
|
+
# @include agt.mixins.Sourcable('agt.geom.Dummy', 'p1', 'p2')
|
8
|
+
#
|
9
|
+
# constructor: (@p1, @p2) ->
|
10
|
+
#
|
11
|
+
# dummy = new Dummy(10,'foo')
|
12
|
+
# dummy.toSource() # "new agt.geom.Dummy(10,'foo')"
|
13
|
+
# ```
|
14
|
+
#
|
15
|
+
# name - The {String} path to the current class.
|
16
|
+
# signature - A list of {String} name of properties
|
17
|
+
agt.mixins.Sourcable = (name, signature...) ->
|
18
|
+
|
19
|
+
# Public: A concrete class is generated and returned by
|
20
|
+
# [Sourcable](../files/mixins/sourcable.coffee.html).
|
21
|
+
class ConcreteSourcable
|
22
|
+
|
23
|
+
# Internal: Generates the source for a property's value.
|
24
|
+
sourceFor = (value) ->
|
25
|
+
switch typeof value
|
26
|
+
when 'object'
|
27
|
+
isArray = Object::toString.call(value).indexOf('Array') isnt -1
|
28
|
+
if isArray
|
29
|
+
"[#{ value.map (el) -> sourceFor el }]"
|
30
|
+
else
|
31
|
+
if value.toSource?
|
32
|
+
value.toSource()
|
33
|
+
else
|
34
|
+
value
|
35
|
+
when 'string'
|
36
|
+
"'#{ value.replace "'", "\\'" }'"
|
37
|
+
else value
|
38
|
+
|
39
|
+
# Public: Returns the source code corresponding to the current instance.
|
40
|
+
#
|
41
|
+
# Returns a {String} with the source of the instance.
|
42
|
+
toSource: ->
|
43
|
+
args = (@[ arg ] for arg in signature).map (o) -> sourceFor o
|
44
|
+
|
45
|
+
"new #{ name }(#{ args.join ',' })"
|
@@ -0,0 +1,47 @@
|
|
1
|
+
namespace('agt.mixins')
|
2
|
+
class agt.mixins.StateMachine
|
3
|
+
@initial: (state) -> @initialState = state
|
4
|
+
|
5
|
+
@event: (name, block) ->
|
6
|
+
@::[name] = block
|
7
|
+
|
8
|
+
@state: (name, defines) ->
|
9
|
+
@states ?= {}
|
10
|
+
@stateProperties ?= []
|
11
|
+
|
12
|
+
@states[name] = defines
|
13
|
+
@::[name] = -> @state is name
|
14
|
+
@stateProperties.push(k) for k,v of defines when k not in @stateProperties
|
15
|
+
|
16
|
+
this
|
17
|
+
|
18
|
+
initializeStateMachine: -> @setState(@constructor.initialState)
|
19
|
+
|
20
|
+
transition: (options) ->
|
21
|
+
throw new Error("From option is mandatory") unless options.from?
|
22
|
+
throw new Error("To option is mandatory") unless options.to?
|
23
|
+
|
24
|
+
{from, to} = options
|
25
|
+
if from is 'all'
|
26
|
+
from = Object.keys(@constructor.states)
|
27
|
+
else
|
28
|
+
from = from.split(' ')
|
29
|
+
|
30
|
+
to = options.to
|
31
|
+
|
32
|
+
for f in from
|
33
|
+
unless @constructor.states[f]
|
34
|
+
throw new Error("From state '#{f}' does not match any defined state")
|
35
|
+
|
36
|
+
unless @constructor.states[to]
|
37
|
+
throw new Error("To state '#{to}' does not match any defined state")
|
38
|
+
|
39
|
+
if @state in from
|
40
|
+
@setState(to)
|
41
|
+
else
|
42
|
+
throw new Error "Can't transition from #{@state} to #{to}"
|
43
|
+
|
44
|
+
setState: (state) ->
|
45
|
+
@state = state
|
46
|
+
state = @constructor.states[@state]
|
47
|
+
@[key] = state[key] for key in @constructor.stateProperties
|
@@ -0,0 +1,165 @@
|
|
1
|
+
namespace('agt.net')
|
2
|
+
# Public: The router class allow to trigger view changes on url changes.
|
3
|
+
# Typically the url changes may be handled by the history API or a url hash
|
4
|
+
# handler.
|
5
|
+
# The routes the router will support are defined in the callback passed to
|
6
|
+
# the router constructor as in the example below:
|
7
|
+
#
|
8
|
+
# ```coffee
|
9
|
+
# router = new Router ->
|
10
|
+
# @match '/posts', ->
|
11
|
+
# # render the post index
|
12
|
+
#
|
13
|
+
# @match '/posts/:id', ({id}) ->
|
14
|
+
# # render a single post identified with an :id
|
15
|
+
# ```
|
16
|
+
class agt.net.Router
|
17
|
+
@extend agt.mixins.Aliasable
|
18
|
+
|
19
|
+
### Public ###
|
20
|
+
|
21
|
+
# Creates a new router instance. The constructor takes a {Function}
|
22
|
+
# and call it in the constructor this the router as the context object
|
23
|
+
# of the call. This is generally inside the block {Function} that you'll
|
24
|
+
# define the route to support.
|
25
|
+
#
|
26
|
+
# block - The initialization {Function}.
|
27
|
+
constructor: (block) ->
|
28
|
+
@routes = {}
|
29
|
+
@beforeFilters = []
|
30
|
+
@afterFilters = []
|
31
|
+
|
32
|
+
block.call(this)
|
33
|
+
@buildRoutesHandlers()
|
34
|
+
|
35
|
+
# Registers a route on this router.
|
36
|
+
#
|
37
|
+
# A route is defined with a `path` {String}, starting with a `/` and that
|
38
|
+
# can contains variables patterns for dynamic routes.
|
39
|
+
#
|
40
|
+
# For instance, the following path `'/posts/:post_id/comments/:id'` sets
|
41
|
+
# that the route defines two variables `post_id` and `id` that can contains
|
42
|
+
# any value. It means that this route will match both `'/posts/1/comments/2'`
|
43
|
+
# or `'/posts/foo/comments/bar'`.
|
44
|
+
#
|
45
|
+
# To limit the legal values for a variable, you can pass a string
|
46
|
+
# in the option object with the name of the variable as key, such as in:
|
47
|
+
#
|
48
|
+
# ```coffee
|
49
|
+
# @match '/posts/:id', id: '(\d+)', ({id}) ->
|
50
|
+
# ```
|
51
|
+
#
|
52
|
+
# In the example above the route can only be matched if the content for the
|
53
|
+
# `id` variable in the path contains only numbers. It means that
|
54
|
+
# `'/posts/1/comments/2'` will be matched by the route but
|
55
|
+
# `'/posts/foo/comments/bar'` won't.
|
56
|
+
#
|
57
|
+
# path - The path {String} to support.
|
58
|
+
# options - An option {Object} that can contain properties matching the
|
59
|
+
# path variables and containing strings that will be used to
|
60
|
+
# construct the regexp String to test paths.
|
61
|
+
match: (path, options={}, handle) ->
|
62
|
+
[options, handle] = [{}, options] if typeof options is 'function'
|
63
|
+
path = path.replace /^\/|\/$/g, ''
|
64
|
+
@routes[path] = {handle, options}
|
65
|
+
|
66
|
+
@alias 'match', 'get'
|
67
|
+
|
68
|
+
# Registers a filter to be called before the route handler.
|
69
|
+
#
|
70
|
+
# ```coffee
|
71
|
+
# @beforeFilter (path, router) ->
|
72
|
+
# # ...
|
73
|
+
# ```
|
74
|
+
#
|
75
|
+
# filter - The {Function} to invoke before the route handler.
|
76
|
+
beforeFilter: (filter) -> @beforeFilters.push filter
|
77
|
+
|
78
|
+
# Registers a filter to be called after the route handler.
|
79
|
+
#
|
80
|
+
# ```coffee
|
81
|
+
# @afterFilter (path, router) ->
|
82
|
+
# # ...
|
83
|
+
# ```
|
84
|
+
#
|
85
|
+
# filter - The {Function} to invoke after the route handler.
|
86
|
+
afterFilter: (filter) -> @afterFilters.push filter
|
87
|
+
|
88
|
+
# Defines a handler for paths that doesn't match any routes.
|
89
|
+
#
|
90
|
+
# notFoundHandle - The {Function} to call for route not found.
|
91
|
+
notFound: (@notFoundHandle) ->
|
92
|
+
|
93
|
+
# Triggers a change of route with the passed-in path.
|
94
|
+
#
|
95
|
+
# A `route:changed` event is dispatched after all the filters and the
|
96
|
+
# handler was called.
|
97
|
+
#
|
98
|
+
# path - The path {String} to match.
|
99
|
+
goto: (path) ->
|
100
|
+
path = '/' if path is '.'
|
101
|
+
path = path.replace(/^\./, '')
|
102
|
+
path = path.replace(/\/$/, '') unless path is '/'
|
103
|
+
path = "/#{path}" if path.indexOf('/') isnt 0
|
104
|
+
|
105
|
+
handler = @findRoute(path)
|
106
|
+
|
107
|
+
@beforeFilters.forEach (filter) => filter(path, this)
|
108
|
+
|
109
|
+
if handler?
|
110
|
+
handler(path)
|
111
|
+
else
|
112
|
+
@notFoundHandle?({path})
|
113
|
+
|
114
|
+
@afterFilters.forEach (filter) => filter(path, this)
|
115
|
+
|
116
|
+
document.dispatchEvent agt.domEvent('route:changed', {path}) if document?
|
117
|
+
|
118
|
+
# Internal: Returns the {Function} to handle the passed-in `path`.
|
119
|
+
#
|
120
|
+
# path - The path {String} to match.
|
121
|
+
#
|
122
|
+
# Returns a {Function}.
|
123
|
+
findRoute: (path) ->
|
124
|
+
for k, {test, handle} of @routes
|
125
|
+
return handle if test(path)
|
126
|
+
|
127
|
+
# Internal: Builds the route handlers.
|
128
|
+
buildRoutesHandlers: ->
|
129
|
+
for path, data of @routes
|
130
|
+
@routes[path] = @buildRouteHandler(path, data)
|
131
|
+
|
132
|
+
# Internal: Build a single route handler using the information provided.
|
133
|
+
#
|
134
|
+
# path - A {String} of the route path pattern.
|
135
|
+
# options - An object with the following properties:
|
136
|
+
# :handle - The {Function} that will handle the route.
|
137
|
+
# :options - An {Object} that contains the path variables patterns.
|
138
|
+
buildRouteHandler: (path, {handle, options}) ->
|
139
|
+
pathArray = path.split '/'
|
140
|
+
pathRe = []
|
141
|
+
pathParams = []
|
142
|
+
|
143
|
+
for part in pathArray
|
144
|
+
params_re = /^:(.+)$/
|
145
|
+
if res = params_re.exec(part)
|
146
|
+
param_name = res[1]
|
147
|
+
pathRe.push options[param_name] ? '([^/]+)'
|
148
|
+
pathParams.push param_name
|
149
|
+
else
|
150
|
+
pathRe.push part
|
151
|
+
|
152
|
+
re = new RegExp('^/' + pathRe.join('/') + '$')
|
153
|
+
|
154
|
+
{
|
155
|
+
options
|
156
|
+
test: (path) -> re.test(path)
|
157
|
+
handle: (path) ->
|
158
|
+
params = {path: path}
|
159
|
+
res = re.exec(path)
|
160
|
+
if res? and res.length > 1
|
161
|
+
for pname,i in pathParams
|
162
|
+
params[pname] = decodeURI(res[i+1])
|
163
|
+
|
164
|
+
handle(params)
|
165
|
+
}
|