cartilage 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.markdown +57 -0
  3. data/Rakefile +84 -0
  4. data/app/assets/images/cartilage/patterns/background.dark.png +0 -0
  5. data/app/assets/images/cartilage/patterns/background.dark.psd +0 -0
  6. data/app/assets/images/cartilage/patterns/background.light.png +0 -0
  7. data/app/assets/images/cartilage/patterns/background.light.psd +0 -0
  8. data/app/assets/images/cartilage/patterns/background.medium.png +0 -0
  9. data/app/assets/images/cartilage/patterns/background.medium.psd +0 -0
  10. data/app/assets/images/cartilage/patterns/background.png +0 -0
  11. data/app/assets/images/cartilage/patterns/background.psd +0 -0
  12. data/app/assets/javascripts/cartilage/application.js.coffee +45 -0
  13. data/app/assets/javascripts/cartilage/collections/segments.js.coffee +4 -0
  14. data/app/assets/javascripts/cartilage/core.js.coffee +8 -0
  15. data/app/assets/javascripts/cartilage/models/model.js.coffee +2 -0
  16. data/app/assets/javascripts/cartilage/models/segment.js.coffee +2 -0
  17. data/app/assets/javascripts/cartilage/views/bar_segment_view.js.coffee +58 -0
  18. data/app/assets/javascripts/cartilage/views/bar_view.js.coffee +85 -0
  19. data/app/assets/javascripts/cartilage/views/content_view.js.coffee +16 -0
  20. data/app/assets/javascripts/cartilage/views/image_view.js.coffee +76 -0
  21. data/app/assets/javascripts/cartilage/views/list_view.js.coffee +515 -0
  22. data/app/assets/javascripts/cartilage/views/list_view_item.js.coffee +85 -0
  23. data/app/assets/javascripts/cartilage/views/loading_indicator_view.js.coffee +147 -0
  24. data/app/assets/javascripts/cartilage/views/matrix_view.js.coffee +253 -0
  25. data/app/assets/javascripts/cartilage/views/matrix_view_item.js.coffee +5 -0
  26. data/app/assets/javascripts/cartilage/views/modal_view.js.coffee +26 -0
  27. data/app/assets/javascripts/cartilage/views/popover_view.js.coffee +212 -0
  28. data/app/assets/javascripts/cartilage/views/source_list_view.js.coffee +69 -0
  29. data/app/assets/javascripts/cartilage/views/source_list_view_item.js.coffee +5 -0
  30. data/app/assets/javascripts/cartilage/views/split_view.js.coffee +175 -0
  31. data/app/assets/javascripts/cartilage/views/usage_bar_view.js.coffee +5 -0
  32. data/app/assets/javascripts/cartilage/views/view.js.coffee +232 -0
  33. data/app/assets/javascripts/cartilage.js.coffee +18 -0
  34. data/app/assets/javascripts/extensions/console.js.coffee +9 -0
  35. data/app/assets/javascripts/extensions/constructor_name.js.coffee +10 -0
  36. data/app/assets/javascripts/extensions/properties.js.coffee +45 -0
  37. data/app/assets/javascripts/extensions/underscore.js.coffee +71 -0
  38. data/app/assets/stylesheets/cartilage/core.css.scss +432 -0
  39. data/app/assets/stylesheets/cartilage/mixins.css.scss +19 -0
  40. data/app/assets/stylesheets/cartilage/responsive.css.scss +192 -0
  41. data/app/assets/stylesheets/cartilage/variables.css.scss +113 -0
  42. data/app/assets/stylesheets/cartilage/views/list_view.css.scss +41 -0
  43. data/app/assets/stylesheets/cartilage/views/list_view_item.css.scss +47 -0
  44. data/app/assets/stylesheets/cartilage/views/loading_indicator_view.css.scss +50 -0
  45. data/app/assets/stylesheets/cartilage/views/matrix_view.css.scss +87 -0
  46. data/app/assets/stylesheets/cartilage/views/popover_view.css.scss +124 -0
  47. data/app/assets/stylesheets/cartilage/views/segmented_view.css.scss +98 -0
  48. data/app/assets/stylesheets/cartilage/views/source_list_view.css.scss +65 -0
  49. data/app/assets/stylesheets/cartilage/views/split_view.css.scss +80 -0
  50. data/app/assets/stylesheets/cartilage/views/usage_bar_view.css.scss +101 -0
  51. data/app/assets/stylesheets/cartilage/views/view.css.scss +13 -0
  52. data/app/assets/stylesheets/cartilage.css.scss +5 -0
  53. data/lib/cartilage/engine.rb +14 -0
  54. data/lib/cartilage/version.rb +3 -0
  55. data/lib/cartilage.rb +5 -0
  56. data/lib/tasks/cartilage_tasks.rake +4 -0
  57. data/test/cartilage_test.rb +7 -0
  58. data/test/dummy/Rakefile +7 -0
  59. data/test/dummy/app/assets/javascripts/application.js +9 -0
  60. data/test/dummy/app/assets/stylesheets/application.css +7 -0
  61. data/test/dummy/app/controllers/application_controller.rb +3 -0
  62. data/test/dummy/app/helpers/application_helper.rb +2 -0
  63. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  64. data/test/dummy/config/application.rb +45 -0
  65. data/test/dummy/config/boot.rb +10 -0
  66. data/test/dummy/config/database.yml +25 -0
  67. data/test/dummy/config/environment.rb +5 -0
  68. data/test/dummy/config/environments/development.rb +30 -0
  69. data/test/dummy/config/environments/production.rb +60 -0
  70. data/test/dummy/config/environments/test.rb +42 -0
  71. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  72. data/test/dummy/config/initializers/inflections.rb +10 -0
  73. data/test/dummy/config/initializers/mime_types.rb +5 -0
  74. data/test/dummy/config/initializers/secret_token.rb +7 -0
  75. data/test/dummy/config/initializers/session_store.rb +8 -0
  76. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  77. data/test/dummy/config/locales/en.yml +5 -0
  78. data/test/dummy/config/routes.rb +58 -0
  79. data/test/dummy/config.ru +4 -0
  80. data/test/dummy/db/readme +6 -0
  81. data/test/dummy/public/404.html +26 -0
  82. data/test/dummy/public/422.html +26 -0
  83. data/test/dummy/public/500.html +26 -0
  84. data/test/dummy/public/favicon.ico +0 -0
  85. data/test/dummy/script/rails +6 -0
  86. data/test/framework/cartilage/application_test.js.coffee +14 -0
  87. data/test/framework/cartilage/views/bar_segment_view_test.js.coffee +5 -0
  88. data/test/framework/cartilage/views/bar_view_test.js.coffee +30 -0
  89. data/test/framework/cartilage/views/image_view_test.js.coffee +27 -0
  90. data/test/framework/cartilage/views/list_view_item_test.js.coffee +17 -0
  91. data/test/framework/cartilage/views/list_view_test.js.coffee +190 -0
  92. data/test/framework/cartilage/views/loading_indicator_view_test.js.coffee +5 -0
  93. data/test/framework/cartilage/views/matrix_view_item_test.js.coffee +5 -0
  94. data/test/framework/cartilage/views/matrix_view_test.js.coffee +5 -0
  95. data/test/framework/cartilage/views/popover_view_test.js.coffee +5 -0
  96. data/test/framework/cartilage/views/source_list_view_item_test.js.coffee +5 -0
  97. data/test/framework/cartilage/views/source_list_view_test.js.coffee +5 -0
  98. data/test/framework/cartilage/views/split_view_test.js.coffee +5 -0
  99. data/test/framework/cartilage/views/usage_bar_view_test.js.coffee +5 -0
  100. data/test/framework/cartilage/views/view_test.js.coffee +85 -0
  101. data/test/framework/extensions/properties_test.js.coffee +82 -0
  102. data/test/framework/extensions/underscore_test.js.coffee +73 -0
  103. data/test/framework/index.html +72 -0
  104. data/test/framework/vendor/backbone.js +1431 -0
  105. data/test/framework/vendor/coffee-script.js +8 -0
  106. data/test/framework/vendor/jquery.js +9440 -0
  107. data/test/framework/vendor/phantomjs-runner.js +196 -0
  108. data/test/framework/vendor/qunit.css +235 -0
  109. data/test/framework/vendor/qunit.js +1977 -0
  110. data/test/framework/vendor/run-qunit.js +81 -0
  111. data/test/framework/vendor/sinon-1.5.0.js +4142 -0
  112. data/test/framework/vendor/sinon-qunit-1.0.0.js +62 -0
  113. data/test/framework/vendor/underscore.js +1059 -0
  114. data/test/test_helper.rb +10 -0
  115. metadata +373 -0
@@ -0,0 +1,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")