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