agt 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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