Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fr/range select #113

Merged
merged 4 commits into from
Mar 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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. |
Copy link
Contributor

@sglanzer-deprecated sglanzer-deprecated Mar 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically this removal would be considered a breaking change - I'm going to let it slide this time because I'm confident this hasn't propagated far and you're likely the only one using it, but you should announce this removal on #frost-announcements

Please be careful in the future about choosing appropriate interfaces up front, even if the design phase takes longer



### 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