agt 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
}
|