cartilage 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.markdown +57 -0
  3. data/Rakefile +84 -0
  4. data/app/assets/images/cartilage/patterns/background.dark.png +0 -0
  5. data/app/assets/images/cartilage/patterns/background.dark.psd +0 -0
  6. data/app/assets/images/cartilage/patterns/background.light.png +0 -0
  7. data/app/assets/images/cartilage/patterns/background.light.psd +0 -0
  8. data/app/assets/images/cartilage/patterns/background.medium.png +0 -0
  9. data/app/assets/images/cartilage/patterns/background.medium.psd +0 -0
  10. data/app/assets/images/cartilage/patterns/background.png +0 -0
  11. data/app/assets/images/cartilage/patterns/background.psd +0 -0
  12. data/app/assets/javascripts/cartilage/application.js.coffee +45 -0
  13. data/app/assets/javascripts/cartilage/collections/segments.js.coffee +4 -0
  14. data/app/assets/javascripts/cartilage/core.js.coffee +8 -0
  15. data/app/assets/javascripts/cartilage/models/model.js.coffee +2 -0
  16. data/app/assets/javascripts/cartilage/models/segment.js.coffee +2 -0
  17. data/app/assets/javascripts/cartilage/views/bar_segment_view.js.coffee +58 -0
  18. data/app/assets/javascripts/cartilage/views/bar_view.js.coffee +85 -0
  19. data/app/assets/javascripts/cartilage/views/content_view.js.coffee +16 -0
  20. data/app/assets/javascripts/cartilage/views/image_view.js.coffee +76 -0
  21. data/app/assets/javascripts/cartilage/views/list_view.js.coffee +515 -0
  22. data/app/assets/javascripts/cartilage/views/list_view_item.js.coffee +85 -0
  23. data/app/assets/javascripts/cartilage/views/loading_indicator_view.js.coffee +147 -0
  24. data/app/assets/javascripts/cartilage/views/matrix_view.js.coffee +253 -0
  25. data/app/assets/javascripts/cartilage/views/matrix_view_item.js.coffee +5 -0
  26. data/app/assets/javascripts/cartilage/views/modal_view.js.coffee +26 -0
  27. data/app/assets/javascripts/cartilage/views/popover_view.js.coffee +212 -0
  28. data/app/assets/javascripts/cartilage/views/source_list_view.js.coffee +69 -0
  29. data/app/assets/javascripts/cartilage/views/source_list_view_item.js.coffee +5 -0
  30. data/app/assets/javascripts/cartilage/views/split_view.js.coffee +175 -0
  31. data/app/assets/javascripts/cartilage/views/usage_bar_view.js.coffee +5 -0
  32. data/app/assets/javascripts/cartilage/views/view.js.coffee +232 -0
  33. data/app/assets/javascripts/cartilage.js.coffee +18 -0
  34. data/app/assets/javascripts/extensions/console.js.coffee +9 -0
  35. data/app/assets/javascripts/extensions/constructor_name.js.coffee +10 -0
  36. data/app/assets/javascripts/extensions/properties.js.coffee +45 -0
  37. data/app/assets/javascripts/extensions/underscore.js.coffee +71 -0
  38. data/app/assets/stylesheets/cartilage/core.css.scss +432 -0
  39. data/app/assets/stylesheets/cartilage/mixins.css.scss +19 -0
  40. data/app/assets/stylesheets/cartilage/responsive.css.scss +192 -0
  41. data/app/assets/stylesheets/cartilage/variables.css.scss +113 -0
  42. data/app/assets/stylesheets/cartilage/views/list_view.css.scss +41 -0
  43. data/app/assets/stylesheets/cartilage/views/list_view_item.css.scss +47 -0
  44. data/app/assets/stylesheets/cartilage/views/loading_indicator_view.css.scss +50 -0
  45. data/app/assets/stylesheets/cartilage/views/matrix_view.css.scss +87 -0
  46. data/app/assets/stylesheets/cartilage/views/popover_view.css.scss +124 -0
  47. data/app/assets/stylesheets/cartilage/views/segmented_view.css.scss +98 -0
  48. data/app/assets/stylesheets/cartilage/views/source_list_view.css.scss +65 -0
  49. data/app/assets/stylesheets/cartilage/views/split_view.css.scss +80 -0
  50. data/app/assets/stylesheets/cartilage/views/usage_bar_view.css.scss +101 -0
  51. data/app/assets/stylesheets/cartilage/views/view.css.scss +13 -0
  52. data/app/assets/stylesheets/cartilage.css.scss +5 -0
  53. data/lib/cartilage/engine.rb +14 -0
  54. data/lib/cartilage/version.rb +3 -0
  55. data/lib/cartilage.rb +5 -0
  56. data/lib/tasks/cartilage_tasks.rake +4 -0
  57. data/test/cartilage_test.rb +7 -0
  58. data/test/dummy/Rakefile +7 -0
  59. data/test/dummy/app/assets/javascripts/application.js +9 -0
  60. data/test/dummy/app/assets/stylesheets/application.css +7 -0
  61. data/test/dummy/app/controllers/application_controller.rb +3 -0
  62. data/test/dummy/app/helpers/application_helper.rb +2 -0
  63. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  64. data/test/dummy/config/application.rb +45 -0
  65. data/test/dummy/config/boot.rb +10 -0
  66. data/test/dummy/config/database.yml +25 -0
  67. data/test/dummy/config/environment.rb +5 -0
  68. data/test/dummy/config/environments/development.rb +30 -0
  69. data/test/dummy/config/environments/production.rb +60 -0
  70. data/test/dummy/config/environments/test.rb +42 -0
  71. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  72. data/test/dummy/config/initializers/inflections.rb +10 -0
  73. data/test/dummy/config/initializers/mime_types.rb +5 -0
  74. data/test/dummy/config/initializers/secret_token.rb +7 -0
  75. data/test/dummy/config/initializers/session_store.rb +8 -0
  76. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  77. data/test/dummy/config/locales/en.yml +5 -0
  78. data/test/dummy/config/routes.rb +58 -0
  79. data/test/dummy/config.ru +4 -0
  80. data/test/dummy/db/readme +6 -0
  81. data/test/dummy/public/404.html +26 -0
  82. data/test/dummy/public/422.html +26 -0
  83. data/test/dummy/public/500.html +26 -0
  84. data/test/dummy/public/favicon.ico +0 -0
  85. data/test/dummy/script/rails +6 -0
  86. data/test/framework/cartilage/application_test.js.coffee +14 -0
  87. data/test/framework/cartilage/views/bar_segment_view_test.js.coffee +5 -0
  88. data/test/framework/cartilage/views/bar_view_test.js.coffee +30 -0
  89. data/test/framework/cartilage/views/image_view_test.js.coffee +27 -0
  90. data/test/framework/cartilage/views/list_view_item_test.js.coffee +17 -0
  91. data/test/framework/cartilage/views/list_view_test.js.coffee +190 -0
  92. data/test/framework/cartilage/views/loading_indicator_view_test.js.coffee +5 -0
  93. data/test/framework/cartilage/views/matrix_view_item_test.js.coffee +5 -0
  94. data/test/framework/cartilage/views/matrix_view_test.js.coffee +5 -0
  95. data/test/framework/cartilage/views/popover_view_test.js.coffee +5 -0
  96. data/test/framework/cartilage/views/source_list_view_item_test.js.coffee +5 -0
  97. data/test/framework/cartilage/views/source_list_view_test.js.coffee +5 -0
  98. data/test/framework/cartilage/views/split_view_test.js.coffee +5 -0
  99. data/test/framework/cartilage/views/usage_bar_view_test.js.coffee +5 -0
  100. data/test/framework/cartilage/views/view_test.js.coffee +85 -0
  101. data/test/framework/extensions/properties_test.js.coffee +82 -0
  102. data/test/framework/extensions/underscore_test.js.coffee +73 -0
  103. data/test/framework/index.html +72 -0
  104. data/test/framework/vendor/backbone.js +1431 -0
  105. data/test/framework/vendor/coffee-script.js +8 -0
  106. data/test/framework/vendor/jquery.js +9440 -0
  107. data/test/framework/vendor/phantomjs-runner.js +196 -0
  108. data/test/framework/vendor/qunit.css +235 -0
  109. data/test/framework/vendor/qunit.js +1977 -0
  110. data/test/framework/vendor/run-qunit.js +81 -0
  111. data/test/framework/vendor/sinon-1.5.0.js +4142 -0
  112. data/test/framework/vendor/sinon-qunit-1.0.0.js +62 -0
  113. data/test/framework/vendor/underscore.js +1059 -0
  114. data/test/test_helper.rb +10 -0
  115. metadata +373 -0
@@ -0,0 +1,147 @@
1
+ #
2
+ # Loading Indicator View
3
+ #
4
+ # Manages the display of a canvas-based spinning loading indicator.
5
+ #
6
+
7
+ class window.Cartilage.Views.LoadingIndicatorView extends Cartilage.View
8
+
9
+ # Properties ---------------------------------------------------------------
10
+
11
+ #
12
+ # The number of bars that should be drawn in the spinner. Defaults to 10.
13
+ #
14
+ @property "barCount", default: 10
15
+
16
+ #
17
+ # The width and height of the bars. Defaults to `{ width: 4, height: 12 }`.
18
+ #
19
+ @property "barSize", default: { width: 4, height: 12 }
20
+
21
+ #
22
+ # The color of the bars.
23
+ #
24
+ @property "barColor", default: { red: 85, green: 85, blue: 85 }
25
+
26
+ #
27
+ # The x and y coordinates for the center point of the loading indicator
28
+ # within the canvas. This should typically be the canvas width and height
29
+ # divided by 2.
30
+ #
31
+ @property "centerPosition", default: { x: 48, y: 48 }
32
+
33
+ #
34
+ # The inner radius of the spinning indicator. Each bar will be drawn from
35
+ # this point, outward.
36
+ #
37
+ @property "innerRadius", default: 10
38
+
39
+ #
40
+ # Whether or not the loading indicator is currently animating.
41
+ #
42
+ @property "isAnimating", access: READONLY, default: no
43
+
44
+ # Internal Properties ------------------------------------------------------
45
+
46
+ # --------------------------------------------------------------------------
47
+
48
+ initialize: (options = {}) ->
49
+
50
+ # Initialize the View
51
+ super(options)
52
+
53
+ # Initialize Canvas Element
54
+ ($ @el).html @_canvasElement = @make "canvas",
55
+ width: 96,
56
+ height: 96
57
+
58
+ # Initialize Canvas Context
59
+ @_canvasContext = @_canvasElement.getContext("2d")
60
+
61
+ # Observe for view events so that we can start or stop the indicator
62
+ # when appropriate.
63
+ @observe @, "willPresent", @start
64
+ @observe @, "removed", @stop
65
+
66
+ prepare: ->
67
+
68
+ # Prepare the View
69
+ super()
70
+
71
+ # Determine the bar color from CSS, if present
72
+ if color = ($ @el).css("color")
73
+ colors = color.split(',')
74
+ red = parseInt(colors[0].substr(4, 3), 10)
75
+ green = parseInt(colors[1], 10)
76
+ blue = parseInt(colors[2], 10)
77
+ @barColor = { red: red, green: green, blue: blue }
78
+
79
+ #
80
+ # Starts the loading indicator animation.
81
+ #
82
+ start: =>
83
+ return if @isAnimating
84
+ @_isAnimating = true
85
+ @_animateNextFrame()
86
+ @isAnimating
87
+
88
+ #
89
+ # Stops drawing the loading indicator and clears its context state.
90
+ #
91
+ stop: ->
92
+ return unless @isAnimating
93
+ @_clearFrame(@_canvasContext)
94
+ @_isAnimating = false
95
+
96
+ cleanup: ->
97
+ @stop()
98
+ super()
99
+
100
+ #
101
+ # Draw a frame.
102
+ #
103
+ _draw: (context, offset) ->
104
+ @_clearFrame(context)
105
+ context.save()
106
+ context.translate(@centerPosition.x, @centerPosition.y)
107
+
108
+ for i in [0..@barCount]
109
+ currentBar = (offset + i) % @barCount
110
+ pos = @_calculatePosition(currentBar)
111
+
112
+ context.save()
113
+ context.translate(pos.x, pos.y)
114
+ context.rotate(pos.angle)
115
+
116
+ @_drawBlock(@_canvasContext, i)
117
+
118
+ context.restore()
119
+
120
+ context.restore()
121
+
122
+ _drawBlock: (context, barNumber) ->
123
+ context.fillStyle = @._makeRGBA(@barColor.red, @barColor.green, @barColor.blue, (@barCount + 1 - barNumber) / (@barCount + 1))
124
+ context.fillRect(-@barSize.width / 2, 0, @barSize.width, @barSize.height)
125
+
126
+ _animateNextFrame: =>
127
+ return unless @isAnimating
128
+ @_currentOffset = if _.isUndefined(@_currentOffset) then 1 else (@_currentOffset + 1) % @barCount
129
+ @_draw(@_canvasContext, @_currentOffset)
130
+ _.delay @_animateNextFrame, 50
131
+
132
+ _clearFrame: (context) ->
133
+ context.clearRect(0, 0, @_canvasElement.clientWidth, @_canvasElement.clientHeight)
134
+
135
+ _calculateAngle: (barNumber) ->
136
+ 2 * barNumber * Math.PI / @barCount
137
+
138
+ _calculatePosition: (barNumber) ->
139
+ angle = @_calculateAngle(barNumber)
140
+ {
141
+ y: (@innerRadius * Math.cos(-angle)),
142
+ x: (@innerRadius * Math.sin(-angle)),
143
+ angle: angle
144
+ }
145
+
146
+ _makeRGBA: ->
147
+ "rgba(#{arguments[0]}, #{arguments[1]}, #{arguments[2]}, #{arguments[3]})"
@@ -0,0 +1,253 @@
1
+ #
2
+ # Matrix View
3
+ #
4
+
5
+ class window.Cartilage.Views.MatrixView extends Cartilage.Views.ListView
6
+
7
+ events: _.extend {
8
+ "mouseup": "onMouseUp",
9
+ "mousemove": "onMouseMove"
10
+ "mousedown": "onMouseDown"
11
+ }, Cartilage.Views.ListView.prototype.events
12
+
13
+ # Properties ---------------------------------------------------------------
14
+
15
+ #
16
+ # Whether or not drag-selection should be enabled.
17
+ #
18
+ @property "allowsDragSelection", default: no
19
+
20
+ # Internal Properties ------------------------------------------------------
21
+
22
+ # --------------------------------------------------------------------------
23
+
24
+ initialize: (options = {}) ->
25
+
26
+ # Initialize the List View
27
+ super(options)
28
+
29
+ # Add the Drag Selection Overlay
30
+ if @allowsDragSelection
31
+ ($ @el).append ($ "<div/>").addClass("overlay").hide()
32
+
33
+ #
34
+ # Handles click events for the entire list elements, including the list
35
+ # container.
36
+ #
37
+ onMouseDown: (event) =>
38
+ super(event)
39
+
40
+ # Only process mouse down events if drag-selection is allowed...
41
+ return unless @allowsDragSelection
42
+
43
+ # Only handle this event if the primary mouse button was pressed...
44
+ return unless event.button is 0
45
+
46
+ @isDragging = true
47
+ @originX = event.pageX - ($ @el).offset().left
48
+ @originY = event.pageY - ($ @el).offset().top
49
+ @originScrollTop = ($ @el).scrollTop()
50
+ @originScrollLeft = ($ @el).scrollLeft()
51
+ (@$ ".overlay").css {
52
+ "width": "0px",
53
+ "height": "0px",
54
+ "left": event.pageX,
55
+ "top": event.pageY
56
+ }
57
+
58
+ onMouseUp: (event) =>
59
+ @isDragging = false
60
+ (@$ ".overlay").hide().css {
61
+ width: "0px",
62
+ height: "0px"
63
+ }
64
+
65
+ onMouseMove: (event) =>
66
+ return unless @isDragging
67
+
68
+ overlayElement = (@$ ".overlay")
69
+ overlayElement.show()
70
+
71
+ left = @originX
72
+ top = @originY
73
+ width = (event.pageX - ($ @el).offset().left) - @originX
74
+ height = (event.pageY - ($ @el).offset().top) - @originY
75
+
76
+ if width < 0
77
+ width = -width
78
+ left = (event.pageX - ($ @el).offset().left)
79
+ else
80
+ left = @originX
81
+
82
+ if height < 0
83
+ height = -height
84
+ top = (event.pageY - ($ @el).offset().top)
85
+ else
86
+ top = @originY
87
+
88
+ if ($ @el).scrollTop() == @originScrollTop
89
+ top = top + @originScrollTop
90
+ else if ($ @el).scrollTop() > @originScrollTop
91
+ top = top + @originScrollTop
92
+ height = height + (($ @el).scrollTop() - @originScrollTop)
93
+ else if ($ @el).scrollTop() < @originScrollTop
94
+ top = top + ($ @el).scrollTop()
95
+ height = height + (($ @el).scrollTop() + @originScrollTop)
96
+
97
+ overlayElement.css {
98
+ left: left + 'px',
99
+ top: top + 'px',
100
+ width: width + 'px',
101
+ height: height + 'px'
102
+ }
103
+
104
+ _.each (@$ "li"), (element) =>
105
+ if ($ element).overlapped left, (top - ($ @el).scrollTop()), width, height
106
+ @selectAnother element
107
+ else
108
+ @deselect element
109
+
110
+ #
111
+ # Handles key down events while the view is focused.
112
+ #
113
+ onKeyDown: (event) =>
114
+ keyCode = event.keyCode
115
+
116
+ # Arrow Keys
117
+ if keyCode in [ 37..40 ] or keyCode in [ 63232..63235 ]
118
+
119
+ event.preventDefault()
120
+
121
+ # Select the first item if there is no selection when the first key press
122
+ # occurs.
123
+ return @selectFirst() unless @selected.length > 0
124
+
125
+ # Route the key event to the proper handler based on the combination of
126
+ # keys that were pressed.
127
+ if event.shiftKey or event.metaKey
128
+ return @expandSelectionLeft event if keyCode in [ 37, 63234 ]
129
+ return @expandSelectionUp event if keyCode in [ 38, 63232 ]
130
+ return @expandSelectionRight event if keyCode in [ 39, 63235 ]
131
+ return @expandSelectionDown event if keyCode in [ 40, 63233 ]
132
+ else
133
+ return @moveSelectionLeft event if keyCode in [ 37, 63234 ]
134
+ return @moveSelectionUp event if keyCode in [ 38, 63232 ]
135
+ return @moveSelectionRight event if keyCode in [ 39, 63235 ]
136
+ return @moveSelectionDown event if keyCode in [ 40, 63233 ]
137
+
138
+ # Command-A
139
+ if keyCode == 65 and event.metaKey
140
+ event.preventDefault()
141
+ return @selectAll()
142
+
143
+ # Enter Key
144
+ if keyCode == 13
145
+ event.preventDefault()
146
+ return @open event.target
147
+
148
+ # Delete Key
149
+ if keyCode in [ 8, 46 ]
150
+ event.preventDefault()
151
+ return @remove event.target
152
+
153
+ # Space Key
154
+ return event.preventDefault() if keyCode == 32
155
+
156
+ #
157
+ # Moves the selection to the left. If there is no selection or the selection
158
+ # is already at the first-most element, the first element will become or
159
+ # remain selected.
160
+ #
161
+ moveSelectionLeft: (e) =>
162
+ element = ($ @focusedElement).prev "li"
163
+ if element.length > 0
164
+ @clearSelection() and @select element
165
+ @focusedElement = ($ element).focus()
166
+
167
+ #
168
+ # Moves the selection to the right. If there is no selection the first
169
+ # item will be selected.
170
+ #
171
+ moveSelectionRight: (e) =>
172
+ element = ($ @focusedElement).next "li"
173
+ if element.length > 0
174
+ @clearSelection() and @select element
175
+ @focusedElement = ($ element).focus()
176
+
177
+ #
178
+ # Moves the selection to the item visually below the selected item. If there
179
+ # is no selection the first item will be selected.
180
+ #
181
+ moveSelectionDown: (e) ->
182
+ centerPoint = ($ @focusedElement).centerPoint()
183
+ centerPoint.y += ($ @focusedElement).outerHeight(true)
184
+
185
+ elements = ($ @focusedElement).nextAll "li"
186
+ element = elements.filter (idx, element) ->
187
+ ($ element).containsPoint(centerPoint)
188
+
189
+ if element.length > 0
190
+ @clearSelection() and @select element
191
+ @focusedElement = ($ element).focus()
192
+
193
+ #
194
+ # Moves the selection to the item visually above the selected item. If there
195
+ # is no selection the first item will be selected.
196
+ #
197
+ moveSelectionUp: (e) ->
198
+ centerPoint = ($ @focusedElement).centerPoint()
199
+ centerPoint.y -= ($ @focusedElement).outerHeight(true)
200
+
201
+ elements = ($ @focusedElement).prevAll "li"
202
+ element = elements.filter (idx, element) ->
203
+ ($ element).containsPoint(centerPoint)
204
+
205
+ if element.length > 0
206
+ @clearSelection() and @select element
207
+ @focusedElement = ($ element).focus()
208
+
209
+ #
210
+ # Expands the selection leftward from the currently selected element.
211
+ #
212
+ expandSelectionLeft: (e) ->
213
+ element = ($ @focusedElement).prev "li"
214
+ if element.length > 0
215
+ @selectAnother element
216
+ @focusedElement = ($ element).focus()
217
+
218
+ #
219
+ # Expands the selection rightward from the currently selected element.
220
+ #
221
+ expandSelectionRight: (e) ->
222
+ element = ($ @focusedElement).next "li"
223
+ if element.length > 0
224
+ @selectAnother element
225
+ @focusedElement = ($ element).focus()
226
+
227
+ #
228
+ # Expands the selection downward from the currently selected element.
229
+ #
230
+ expandSelectionDown: (e) ->
231
+ centerPoint = ($ @focusedElement).centerPoint()
232
+ centerPoint.y += ($ @focusedElement).outerHeight(true)
233
+
234
+ elements = ($ @focusedElement).nextAll "li"
235
+ element = elements.filter (idx, element) ->
236
+ ($ element).containsPoint(centerPoint)
237
+
238
+ @selectAnother element if element.length > 0
239
+ @focusedElement = ($ element).focus()
240
+
241
+ #
242
+ # Expands the selection upward from the currently selected element.
243
+ #
244
+ expandSelectionUp: (e) ->
245
+ centerPoint = ($ @focusedElement).centerPoint()
246
+ centerPoint.y -= ($ @focusedElement).outerHeight(true)
247
+
248
+ elements = ($ @focusedElement).prevAll "li"
249
+ element = elements.filter (idx, element) ->
250
+ ($ element).containsPoint(centerPoint)
251
+
252
+ @selectAnother element if element.length > 0
253
+ @focusedElement = ($ element).focus()
@@ -0,0 +1,5 @@
1
+ #
2
+ # Matrix View Item
3
+ #
4
+
5
+ class window.Cartilage.Views.MatrixViewItem extends Cartilage.Views.ListViewItem
@@ -0,0 +1,26 @@
1
+ #
2
+ # Modal View
3
+ #
4
+ # This is a thin wrapper around Bootstrap's Modal plugin that allows you
5
+ # to contain your modal view logic in its own class.
6
+ #
7
+
8
+ class window.Cartilage.Views.ModalView extends Cartilage.View
9
+
10
+ className: "modal fade"
11
+
12
+ render: ->
13
+ ($ @el).html @template { model: @model }
14
+ @
15
+
16
+ show: ->
17
+ ($ document.body).append @render().el
18
+ ($ @el).modal('show')
19
+ ($ @el).on 'hidden', @cleanup
20
+
21
+ hide: =>
22
+ ($ @el).modal('hide')
23
+
24
+ cleanup: =>
25
+ ($ @el).off 'hidden'
26
+ super()
@@ -0,0 +1,212 @@
1
+ #
2
+ # Popover View
3
+ #
4
+
5
+ class window.Cartilage.Views.PopoverView extends Cartilage.View
6
+
7
+ # Properties ---------------------------------------------------------------
8
+
9
+ #
10
+ # The view that the popover instance is attached to. This is used for
11
+ # calculating where the popover should be displayed.
12
+ #
13
+ @property "attachedView"
14
+
15
+ #
16
+ # Where the popover should be positioned relative to the attached view.
17
+ # Valid options are top, bottom, left and right.
18
+ #
19
+ @property "position", default: "top"
20
+
21
+ # Internal Properties ------------------------------------------------------
22
+
23
+ # TODO Make this @contentView, an actual read-only view
24
+ _contentElement: null
25
+ _triangleElement: null
26
+
27
+ # --------------------------------------------------------------------------
28
+
29
+ initialize: (options = {}) ->
30
+
31
+ # Initialize the View
32
+ super(options)
33
+
34
+ # Initialize the Triange Element
35
+ ($ @el).append @_triangleElement = @make "div",
36
+ class: "triangle"
37
+
38
+ # Initialize the Content Element
39
+ # TODO Should be an instance of View...
40
+ ($ @el).append @_contentElement = @make "div",
41
+ class: "content-view"
42
+
43
+ prepare: ->
44
+
45
+ # Prepare the View
46
+ super()
47
+
48
+ # Calculate Position
49
+ @_calculatePosition()
50
+
51
+ _calculatePosition: () ->
52
+ return unless @attachedElement
53
+
54
+ @resizeToFitContents()
55
+
56
+ offset = ($ @attachedElement).offset()
57
+ width = ($ @attachedElement).outerWidth()
58
+ height = ($ @attachedElement).outerHeight()
59
+ top = null
60
+ left = null
61
+
62
+ switch @position
63
+
64
+ when "top"
65
+ top = offset.top - ($ @el).outerHeight(true)
66
+ left = (offset.left + (width / 2)) - (($ @el).outerWidth(true) / 2)
67
+ if offset.top - ($ @el).outerHeight(true) < 0
68
+ top = offset.top + height
69
+ @position = "bottom"
70
+
71
+ when "bottom"
72
+ top = offset.top + height
73
+ left = (offset.left + (width / 2)) - (($ @el).outerWidth(true) / 2)
74
+ maxHeight = ($ document).height()
75
+ if offset.top + height + ($ @el).outerHeight(true) > maxHeight
76
+ top = offset.top - ($ @el).outerHeight(true)
77
+ @position = "top"
78
+
79
+ when "left"
80
+ top = (offset.top + (height / 2)) - (($ @el).outerHeight(true) / 2)
81
+ left = offset.left - ($ @el).outerWidth(true)
82
+ if offset.left - ($ @el).outerWidth(true) < 0
83
+ left = offset.left + width
84
+ @position = "right"
85
+
86
+ when "right"
87
+ top = (offset.top + (height / 2)) - (($ @el).outerHeight(true) / 2)
88
+ left = offset.left + width
89
+ maxWidth = ($ document).width()
90
+ if offset.left + width + ($ @el).outerWidth(true) > maxWidth
91
+ left = offset.left - ($ @el).outerWidth(true)
92
+ @position = "left"
93
+
94
+ ($ @el).removeClass("top")
95
+ ($ @el).removeClass("right")
96
+ ($ @el).removeClass("bottom")
97
+ ($ @el).removeClass("left")
98
+ ($ @el).addClass(@position)
99
+
100
+ # Adjust for Screen
101
+ # adjustment = 0
102
+ # marginLeft = (($ @triangleElement).width() / 2) * -1
103
+ #
104
+ # if (left < 0)
105
+ # adjustment = (left * -1) * -1
106
+ # left = 0
107
+ #
108
+ # else if left > (($ document).width() - ($ @el).outerWidth(true))
109
+ # adjustment = left - (($ document).width() - ($ @el).outerWidth(true))
110
+ # left = (($ document).width() - ($ @el).outerWidth(true))
111
+ #
112
+ # console.log "Adjusting triangle position by " + (marginLeft + adjustment) + " pixels"
113
+ #
114
+ # ($ @triangleElement).css {
115
+ # marginLeft: "#{marginLeft + adjustment}px"
116
+ # }
117
+
118
+ ($ @el).css {
119
+ top: "#{top}px",
120
+ left: "#{left}px"
121
+ }
122
+
123
+ presentRelativeToElement: (element, position) ->
124
+ @attachedElement = element
125
+ @position = (position ?= "top")
126
+ ($ @el).css { visibility: "hidden" }
127
+ ($ @_contentElement).children().css("display", "block")
128
+ ($ document.body).append @render().el
129
+ @_calculatePosition()
130
+ ($ @el).hide().css { visibility: "visible" }
131
+ ($ @el).fadeIn()
132
+
133
+ ($ document).unbind("click", @handleDocumentClickEvent).click @handleDocumentClickEvent
134
+
135
+ resizeToFitContents: ->
136
+ element = ($ @_contentElement).children()[0]
137
+ width = ($ element).outerWidth()
138
+ height = ($ element).outerHeight()
139
+
140
+ ($ @_contentElement).css {
141
+ width: "#{width}px",
142
+ height: "#{height}px"
143
+ }
144
+
145
+ ($ @el).css {
146
+ width: "#{width}px",
147
+ height: "#{height}px"
148
+ }
149
+
150
+ handleDocumentClickEvent: (event) =>
151
+
152
+ return if event.target == @attachedElement
153
+
154
+ ($ document).unbind("click", @handleDocumentClickEvent)
155
+
156
+ ($ @el).fadeOut "fast", =>
157
+ ($ @el).remove()
158
+
159
+ # // Event Handlers ----------------------------------------------------------
160
+ #
161
+ # _startObserving: function($super)
162
+ # {
163
+ # $super();
164
+ #
165
+ # // Document Click Events
166
+ # if (this.handleDocumentClickEvent && !this._handleDocumentClickEventListener)
167
+ # {
168
+ # $L.debug("Observing for Document Click Events", this);
169
+ # this._handleDocumentClickEventListener = this.handleDocumentClickEvent.bindAsEventListener(this);
170
+ # Event.observe(document, "click", this._handleDocumentClickEventListener);
171
+ # }
172
+ # },
173
+ #
174
+ # _stopObserving: function($super)
175
+ # {
176
+ # $super();
177
+ #
178
+ # // Document Click Events
179
+ # if (this._handleDocumentClickEventListener)
180
+ # {
181
+ # Event.stopObserving(document, "click", this._handleDocumentClickEventListener);
182
+ # this._handleDocumentClickEventListener = false;
183
+ # }
184
+ #
185
+ # // Handle Scroll Notifications
186
+ # this.addObserver(this.handleViewDidScrollNotification, "ViewDidScrollNotification");
187
+ # },
188
+ #
189
+ # handleDocumentClickEvent: function(event)
190
+ # {
191
+ # var element = event.element();
192
+ #
193
+ # // Ignore Clicks in PopoverView
194
+ # if (element == this.get("element") || (element != this.get("element") && element.descendantOf(this.get("element"))))
195
+ # return;
196
+ #
197
+ # // Ignore clicks in Attached View
198
+ # if (this.get("attachedView") && (element == this.get("attachedView.element") || (element != this.get("attachedView.element") && element.descendantOf(this.get("attachedView.element")))))
199
+ # return;
200
+ #
201
+ # $L.info("handleDocumentClickEvent", this);
202
+ #
203
+ # this.removeFromSuperviewAnimated();
204
+ # },
205
+ #
206
+ # handleViewDidScrollNotification: function(view)
207
+ # {
208
+ # if (this.get("superview"))
209
+ # this.calculatePosition();
210
+ # }
211
+ #
212
+ # });