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,37 @@
|
|
1
|
+
namespace('agt.mixins')
|
2
|
+
# Public: An `Equatable` object can be compared in equality with another object.
|
3
|
+
# Objects are considered as equal if all the listed properties are equal.
|
4
|
+
#
|
5
|
+
# ```coffeescript
|
6
|
+
# class Dummy
|
7
|
+
# @include agt.mixins.Equatable('p1', 'p2')
|
8
|
+
#
|
9
|
+
# constructor: (@p1, @p2) ->
|
10
|
+
# # ...
|
11
|
+
#
|
12
|
+
# dummy = new Dummy(10, 'foo')
|
13
|
+
# dummy.equals p1: 10, p2: 'foo' # true
|
14
|
+
# dummy.equals new Dummy(5, 'bar') # false
|
15
|
+
# ```
|
16
|
+
#
|
17
|
+
# The `Equatable` mixin is called a parameterized mixin as
|
18
|
+
# it's in fact a function that will generate a mixin based
|
19
|
+
# on its arguments.
|
20
|
+
#
|
21
|
+
# properties - A list of {String} of the properties to compare to set equality.
|
22
|
+
#
|
23
|
+
# Returns a {ConcreteEquatable} mixin.
|
24
|
+
agt.mixins.Equatable = (properties...) ->
|
25
|
+
|
26
|
+
# Public: A concrete mixin is generated and returned by the
|
27
|
+
# [Equatable](../files/mixins/equatable.coffee.html) generator.
|
28
|
+
class ConcreteEquatable
|
29
|
+
|
30
|
+
# Public: Compares the `properties` of the passed-in object with the current
|
31
|
+
# object and return `true` if all the values are equal.
|
32
|
+
#
|
33
|
+
# o - The {Object} to compare to this instance.
|
34
|
+
#
|
35
|
+
# Returns a {Boolean} of whether the objects are equal or not.
|
36
|
+
equals: (o) -> o? and properties.every (p) =>
|
37
|
+
if @[ p ].equals? then @[ p ].equals o[ p ] else o[p] is @[ p ]
|
@@ -0,0 +1,52 @@
|
|
1
|
+
namespace('agt.mixins')
|
2
|
+
# Public: A `Formattable` object provides a `toString` that return
|
3
|
+
# a string representation of the current instance.
|
4
|
+
#
|
5
|
+
# ```coffeescript
|
6
|
+
# class Dummy
|
7
|
+
# @include agt.mixins.Formattable('Dummy', 'p1', 'p2')
|
8
|
+
#
|
9
|
+
# constructor: (@p1, @p2) ->
|
10
|
+
# # ...
|
11
|
+
#
|
12
|
+
# dummy = new Dummy(10, 'foo')
|
13
|
+
# dummy.toString()
|
14
|
+
# # [Dummy(p1=10, p2=foo)]
|
15
|
+
# ```
|
16
|
+
#
|
17
|
+
# You may wonder why the class name is passed in the `Formattable`
|
18
|
+
# call, the reason is that javascript minification can alter the
|
19
|
+
# naming of the functions and in that case, the constructor function
|
20
|
+
# name can't be relied on anymore.
|
21
|
+
# Passing the class name will ensure that the initial class name
|
22
|
+
# is always accessible through an instance.
|
23
|
+
#
|
24
|
+
# classname - The {String} name of the class for which generate a mixin.
|
25
|
+
# properties - A list of {String} of the properties to include
|
26
|
+
# in the formatted output.
|
27
|
+
#
|
28
|
+
# Returns a {ConcreteFormattable} mixin.
|
29
|
+
agt.mixins.Formattable = (classname, properties...) ->
|
30
|
+
# Public: The concrete class as returned by the
|
31
|
+
# [Formattable](../files/mixins/formattable.coffee.html) generator.
|
32
|
+
class ConcreteFormattable
|
33
|
+
### Public ###
|
34
|
+
|
35
|
+
# Returns the object representation as a {String}.
|
36
|
+
# By default the object representation looks like `[ClassName]`.
|
37
|
+
# If the mixin was configurated using at least on property the
|
38
|
+
# object representation will now looks like
|
39
|
+
# `[ClassName(property=value)]`
|
40
|
+
#
|
41
|
+
# Returns a {String}.
|
42
|
+
toString: -> "[#{ classname }]"
|
43
|
+
|
44
|
+
if properties.length > 0
|
45
|
+
@::toString = ->
|
46
|
+
formattedProperties = ("#{ p }=#{ @[ p ] }" for p in properties)
|
47
|
+
"[#{ classname }(#{ formattedProperties.join ', ' })]"
|
48
|
+
|
49
|
+
# Returns the class name {String} of this instance.
|
50
|
+
#
|
51
|
+
# Returns a {String}
|
52
|
+
classname: -> classname
|
@@ -0,0 +1,175 @@
|
|
1
|
+
namespace('agt.mixins')
|
2
|
+
|
3
|
+
# Internal: The list of properties that are unglobalizable by default.
|
4
|
+
DEFAULT_UNGLOBALIZABLE = [
|
5
|
+
'globalizable'
|
6
|
+
'unglobalizable'
|
7
|
+
'globalized'
|
8
|
+
'globalize'
|
9
|
+
'unglobalize'
|
10
|
+
'globalizeMember'
|
11
|
+
'unglobalizeMember'
|
12
|
+
'keepContext'
|
13
|
+
'previousValues'
|
14
|
+
'previousDescriptors'
|
15
|
+
]
|
16
|
+
|
17
|
+
# Public: A `Globalizable` object can expose some methods on the
|
18
|
+
# specified global object (`window` in a browser or `global` in nodejs
|
19
|
+
# when using methods from the `vm` module).
|
20
|
+
#
|
21
|
+
# The *globalization* process is **reversible** and take care to preserve
|
22
|
+
# the initial properties of the global that may be overriden.
|
23
|
+
#
|
24
|
+
# The properties exposed on the global object are defined
|
25
|
+
# in the `globalizable` property.
|
26
|
+
#
|
27
|
+
# ```coffeescript
|
28
|
+
# class Dummy
|
29
|
+
# @include agt.mixins.Globalizable window
|
30
|
+
#
|
31
|
+
# globalizable: ['someMethod']
|
32
|
+
#
|
33
|
+
# someMethod: -> console.log 'in some method'
|
34
|
+
#
|
35
|
+
# instance = new Dummy
|
36
|
+
# instance.globalize()
|
37
|
+
#
|
38
|
+
# someMethod()
|
39
|
+
# # output: 'in some method'
|
40
|
+
# ```
|
41
|
+
#
|
42
|
+
# The process can be reversed with the `unglobalize` method.
|
43
|
+
#
|
44
|
+
# ```coffeescript
|
45
|
+
# instance.unglobalize()
|
46
|
+
# ```
|
47
|
+
#
|
48
|
+
# The `Globalizable` function takes the target global object as the first
|
49
|
+
# argument. The second argument define whether the functions on
|
50
|
+
# a globalized object are bound to this object or to the global object.
|
51
|
+
#
|
52
|
+
# global - The global {Object} onto which adds globalized methods
|
53
|
+
# and properties.
|
54
|
+
# keepContext - A {Boolean} defining whether the initial context
|
55
|
+
# of the methods are preserved or not.
|
56
|
+
#
|
57
|
+
# Returns a {ConcreteGlobalizable} mixin to decorate a class with.
|
58
|
+
agt.mixins.Globalizable = (global, keepContext=true) ->
|
59
|
+
|
60
|
+
# Public: The concrete globalizable mixin as returned by the
|
61
|
+
# [Globalizable](../files/mixins/globalizable.coffee.html) generator.
|
62
|
+
class ConcreteGlobalizable
|
63
|
+
|
64
|
+
# Public: An {Array} storing the {String} name of the properties that
|
65
|
+
# can't be globalized. This takes precedence over the `globalizable`
|
66
|
+
# property of the decorated class.
|
67
|
+
@unglobalizable: DEFAULT_UNGLOBALIZABLE.concat()
|
68
|
+
|
69
|
+
# Public: {Boolean} that defines whether the methods context
|
70
|
+
# are preserved or not.
|
71
|
+
keepContext: keepContext
|
72
|
+
|
73
|
+
# Public: The method that actually exposes the object methods on global.
|
74
|
+
globalize: ->
|
75
|
+
# But only if the object isn't already `globalized`.
|
76
|
+
return if @globalized
|
77
|
+
|
78
|
+
# Creates the objects that will stores the previous values
|
79
|
+
# and property descriptors present on `global` before the
|
80
|
+
# object globalization.
|
81
|
+
@previousValues = {}
|
82
|
+
@previousDescriptors = {}
|
83
|
+
|
84
|
+
# Then for each properties set for globalization the
|
85
|
+
# `globalizeMember` method is called.
|
86
|
+
@globalizable.forEach (k) =>
|
87
|
+
unless k in (@constructor.unglobalizable or ConcreteGlobalizable.unglobalizable)
|
88
|
+
@globalizeMember k
|
89
|
+
|
90
|
+
# And the object is marked as `globalized`.
|
91
|
+
@globalized = true
|
92
|
+
|
93
|
+
# Public: The reverse process of `globalize`.
|
94
|
+
unglobalize: ->
|
95
|
+
return unless @globalized
|
96
|
+
|
97
|
+
# For each properties set for globalization the
|
98
|
+
# `unglobalizeMember` method is called.
|
99
|
+
@globalizable.forEach (k) =>
|
100
|
+
unless k in (@constructor.unglobalizable or ConcreteGlobalizable.unglobalizable)
|
101
|
+
@unglobalizeMember k
|
102
|
+
|
103
|
+
# And then the object is cleaned of the globalization artifacts
|
104
|
+
# and the `globalized` mark is removed.
|
105
|
+
@previousValues = null
|
106
|
+
@previousDescriptors = null
|
107
|
+
@globalized = false
|
108
|
+
|
109
|
+
# Internal: Exposes a member of the current object on global.
|
110
|
+
#
|
111
|
+
# key - The {String} name of the property to globalize.
|
112
|
+
globalizeMember: (key) ->
|
113
|
+
# If possible we prefer using property descriptors rather than
|
114
|
+
# accessing directly the properties. It will allow to correctly
|
115
|
+
# expose virtual properties (get/set) created through
|
116
|
+
# `Object.defineProperty`.
|
117
|
+
oldDescriptor = Object.getPropertyDescriptor global, key
|
118
|
+
selfDescriptor = Object.getPropertyDescriptor this, key
|
119
|
+
|
120
|
+
# If we have a property descriptor for the previous global property
|
121
|
+
# we store it to restore it in the `unglobalize` process.
|
122
|
+
if oldDescriptor?
|
123
|
+
@previousDescriptors[ key ] = oldDescriptor
|
124
|
+
# Otherwise the property value is stored.
|
125
|
+
else if @[ key ]?
|
126
|
+
@previousValues[ key ] = global if global[ key ]?
|
127
|
+
|
128
|
+
# If we have a property descriptor for the object property, we'll
|
129
|
+
# use it to create the property on global with the same settings.
|
130
|
+
if selfDescriptor?
|
131
|
+
# But if we have to bind functions to the object there'll be
|
132
|
+
# a need for additional setup.
|
133
|
+
if keepContext
|
134
|
+
# For instance, if the descriptor contains a `get` and `set`
|
135
|
+
# property then we have to bind both.
|
136
|
+
if selfDescriptor.get? or selfDescriptor.set?
|
137
|
+
selfDescriptor.get = selfDescriptor.get?.bind(@)
|
138
|
+
selfDescriptor.set = selfDescriptor.set?.bind(@)
|
139
|
+
# Otherwise, if the value is a function we bind it.
|
140
|
+
else if typeof selfDescriptor.value is 'function'
|
141
|
+
selfDescriptor.value = selfDescriptor.value.bind(@)
|
142
|
+
|
143
|
+
# Finally the descriptor is used to create the new property
|
144
|
+
# on the global object.
|
145
|
+
Object.defineProperty global, key, selfDescriptor
|
146
|
+
|
147
|
+
# Without a property descriptor for the object's property
|
148
|
+
# the value is retreived and used to create a new property
|
149
|
+
# descriptor.
|
150
|
+
else
|
151
|
+
value = @[ key ]
|
152
|
+
value = value.bind(@) if typeof value is 'function' and keepContext
|
153
|
+
Object.defineProperty global, key, {
|
154
|
+
value
|
155
|
+
enumerable: true
|
156
|
+
writable: true
|
157
|
+
configurable: true
|
158
|
+
}
|
159
|
+
|
160
|
+
# Internal: The inverse process of `globalizeMember`.
|
161
|
+
#
|
162
|
+
# key - The {String} name of the property to unglobalize.
|
163
|
+
unglobalizeMember: (key) ->
|
164
|
+
# If we have a previous descriptor we restore ot on global.
|
165
|
+
if @previousDescriptors[ key ]?
|
166
|
+
Object.defineProperty global, key, @previousDescriptors[ key ]
|
167
|
+
|
168
|
+
# If there's no previous descriptor but a previous value,
|
169
|
+
# the value is affected to the global property.
|
170
|
+
else if @previousValues[ key ]?
|
171
|
+
global[ key ] = @previousValues[ key ]
|
172
|
+
|
173
|
+
# And if there's nothing the property is unset.
|
174
|
+
else
|
175
|
+
global[ key ] = undefined
|
@@ -0,0 +1,47 @@
|
|
1
|
+
namespace('agt.mixins')
|
2
|
+
|
3
|
+
# Public: The `HasAncestors` mixin adds several methods to instance to deal
|
4
|
+
# with parents and ancestors.
|
5
|
+
#
|
6
|
+
# ```coffee
|
7
|
+
# class Dummy
|
8
|
+
# @concern agt.mixins.HasAncestors through: 'parentNode'
|
9
|
+
#
|
10
|
+
# @ancestorScope 'activeAncestors', (ancestor) -> ancestor.active
|
11
|
+
# ```
|
12
|
+
#
|
13
|
+
# options - The option {Object}:
|
14
|
+
# :through - The {String} name of the property giving access
|
15
|
+
# to the instance parent.
|
16
|
+
#
|
17
|
+
# Returns a {ConcreteHasAncestors} mixin.
|
18
|
+
agt.mixins.HasAncestors = (options={}) ->
|
19
|
+
through = options.through or 'parent'
|
20
|
+
|
21
|
+
# Public: The concrete mixin as returned by the
|
22
|
+
# [HasAncestors](../files/mixins/has_ancestors.coffee.html) generator.
|
23
|
+
class ConcreteHasAncestors
|
24
|
+
|
25
|
+
# Public: Returns an array of all the ancestors of the current object.
|
26
|
+
# The ancestors are ordered such as the first element is the direct
|
27
|
+
# parent of the current object.
|
28
|
+
#
|
29
|
+
# Returns an {Array}
|
30
|
+
@getter 'ancestors', ->
|
31
|
+
ancestors = []
|
32
|
+
parent = @[ through ]
|
33
|
+
|
34
|
+
while parent?
|
35
|
+
ancestors.push parent
|
36
|
+
parent = parent[ through ]
|
37
|
+
|
38
|
+
ancestors
|
39
|
+
|
40
|
+
# Public: Returns an object containing the current object followed by its
|
41
|
+
# parent and ancestors.
|
42
|
+
@getter 'selfAndAncestors', -> [ this ].concat @ancestors
|
43
|
+
|
44
|
+
# Public: Defines a getter property on instances named with `name` and that
|
45
|
+
# filter the `ancestors` array with the given `block`.
|
46
|
+
@ancestorsScope: (name, block) ->
|
47
|
+
@getter name, -> @ancestors.filter(block, this)
|
@@ -0,0 +1,107 @@
|
|
1
|
+
namespace('agt.mixins')
|
2
|
+
|
3
|
+
# Public: The `HasCollection` mixin provides methods to expose a collection
|
4
|
+
# in a class. The mixin is created using two strings.
|
5
|
+
#
|
6
|
+
# ```coffeescript
|
7
|
+
# class Dummy
|
8
|
+
# @concern agt.mixins.HasCollection 'children', 'child'
|
9
|
+
#
|
10
|
+
# @childrenScope 'activeChildren', (child) -> child.active
|
11
|
+
#
|
12
|
+
# constructor: ->
|
13
|
+
# @children = []
|
14
|
+
# ```
|
15
|
+
#
|
16
|
+
# The `plural` string is used to access the collection in all methods
|
17
|
+
# provided by the mixin. The `singular` string will be used to create
|
18
|
+
# the collection managing methods.
|
19
|
+
#
|
20
|
+
# For instance, given that `'children'` and `'child'` was passed as arguments
|
21
|
+
# to `HasCollection` the following methods and properties will be created:
|
22
|
+
#
|
23
|
+
# - `childrenSize` [getter]
|
24
|
+
# - `childrenCount` [getter]
|
25
|
+
# - `childrenLength` [getter]
|
26
|
+
# - `hasChildren` [getter]
|
27
|
+
# - `addChild`
|
28
|
+
# - `removeChild`
|
29
|
+
# - `hasChild`
|
30
|
+
# - `containsChild`
|
31
|
+
#
|
32
|
+
# plural - The {String} name of the property where the collection can be found.
|
33
|
+
# singular - The singularized {String} name.
|
34
|
+
#
|
35
|
+
# Returns a {ConcreteHasCollection} mixin.
|
36
|
+
agt.mixins.HasCollection = (plural, singular) ->
|
37
|
+
|
38
|
+
pluralPostfix = plural.replace /^./, (s) -> s.toUpperCase()
|
39
|
+
singularPostfix = singular.replace /^./, (s) -> s.toUpperCase()
|
40
|
+
|
41
|
+
# Public: The concrete mixin as returned by the
|
42
|
+
# [HasCollection](../files/mixins/has_collection.coffee.html) generator.
|
43
|
+
class ConcreteHasCollection
|
44
|
+
# The mixin integrates `Aliasable` to create various alias to the
|
45
|
+
# collection methods.
|
46
|
+
@extend agt.mixins.Aliasable
|
47
|
+
|
48
|
+
# Public: Creates a `name` scope on instances that filter
|
49
|
+
# the collection using the passed-in `block`.
|
50
|
+
#
|
51
|
+
# name - The {String} name of the collection scope.
|
52
|
+
# block - The {Function} filter for the scope.
|
53
|
+
@[ "#{ plural }Scope" ] = (name, block) ->
|
54
|
+
@getter name, -> @[ plural ].filter block, this
|
55
|
+
|
56
|
+
# Public: A property returning the number of elements in the collection.
|
57
|
+
@getter "#{ plural }Size", -> @[ plural ].length
|
58
|
+
|
59
|
+
# Creates aliases for the collection size property.
|
60
|
+
@alias "#{ plural }Size", "#{ plural }Length", "#{ plural }Count"
|
61
|
+
|
62
|
+
# Public: Returns `true` if the passed-in `item` is present
|
63
|
+
# in the collection.
|
64
|
+
#
|
65
|
+
# item - The item {Object} to search in the collection.
|
66
|
+
#
|
67
|
+
# Returns a {Boolean} of whether the item is present
|
68
|
+
# in the collection or not.
|
69
|
+
@::[ "has#{ singularPostfix }" ] = (item) -> item in @[ plural ]
|
70
|
+
|
71
|
+
# Creates an alias for `has<Item>` named `contains<Item>`.
|
72
|
+
@alias "has#{ singularPostfix }", "contains#{ singularPostfix }"
|
73
|
+
|
74
|
+
# Public: Returns `true` if the collection has at least one item.
|
75
|
+
#
|
76
|
+
# Returns a {Boolean} of whether the collection has items or not.
|
77
|
+
@getter "has#{ pluralPostfix }", -> @[ plural ].length > 0
|
78
|
+
|
79
|
+
# Public: Adds `item` in the collection unless it's already present.
|
80
|
+
#
|
81
|
+
# item - The item {Object} to append to the collection.
|
82
|
+
#
|
83
|
+
# Returns the {Number} of items in the collection.
|
84
|
+
@::[ "add#{ singularPostfix }" ] = (item) ->
|
85
|
+
@[ plural ].push item unless @[ "has#{ singularPostfix }" ] item
|
86
|
+
@[ "#{ plural }Size" ]
|
87
|
+
|
88
|
+
# Public: Removes `item` from the collection.
|
89
|
+
#
|
90
|
+
# item - The item {Object} to remove from the collection.
|
91
|
+
#
|
92
|
+
# Returns the {Number} of items in the collection.
|
93
|
+
@::[ "remove#{ singularPostfix }" ] = (item) ->
|
94
|
+
if @[ "has#{ singularPostfix }" ] item
|
95
|
+
@[ plural ].splice @[ "find#{ singularPostfix }" ](item), 1
|
96
|
+
@[ "#{ plural }Size" ]
|
97
|
+
|
98
|
+
# Public: Returns the index at which `item` is stored
|
99
|
+
# in the collection. It returns `-1` if `item` can't be found.
|
100
|
+
#
|
101
|
+
# item - The item {Object} to search in the collection.
|
102
|
+
#
|
103
|
+
# Returns the index {Number} of the passed-in item or `-1`.
|
104
|
+
@::[ "find#{ singularPostfix }" ] = (item) -> @[ plural ].indexOf item
|
105
|
+
|
106
|
+
# Creates an alias for `find<Item>` named `indexOf<Item>`
|
107
|
+
@alias "find#{ singularPostfix }", "indexOf#{ singularPostfix }"
|
@@ -0,0 +1,51 @@
|
|
1
|
+
namespace('agt.mixins')
|
2
|
+
# Public: The `HasNestedCollection` adds a property with named `name`
|
3
|
+
# that collects and concatenates all the descendants collections
|
4
|
+
# into a single array.
|
5
|
+
# It operates on classes that already includes the `HasCollection` mixin.
|
6
|
+
#
|
7
|
+
# ```coffeescript
|
8
|
+
# class Dummy
|
9
|
+
# @concern agt.mixins.HasCollection 'children', 'child'
|
10
|
+
# @concern agt.mixins.HasNestedCollection 'descendants', through: 'children'
|
11
|
+
#
|
12
|
+
# @descendantsScope 'activeDescendants', (descendant) -> descendant.active
|
13
|
+
#
|
14
|
+
# constructor: ->
|
15
|
+
# @children = []
|
16
|
+
# ```
|
17
|
+
#
|
18
|
+
# name - The {String} name of the nested collection accessor.
|
19
|
+
# options - The options {Object}:
|
20
|
+
# :through - The {String} name of the collection accessor
|
21
|
+
# to collect on the collection items the nested
|
22
|
+
# collections.
|
23
|
+
#
|
24
|
+
# Returns a {ConcreteHasNestedCollection} mixin.
|
25
|
+
agt.mixins.HasNestedCollection = (name, options={}) ->
|
26
|
+
|
27
|
+
# The collection is accessed with the named passed in the `through`option.
|
28
|
+
through = options.through
|
29
|
+
throw new Error('missing through option') unless through?
|
30
|
+
|
31
|
+
# Public: The concrete mixin as returned by the
|
32
|
+
# [HasNestedCollection](../files/mixins/has_nested_collection.coffee.html)
|
33
|
+
# generator.
|
34
|
+
class ConcreteHasNestedCollection
|
35
|
+
|
36
|
+
# Public: Creates a property on instances that filters the nested
|
37
|
+
# collections items using the passed-in `block`.
|
38
|
+
#
|
39
|
+
# scopeName - The {String} name for the scope.
|
40
|
+
# block - The {Function} filter of the scope.
|
41
|
+
@[ "#{ name }Scope" ] = (scopeName, block) ->
|
42
|
+
@getter scopeName, -> @[ name ].filter block, this
|
43
|
+
|
44
|
+
# Public: Returns a flat array containing all the items contained
|
45
|
+
# in all the nested collections.
|
46
|
+
@getter name, ->
|
47
|
+
items = []
|
48
|
+
@[ through ].forEach (item) ->
|
49
|
+
items.push(item)
|
50
|
+
items = items.concat(item[ name ]) if item[ name ]?
|
51
|
+
items
|