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.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +7 -0
  3. data/README.md +5 -0
  4. data/app/assets/javascripts/agt/config.coffee +9 -0
  5. data/app/assets/javascripts/agt/dom.coffee +12 -0
  6. data/app/assets/javascripts/agt/function.coffee +317 -0
  7. data/app/assets/javascripts/agt/geom/circle.coffee +338 -0
  8. data/app/assets/javascripts/agt/geom/cubic_bezier.coffee +37 -0
  9. data/app/assets/javascripts/agt/geom/diamond.coffee +241 -0
  10. data/app/assets/javascripts/agt/geom/ellipsis.coffee +141 -0
  11. data/app/assets/javascripts/agt/geom/linear_spline.coffee +56 -0
  12. data/app/assets/javascripts/agt/geom/matrix.coffee +240 -0
  13. data/app/assets/javascripts/agt/geom/mixins/geometry.coffee +171 -0
  14. data/app/assets/javascripts/agt/geom/mixins/intersections.coffee +150 -0
  15. data/app/assets/javascripts/agt/geom/mixins/path.coffee +145 -0
  16. data/app/assets/javascripts/agt/geom/mixins/proxyable.coffee +9 -0
  17. data/app/assets/javascripts/agt/geom/mixins/spline.coffee +329 -0
  18. data/app/assets/javascripts/agt/geom/mixins/surface.coffee +55 -0
  19. data/app/assets/javascripts/agt/geom/mixins/triangulable.coffee +112 -0
  20. data/app/assets/javascripts/agt/geom/point.coffee +454 -0
  21. data/app/assets/javascripts/agt/geom/polygon.coffee +119 -0
  22. data/app/assets/javascripts/agt/geom/quad_bezier.coffee +43 -0
  23. data/app/assets/javascripts/agt/geom/quint_bezier.coffee +42 -0
  24. data/app/assets/javascripts/agt/geom/rectangle.coffee +599 -0
  25. data/app/assets/javascripts/agt/geom/spiral.coffee +90 -0
  26. data/app/assets/javascripts/agt/geom/transformation_proxy.coffee +43 -0
  27. data/app/assets/javascripts/agt/geom/triangle.coffee +442 -0
  28. data/app/assets/javascripts/agt/impulse.coffee +105 -0
  29. data/app/assets/javascripts/agt/index.coffee +56 -0
  30. data/app/assets/javascripts/agt/inflector/inflection.coffee +21 -0
  31. data/app/assets/javascripts/agt/inflector/inflections.coffee +75 -0
  32. data/app/assets/javascripts/agt/inflector/inflector.coffee +235 -0
  33. data/app/assets/javascripts/agt/inheritance.coffee +132 -0
  34. data/app/assets/javascripts/agt/math.coffee +45 -0
  35. data/app/assets/javascripts/agt/mixins/activable.coffee +31 -0
  36. data/app/assets/javascripts/agt/mixins/aliasable.coffee +25 -0
  37. data/app/assets/javascripts/agt/mixins/alternate_case.coffee +72 -0
  38. data/app/assets/javascripts/agt/mixins/cloneable.coffee +71 -0
  39. data/app/assets/javascripts/agt/mixins/delegation.coffee +90 -0
  40. data/app/assets/javascripts/agt/mixins/disposable.coffee +7 -0
  41. data/app/assets/javascripts/agt/mixins/equatable.coffee +37 -0
  42. data/app/assets/javascripts/agt/mixins/formattable.coffee +52 -0
  43. data/app/assets/javascripts/agt/mixins/globalizable.coffee +175 -0
  44. data/app/assets/javascripts/agt/mixins/has_ancestors.coffee +47 -0
  45. data/app/assets/javascripts/agt/mixins/has_collection.coffee +107 -0
  46. data/app/assets/javascripts/agt/mixins/has_nested_collection.coffee +51 -0
  47. data/app/assets/javascripts/agt/mixins/memoizable.coffee +64 -0
  48. data/app/assets/javascripts/agt/mixins/parameterizable.coffee +101 -0
  49. data/app/assets/javascripts/agt/mixins/poolable.coffee +62 -0
  50. data/app/assets/javascripts/agt/mixins/sourcable.coffee +45 -0
  51. data/app/assets/javascripts/agt/mixins/state_machine.coffee +47 -0
  52. data/app/assets/javascripts/agt/net/router.coffee +165 -0
  53. data/app/assets/javascripts/agt/object.coffee +9 -0
  54. data/app/assets/javascripts/agt/particles/actions/base_action.coffee +7 -0
  55. data/app/assets/javascripts/agt/particles/actions/die_on_surface.coffee +14 -0
  56. data/app/assets/javascripts/agt/particles/actions/force.coffee +11 -0
  57. data/app/assets/javascripts/agt/particles/actions/friction.coffee +14 -0
  58. data/app/assets/javascripts/agt/particles/actions/live.coffee +9 -0
  59. data/app/assets/javascripts/agt/particles/actions/macro_action.coffee +13 -0
  60. data/app/assets/javascripts/agt/particles/actions/move.coffee +11 -0
  61. data/app/assets/javascripts/agt/particles/actions/null_action.coffee +9 -0
  62. data/app/assets/javascripts/agt/particles/counters/by_rate.coffee +17 -0
  63. data/app/assets/javascripts/agt/particles/counters/fixed.coffee +9 -0
  64. data/app/assets/javascripts/agt/particles/counters/null_counter.coffee +9 -0
  65. data/app/assets/javascripts/agt/particles/emission.coffee +35 -0
  66. data/app/assets/javascripts/agt/particles/emitters/null_emitter.coffee +7 -0
  67. data/app/assets/javascripts/agt/particles/emitters/path.coffee +10 -0
  68. data/app/assets/javascripts/agt/particles/emitters/ponctual.coffee +9 -0
  69. data/app/assets/javascripts/agt/particles/emitters/surface.coffee +10 -0
  70. data/app/assets/javascripts/agt/particles/initializers/explosion.coffee +19 -0
  71. data/app/assets/javascripts/agt/particles/initializers/life.coffee +16 -0
  72. data/app/assets/javascripts/agt/particles/initializers/macro_initializer.coffee +10 -0
  73. data/app/assets/javascripts/agt/particles/initializers/null_initializer.coffee +7 -0
  74. data/app/assets/javascripts/agt/particles/initializers/particle_sub_system.coffee +12 -0
  75. data/app/assets/javascripts/agt/particles/initializers/stream.coffee +20 -0
  76. data/app/assets/javascripts/agt/particles/mixins/randomizable.coffee +10 -0
  77. data/app/assets/javascripts/agt/particles/particle.coffee +32 -0
  78. data/app/assets/javascripts/agt/particles/sub_system.coffee +11 -0
  79. data/app/assets/javascripts/agt/particles/system.coffee +103 -0
  80. data/app/assets/javascripts/agt/particles/timers/instant.coffee +11 -0
  81. data/app/assets/javascripts/agt/particles/timers/limited.coffee +19 -0
  82. data/app/assets/javascripts/agt/particles/timers/null_timer.coffee +11 -0
  83. data/app/assets/javascripts/agt/particles/timers/unlimited.coffee +9 -0
  84. data/app/assets/javascripts/agt/particles/timers/until_death.coffee +11 -0
  85. data/app/assets/javascripts/agt/promise.coffee +214 -0
  86. data/app/assets/javascripts/agt/random/random.coffee +100 -0
  87. data/app/assets/javascripts/agt/random/seeds/lagged_fibonnacci.coffee +53 -0
  88. data/app/assets/javascripts/agt/random/seeds/linear.coffee +16 -0
  89. data/app/assets/javascripts/agt/random/seeds/linear_congruential.coffee +23 -0
  90. data/app/assets/javascripts/agt/random/seeds/math_random.coffee +9 -0
  91. data/app/assets/javascripts/agt/random/seeds/mersenne_twister.coffee +42 -0
  92. data/app/assets/javascripts/agt/random/seeds/no_random.coffee +12 -0
  93. data/app/assets/javascripts/agt/random/seeds/paul_houle.coffee +19 -0
  94. data/app/assets/javascripts/agt/signal.coffee +272 -0
  95. data/app/assets/javascripts/agt/sprites/animation.coffee +13 -0
  96. data/app/assets/javascripts/agt/sprites/sprite.coffee +30 -0
  97. data/app/assets/javascripts/agt/widgets/hash.coffee +36 -0
  98. data/app/assets/javascripts/agt/widgets/widgets.coffee +194 -0
  99. data/app/assets/javascripts/agt/widgets/widgets/checked_input.coffee +7 -0
  100. data/app/assets/javascripts/agt/widgets/widgets/focus_bubbling.coffee +15 -0
  101. data/lib/agt.rb +8 -0
  102. data/lib/agt/version.rb +5 -0
  103. metadata +173 -0
@@ -0,0 +1,7 @@
1
+ namespace('agt.mixins')
2
+ class agt.mixins.Disposable
3
+ init: ->
4
+ @initialized?()
5
+
6
+ dispose: ->
7
+ @disposed?()
@@ -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