symphony-integration-commons
Version:
Common components for 3rd party developers build the user facing application for Symphony Integrations.
365 lines (335 loc) • 10.6 kB
JSX
/* eslint-disable react/no-unused-prop-types */
import React, { PropTypes, Component } from 'react';
import RoomBox from '../RoomBox/RoomBox';
import './styles/styles.less';
// Use named export for unconnected component (for tests)
export class SuggestionsRooms extends Component {
constructor(props) {
super(props);
this.state = {
filters: [], // stores the room filtered
filteredRooms: [], // rooms that appears according to user typing
suggestionsList: [], // rooms available to be filtered
listening: false, // check if input search has event listener
focused: -1, // handles list items focus,
filled: false,
clear: false, // handles when to show the clear input text button
};
this.container = null;
this.input = null;
this.list = null;
this.onChangeSearch = this.onChangeSearch.bind(this);
this.inputListener = this.inputListener.bind(this);
this.addInputListener = this.addInputListener.bind(this);
this.removeInputListener = this.removeInputListener.bind(this);
this.clearInput = this.clearInput.bind(this);
this.addFilter = this.addFilter.bind(this);
this.removeFilter = this.removeFilter.bind(this);
this.sort = this.sort.bind(this);
this.setupSuggestionsList = this.setupSuggestionsList.bind(this);
}
componentWillMount() {
this.setupSuggestionsList();
this.removeDuplicateFilter();
}
componentDidMount() {
this.input.focus();
}
componentWillReceiveProps(nextProps) {
if (nextProps.userRooms !== this.props.userRooms) {
this.setState({
suggestionsList: nextProps.userRooms.slice(),
});
}
}
removeDuplicateFilter() {
let _filters = this.props.filters;
let seen = {};
let uniqueArray = _filters.filter(item => {
return seen.hasOwnProperty(item.threadId) ? false : (seen[item.threadId] = true);
});
this.setState({
filters: uniqueArray,
});
}
setupSuggestionsList() {
const _suggestions = this.props.userRooms.slice();
this.sort(_suggestions, 'name');
if (this.props.filters.length > 0) {
const rooms = []; // store the indexes to be removed
this.props.filters.map((_filter) => {
// store the _suggestions array indexes that are posting locations
for (let k = 0, n = _suggestions.length; k < n; k += 1) {
if (_suggestions[k].threadId === _filter.threadId) {
rooms.push(k);
}
}
// remove all posting locations from the _suggestions array
for (let i = 0, n = _suggestions.length; i < n; i += 1) {
for (let j = 0, l = rooms.length; j < l; j += 1) {
if (i === rooms[j]) {
_suggestions.splice(i, 1);
rooms.splice(j, 1);
for (let k = 0, s = rooms.length; k < s; k += 1) {
rooms[k] -= 1;
}
i -= 1;
break;
}
}
}
});
this.setState({
filters: this.props.filters.slice(),
suggestionsList: _suggestions.slice(),
});
this.props.resetPostingLocation();
// add the posting locations to the instance stream state
this.props.filters.map(item => this.props.addStreamToInstance(item));
} else {
this.setState({
suggestionsList: _suggestions,
});
}
}
onChangeSearch(e) {
this.props.updateRooms();
const target = e.target;
if (target.value === '') {
this.setState({
filteredRooms: [],
filled: false,
clear: false,
});
if (this.state.listening) {
this.removeInputListener();
}
return;
}
let suggestionsList = this.state.suggestionsList;
suggestionsList = suggestionsList.filter(item =>
item.name.toLowerCase().search(e.target.value.toLowerCase()) !== -1
);
if (!this.state.listening) {
this.addInputListener();
}
// Remove rooms already filtered from suggestions
const filterHashMap = this.state.filters.reduce(function(map,obj) {
map[obj.threadId] = obj;
return map
}, {});
const uniqueArray = suggestionsList.filter(function(item) {
return !(filterHashMap[item.threadId] != null)
});
this.setState({
filteredRooms: uniqueArray,
clear: true,
});
}
inputListener(event) {
let idx = this.state.focused;
const key = event.keyCode;
// limit case 1: Focus is on input and key is arrow up: return...
if (idx === -1 && key === 38) {
setTimeout(() => {
this.input.value = this.input.value;
this.input.focus();
}, 10);
return;
}
// limit case 2: Focus is on the last list item, and key is arrow down: return...
if (idx === this.state.filteredRooms.length - 1 && key === 40) {
return;
}
// Focus is on input and key is arrow down
if (idx === -1 && key === 40) {
this.input.blur();
this.removeInputListener();
this.list.addEventListener('keydown', this.inputListener);
idx += 1;
} else if (idx === 0) { // Focus is on the first list item
// key is arrow up
if (key === 38) {
// setInterval put the cursor after the input value...
const tmr = setInterval(() => {
if (this.input.value !== '') {
clearInterval(tmr);
this.input.focus();
}
}, 50);
this.list.removeEventListener('keydown', this.inputListener);
this.addInputListener();
idx -= 1;
} else if (key === 40) { // key is arrow down
idx += 1;
}
} else if (idx > 0) { // Focus is on any list item, except the first and last...
if (key === 38) {
idx -= 1;
} else if (key === 40) {
idx += 1;
}
}
// Handles focus change
if (this.state.filteredRooms.length > 0 && idx >= 0) {
this.list.childNodes[idx].childNodes[0].focus();
}
// Update state
this.setState({
focused: idx,
});
}
addInputListener() {
this.input.addEventListener('keydown', this.inputListener);
this.setState({
listening: true,
});
}
removeInputListener() {
this.input.removeEventListener('keydown', this.inputListener);
this.setState({
listening: false,
});
}
clearInput() {
this.setState({
filteredRooms: [],
focused: -1,
clear: false,
});
this.input.value = '';
this.input.focus();
}
addFilter(e, filter) {
e.preventDefault();
const suggestions = this.state.suggestionsList.slice();
let postingLocationRoom;
suggestions.some((item, idx) => {
if (item.name === filter.name) {
postingLocationRoom = suggestions.splice(idx, 1)[0];
}
});
this.setState({
filteredRooms: [],
focused: -1,
filters: this.state.filters.concat(filter),
suggestionsList: suggestions.slice(),
filled: true,
clear: false,
});
this.props.addStreamToInstance(postingLocationRoom);
this.input.value = '';
this.input.focus();
}
removeFilter(_id) {
const _filters = this.state.filters.slice();
let required = true;
let postingLocationRoom;
_filters.some((item, idx) => {
if (item.threadId === _id) {
postingLocationRoom = item;
_filters.splice(idx, 1);
}
});
this.input.value = '';
this.input.focus();
required = _filters.length > 0;
this.setState({
filters: _filters.slice(),
filled: required,
});
this.props.removeStreamFromInstance(postingLocationRoom);
}
sort(_obj, key) {
_obj.sort((a, b) => {
if (a[key] < b[key]) return -1;
if (a[key] > b[key]) return 1;
return 0;
});
}
render() {
return (
<div className='suggestions-rooms'>
<div className='required'>
<div className='input-search-container'>
<input
type='text'
id='input-posting-location'
onChange={this.onChangeSearch}
ref={(input) => { this.input = input; }}
placeholder={this.props.loading ? 'Loading...' : 'Search rooms'}
/>
<button
className={this.state.clear ? 'visible' : 'hidden'}
onClick={this.clearInput}
>
<i className='fa fa-times' />
</button>
</div>
{
!this.state.filled &&
<span id='rooms-required-field'>
<i className="fa fa-asterisk" aria-hidden="true" />
</span>
}
</div>
<ul
className='filter-container'
ref={(list) => { this.list = list; }}
>
{
this.state.filteredRooms.map((filter, idx) =>
<li className='filter-box' key={idx}>
<a
href='#'
className='filter-box-clickable'
onClick={e => this.addFilter(e, filter, idx)}
id={idx}
>
<div>
<span>{filter.name}</span>
{!filter.publicRoom && (<span><i className="fa fa-lock" /></span>)}
</div>
<div className='room-info'>
<span>
{
`${filter.memberCount} Member${filter.memberCount > 1 ? 's' : ''},
created by ${filter.creatorPrettyName}`
}
</span>
</div>
</a>
</li>
)
}
</ul>
{this.state.filters.length > 0 &&
(
this.state.filters.map((room, idx) =>
<RoomBox
key={idx}
name={room.name}
threadId={room.threadId}
public={room.publicRoom}
memberCount={room.memberCount}
creatorPrettyName={room.creatorPrettyName}
removeFilter={this.removeFilter}
/>
)
)}
</div>
);
}
}
SuggestionsRooms.propTypes = {
addStreamToInstance: PropTypes.func.isRequired,
removeStreamFromInstance: PropTypes.func.isRequired,
resetPostingLocation: PropTypes.func.isRequired,
userRooms: PropTypes.arrayOf(PropTypes.object).isRequired,
updateRooms: PropTypes.func.isRequired,
filters: PropTypes.arrayOf(PropTypes.object),
};
SuggestionsRooms.defaultProps = {
filters: [],
};
export default SuggestionsRooms;