cartilage 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.markdown +57 -0
- data/Rakefile +84 -0
- data/app/assets/images/cartilage/patterns/background.dark.png +0 -0
- data/app/assets/images/cartilage/patterns/background.dark.psd +0 -0
- data/app/assets/images/cartilage/patterns/background.light.png +0 -0
- data/app/assets/images/cartilage/patterns/background.light.psd +0 -0
- data/app/assets/images/cartilage/patterns/background.medium.png +0 -0
- data/app/assets/images/cartilage/patterns/background.medium.psd +0 -0
- data/app/assets/images/cartilage/patterns/background.png +0 -0
- data/app/assets/images/cartilage/patterns/background.psd +0 -0
- data/app/assets/javascripts/cartilage/application.js.coffee +45 -0
- data/app/assets/javascripts/cartilage/collections/segments.js.coffee +4 -0
- data/app/assets/javascripts/cartilage/core.js.coffee +8 -0
- data/app/assets/javascripts/cartilage/models/model.js.coffee +2 -0
- data/app/assets/javascripts/cartilage/models/segment.js.coffee +2 -0
- data/app/assets/javascripts/cartilage/views/bar_segment_view.js.coffee +58 -0
- data/app/assets/javascripts/cartilage/views/bar_view.js.coffee +85 -0
- data/app/assets/javascripts/cartilage/views/content_view.js.coffee +16 -0
- data/app/assets/javascripts/cartilage/views/image_view.js.coffee +76 -0
- data/app/assets/javascripts/cartilage/views/list_view.js.coffee +515 -0
- data/app/assets/javascripts/cartilage/views/list_view_item.js.coffee +85 -0
- data/app/assets/javascripts/cartilage/views/loading_indicator_view.js.coffee +147 -0
- data/app/assets/javascripts/cartilage/views/matrix_view.js.coffee +253 -0
- data/app/assets/javascripts/cartilage/views/matrix_view_item.js.coffee +5 -0
- data/app/assets/javascripts/cartilage/views/modal_view.js.coffee +26 -0
- data/app/assets/javascripts/cartilage/views/popover_view.js.coffee +212 -0
- data/app/assets/javascripts/cartilage/views/source_list_view.js.coffee +69 -0
- data/app/assets/javascripts/cartilage/views/source_list_view_item.js.coffee +5 -0
- data/app/assets/javascripts/cartilage/views/split_view.js.coffee +175 -0
- data/app/assets/javascripts/cartilage/views/usage_bar_view.js.coffee +5 -0
- data/app/assets/javascripts/cartilage/views/view.js.coffee +232 -0
- data/app/assets/javascripts/cartilage.js.coffee +18 -0
- data/app/assets/javascripts/extensions/console.js.coffee +9 -0
- data/app/assets/javascripts/extensions/constructor_name.js.coffee +10 -0
- data/app/assets/javascripts/extensions/properties.js.coffee +45 -0
- data/app/assets/javascripts/extensions/underscore.js.coffee +71 -0
- data/app/assets/stylesheets/cartilage/core.css.scss +432 -0
- data/app/assets/stylesheets/cartilage/mixins.css.scss +19 -0
- data/app/assets/stylesheets/cartilage/responsive.css.scss +192 -0
- data/app/assets/stylesheets/cartilage/variables.css.scss +113 -0
- data/app/assets/stylesheets/cartilage/views/list_view.css.scss +41 -0
- data/app/assets/stylesheets/cartilage/views/list_view_item.css.scss +47 -0
- data/app/assets/stylesheets/cartilage/views/loading_indicator_view.css.scss +50 -0
- data/app/assets/stylesheets/cartilage/views/matrix_view.css.scss +87 -0
- data/app/assets/stylesheets/cartilage/views/popover_view.css.scss +124 -0
- data/app/assets/stylesheets/cartilage/views/segmented_view.css.scss +98 -0
- data/app/assets/stylesheets/cartilage/views/source_list_view.css.scss +65 -0
- data/app/assets/stylesheets/cartilage/views/split_view.css.scss +80 -0
- data/app/assets/stylesheets/cartilage/views/usage_bar_view.css.scss +101 -0
- data/app/assets/stylesheets/cartilage/views/view.css.scss +13 -0
- data/app/assets/stylesheets/cartilage.css.scss +5 -0
- data/lib/cartilage/engine.rb +14 -0
- data/lib/cartilage/version.rb +3 -0
- data/lib/cartilage.rb +5 -0
- data/lib/tasks/cartilage_tasks.rake +4 -0
- data/test/cartilage_test.rb +7 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +9 -0
- data/test/dummy/app/assets/stylesheets/application.css +7 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config/application.rb +45 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +30 -0
- data/test/dummy/config/environments/production.rb +60 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +10 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +58 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/readme +6 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +26 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/framework/cartilage/application_test.js.coffee +14 -0
- data/test/framework/cartilage/views/bar_segment_view_test.js.coffee +5 -0
- data/test/framework/cartilage/views/bar_view_test.js.coffee +30 -0
- data/test/framework/cartilage/views/image_view_test.js.coffee +27 -0
- data/test/framework/cartilage/views/list_view_item_test.js.coffee +17 -0
- data/test/framework/cartilage/views/list_view_test.js.coffee +190 -0
- data/test/framework/cartilage/views/loading_indicator_view_test.js.coffee +5 -0
- data/test/framework/cartilage/views/matrix_view_item_test.js.coffee +5 -0
- data/test/framework/cartilage/views/matrix_view_test.js.coffee +5 -0
- data/test/framework/cartilage/views/popover_view_test.js.coffee +5 -0
- data/test/framework/cartilage/views/source_list_view_item_test.js.coffee +5 -0
- data/test/framework/cartilage/views/source_list_view_test.js.coffee +5 -0
- data/test/framework/cartilage/views/split_view_test.js.coffee +5 -0
- data/test/framework/cartilage/views/usage_bar_view_test.js.coffee +5 -0
- data/test/framework/cartilage/views/view_test.js.coffee +85 -0
- data/test/framework/extensions/properties_test.js.coffee +82 -0
- data/test/framework/extensions/underscore_test.js.coffee +73 -0
- data/test/framework/index.html +72 -0
- data/test/framework/vendor/backbone.js +1431 -0
- data/test/framework/vendor/coffee-script.js +8 -0
- data/test/framework/vendor/jquery.js +9440 -0
- data/test/framework/vendor/phantomjs-runner.js +196 -0
- data/test/framework/vendor/qunit.css +235 -0
- data/test/framework/vendor/qunit.js +1977 -0
- data/test/framework/vendor/run-qunit.js +81 -0
- data/test/framework/vendor/sinon-1.5.0.js +4142 -0
- data/test/framework/vendor/sinon-qunit-1.0.0.js +62 -0
- data/test/framework/vendor/underscore.js +1059 -0
- data/test/test_helper.rb +10 -0
- 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,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
|
+
# });
|