Skip to content

Commit

Permalink
Merge pull request #113 from frosetti/fr/range_select
Browse files Browse the repository at this point in the history
Fr/range select
  • Loading branch information
sglanzer committed Mar 3, 2017
2 parents f62b120 + d71a500 commit 09e99a4
Show file tree
Hide file tree
Showing 6 changed files with 681 additions and 192 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ addons:
- g++-4.8
env:
matrix:
- EMBER_TRY_SCENARIO=ember-2-3
- EMBER_TRY_SCENARIO=ember-2-8
- EMBER_TRY_SCENARIO=default
- EMBER_TRY_SCENARIO=ember-release
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Detailed API and example usage can be found in the sample application in tests/d
| `Sub Attribute` | `activeSorting` | `array` | | Array that specifies the sort order. eg. [{"direction: "asc/desc", "value": <attr-name>}], This is an attribute on frost-list-expansion component.|
| `Sub Attribute` | `properties` | `array` | | Array of sortable attributes. eg. [{"label: "foo", "value": "bar"}], This is an attribute on frost-sort component.|
| `Sub Attribute` | `onSort` | `action closure` | | callback functions user provided to handle sorting. This is an attribute on frost-sort component.|
| `Attribute` | `itemComparator` | `action closure` | | callback functions user provided to handle custom item comparisons.|
| `Attribute` | `itemKey` | `string` | | Optional: With itemKey set, item.get(itemKey) will be used for comparision, Else the default item === item comparison used. |


### Infinite scroll
Expand Down Expand Up @@ -261,7 +261,7 @@ execute the test suite and output code coverage.
[cov-img]: https://coveralls.io/repos/github/ciena-frost/ember-frost-list/badge.svg?branch=master "Code Coverage"
[cov-url]: https://coveralls.io/github/ciena-frost/ember-frost-list

[ember-img]: https://img.shields.io/badge/ember-2.0.0+-orange.svg "Ember 2.0.0+"
[ember-img]: https://img.shields.io/badge/ember-2.8.0+-orange.svg "Ember 2.8.0+"

[npm-img]: https://img.shields.io/npm/v/ember-frost-list.svg "NPM Version"
[npm-url]: https://www.npmjs.com/package/ember-frost-list
40 changes: 27 additions & 13 deletions addon/components/frost-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

import Ember from 'ember'
const {$, A, isEmpty, run, set} = Ember
const {$, A, get, isEmpty, isNone, run, set} = Ember
import computed, {readOnly} from 'ember-computed-decorators'
import {Component} from 'ember-frost-core'
import {selection} from 'ember-frost-list'
Expand Down Expand Up @@ -42,7 +42,7 @@ export default Component.extend({
PropTypes.object
])),
onSelectionChange: PropTypes.func,
itemComparator: PropTypes.func,
itemKey: PropTypes.string,

// Options - sub-components
pagination: PropTypes.EmberComponent,
Expand All @@ -56,6 +56,9 @@ export default Component.extend({
bufferSize: PropTypes.number,
defaultHeight: PropTypes.number,

// Private
_itemComparator: PropTypes.func,

// State
_isShiftDown: PropTypes.bool,

Expand All @@ -75,7 +78,6 @@ export default Component.extend({
return {
// Options - general
scrollTop: 0,
itemComparator: (rhs, lhs) => { return rhs === lhs },

// Smoke and mirrors options
alwaysUseDefaultHeight: false,
Expand All @@ -93,18 +95,19 @@ export default Component.extend({
// == Computed Properties ===================================================

@readOnly
@computed('expandedItems.[]', 'items.[]', 'selectedItems.[]', 'itemComparator')
_items (expandedItems, items, selectedItems, itemComparator) {
@computed('expandedItems.[]', 'items.[]', 'selectedItems.[]', '_itemComparator')
_items (expandedItems, items, selectedItems, _itemComparator) {
if (isEmpty(items)) {
return []
}

return items.map(item => {
run.next(() => {
set(item, 'isExpanded', isEmpty(expandedItems) ? false : expandedItems.some(
selectedItem => itemComparator(selectedItem, item))
selectedItem => _itemComparator(selectedItem, item))
)
set(item, 'isSelected', isEmpty(selectedItems) ? false : selectedItems.some(
selectedItem => itemComparator(selectedItem, item))
selectedItem => _itemComparator(selectedItem, item))
)
})
return item
Expand Down Expand Up @@ -136,6 +139,16 @@ export default Component.extend({

init () {
this._super(...arguments)
const itemKey = this.get('itemKey')
if (itemKey) {
this.set('_itemComparator', function (lhs, rhs) {
return isNone(lhs) || isNone(rhs) ? false : get(lhs, itemKey) === get(rhs, itemKey)
})
} else {
this.set('_itemComparator', function (lhs, rhs) {
return lhs === rhs
})
}

$(document).on(`keyup.${this.elementId} keydown.${this.elementId}`, this.setShift.bind(this))
},
Expand All @@ -153,8 +166,8 @@ export default Component.extend({

_expand (item) {
const clonedExpandedItems = A(this.get('expandedItems').slice())
const itemComparator = this.get('itemComparator')
const index = clonedExpandedItems.findIndex(expandedItem => itemComparator(expandedItem, item))
const _itemComparator = this.get('_itemComparator')
const index = clonedExpandedItems.findIndex(expandedItem => _itemComparator(expandedItem, item))
if (index >= 0) {
clonedExpandedItems.removeAt(index)
} else {
Expand All @@ -169,17 +182,18 @@ export default Component.extend({

_select ({isRangeSelect, isSpecificSelect, item}) {
const items = this.get('items')
const itemComparator = this.get('itemComparator')
const itemKey = this.get('itemKey')
const _itemComparator = this.get('_itemComparator')
const clonedSelectedItems = A(this.get('selectedItems').slice())
const _rangeState = this.get('_rangeState')

// Selects are proccessed in order of precedence: specific, range, basic
if (isSpecificSelect) {
selection.specific(clonedSelectedItems, item, _rangeState, itemComparator)
selection.specific(clonedSelectedItems, item, _rangeState, _itemComparator)
} else if (isRangeSelect) {
selection.range(items, clonedSelectedItems, item, _rangeState, itemComparator)
selection.range(items, clonedSelectedItems, item, _rangeState, _itemComparator, itemKey)
} else {
selection.basic(clonedSelectedItems, item, _rangeState, itemComparator)
selection.basic(clonedSelectedItems, item, _rangeState, _itemComparator)
}
this.onSelectionChange(clonedSelectedItems)
}
Expand Down
46 changes: 24 additions & 22 deletions addon/utils/selection.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
* TODO Selection utilities
*/

import Ember from 'ember'
const {isNone} = Ember

export default {

/**
* Basic selection acts conditionally based on the presence of additional selections.
*
Expand All @@ -30,23 +28,22 @@ export default {
rangeState['anchor'] = item

// New anchor, clear any previous endpoint
rangeState['endpoint'] = Ember.Object.create()
rangeState['endpoint'] = null
} else {
// Toggle the item selection

const isCurrentlySelected = (index >= 0)
const isCurrentlySelected = index >= 0
const isSelected = !isCurrentlySelected
if (isSelected) {
selectedItems.pushObject(item)
selectedItems.addObject(item)
} else {
selectedItems.removeAt(index)
}

// Set the range anchor if selected, otherwise clear the anchor
rangeState['anchor'] = isSelected ? item : Ember.Object.create()
rangeState['anchor'] = isSelected ? item : null

// New or no anchor, clear any previous endpoint
rangeState['endpoint'] = Ember.Object.create()
rangeState['endpoint'] = null
}
},

Expand All @@ -63,31 +60,30 @@ export default {
* @param {Function} itemComparator - comparator for items
*/
/* eslint-disable complexity */
range (items, selectedItems, item, rangeState, itemComparator) {
// If an anchor isn't set, then set the anchor and exit
range (items, selectedItems, item, rangeState, itemComparator, itemKey) {
// If an anchor isn't set or in the current list of items, then set the anchor and exit
const rangeAnchor = rangeState['anchor']
if (isNone(rangeAnchor)) {
const anchor = rangeAnchor ? items.findIndex(currentItem => itemComparator(currentItem, rangeAnchor)) : -1
if (anchor === -1) {
// Range select is always a positive selection (no deselect)
rangeState['anchor'] = item

// New anchor, clear any previous endpoint
rangeState['endpoint'] = Ember.Object.create()
rangeState['endpoint'] = null

// Add the anchor to the selected items
selectedItems.pushObject(item)
selectedItems.addObject(item)

return
}

// Find the indicies of the anchor and endpoint
const anchor = items.findIndex(currentItem => itemComparator(currentItem, rangeState['anchor']))
// Find the indices of the endpoint
const endpoint = items.findIndex(currentItem => itemComparator(currentItem, item))

// Select all of the items between the anchor and the item (inclusive)
if (anchor < endpoint) {
selectedItems.pushObjects(items.slice(anchor, endpoint + 1))
selectedItems.addObjects(items.slice(anchor, endpoint + 1))
} else {
selectedItems.pushObjects(items.slice(endpoint, anchor + 1))
selectedItems.addObjects(items.slice(endpoint, anchor + 1))
}

// If an endpoint was already selected remove selected items that were
Expand All @@ -114,6 +110,12 @@ export default {
}
}

// If items in the list are compared using itemKey rather than by reference
// then addObject(s) won't guarentee uniqueness, so do a uniqueness pass
if (itemKey) {
selectedItems.setObjects(selectedItems.uniqBy(itemKey))
}

// Store the new endpoint
rangeState['endpoint'] = item
},
Expand All @@ -134,13 +136,13 @@ export default {
const isSelected = !isCurrentlySelected

// Set the range anchor if selected, otherwise clear the anchor
rangeState['anchor'] = isSelected ? item : Ember.Object.create()
rangeState['anchor'] = isSelected ? item : null
// New or no anchor, clear any previous endpoint
rangeState['endpoint'] = Ember.Object.create()
rangeState['endpoint'] = null

// Store the selection
if (isSelected) {
selectedItems.pushObject(item)
selectedItems.addObject(item)
} else {
selectedItems.removeAt(index)
}
Expand Down
1 change: 1 addition & 0 deletions tests/dummy/app/pods/paged/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
hook='demo'
item=(component 'list-item')
itemExpansion=(component 'list-item-expansion')
itemKey='id'
items=items
scrollTop=scrollTop
expandedItems=expandedItems
Expand Down
Loading

0 comments on commit 09e99a4

Please sign in to comment.