Skip to content

Instantly share code, notes, and snippets.

@THEozmic
Last active November 28, 2017 09:04
Show Gist options
  • Save THEozmic/56a68d0828386d495068ff6d5ec60123 to your computer and use it in GitHub Desktop.
Save THEozmic/56a68d0828386d495068ff6d5ec60123 to your computer and use it in GitHub Desktop.
Search Component that lets users search, select search results and submit selected results
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { Form } from './Form';
import {
apiFetchGroup,
apiSearch,
apiUpdateMembers,
stopSearch } from '../../actions/';
/**
* Search Page
* @class Search
* @extends {React.Component}
*/
export class Search extends React.Component {
/**
* Creates an instance of Search
* @param {any} props -
* @memberof Search
*/
constructor(props) {
super(props);
this.onSearchChange = this.onSearchChange.bind(this);
this.nextPage = this.nextPage.bind(this);
this.prevPage = this.prevPage.bind(this);
this.makeSearch = this.makeSearch.bind(this);
this.onFinishClick = this.onFinishClick.bind(this);
this.isAdmin = this.isAdmin.bind(this);
this.onChange = this.onChange.bind(this);
this.showConfirmMessage = this.showConfirmMessage.bind(this);
this.onCancelClick = this.onCancelClick.bind(this);
this.onFocus = this.onFocus.bind(this);
this.state = {
foundUsers: [],
selectedUsers: [],
selectedGroup: {},
groupMembers: [],
currentPage: 0,
totalPages: 1,
prevSearchQuery: '',
noUsersFound: false,
searchTerm: '',
updateConfirmed: false,
confirmMessage: '',
buttonText: 'Finish',
showCancelBtn: false,
errorMessage: '',
disableBtn: false
};
}
/**
* @returns {void}
*/
componentWillMount() {
const id = this.props.match.params.id;
this.props.apiFetchGroup(id, false);
}
/**
* @returns {void}
* @param {object} nextProps
*/
componentWillReceiveProps(nextProps) {
if (nextProps.selectedGroup.users !== undefined) {
this.setState({
groupMembers: nextProps.selectedGroup.users
});
}
if (nextProps.searchResults.status === 'complete') {
this.props.stopSearch();
location.href = `/#/group/${nextProps.selectedGroup.id}`;
Materialize.toast('Group members list updated!', 4000);
}
if (nextProps.searchResults.pages !== undefined &&
this.state.searchTerm !== '') {
const searchResults = nextProps.searchResults;
// no search results gotten
// set the current page to zero
if (searchResults.pages === 0) {
this.setState({ currentPage: 0, foundUsers: [], noUsersFound: true });
return;
}
// update the totalPages
this.setState({
totalPages: searchResults.pages
});
// determine who's been selected or not
const nUsers = searchResults.users.map((user) => {
this.state.selectedUsers.map((sUser) => {
if (sUser.id === user.id) {
user.ingroup = true;
}
return searchResults;
});
return user;
});
this.setState({ foundUsers: nUsers });
}
}
/**
* @param {object} event
* @returns {void} this returns nothing
* It is called when the "Finish" button is clicked
* It sends the list of users whose "in-group" status of the
* selected group needs to be changed
*/
onFinishClick(event) {
event.preventDefault();
if (this.state.selectedUsers.length !== 0) {
if (this.state.confirmMessage !== '') {
const selectedUsers = JSON.stringify(this.state.selectedUsers);
this.setState({ disableBtn: true });
this.props.apiUpdateMembers(selectedUsers, this.props.selectedGroup.id);
} else {
this.showConfirmMessage();
}
} else {
if (this.state.searchTerm.trim() === ''
&& this.state.foundUsers.length === 0) {
this.setState({
errorMessage: 'Please search for a user or click cancel to go back'
});
return;
}
this.setState({
errorMessage:
'Please click on a username to add or remove the user from this group'
});
}
}
/**
* @returns {void}
*/
onCancelClick() {
this.setState({
confirmMessage: '',
buttonText: 'Finish',
showCancelBtn: false
});
}
/**
* This function changes intial states based on onChange events
* @param {object} event [the events object parameter]
* @return {[type]} [description]
*/
onChange(event) {
this.setState({ [event.target.name]: event.target.value },
this.onSearchChange);
}
/**
* @return {void}
* This method is called when the user focuses on the input,
* if there's an error relating to that input, it clears it.
*/
onFocus() {
this.setState({ errorMessage: '' });
}
/**
* @param {string} nav
* @returns {void} returns nothing
* only requests for the search results
* it's called when the search input value changes
*/
onSearchChange(nav) {
this.setState({ noUsersFound: false });
if (this.state.searchTerm.trim() !== '') {
if (this.state.currentPage === 0) {
this.setState({
currentPage: 1
});
this.makeSearch(0);
this.setState({
prevSearchQuery: this.state.searchTerm.trim()
});
return;
}
if (this.state.prevSearchQuery !== this.state.searchTerm.trim()) {
this.setState({
currentPage: 1
});
this.makeSearch(0);
this.setState({
prevSearchQuery: this.state.searchTerm.trim()
});
return;
}
if (nav === 'prev') {
if (this.state.currentPage === 1) {
return;
}
this.makeSearch(this.state.currentPage - 2);
this.setState({
currentPage: this.state.currentPage - 1
});
}
if (nav === 'next') {
if (this.state.totalPages === this.state.currentPage) {
return;
}
this.setState({
currentPage: this.state.currentPage + 1
});
this.makeSearch(this.state.currentPage);
}
this.setState({
prevSearchQuery: this.state.searchTerm.trim()
});
}
return true;
}
/**
* @returns {void} this method doesn't return anything
* It forms the array of users whose "in-group" status need to
* be changed
*
* @param {object} event
* @param {object} user
*/
onSelectUser(event, user) {
event.preventDefault();
this.setState({ errorMessage: '' });
let selectedUsers = this.state.selectedUsers;
const isFound = selectedUsers.find(sUser => sUser.id === user.id);
if (!this.isAdmin() && user.ingroup === true &&
typeof isFound === 'undefined') {
Materialize.toast('Only an Admin can do that', 4000);
return;
}
if (typeof isFound !== 'undefined') {
selectedUsers = selectedUsers.filter(sUser => sUser.id === user.id);
} else {
selectedUsers.push(user);
}
user.ingroup = !user.ingroup;
let foundUsers = this.state.foundUsers;
foundUsers = foundUsers.filter(sUser => sUser.id !== user.id);
foundUsers.push(user);
this.setState({ foundUsers });
this.setState({ selectedUsers });
}
/**
* @returns {void}
*/
showConfirmMessage() {
this.setState({
confirmMessage: 'Are you sure you want to update users in this group?',
buttonText: 'Yes',
showCancelBtn: true,
errorMessage: ''
});
}
/**
* @return {void}
* @param {int} page
*/
makeSearch(page) {
this.props.apiSearch(this.props.selectedGroup.id, this.state.searchTerm,
page);
}
/**
* @param {object} event
* @returns {void}
* This method simply updates the state of the "next"
* buttons when they are clicked
*/
nextPage(event) {
event.preventDefault();
this.onSearchChange('next');
}
/**
* @param {object} event
* @returns {void}
* This method simply updates the state of the "prev"
* buttons when they are clicked
*/
prevPage(event) {
event.preventDefault();
this.onSearchChange('prev');
}
/**
* @returns {boolean} true or false based on if the "current user" is
* the admin of the selected group
*/
isAdmin() {
if (this.props.selectedGroup.admin ===
this.props.user.id) {
return true;
}
return false;
}
/**
* @returns {JSX} This returns the JSX for teh Search page DOM
*/
render() {
// Here we are doing to dynamic form title,
// depending on if the user is an admin
let action = 'Add members to ';
if (this.isAdmin()) {
action = 'Add/Remove members from ';
}
const title = { action: `${action}`,
group: this.props.selectedGroup.name,
last: ' group' };
const members = [];
this.state.groupMembers.map(member => members.push(member.username));
return (
<Form
title={title}
active="search"
showSideMenu
showSearchLink
onSubmit={event => event.preventDefault()}
>
<div>
{ this.state.confirmMessage === '' ?
<div>
<div className="input-field">
<input
type="text"
id="search"
name="searchTerm"
onChange={this.onChange}
value={this.state.searchTerm}
onFocus={this.onFocus}
/>
<label htmlFor="search">Search by username</label>
</div>
<div className="search-results">
{
this.state.foundUsers.map(fUser =>
(
<button
key={fUser.id}
onClick={event => this.onSelectUser(event, fUser)}
className={fUser.ingroup ? 'ingroup' : ''}
>{fUser.ingroup ? <span>&#10004; </span> : ''}
@{fUser.username}</button>)
) }
{ this.state.noUsersFound ?
'No users found' : <div className="space" />
}
{this.state.foundUsers.length !== 0 ?
<div className="search-pages">
<button
onClick={event => this.prevPage(event)}
className="search-prev"
>Prev</button>
<span>
{this.state.currentPage}/{this.state.totalPages}
</span>
<button
onClick={event => this.nextPage(event)}
className="search-next"
>Next</button>
</div> : ''
}
</div>
</div>
:
<div className="blue-text text-darken-2 section">
{this.state.confirmMessage}</div>
}
{ this.state.errorMessage === '' ? '' :
<div
className="red card"
>{this.state.errorMessage}</div>}
<button
className="waves-effect waves-light btn action-btn"
onClick={this.onFinishClick}
disabled={this.state.disableBtn}
>{this.state.buttonText}</button>
{ this.state.showCancelBtn ?
<button
className="waves-effect waves-teal red btn action-btn"
onClick={this.onCancelClick}
>No</button>
: '' }
<Link
className="right waves-effect waves-teal btn-flat action-btn"
to={`/group/${this.props.selectedGroup.id}`}
>Cancel</Link>
</div>
</Form>);
}
}
Search.propTypes = {
match: PropTypes.object.isRequired,
apiFetchGroup: PropTypes.func.isRequired,
selectedGroup: PropTypes.object.isRequired,
apiSearch: PropTypes.func.isRequired,
searchResults: PropTypes.object.isRequired,
apiUpdateMembers: PropTypes.func.isRequired,
user: PropTypes.object,
stopSearch: PropTypes.func.isRequired
};
Search.defaultProps = {
user: {}
};
const mapStateToProps = state => ({
selectedGroup: state.selectedGroup,
searchResults: state.search,
user: state.user
});
const mapDispatchToProps = dispatch => ({
apiFetchGroup: (groupId, withMessages) =>
dispatch(apiFetchGroup(groupId, withMessages)),
apiSearch: (groupId, searchTerm, page) =>
dispatch(apiSearch(groupId, searchTerm, page)),
apiUpdateMembers: (selectedUsers, groupId) =>
dispatch(apiUpdateMembers(selectedUsers, groupId)),
stopSearch: () =>
dispatch(stopSearch())
});
export default connect(mapStateToProps, mapDispatchToProps)(Search);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment