cartilage 0.1.1

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