Created
January 9, 2017 20:56
-
-
Save sglanzer-deprecated/8622da8bc3ffeaca1d369494b08a7c66 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
click (event) { | |
// Acceptable event modifiers for range selection | |
const isSimpleClick = Ember.ViewUtils.isSimpleClick(event) | |
const isSpecificSelect = (new window.UAParser()).getOS() === 'Mac OS' ? event.ctrlKey : event.metaKey // TODO Move instance to a service | |
const isRangeSelect = event.shiftKey | |
// Only process simple clicks or clicks with the acceptable modifiers | |
if (isSimpleClick || isSpecificSelect || isRangeSelect) { | |
event.preventDefault() | |
event.stopPropagation() | |
// Each list item provides a checkbox which behaves as a specific select | |
const isCheckboxSelect = $(event.target).hasClass('frost-list-selection-indicator') | |
this.onSelect({ | |
isRangeSelect, | |
isSpecificSelect: isSpecificSelect || isCheckboxSelect, | |
item: this.get('model') | |
}) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
this.set('selectedItems', []) // TODO prefix | |
this.set('rangeState', {}) // TODO prefix | |
this.set('actions.select', function ({isRangeSelect, isSpecificSelect, item}) { // TODO does this need a prefix? | |
const items = this.get('items') // TODO prefix | |
const selectedItems = this.get('selectedItems') // TODO prefix | |
const rangeState = this.get('rangeState') // TODO prefix | |
// Selects are proccessed in order of precedence: specific, range, basic | |
if (isSpecificSelect) { | |
selection.specific(selectedItems, item, rangeState) | |
} else if (isRangeSelect) { | |
selection.range(items, selectedItems, item, rangeState) | |
} else { | |
selection.basic(selectedItems, item, rangeState) | |
} | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Ember from 'ember' | |
const {isNone} = Ember | |
export default { | |
/** | |
* Basic selection acts conditionally based on the presence of additional selections. | |
* | |
* If no other selections are present the selection simply toggles the given item's selection state. | |
* | |
* If other selections are present the selection clears the other selections, but positively selects | |
* the given item. | |
* | |
* @param {Object[]} selectedItems - currently selected items | |
* @param {Object} item - selection event target | |
* @param {Object} rangeState - tracking the anchor and endpoint | |
*/ | |
basic (selectedItems, item, rangeState) { | |
// If a previous set of selections is present | |
if (selectedItems.get('length') > 1) { | |
// Clear the other selections and select the item | |
selectedItems.setObjects([item]) | |
// Set the range anchor | |
rangeState['anchor'] = item | |
// New anchor, clear any previous endpoint | |
rangeState['endpoint'] = null | |
} else { | |
// Toggle the item selection | |
const isCurrentlySelected = selectedItems.indexOf(item) >= 0 | |
const isSelected = !isCurrentlySelected | |
if (isSelected) { | |
selectedItems.pushObject(item) | |
} else { | |
selectedItems.removeObject(item) | |
} | |
// Set the range anchor if selected, otherwise clear the anchor | |
rangeState['anchor'] = isSelected ? item : null | |
// New or no anchor, clear any previous endpoint | |
rangeState['endpoint'] = null | |
} | |
}, | |
/** | |
* Range selection requires an anchor and an endpoint; items between the | |
* anchor and endpoint are added to the selected items (inclusive). | |
* This means that a range selection event is either setting an anchor | |
* or selecting items between the anchor and a new endpoint | |
* | |
* @param {Object[]} items - all items available | |
* @param {Object[]} selectedItems - currently selected items | |
* @param {Object} item - selection event target | |
* @param {Object} rangeState - tracking the anchor and endpoint | |
*/ | |
/* eslint-disable complexity */ | |
range (items, selectedItems, item, rangeState) { | |
// If an anchor isn't set, then set the anchor and exit | |
const rangeAnchor = rangeState['anchor'] | |
if (isNone(rangeAnchor)) { | |
// Range select is always a positive selection (no deselect) | |
rangeState['anchor'] = item | |
// New anchor, clear any previous endpoint | |
rangeState['endpoint'] = null | |
// Add the anchor to the selected items | |
selectedItems.pushObject(item) | |
return | |
} | |
// Find the indicies of the anchor and endpoint | |
const anchor = items.indexOf(rangeState['anchor']) | |
const endpoint = items.indexOf(item) | |
// Select all of the items between the anchor and the item (inclusive) | |
if (anchor < endpoint) { | |
selectedItems.pushObjects(items.slice(anchor, endpoint + 1)) | |
} else { | |
selectedItems.pushObjects(items.slice(endpoint, anchor + 1)) | |
} | |
// 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']) | |
if (previousEndpoint >= 0) { | |
// If both endpoints are above the anchor | |
if (anchor < endpoint && anchor < previousEndpoint) { | |
// and the new range encompasses less | |
if (endpoint < previousEndpoint) { | |
selectedItems.removeObjects(items.slice(endpoint + 1, previousEndpoint + 1)) | |
} | |
// If both endpoints are below the anchor | |
} else if (anchor > endpoint && anchor > previousEndpoint) { | |
// and the new range encompasses less | |
if (endpoint > previousEndpoint) { | |
selectedItems.removeObjects(items.slice(previousEndpoint, endpoint)) | |
} | |
// Pivoted over the anchor, deselect all items in the previous range minus the anchor | |
} else if (anchor > previousEndpoint) { | |
selectedItems.removeObjects(items.slice(previousEndpoint, anchor)) | |
} else { | |
selectedItems.removeObjects(items.slice(anchor + 1, previousEndpoint + 1)) | |
} | |
} | |
// Store the new endpoint | |
rangeState['endpoint'] = item | |
}, | |
/* eslint-enable complexity */ | |
/** | |
* Specific selection toggles the current selection state for a given | |
* item without impacting the selection state for any other items | |
* | |
* @param {Object[]} selectedItems - currently selected items | |
* @param {Object} item - selection event target | |
* @param {Object} rangeState - tracking the anchor and endpoint | |
*/ | |
specific (selectedItems, item, rangeState) { | |
const isCurrentlySelected = selectedItems.indexOf(item) >= 0 | |
const isSelected = !isCurrentlySelected | |
// Set the range anchor if selected, otherwise clear the anchor | |
rangeState['anchor'] = isSelected ? item : null | |
// New or no anchor, clear any previous endpoint | |
rangeState['endpoint'] = null | |
// Store the selection | |
if (isSelected) { | |
selectedItems.pushObject(item) | |
} else { | |
selectedItems.removeObject(item) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment