Last active
September 9, 2017 19:43
-
-
Save souporserious/0e7819f3d8a567e62fc643ff821bf304 to your computer and use it in GitHub Desktop.
Allows a list of items to be selected for things like highlighting or maintaining a list of selected item[s]
This file contains hidden or 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 React, { Component } from 'react' | |
const ITEM_SELECTED = 'ITEM_SELECTED' | |
const ITEM_DESELECTED = 'ITEM_DESELECTED' | |
class Controller extends Component { | |
static contextTypes = { | |
itemLists: PropTypes.object, | |
} | |
static defaultProps = { | |
channel: 'item-list', | |
} | |
selectedItems = [] | |
lastSelectedItem = null | |
componentWillMount() { | |
this.itemList = this.context.itemLists[this.props.channel] | |
this.itemList.emitter.on(ITEM_SELECTED, this.addSelectedItem) | |
this.itemList.emitter.on(ITEM_DESELECTED, this.removeSelectedItem) | |
} | |
componentWillUnmount() { | |
this.itemList.emitter.off(ITEM_SELECTED, this.addSelectedItem) | |
this.itemList.emitter.off(ITEM_DESELECTED, this.removeSelectedItem) | |
} | |
addSelectedItem = item => { | |
this.selectedItems.push(item) | |
this.lastSelectedItem = item | |
this.forceUpdate() | |
} | |
removeSelectedItem = item => { | |
const position = this.selectedItems.indexOf(item) | |
if (position > -1) { | |
this.selectedItems.splice(position, 1) | |
} | |
if (item === this.lastSelectedItem) { | |
this.lastSelectedItem = null | |
} | |
this.forceUpdate() | |
} | |
moveSelectedItem = (amount = 0, deselectOldItem = true) => { | |
if (this.lastSelectedItem) { | |
this.itemList.updateItems((item, index) => { | |
if (item === this.lastSelectedItem) { | |
if (deselectOldItem) { | |
this.lastSelectedItem.deselect() | |
} | |
const newNode = this.itemList.itemNodes[index + amount] | |
if (newNode) { | |
this.itemList.items[newNode.id].select() | |
} | |
return true | |
} | |
}) | |
} else { | |
const nextIndex = | |
amount >= 0 ? amount - 1 : this.itemList.itemNodesLength + amount | |
const node = this.itemList.itemNodes[nextIndex] | |
if (node) { | |
this.itemList.items[node.id].select() | |
} | |
} | |
} | |
selectAllItems = () => { | |
this.itemList.updateItems(item => item.select()) | |
} | |
deselectAllItems = () => { | |
this.itemList.updateItems(item => item.deselect()) | |
} | |
render() { | |
return this.props.children({ | |
id: this.itemList.id, | |
deselectAllItems: this.deselectAllItems, | |
moveSelectedItem: this.moveSelectedItem, | |
selectAllItems: this.selectAllItems, | |
selectedItems: this.selectedItems, | |
}) | |
} | |
} |
This file contains hidden or 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 React, { Component } from 'react' | |
const ITEM_SELECTED = 'ITEM_SELECTED' | |
const ITEM_DESELECTED = 'ITEM_DESELECTED' | |
let itemId = 0 | |
class Item extends Component { | |
static contextTypes = { | |
itemLists: PropTypes.object, | |
} | |
static defaultProps = { | |
channel: 'item-list', | |
} | |
state = { | |
isSelected: false, | |
} | |
componentWillMount() { | |
this.itemList = this.context.itemLists[this.props.channel] | |
this.id = `${this.itemList.id}-item-${itemId++}` | |
this.itemList.addItem(this) | |
} | |
componentWillUnmount() { | |
this.deselect() | |
this.itemList.removeItem(this) | |
} | |
setSelectedState = isSelected => { | |
if (this.props.onSelect) { | |
this.props.onSelect(isSelected) | |
} | |
this.setState({ isSelected }) | |
} | |
getSelectedState = () => { | |
return this.state.isSelected | |
} | |
select = () => { | |
this.itemList.emitter.emit(ITEM_SELECTED, this) | |
this.setSelectedState(true) | |
} | |
deselect = () => { | |
this.itemList.emitter.emit(ITEM_DESELECTED, this) | |
this.setSelectedState(false) | |
} | |
toggle = () => { | |
this.setSelectedState(!this.state.isSelected) | |
} | |
render() { | |
return this.props.children({ | |
id: this.id, | |
select: this.select, | |
deselect: this.deselect, | |
toggle: this.toggle, | |
isSelected: this.state.isSelected, | |
}) | |
} | |
} |
This file contains hidden or 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 React, { Component } from 'react' | |
import mitt from 'mitt' | |
let itemListId = 0 | |
class ItemList extends Component { | |
static Controller = Controller | |
static Item = Item | |
static childContextTypes = { | |
itemLists: PropTypes.object, | |
} | |
static defaultProps = { | |
channel: 'item-list', | |
} | |
emitter = mitt() | |
id = `${this.props.channel}-${itemListId++}` | |
items = {} | |
getChildContext() { | |
return { | |
itemLists: { | |
...this.context.itemLists, | |
[this.props.channel]: this, | |
}, | |
} | |
} | |
componentDidMount() { | |
this.setItemNodes() | |
} | |
componentDidUpdate() { | |
this.setItemNodes() | |
} | |
addItem = item => { | |
this.items[item.id] = item | |
} | |
removeItem = item => { | |
delete this.items[item.id] | |
} | |
setItemNodes() { | |
this.itemNodes = document.querySelectorAll(`[id^="${this.id}-item-"]`) | |
this.itemNodesLength = this.itemNodes.length | |
} | |
updateItems = cb => { | |
for (let index = 0; index < this.itemNodesLength; index++) { | |
const node = this.itemNodes[index] | |
const item = this.items[node.id] | |
if (cb(item, index)) { | |
return | |
} | |
} | |
} | |
render() { | |
return this.props.children | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example Usage: