goldfish-search
Version:
A SharePoint 2013 inline People Search component
335 lines (279 loc) • 9.42 kB
JSX
import React from 'react';
import Autosuggest from './autosuggest/AutosuggestContainer';
import AutosuggestHighlight from 'autosuggest-highlight';
import Tag from '../tag/Tag';
import Button from '../../ui/Button.jsx';
import styles from './Suggest.css';
import cssModules from 'react-css-modules';
import TaxonomyStore from '../../stores/TaxonomyStore';
import PeopleSearchActions from '../../actions/PeopleSearchActions';
import DefaultConstants from '../../constants/default.js';
function renderLayout(tagComponents) {
return (
<span>
{tagComponents}
</span>
);
}
function getSearchFromTags(tags) {
// get all the search terms from the tags and return them in a new array
return tags.map(function(tag) {
return tag.search;
}).join(' ');
}
// https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions#Using_Special_Characters
function escapeRegexCharacters(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function getSuggestions(value) {
const escapedValue = escapeRegexCharacters(value.trim());
if (escapedValue === '') {
return [];
}
const regex = new RegExp('^' + escapedValue, 'i');
return TaxonomyStore.getCurrentSuggestions()
.map(section => {
return {
title: section.title,
terms: section.terms.filter(term => regex.test(term.name)),
};
})
.filter(section => section.terms.length > 0);
}
function renderSuggestion(suggestion, { value, valueBeforeUpDown }) {
const query = (valueBeforeUpDown || value).trim();
const matches = AutosuggestHighlight.match(suggestion.name, query);
const parts = AutosuggestHighlight.parse(suggestion.name, matches);
return (
<span className="animated">
{
parts.map((part, index) => {
const className = part.highlight ? 'highlight' : null;
return (
<span className={className} key={index}>
{part.text}
</span>
);
})
}
</span>
);
}
function renderSectionTitle(section) {
return (
<strong>{section.title}</strong>
);
}
function getSectionSuggestions(section) {
return section.terms;
}
function setTaxonomySearchResults() {
this.setState({ suggestions: TaxonomyStore.getCurrentSuggestions() });
}
class Suggest extends React.Component {
constructor(props) {
super(props);
this.state = {
tag: '',
value: '',
suggestions: getSuggestions(''),
};
this.onChange = this.onChange.bind(this);
this.onSuggestionsUpdateRequested = this.onSuggestionsUpdateRequested.bind(this);
}
onComponentChange() {
if (this.state === null) {
setTaxonomySearchResults();
}
}
getSuggestionValue(suggestion, event) {
// do not load the selected term in the input field
if (event.type === 'click') {
// enter or click event
const {validationRegex} = this.props;
if (validationRegex.test(suggestion.name)) {
this._addTag(suggestion);
}
return '';
}
this.setState({ tag: suggestion });
return suggestion.name;
}
componentWillMount() {
TaxonomyStore.removeChangeListener(this.onComponentChange);
}
componentDidMount() {
TaxonomyStore.addChangeListener(this.onComponentChange.bind(this));
}
onSearch(event) {
// take the array values and join them for the search (invoke search in parent)
this.props.onChange(getSearchFromTags(this.props.tags));
}
componentWillReceiveProps(newProps) {
if (typeof newProps !== 'undefined') {
if (typeof newProps.termsets !== 'undefined') {
if (newProps.termsets.length > 0) {
// fetch the data for Taxonomy and Job Titles for the Auto Suggest tool
PeopleSearchActions.getTaxonomy(newProps.termsets);
}
}
if (typeof newProps.userInformationFields !== 'undefined') {
if (newProps.userInformationFields.length > 0) {
PeopleSearchActions.getBusinessInformation(newProps.userInformationFields);
}
} else {
// Defaults are set for auto suggest if none are supplied in the options
PeopleSearchActions.getBusinessInformation(DefaultConstants.DEFAULT_USERINFORMATION_FIELDS);
}
}
}
componentDidUpdate() {
if (this.state === null) {
this.setState({
tag: {},
value: '',
suggestions: getSuggestions(''),
});
}
}
removeTag(index) {
const tags = this.props.tags.concat([]);
if (index > -1 && index < tags.length) {
tags.splice(index, 1);
this.props.onTagsChange(tags);
}
}
_clearInput() {
this.setState({ tag: {} });
}
renderSearchButton() {
if (typeof this.props.tags !== 'undefined') {
if (this.props.tags.length > 0) {
return (
<div styleName="tag-holder">
<Button
icon="search"
label="Search"
raised primary
onClick={this.onSearch.bind(this)} />
</div>
);
}
}
}
_addTag(tag) {
const {onlyUnique} = this.props;
const isUnique = (this.props.tags.filter(function(item) {
return item.name !== tag.name;
})).length === 0;
const limit = this._maxTags(this.props.tags.length);
if (typeof tag.name !== 'undefined' && limit && (isUnique || !onlyUnique)) {
const tags = this.props.tags.concat([tag]);
this.props.onTagsChange(tags);
this._clearInput();
}
}
onKeyDown(event) {
// tab or enter key down event
if (event.keyCode === 9 || event.keyCode === 13) {
event.preventDefault();
if (Object.keys(this.state.tag).length > 0) {
this._addTag(this.state.tag);
this.setState({
tag: {},
value: '',
});
} else if (this.state.value !== '') {
// vanilla search term
// if a contains search is being done, display only the right handside value in the tag (but keep the full search term in the search property)
let seperator = '';
if (this.state.value.indexOf(':') > -1) {
seperator = ':';
} else if (this.state.value.indexOf('=') > -1) {
seperator = '=';
}
this._addTag({
name: (seperator === '' ? this.state.value : this.state.value.split(seperator)[1]).replace(/"/g, ''),
search: this.state.value,
});
this.setState({ value: '' });
}
}
}
onChange(event, { newValue }) {
this.setState({ value: newValue });
this.props.onChange(newValue);
}
onSuggestionsUpdateRequested({ value }) {
this.setState({ suggestions: getSuggestions(value) });
}
_maxTags(tags) {
return this.props.maxTags !== -1 ? tags < this.props.maxTags : true;
}
render() {
if (this.state !== null) {
const { tags, onTagsChange, addKeys, removeKeys, ...other} = this.props;
const { value, suggestions } = this.state;
const inputProps = {
placeholder: 'Add tags and then search...',
value,
onChange: this.onChange.bind(this),
onKeyDown: this.onKeyDown.bind(this),
};
const tagComponents = tags.length > 0 ?
renderLayout(tags.map((element, index) => {
return (
<Tag
key={index}
item={index}
tag={element}
onRemove={this.removeTag.bind(this)}
className="react-tagsinput-tag"
removeClassName="react-tagsinput-remove" />
);
})) : '';
return (
<div key="autosuggest-region">
<Autosuggest
key="goldfish-autosuggest"
className="animated flipInX"
multiSection={true}
suggestions={suggestions}
onSuggestionsUpdateRequested={this.onSuggestionsUpdateRequested.bind(this)}
getSuggestionValue={this.getSuggestionValue.bind(this)}
renderSuggestion={renderSuggestion.bind(this)}
renderSectionTitle={renderSectionTitle.bind(this)}
getSectionSuggestions={getSectionSuggestions.bind(this)}
inputProps={inputProps} />
<div ref="div" {...other}>
{tagComponents}
</div>
{this.renderSearchButton()}
</div>
);
}
this.componentDidUpdate();
}
}
Suggest.propTypes = {
onChange: React.PropTypes.func,
addKeys: React.PropTypes.array,
addOnBlur: React.PropTypes.bool,
onTagsChange: React.PropTypes.func.isRequired,
removeKeys: React.PropTypes.array,
onlyUnique: React.PropTypes.bool,
tags: React.PropTypes.array.isRequired,
maxTags: React.PropTypes.number,
validationRegex: React.PropTypes.instanceOf(RegExp),
termsets: React.PropTypes.array,
fields: React.PropTypes.array,
};
Suggest.defaultProps = {
className: 'react-tagsinput',
addKeys: [9, 13],
removeKeys: [8],
onlyUnique: false,
maxTags: -1,
validationRegex: /.*/,
};
export default cssModules(Suggest, styles, { allowMultiple: true });