Skip to content

Commit

Permalink
Merge pull request #102 from frosetti/fr/itemComparator
Browse files Browse the repository at this point in the history
Comparator functionality
  • Loading branch information
TheOtherDude committed Feb 15, 2017
2 parents 64c4496 + 5b31f45 commit dd8fef1
Show file tree
Hide file tree
Showing 5 changed files with 349 additions and 55 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ 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. This is an attribute on frost-list component.|


### Infinite scroll

Expand Down
29 changes: 18 additions & 11 deletions addon/components/frost-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export default Component.extend({
PropTypes.object
])),
onSelectionChange: PropTypes.func,
itemComparator: PropTypes.func,

// Options - sub-components
pagination: PropTypes.EmberComponent,
Expand Down Expand Up @@ -74,6 +75,7 @@ export default Component.extend({
return {
// Options - general
scrollTop: 0,
itemComparator: (rhs, lhs) => { return rhs === lhs },

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

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

return items.map(item => {
set(item, 'isExpanded', isEmpty(expandedItems) ? false : expandedItems.indexOf(item) >= 0)
set(item, 'isSelected', isEmpty(selectedItems) ? false : selectedItems.indexOf(item) >= 0)
set(item, 'isExpanded', isEmpty(expandedItems) ? false : expandedItems.some(
selectedItem => itemComparator(selectedItem, item))
)
set(item, 'isSelected', isEmpty(selectedItems) ? false : selectedItems.some(
selectedItem => itemComparator(selectedItem, item))
)
return item
})
},
Expand Down Expand Up @@ -146,8 +151,10 @@ export default Component.extend({

_expand (item) {
const clonedExpandedItems = A(this.get('expandedItems').slice())
if (clonedExpandedItems.indexOf(item) >= 0) {
clonedExpandedItems.removeObject(item)
const itemComparator = this.get('itemComparator')
const index = clonedExpandedItems.findIndex(expandedItem => itemComparator(expandedItem, item))
if (index >= 0) {
clonedExpandedItems.removeAt(index)
} else {
clonedExpandedItems.pushObject(item)
}
Expand All @@ -160,16 +167,16 @@ export default Component.extend({

_select ({isRangeSelect, isSpecificSelect, item}) {
const items = this.get('items')
const clonedSelectedItems = this.get('selectedItems').slice()
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)
selection.specific(clonedSelectedItems, item, _rangeState, this.get('itemComparator'))
} else if (isRangeSelect) {
selection.range(items, clonedSelectedItems, item, _rangeState)
selection.range(items, clonedSelectedItems, item, _rangeState, this.get('itemComparator'))
} else {
selection.basic(clonedSelectedItems, item, _rangeState)
selection.basic(clonedSelectedItems, item, _rangeState, this.get('itemComparator'))
}

this.onSelectionChange(clonedSelectedItems)
Expand Down
7 changes: 5 additions & 2 deletions addon/templates/components/frost-list.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@
{{if (eq index 0) ' first'}}
{{if (eq index (sub _items.length 1)) ' last'}}
{{if model.isSelected ' is-selected'}}
{{if (is-lead-selection _items model) ' is-lead-selection'}}
'>
{{if (is-lead-selection _items model) ' is-lead-selection'}}'
data-test={{hook (concat hook '-item-container') index=index }}
>
<div class='frost-list-item-container-base'>
{{#if itemExpansion}}
{{frost-list-item-expansion
hook=(concat hookPrefix '-expansion')
hookQualifiers=(hash index=index)
model=model
onExpand=(action '_expand')
}}
Expand All @@ -60,6 +62,7 @@
{{#if onSelectionChange}}
{{frost-list-item-selection
hook=(concat hookPrefix '-selection')
hookQualifiers=(hash index=index)
model=model
onSelect=(action '_select')
}}
Expand Down
30 changes: 18 additions & 12 deletions addon/utils/selection.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ export default {
* @param {Object[]} selectedItems - currently selected items
* @param {Object} item - selection event target
* @param {Object} rangeState - tracking the anchor and endpoint
* @param {Function} itemComparator - comparator for items
*/
basic (selectedItems, item, rangeState) {
basic (selectedItems, item, rangeState, itemComparator) {
// If a previous set of selections is present
if (selectedItems.get('length') > 1 || selectedItems.indexOf(item) === -1) {
const index = selectedItems.findIndex(selectedItem => itemComparator(selectedItem, item))
if (selectedItems.get('length') > 1 || index === -1) {
// Clear the other selections and select the item
selectedItems.setObjects([item])

Expand All @@ -31,15 +33,16 @@ export default {
rangeState['endpoint'] = null
} else {
// Toggle the item selection
const isCurrentlySelected = selectedItems.indexOf(item) >= 0

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

// Set the range anchor if selected, otherwise clear the anchor
// Set the range anchor if selected, otherwise clear the anchor
rangeState['anchor'] = isSelected ? item : null

// New or no anchor, clear any previous endpoint
Expand All @@ -57,9 +60,10 @@ export default {
* @param {Object[]} selectedItems - currently selected items
* @param {Object} item - selection event target
* @param {Object} rangeState - tracking the anchor and endpoint
* @param {Function} itemComparator - comparator for items
*/
/* eslint-disable complexity */
range (items, selectedItems, item, rangeState) {
range (items, selectedItems, item, rangeState, itemComparator) {
// If an anchor isn't set, then set the anchor and exit
const rangeAnchor = rangeState['anchor']
if (isNone(rangeAnchor)) {
Expand All @@ -76,8 +80,8 @@ export default {
}

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

// Select all of the items between the anchor and the item (inclusive)
if (anchor < endpoint) {
Expand All @@ -88,7 +92,7 @@ export default {

// If an endpoint was already selected remove selected items that were
// in the previous range but aren't in the new range
const previousEndpoint = items.indexOf(rangeState['endpoint'])
const previousEndpoint = items.findIndex(currentItem => itemComparator(currentItem, rangeState['endpoint']))
if (previousEndpoint >= 0) {
// If both endpoints are above the anchor
if (anchor < endpoint && anchor < previousEndpoint) {
Expand Down Expand Up @@ -122,9 +126,11 @@ export default {
* @param {Object[]} selectedItems - currently selected items
* @param {Object} item - selection event target
* @param {Object} rangeState - tracking the anchor and endpoint
* @param {Function} itemComparator - comparator for items
*/
specific (selectedItems, item, rangeState) {
const isCurrentlySelected = selectedItems.indexOf(item) >= 0
specific (selectedItems, item, rangeState, itemComparator) {
const index = selectedItems.findIndex(selectedItem => itemComparator(selectedItem, item))
const isCurrentlySelected = (index >= 0)
const isSelected = !isCurrentlySelected

// Set the range anchor if selected, otherwise clear the anchor
Expand All @@ -136,7 +142,7 @@ export default {
if (isSelected) {
selectedItems.pushObject(item)
} else {
selectedItems.removeObject(item)
selectedItems.removeAt(index)
}
}
}
Loading

0 comments on commit dd8fef1

Please sign in to comment.