cartilage 0.1.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 (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
+ # });