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,515 @@
|
|
1
|
+
#
|
2
|
+
# List View
|
3
|
+
#
|
4
|
+
# Manages a list of items as an unordered list (<ul/>), providing rich
|
5
|
+
# interaction support (selection, keyboard navigation, removal, reordering,
|
6
|
+
# etc).
|
7
|
+
#
|
8
|
+
|
9
|
+
#= require cartilage/views/list_view_item
|
10
|
+
|
11
|
+
class window.Cartilage.Views.ListView extends Cartilage.View
|
12
|
+
|
13
|
+
# Properties ---------------------------------------------------------------
|
14
|
+
|
15
|
+
#
|
16
|
+
# Whether or not items may be removed from the list view through keyboard
|
17
|
+
# actions (i.e. pressing delete while the item is selected).
|
18
|
+
#
|
19
|
+
@property "allowsRemove", default: no
|
20
|
+
|
21
|
+
#
|
22
|
+
# Whether or not selections of list view items can be made in the list
|
23
|
+
# view.
|
24
|
+
#
|
25
|
+
@property "allowsSelection", default: yes
|
26
|
+
|
27
|
+
#
|
28
|
+
# Whether or not the list view may clear its selection through user
|
29
|
+
# actions, such as clicking on the empty areas of a list view.
|
30
|
+
#
|
31
|
+
@property "allowsDeselection", default: yes
|
32
|
+
|
33
|
+
#
|
34
|
+
# Whether or not multiple list view items may be selected at once.
|
35
|
+
#
|
36
|
+
@property "allowsMultipleSelection", default: no
|
37
|
+
|
38
|
+
#
|
39
|
+
# Whether or not the user can drag list view items around to reorder them.
|
40
|
+
#
|
41
|
+
@property "allowsDragToReorder", default: no
|
42
|
+
|
43
|
+
#
|
44
|
+
# The default class for all list view items that are not explicitly
|
45
|
+
# initialized.
|
46
|
+
#
|
47
|
+
@property "itemView", default: Cartilage.Views.ListViewItem
|
48
|
+
|
49
|
+
#
|
50
|
+
# An array containing the view of each item rendered as a part of the list
|
51
|
+
#
|
52
|
+
@property "listItemViews", access: READONLY, default: []
|
53
|
+
|
54
|
+
#
|
55
|
+
# A collection containing the models of the currently selected items.
|
56
|
+
#
|
57
|
+
# TODO Rename to selectedModels
|
58
|
+
# TODO Add selectedItems
|
59
|
+
@property "selected", access: READONLY
|
60
|
+
|
61
|
+
#
|
62
|
+
# A collection containing all of the list view items managed by the list
|
63
|
+
# view.
|
64
|
+
#
|
65
|
+
@property "draggedItems"
|
66
|
+
|
67
|
+
# --------------------------------------------------------------------------
|
68
|
+
|
69
|
+
events:
|
70
|
+
"dblclick ul.list-view-items-container > li": "open"
|
71
|
+
"click ul.list-view-items-container > li": "onFocus"
|
72
|
+
"keydown ul.list-view-items-container": "onKeyDown"
|
73
|
+
"mousedown ul.list-view-items-container": "onMouseDown"
|
74
|
+
"dragover ul.list-view-items-container": "onDragOver"
|
75
|
+
"drop ul.list-view-items-container": "onDrop"
|
76
|
+
|
77
|
+
initialize: (options = {}) ->
|
78
|
+
# Initialize the View
|
79
|
+
super(options)
|
80
|
+
|
81
|
+
# Initialize List View Items Container
|
82
|
+
($ @el).append @_listViewItemsContainer = @make "ul",
|
83
|
+
class: "list-view-items-container"
|
84
|
+
tabindex: 0
|
85
|
+
|
86
|
+
# Keep the selected collection in the same order as the view collection
|
87
|
+
@_selected = new Backbone.Collection(
|
88
|
+
null,
|
89
|
+
comparator: (item) =>
|
90
|
+
@collection.indexOf(item)
|
91
|
+
)
|
92
|
+
|
93
|
+
# _.clone of properties is shallow only so we won't get a clean copy if we use the
|
94
|
+
# default method in property - instead, we'll maintain explicit creation here.
|
95
|
+
@_draggedItems = new Backbone.Collection
|
96
|
+
|
97
|
+
@observe @collection, "add", @addModel
|
98
|
+
@observe @collection, "reset", @update # TODO Don't re-render the entire view for removals
|
99
|
+
@observe @collection, "remove", @prepare # TODO Don't re-render the entire view for removals
|
100
|
+
|
101
|
+
prepare: ->
|
102
|
+
|
103
|
+
# Prepare the View
|
104
|
+
super()
|
105
|
+
|
106
|
+
# Update the View
|
107
|
+
@update()
|
108
|
+
|
109
|
+
update: ->
|
110
|
+
# Clean up all existing item views and their container elements.
|
111
|
+
(@$ "ul.list-view-items-container > li.list-view-item").each (idx, element) =>
|
112
|
+
@removeItem(element)
|
113
|
+
|
114
|
+
_.each @renderModels(), (view) => @addItem(view)
|
115
|
+
@restoreSelection() if @_selected.models.length > 0
|
116
|
+
|
117
|
+
renderModels: =>
|
118
|
+
items = _.map @collection.models, (model) =>
|
119
|
+
@renderModel(model)
|
120
|
+
|
121
|
+
renderModel: (model) =>
|
122
|
+
if @itemView? then new @itemView { model: model, listView: @ } else console.warn("Could not find corresponding itemView for #{@constructor.name}")
|
123
|
+
|
124
|
+
addModel: (model) =>
|
125
|
+
@addItem @renderModel(model)
|
126
|
+
|
127
|
+
#
|
128
|
+
# Adds an already instantiated list view item (must derive from
|
129
|
+
# Cartilage.Views.ListViewItem) to the list view. The list view item's
|
130
|
+
# model will automatically be added to the list view's collection, but
|
131
|
+
# will not trigger any notifications that it was added.
|
132
|
+
#
|
133
|
+
addItem: (itemView, container = @_listViewItemsContainer) =>
|
134
|
+
|
135
|
+
# Store the view in order in the _listItemView array so we can
|
136
|
+
# insert views in the correct order.
|
137
|
+
index = @collection.indexOf(itemView.model)
|
138
|
+
if @_listItemViews[index]?
|
139
|
+
@_listItemViews.splice(@collection.indexOf(itemView.model), 0, itemView)
|
140
|
+
else
|
141
|
+
@_listItemViews[index] = itemView
|
142
|
+
|
143
|
+
# TODO Ensure that the item derives from ListViewItem
|
144
|
+
@addSubview itemView, container
|
145
|
+
|
146
|
+
#
|
147
|
+
# Removes a passed element from the list view's superview as well
|
148
|
+
# as from the @_listViewItems array.
|
149
|
+
#
|
150
|
+
removeItem: (element) =>
|
151
|
+
if view = ($ element).data("view")
|
152
|
+
view.removeFromSuperview()
|
153
|
+
_.remove(@_listViewItems, view) if @_listViewItems?
|
154
|
+
else
|
155
|
+
($ element).remove()
|
156
|
+
|
157
|
+
|
158
|
+
#
|
159
|
+
# Override View's insertSubviewItem method so a list view item
|
160
|
+
# can be added at the correct position in the list
|
161
|
+
#
|
162
|
+
insertSubviewElement: (view, container) ->
|
163
|
+
if @collection.length == 1
|
164
|
+
($ container ).append(view.el)
|
165
|
+
else
|
166
|
+
flattenedListItemViews = _.flatten(@_listItemViews)
|
167
|
+
index = _.indexOf(flattenedListItemViews, view)
|
168
|
+
if index == -1
|
169
|
+
($ container ).append(view.el)
|
170
|
+
else if index == 0
|
171
|
+
($ container ).prepend(view.el)
|
172
|
+
else
|
173
|
+
($ flattenedListItemViews[index - 1].el ).after(view.el)
|
174
|
+
|
175
|
+
#
|
176
|
+
# Selects the specified list item. Returns true if the item was selected or
|
177
|
+
# false if no action was performed.
|
178
|
+
#
|
179
|
+
select: (e) =>
|
180
|
+
return unless @allowsSelection
|
181
|
+
|
182
|
+
element = unless _.isUndefined(e.target) then e.target else e
|
183
|
+
model = ($ element).data("model")
|
184
|
+
|
185
|
+
# Do not attempt to select the element if it is already selected.
|
186
|
+
return if @_selected.contains(model)
|
187
|
+
|
188
|
+
# If an element is already selected, deselect it before continuing.
|
189
|
+
if @allowsMultipleSelection
|
190
|
+
# In multi-select, do a full reset so observing "reset" on @_selected works as expected
|
191
|
+
@clearSelection() if @_selected.length > 0
|
192
|
+
else
|
193
|
+
# In single select, call deselect on each selected element so observing "deselect" works as expected
|
194
|
+
_.each(($ @el).find("li.list-view-item.selected"), (element) => @deselect(element))
|
195
|
+
|
196
|
+
@_selected.add model
|
197
|
+
($ element).addClass "selected"
|
198
|
+
# ($ element).focus() unless ($ element).is(":focus")
|
199
|
+
@focusedElement = ($ element)
|
200
|
+
@trigger "select", @_selected
|
201
|
+
|
202
|
+
selectAnother: (e) =>
|
203
|
+
# If multiple selection is not allowed, simply select the requested item
|
204
|
+
# and return instead of adding it to the selection.
|
205
|
+
unless @allowsSelection and @allowsMultipleSelection
|
206
|
+
@select(e)
|
207
|
+
return
|
208
|
+
|
209
|
+
element = e.target || e
|
210
|
+
model = ($ element).data("model")
|
211
|
+
|
212
|
+
# Do not attempt to select the element if it is already selected.
|
213
|
+
return if @_selected.contains(model)
|
214
|
+
|
215
|
+
@_selected.add model
|
216
|
+
($ element).addClass "selected"
|
217
|
+
@trigger "select", @_selected
|
218
|
+
|
219
|
+
#
|
220
|
+
# Selects the first element in the list.
|
221
|
+
#
|
222
|
+
selectFirst: ->
|
223
|
+
element = ($ @el).find("ul.list-view-items-container > li.list-view-item").first()
|
224
|
+
@select element
|
225
|
+
|
226
|
+
#
|
227
|
+
# Selects the last element in the list.
|
228
|
+
#
|
229
|
+
selectLast: ->
|
230
|
+
element = ($ @el).find "ul.list-view-items-container > li.list-view-item:last-of-type"
|
231
|
+
@select element
|
232
|
+
|
233
|
+
#
|
234
|
+
# Selects all elements in the list.
|
235
|
+
#
|
236
|
+
selectAll: ->
|
237
|
+
return unless @allowsSelection and @allowsMultipleSelection
|
238
|
+
elements = ($ @el).find "ul.list-view-items-container > li.list-view-item:not(.selected)"
|
239
|
+
_.each elements, @selectAnother
|
240
|
+
|
241
|
+
#
|
242
|
+
# Restores the selection after a re-rendering.
|
243
|
+
#
|
244
|
+
restoreSelection: =>
|
245
|
+
elements = ($ @el).find "ul.list-view-items-container > li.list-view-item"
|
246
|
+
_.each elements, (element) =>
|
247
|
+
model = ($ element).data("model")
|
248
|
+
if @_selected.contains(model)
|
249
|
+
($ element).addClass("selected")
|
250
|
+
else
|
251
|
+
@_selected.remove model
|
252
|
+
|
253
|
+
#
|
254
|
+
# Deselects the specified list view item, if it is currently selected.
|
255
|
+
# Returns true if the item was deselected or false if no action was
|
256
|
+
# performed.
|
257
|
+
#
|
258
|
+
deselect: (element) ->
|
259
|
+
model = ($ element).data("model")
|
260
|
+
($ element).removeClass "selected"
|
261
|
+
@_selected.remove model
|
262
|
+
@trigger "deselect", element
|
263
|
+
|
264
|
+
#
|
265
|
+
# Deselects the specified list view item, if it is currently selected.
|
266
|
+
# Returns true if the item was deselected or false if no action was
|
267
|
+
# performed.
|
268
|
+
#
|
269
|
+
clearSelection: (options = {}) ->
|
270
|
+
($ @el).find("ul.list-view-items-container > li.list-view-item.selected").removeClass "selected"
|
271
|
+
@_selected.reset(null, { silent: options["silent"] })
|
272
|
+
@trigger("clear", @_selected) unless options["silent"]
|
273
|
+
|
274
|
+
#
|
275
|
+
# Opens the selected item(s).
|
276
|
+
#
|
277
|
+
open: (e) ->
|
278
|
+
@trigger "open", @_selected
|
279
|
+
|
280
|
+
#
|
281
|
+
# Removes the selected item(s).
|
282
|
+
#
|
283
|
+
remove: (e) =>
|
284
|
+
return unless @allowsRemove
|
285
|
+
@collection.remove @_selected.models
|
286
|
+
@_selected.reset()
|
287
|
+
@trigger "remove", @_selected
|
288
|
+
|
289
|
+
#
|
290
|
+
# Handles click events for the entire list elements, including the list
|
291
|
+
# container.
|
292
|
+
#
|
293
|
+
onMouseDown: (event) =>
|
294
|
+
return unless @allowsSelection
|
295
|
+
# Get the list item element
|
296
|
+
element = if ($ event.target).is("li.list-view-item") then event.target else ($ event.target).parents("li.list-view-item")
|
297
|
+
|
298
|
+
# Clear the selection if the user clicks in the list container.
|
299
|
+
@clearSelection() if @allowsDeselection and event.target.tagName == "UL"
|
300
|
+
|
301
|
+
if event.metaKey
|
302
|
+
model = ($ element).data("model")
|
303
|
+
if model?
|
304
|
+
if @_selected.contains(model)
|
305
|
+
@deselect element
|
306
|
+
else
|
307
|
+
@selectAnother element
|
308
|
+
event.preventDefault()
|
309
|
+
|
310
|
+
else if event.shiftKey
|
311
|
+
element = event.target
|
312
|
+
@expandSelectionToElement ($ element).parents("li.list-view-item") || element
|
313
|
+
event.preventDefault()
|
314
|
+
|
315
|
+
#
|
316
|
+
# Handles focus events.
|
317
|
+
#
|
318
|
+
onFocus: (event) =>
|
319
|
+
return unless @allowsSelection
|
320
|
+
@focusedElement = $(event.target).closest("li.list-view-item")
|
321
|
+
@select @focusedElement unless event.metaKey
|
322
|
+
event.preventDefault()
|
323
|
+
|
324
|
+
#
|
325
|
+
# Handles key down events while the view is focused.
|
326
|
+
#
|
327
|
+
onKeyDown: (event) =>
|
328
|
+
keyCode = event.keyCode
|
329
|
+
|
330
|
+
# Arrow Keys
|
331
|
+
if keyCode in [ 38, 40, 63232, 63233 ]
|
332
|
+
|
333
|
+
event.preventDefault()
|
334
|
+
|
335
|
+
# Select the first item if there is no selection when the first key press
|
336
|
+
# occurs.
|
337
|
+
return @selectFirst() unless @_selected.length > 0
|
338
|
+
|
339
|
+
# Route the key event to the proper handler based on the combination of
|
340
|
+
# keys that were pressed.
|
341
|
+
if event.shiftKey or event.metaKey
|
342
|
+
return @expandSelectionUp event if keyCode in [ 38, 63232 ]
|
343
|
+
return @expandSelectionDown event if keyCode in [ 40, 63233 ]
|
344
|
+
else
|
345
|
+
return @moveSelectionUp event if keyCode in [ 38, 63232 ]
|
346
|
+
return @moveSelectionDown event if keyCode in [ 40, 63233 ]
|
347
|
+
|
348
|
+
# Command-A
|
349
|
+
if keyCode == 65 and event.metaKey
|
350
|
+
event.preventDefault()
|
351
|
+
return @selectAll()
|
352
|
+
|
353
|
+
# Enter Key
|
354
|
+
if keyCode == 13
|
355
|
+
event.preventDefault()
|
356
|
+
return @open event.target
|
357
|
+
|
358
|
+
# Delete Key
|
359
|
+
if keyCode in [ 8, 46 ]
|
360
|
+
event.preventDefault()
|
361
|
+
return @remove event.target
|
362
|
+
|
363
|
+
# Space Key
|
364
|
+
return event.preventDefault() if keyCode == 32
|
365
|
+
|
366
|
+
onDragOver: (event) =>
|
367
|
+
return unless @allowsDragToReorder
|
368
|
+
|
369
|
+
allowed = true
|
370
|
+
|
371
|
+
# Ensure that the dragged item is a list view item...
|
372
|
+
unless "application/x-list-view-item-id" in event.originalEvent.dataTransfer.types
|
373
|
+
allowed = false
|
374
|
+
|
375
|
+
# Ensure that the dragged item belongs to us...
|
376
|
+
unless @collection.contains(@draggedItems.models[0])
|
377
|
+
allowed = false
|
378
|
+
|
379
|
+
event.preventDefault() if allowed
|
380
|
+
|
381
|
+
onDrop: (event) =>
|
382
|
+
return unless @allowsDragToReorder
|
383
|
+
|
384
|
+
draggedElementId = event.originalEvent.dataTransfer.getData("application/x-list-view-item-id")
|
385
|
+
draggedElement = ($ "##{draggedElementId}")
|
386
|
+
droppedElement = ($ event.target).parents("li.list-view-item")[0] || ($ event.target)[0]
|
387
|
+
|
388
|
+
# Ensure that the dragged element is not the same as the dropped element
|
389
|
+
unless $(draggedElement)[0] is $(droppedElement)[0]
|
390
|
+
if droppedElement and droppedElement.tagName is "LI"
|
391
|
+
if event.originalEvent.offsetY < (($ droppedElement).height() / 2)
|
392
|
+
($ draggedElement).detach().insertBefore(droppedElement)
|
393
|
+
else
|
394
|
+
($ draggedElement).detach().insertAfter(droppedElement)
|
395
|
+
|
396
|
+
($ droppedElement).removeClass("drop-before").removeClass("drop-after")
|
397
|
+
|
398
|
+
# Clear the global draggedItem reference
|
399
|
+
@trigger 'drop', @_draggedItems
|
400
|
+
@_draggedItems.reset()
|
401
|
+
|
402
|
+
#
|
403
|
+
# Moves the selection to the item visually above the selected item. If there
|
404
|
+
# is no selection the first item will be selected.
|
405
|
+
#
|
406
|
+
moveSelectionUp: (e) =>
|
407
|
+
element = ($ @focusedElement).prev "li.list-view-item"
|
408
|
+
@select element if element.length > 0
|
409
|
+
|
410
|
+
#
|
411
|
+
# Moves the selection to the item visually below the selected item. If there
|
412
|
+
# is no selection the first item will be selected.
|
413
|
+
#
|
414
|
+
moveSelectionDown: (e) =>
|
415
|
+
element = ($ @focusedElement).next "li.list-view-item"
|
416
|
+
@select element if element.length > 0
|
417
|
+
|
418
|
+
#
|
419
|
+
# Expands the selection downward from the currently selected element.
|
420
|
+
#
|
421
|
+
expandSelectionDown: (e) ->
|
422
|
+
element = ($ @focusedElement).next "li.list-view-item"
|
423
|
+
if element.length > 0
|
424
|
+
@selectAnother element
|
425
|
+
@focusedElement = ($ element).focus()
|
426
|
+
|
427
|
+
#
|
428
|
+
# Expands the selection upward from the currently selected element.
|
429
|
+
#
|
430
|
+
expandSelectionUp: (e) ->
|
431
|
+
element = ($ @focusedElement).prev "li.list-view-item"
|
432
|
+
if element.length > 0
|
433
|
+
@selectAnother element
|
434
|
+
@focusedElement = ($ element).focus()
|
435
|
+
|
436
|
+
#
|
437
|
+
# Expands the selection from the currently selected element to the passed
|
438
|
+
# element.
|
439
|
+
#
|
440
|
+
expandSelectionToElement: (element) ->
|
441
|
+
indexes = [
|
442
|
+
($ @focusedElement).index(),
|
443
|
+
($ element).index()
|
444
|
+
].sort (a, b) -> a - b
|
445
|
+
|
446
|
+
# Increment the end range since slice does not include it...
|
447
|
+
indexes[1] += 1
|
448
|
+
|
449
|
+
# Select the elements in the requested indexes.
|
450
|
+
elements = ($ @el).find("li.list-view-item").slice indexes...
|
451
|
+
_.each elements, @selectAnother
|
452
|
+
|
453
|
+
#
|
454
|
+
# Scrolls the list view to fully expose the selected item if it is not
|
455
|
+
# fully visible within the list view. Returns true or false depending on
|
456
|
+
# whether the scroll was performed.
|
457
|
+
#
|
458
|
+
# scrollToSelectedItem: =>
|
459
|
+
# console.log "scrollToSelectedItem"
|
460
|
+
#
|
461
|
+
# scrollElement = ($ @el).parent()[0]
|
462
|
+
# console.log scrollElement
|
463
|
+
#
|
464
|
+
# if ($ scrollElement).scrollHeight < ($ scrollElement).innerHeight() and ($ scrollElement).scrollWidth < ($ scrollElement).innerWidth()
|
465
|
+
# console.log "returning"
|
466
|
+
# return
|
467
|
+
#
|
468
|
+
# selectedItemTop = @_selectedElement.offset().top - ($ scrollElement).offset().top
|
469
|
+
# selectedItemBottom = selectedItemTop + @_selectedElement.innerHeight()
|
470
|
+
# selectedItemLeft = @_selectedElement.offset().left - ($ scrollElement).offset().left
|
471
|
+
# selectedItemRight = selectedItemLeft + @_selectedElement.innerWidth()
|
472
|
+
# currentScrollTop = ($ scrollElement).scrollTop()
|
473
|
+
# currentScrollBottom = ($ scrollElement).scrollTop() + ($ scrollElement).innerHeight()
|
474
|
+
# currentScrollLeft = ($ scrollElement).scrollLeft()
|
475
|
+
# currentScrollRight = ($ scrollElement).scrollLeft() + ($ scrollElement).innerWidth()
|
476
|
+
# itemTopMargin = parseInt(@_selectedElement.css("margin-top"), 10)
|
477
|
+
# itemBottomMargin = parseInt(@_selectedElement.css("margin-bottom"), 10)
|
478
|
+
# itemLeftMargin = parseInt(@_selectedElement.css("margin-left"), 10)
|
479
|
+
# itemRightMargin = parseInt(@_selectedElement.css("margin-right"), 10)
|
480
|
+
# scrollTopValue = selectedItemTop - itemTopMargin
|
481
|
+
# scrollLeftValue = selectedItemLeft - itemLeftMargin
|
482
|
+
# shouldScrollTop = false
|
483
|
+
# shouldScrollLeft = false
|
484
|
+
#
|
485
|
+
# # selectedItem is above current scroll position
|
486
|
+
# if selectedItemTop < currentScrollTop
|
487
|
+
# shouldScrollTop = true
|
488
|
+
#
|
489
|
+
# # selectedItem is below current, viewable scroll position
|
490
|
+
# if selectedItemTop >= currentScrollBottom
|
491
|
+
# shouldScrollTop = true
|
492
|
+
#
|
493
|
+
# # selectedItem is partially visible below the current, viewable scroll position
|
494
|
+
# else if selectedItemBottom > currentScrollBottom
|
495
|
+
# shouldScrollTop = true
|
496
|
+
# scrollTopValue = currentScrollTop + (selectedItemBottom - currentScrollBottom) + itemBottomMargin
|
497
|
+
#
|
498
|
+
# # selectedItem is left of current scroll position
|
499
|
+
# if selectedItemLeft < currentScrollLeft
|
500
|
+
# shouldScrollLeft = true
|
501
|
+
#
|
502
|
+
# # selectedItem is right of current, viewable scroll position
|
503
|
+
# if selectedItemLeft >= currentScrollRight
|
504
|
+
# shouldScrollLeft = true
|
505
|
+
#
|
506
|
+
# # selectedItem is partially visible below the current, viewable scroll position
|
507
|
+
# else if selectedItemRight > currentScrollRight
|
508
|
+
# shouldScrollLeft = true
|
509
|
+
# scrollLeftValue = currentScrollLeft + (selectedItemRight - currentScrollRight) + itemRightMargin
|
510
|
+
#
|
511
|
+
# if shouldScrollTop or shouldScrollLeft
|
512
|
+
# ($ @el).scrollTop(scrollTopValue) if shouldScrollTop
|
513
|
+
# ($ @el).scrollLeft(scrollLeftValue) if shouldScrollLeft
|
514
|
+
#
|
515
|
+
# (shouldScrollTop or shouldScrollLeft)
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#
|
2
|
+
# List View Item
|
3
|
+
#
|
4
|
+
|
5
|
+
class window.Cartilage.Views.ListViewItem extends Cartilage.View
|
6
|
+
|
7
|
+
# View Configuration -------------------------------------------------------
|
8
|
+
|
9
|
+
id: -> # TODO Hacky
|
10
|
+
"list-view-item-#{Math.floor(Math.random() * 1000000000)}"
|
11
|
+
|
12
|
+
tagName: "li"
|
13
|
+
|
14
|
+
events:
|
15
|
+
"dragover": "onDragOver"
|
16
|
+
"dragleave": "onDragLeave"
|
17
|
+
"dragstart": "onDragStart"
|
18
|
+
|
19
|
+
# Properties ---------------------------------------------------------------
|
20
|
+
|
21
|
+
#
|
22
|
+
# The list view instance that the list view item currently belongs to.
|
23
|
+
#
|
24
|
+
@property "listView"
|
25
|
+
|
26
|
+
#
|
27
|
+
# Whether or not the list view item is currently selected.
|
28
|
+
#
|
29
|
+
@property "isSelected", access: READONLY, default: no
|
30
|
+
|
31
|
+
# Internal Properties ------------------------------------------------------
|
32
|
+
|
33
|
+
# --------------------------------------------------------------------------
|
34
|
+
|
35
|
+
initialize: (options = {}) ->
|
36
|
+
|
37
|
+
# Initialize the View
|
38
|
+
super(options)
|
39
|
+
|
40
|
+
# Give the Element a tab index so that it can be focused
|
41
|
+
($ @el).attr "tabindex", 0
|
42
|
+
|
43
|
+
# Assign the Model to the Element
|
44
|
+
($ @el).data "model", @model
|
45
|
+
|
46
|
+
# Assign the List View Item to the Element
|
47
|
+
($ @el).data "view", @
|
48
|
+
|
49
|
+
prepare: ->
|
50
|
+
|
51
|
+
# Prepare the View
|
52
|
+
super()
|
53
|
+
|
54
|
+
# Enable dragging, if configured
|
55
|
+
if @listView and @listView.allowsDragToReorder
|
56
|
+
($ @el).attr "draggable", true
|
57
|
+
|
58
|
+
($ @el).attr('data-model-id', @model.get('id')) if @model?
|
59
|
+
|
60
|
+
onDragLeave: (event) =>
|
61
|
+
return unless @listView.allowsDragToReorder
|
62
|
+
($ @el).removeClass("drop-before").removeClass("drop-after")
|
63
|
+
|
64
|
+
onDragStart: (event) =>
|
65
|
+
return unless @listView.allowsDragToReorder
|
66
|
+
event.originalEvent.dataTransfer.setData("application/x-list-view-item-id", ($ @el).attr("id"))
|
67
|
+
@listView.draggedItems.add(@model)
|
68
|
+
|
69
|
+
onDragOver: (event) =>
|
70
|
+
return unless @listView.allowsDragToReorder
|
71
|
+
allowed = true
|
72
|
+
|
73
|
+
# Ensure that the dragged item is a list view item...
|
74
|
+
unless "application/x-list-view-item-id" in event.originalEvent.dataTransfer.types
|
75
|
+
allowed = false
|
76
|
+
|
77
|
+
# Ensure that the dragged item belongs to us...
|
78
|
+
unless @listView.collection.contains(@listView.draggedItems.models[0])
|
79
|
+
allowed = false
|
80
|
+
|
81
|
+
if allowed
|
82
|
+
if event.originalEvent.offsetY < (($ @el).height() / 2)
|
83
|
+
($ @el).removeClass("drop-after").addClass("drop-before")
|
84
|
+
else
|
85
|
+
($ @el).removeClass("drop-before").addClass("drop-after")
|