Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support query search in V1.0.0 alpha.6 #1

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Initial commit for query search
sameer79 committed Dec 6, 2019
commit c7a0eb0077f3593cdae7e664baa57962c9314779
33 changes: 23 additions & 10 deletions example/src/App.js
Original file line number Diff line number Diff line change
@@ -12,21 +12,24 @@ export default class App extends Component {
// NOTE: The operator will seen to UI only if props isAllowOperator={true}
this.options = [
{
conditional:null,
category: "Symbol",
type: "textoptions",
operator: ["==", "!="],
options: this.getSymbolOptions
},
{
conditional:null,
category: "Name",
type: "text",
isAllowDuplicateCategories: false,
operator: () => null
operator: null
},
{ category: "Price", type: "number" },
{ category: "MarketCap", type: "number" },
{ category: "IPO", type: "date" },
{ conditional:null, category: "Price", type: "number" },
{ conditional:null, category: "MarketCap", type: "number" },
{ conditional:null, category: "IPO", type: "date" },
{
conditional:null,
category: "Sector",
type: "textoptions",
fuzzySearchKeyAttribute: "sectorName",
@@ -36,10 +39,19 @@ export default class App extends Component {
options: this.getSectorOptions
},
{
conditional:null,
category: "Industry",
type: "textoptions",
isAllowCustomValue: false,
options: this.getIndustryOptions
},
{
conditional: null,
category: "Query",
type: "custom",
isAllowCustomValue: true,
options: null,
operator: null,
}
];
}
@@ -80,7 +92,7 @@ export default class App extends Component {

getTokenItem(obj) {
let val = obj.children;
return `${val["category"]}: val`;
return <div>{`testing`}</div>;
}

render() {
@@ -89,12 +101,13 @@ export default class App extends Component {
<ReactStructuredQuerySearch
isAllowOperator={true}
defaultSelected={[
{ category: "Sector", value: { sectorName: "Finance", id: 1 } },
{ category: "Sector", value: { sectorName: "Consumer Services", id: 2 } },
{ category: "Industry", value: { name: "Other Specialty Stores", id: 2 } }
{ conditional: null, category: "Sector", value: { sectorName: "Finance", id: 1 } },
{ conditional: null, category: "Sector", value: { sectorName: "Consumer Services", id: 2 } },
{ conditional: null, category: "Industry", value: { name: "Other Specialty Stores", id: 2 } },
// { conditional: null, category: "Query", value: "AND ( Demo == PIL )"}
]}
options={this.options}
//renderTokenItem={this.getTokenItem}
// renderTokenItem={this.getTokenItem}
updateOptions={({ updatedValues, addedValue }) => {
if (addedValue && addedValue.category === "Symbol" && addedValue.value === "TFSC") {
this.options.push({
@@ -114,4 +127,4 @@ export default class App extends Component {
</div>
);
}
}
}
96 changes: 96 additions & 0 deletions src/CustomQueryTokenizer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React, {Component, Fragment} from 'react';

import ReactStructuredQuerySearch from "react-structured-query-search";

export default class CustomQueryTokenizer extends Component {
constructor(props) {
super(props);
this.state = {
selected : []
};
this.options = [
{
conditional: "AND",
category: "Demo",
type: "textoptions",
operator: this.getOperatorOptions,
options: this.getSymbolOptions
},
{
conditional: "OR",
category: "Sample",
type: "textoptions",
operator: this.getOperatorOptions,
options: this.getSymbolOptions
},
{
conditional: ",",
category: "",
type: "textoptions",
operator: this.getOperatorOptions,
options: this.getSymbolOptions
},
{
conditional: " )",
category: "",
type: "text",
operator: null,
options: null
}
];
}

getOperatorOptions () {
return ["==", "!="];
}

getSymbolOptions () {
return ["TFSC", "PIL", "VNET"];
}

getTokenItem (obj) {
const val = obj.children;
return `${val.conditional} ${val.category} ${val.operator} ${val.value}`;
}

trimText (val) {
return val.trim() === "" ? val.trim() : val + " ";
}

updateParentInputText () {
let str = '';
this.state.selected.forEach((s) => {
str += this.trimText(s.conditional);
str += this.trimText(s.category);
str += this.trimText(s.operator);
str += this.trimText(s.value);
});
this.props.updatedInputText(str);
}

render() {
return(
<Fragment>
<ReactStructuredQuerySearch
isAllowOperator={true}
defaultSelected={this.state.selected}
options={this.options}
renderTokenItem={this.getTokenItem}
conditionalHeader={"Conditional"}
categoryHeader={'Selection'}
onTokenAdd={val => console.log(val, 'onTokenAdd')}
customClasses={{
input: "filter-tokenizer-text-input",
results: "filter-tokenizer-list__container",
listItem: "filter-tokenizer-list__item",
query: "custom-query"
}}
emptyParentCategoryState={this.props.emptyParentCategoryState}
updateParentInputText={this.updateParentInputText.bind(this)}
customQuery={true}
autoFocus={true}
/>
</Fragment>
);
}
};
193 changes: 150 additions & 43 deletions src/OTokenizer.js
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ import ReactDOM from "react-dom";
import PropTypes from "prop-types";
import { Tokenizer } from "./lib/react-structured-filter/react-typeahead/react-typeahead";
import Typeahead from "./OTypeahead";
import classNames from "classnames";

// Override the Tokenizer
export default class OTokenizer extends Tokenizer {
@@ -49,7 +50,7 @@ export default class OTokenizer extends Tokenizer {
) {
return;
}
if (this.state.focused === true && !this.typeaheadRef.isOptionsLoading()) {
if (this.state.focused === true && this.typeaheadRef && !this.typeaheadRef.isOptionsLoading()) {
this.setState({ focused: false });
}
}
@@ -60,7 +61,9 @@ export default class OTokenizer extends Tokenizer {
};

_getInputType() {
if (this.state.category != "" && (this.props.isAllowOperator && this._getCategoryOperator() !== null ? this.state.operator != "" : true)) {
if (this.state.category === "Query") {
return "custom";
} else if (this.state.category != "" && (this.props.isAllowOperator && this._getCategoryOperator() !== null ? this.state.operator != "" : true)) {
return this._getCategoryType();
} else {
return "text";
@@ -75,8 +78,53 @@ export default class OTokenizer extends Tokenizer {
}
}

_checkConditionalOptions () {
return this.state.options.filter(function(o) {
return o.conditional !== null && o.conditional !== undefined && o.conditional !== "";
}).length > 0 ? true : false;
}

_checkSpeacialChar (val) {
var match = new RegExp(/[^a-zA-Z]/g);
return match.test(val);
}

_showCloseBracketOptions (val) {
let showCloseBracket = this.state.selected.length > 0 ? true : !this._checkSpeacialChar(val);
if (this.state.selected.length > 0 && (val.includes(',') || val.includes(' )'))) {
showCloseBracket = this._bracketHasClosed() && !this.state.ediTableTokenId ? false : true;
}
return showCloseBracket;
}

_bracketHasClosed = () => {
if (this.state.selected.length === 0) {
return false;
}
const obj = { open: 0, close: 0 };
this.state.selected.map((s) => {
if (s.conditional.includes('(')) {
obj.open = ++obj.open
} else if (s.conditional.includes(')')) {
obj.close = ++obj.close
}
});
return obj.open === obj.close;
}

_getOptionsForTypeahead() {
if (this.state.category == "") {
const closeBracket = this.state.conditional && this.state.conditional.includes(')') ? true : false;
if (this.state.conditional == "" && this._checkConditionalOptions()) {
var conditional = [];
for (var i = 0; i < this.state.options.length; i++) {
var options = this.state.options[i],
condition = options.conditional;
if (condition && this._showCloseBracketOptions(condition)) {
conditional.push(condition);
}
}
return conditional;
} else if (this.state.category == "" && !closeBracket ) {
var categories = [];
for (var i = 0; i < this.state.options.length; i++) {
let options = this.state.options[i],
@@ -91,15 +139,17 @@ export default class OTokenizer extends Tokenizer {
let foundCategory = this.state.selected.find(function(obj) {
return obj.category == category;
});
if (!foundCategory) {
if (!foundCategory && category.trim() !== "") {
categories.push(category);
}
} else {
categories.push(category);
if (category.trim() !== "") {
categories.push(category);
}
}
}
return categories;
} else if (this.props.isAllowOperator && this._getCategoryOperator() !== null && this.state.operator == "") {
} else if (this.props.isAllowOperator && this._getCategoryOperator() !== null && this.state.operator == "" && !this.state.conditional.includes(')')) {
let categoryType = this._getCategoryType();
let categoryOperator = this._getCategoryOperator();
if (categoryOperator) {
@@ -132,7 +182,7 @@ export default class OTokenizer extends Tokenizer {
} else {
if (typeof options === "function") {
let opt = options();
if (typeof opt == "object") {
if (typeof opt == "object" && !this.state.conditional.includes(')')) {
if (opt instanceof Promise) {
return opt;
} else {
@@ -198,7 +248,9 @@ export default class OTokenizer extends Tokenizer {
}

_getHeader() {
if (this.state.category == "") {
if (this.state.conditional == "" && this._checkConditionalOptions()) {
return this.props.conditionalHeader || "Conditional";
} else if (this.state.category == "") {
return this.props.categoryHeader || "Category";
} else if (this.props.isAllowOperator && this.state.operator == "") {
return this.props.operatorHeader || "Operator";
@@ -255,7 +307,7 @@ export default class OTokenizer extends Tokenizer {
_focusInput() {
if (this.typeaheadRef) {
var entry = this.typeaheadRef.getInputRef();
if (entry) {
if (entry && entry.focus) {
entry.focus();
}
}
@@ -302,8 +354,19 @@ export default class OTokenizer extends Tokenizer {
return;
}
let { isAllowOperator } = this.props;
const closeBracket = this.state.conditional && this.state.conditional.includes(')') ? true : false;
if (this.state.conditional == "" && this._checkConditionalOptions()) {
var val = this._checkSpeacialChar(value) ? value : value + " ( " ;
this.state.conditional = val;
this.setState({ conditional: val});
this.typeaheadRef.setEntryText("");
if (this.props.customQuery && val === " )") {
this._addToken({value: val, isAllowOperator: false, closeToken: true});
}
return;
}

if (this.state.category == "") {
if (this.state.category == "" && !closeBracket) {
this.state.category = value;
this.setState({ category: value });
this.typeaheadRef.setEntryText("");
@@ -312,23 +375,40 @@ export default class OTokenizer extends Tokenizer {
if (this.state.category !== "" && isAllowOperator) {
isAllowOperator = this._getCategoryOperator() !== null;
}
if (isAllowOperator && this.state.operator == "") {
if (isAllowOperator && this.state.operator == "" && !closeBracket) {
this.state.operator = value;
this.setState({ operator: value });
this.typeaheadRef.setEntryText("");
return;
}
this._addToken({value, isAllowOperator});
};

_addToken = ({value, isAllowOperator, closeToken}) => {

value = {
conditional: this.state.conditional,
category: this.state.category,
value: value
};

this.state.selected.push(value);
if (closeToken) {
value.value = "";
value.operator = "";
}

if (this.state.ediTableTokenId !== null) {
this.state.selected[this.state.ediTableTokenId] = value;
this.state.ediTableTokenId = null;
} else {
this.state.selected.push(value);
}

let stateObj = {
selected: this.state.selected,
category: ""
conditional: "",
category: "",
ediTableTokenId: this.state.ediTableTokenId
};

if (isAllowOperator) {
@@ -347,9 +427,12 @@ export default class OTokenizer extends Tokenizer {
Object.assign(stateObj, { options: newOptions });
}
}
this.setState(stateObj, () => this.props.onTokenAdd(this.state.selected));
this.setState(stateObj, () => {
this.props.onTokenAdd(this.state.selected);
this._focusInput();
});
return;
};
}

_onClearAll = () => {
if (this.props.disabled) {
@@ -380,34 +463,58 @@ export default class OTokenizer extends Tokenizer {
);
}

_getTypeahed({ classList }) {
_emptyParentCategoryState = () => {
this.setState({"category": ""}, () => {
this._focusInput();
});
}

getTypeHeadHtmlContainer = (component, uniqKey) => {
return (
<Typeahead
ref={ref => (this.typeaheadRef = ref)}
disabled={this.props.disabled}
isAllowOperator={this.props.isAllowOperator}
onElementFocused={this.onElementFocused}
isElemenFocused={this.state.focused}
fuzzySearchEmptyMessage={this.props.fuzzySearchEmptyMessage}
fuzzySearchKeyAttribute={this._getFuzzySearchKeyAttribute({
category: this.state.category
})}
isAllowSearchDropDownHeader={this.props.isAllowSearchDropDownHeader}
renderSearchItem={this.props.renderSearchItem}
className={classList}
placeholder={this.props.placeholder}
customClasses={this.props.customClasses}
options={this._getOptionsForTypeahead()}
header={this._getHeader()}
datatype={this._getInputType()}
isAllowCustomValue={this._getAllowCustomValue({
category: this.state.category
})}
defaultValue={this.props.defaultValue}
onOptionSelected={this._addTokenForValue}
onKeyDown={this._onKeyDown}
fromTokenizer={true}
/>
<div className="filter-input-group" key={uniqKey || new Date().getMilliseconds()}>
<div className="filter-conditional">{this.state.conditional}</div>
<div className="filter-category">{this.state.category}</div>
<div className="filter-operator">{this.state.operator}</div>
{ component }
</div>
);
}
}

_getTypeahed({mykey, show}) {
var classes = {};
classes[this.props.customClasses.typeahead] = !!this.props.customClasses.typeahead;
var classList = classNames(classes);
var typeHeadComp = <Typeahead
ref={ref => this.typeaheadRef = ref}
disabled={this.props.disabled}
isAllowOperator={this.props.isAllowOperator}
onElementFocused={this.onElementFocused}
isElemenFocused={this.state.focused}
fuzzySearchEmptyMessage={this.props.fuzzySearchEmptyMessage}
fuzzySearchKeyAttribute={this._getFuzzySearchKeyAttribute({
category: this.state.category
})}
isAllowSearchDropDownHeader={this.props.isAllowSearchDropDownHeader}
renderSearchItem={this.props.renderSearchItem}
className={classList}
placeholder={this.props.placeholder}
customClasses={this.props.customClasses}
options={this._getOptionsForTypeahead()}
header={this._getHeader()}
datatype={this._getInputType()}
isAllowCustomValue={this._getAllowCustomValue({
category: this.state.category
})}
defaultValue={this.props.defaultValue}
onOptionSelected={this._addTokenForValue}
onKeyDown={this._onKeyDown}
fromTokenizer={true}
emptyParentCategoryState={this._emptyParentCategoryState}
customQuery={this.props.customQuery}
bracketHasClosed={this._bracketHasClosed}
updateParentInputText={this.props.updateParentInputText}
ediTableTokenId={this.state.ediTableTokenId}
/>;
return show ? this.getTypeHeadHtmlContainer(typeHeadComp, mykey) : typeHeadComp;
}
}
58 changes: 33 additions & 25 deletions src/OTypeahead.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Typeahead } from "./lib/react-structured-filter/react-typeahead/react-typeahead";
import CustomQueryTokenizer from './CustomQueryTokenizer';

// Override the Tokenizer
export default class OTypeahead extends Typeahead {
@@ -66,31 +67,38 @@ export default class OTypeahead extends Typeahead {
}
}

_getTypeaheadInput({ classList, inputClassList }) {
_getTypeaheadInput({classList, inputClassList}) {
return (
<div className={classList}>
{this.state.loadingOptions ? (
this.props.renderLoading ? (
this.props.renderLoading()
) : (
<div>Loading...</div>
)
) : (
<span ref={ref => (this.inputRef = ref)} onFocus={this._onFocus}>
<input
ref={ref => (this.entryRef = ref)}
type={this.state.datatype == "number" ? "number" : "text"}
placeholder={this.props.placeholder}
className={inputClassList}
defaultValue={this.state.entryValue}
onChange={this._onTextEntryUpdated}
onKeyDown={this._onKeyDown}
disabled={this.props.disabled}
/>
{this._renderIncrementalSearchResults()}
</span>
)}
</div>
<div className={classList}>
{this.state.loadingOptions
? this.props.renderLoading
? this.props.renderLoading ()
: <div>Loading...</div>
: <span ref={ref => (this.inputRef = ref)} onFocus={this._onFocus}>
{this.state.datatype == 'custom'
? <CustomQueryTokenizer
ref={ref => (this.entryRef = ref)}
type={this.state.datatype}
placeholder={this.props.placeholder}
defaultValue={this.state.entryValue}
parentCallBack={this.props.parentCallBack}
disabled={this.props.disabled}
updatedInputText={this._onTextEntryUpdated}
{...this.props}
/>
: <input
ref={ref => (this.entryRef = ref)}
type={this.state.datatype == 'number' ? 'number' : 'text'}
placeholder={this.props.placeholder}
className={inputClassList}
defaultValue={this.state.entryValue}
onChange={this._onTextEntryUpdated}
onKeyDown={this._onKeyDown}
disabled={this.props.disabled}
/>}
{this._renderIncrementalSearchResults ()}
</span>}
</div>
);
}
}
}
54 changes: 37 additions & 17 deletions src/lib/react-structured-filter/react-typeahead/tokenizer/index.js
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import propTypes from "prop-types";
import Token from "./token";
import KeyEvent from "../keyevent";
import Typeahead from "../typeahead";
import classNames from "classNames";
import classNames from "classnames";

/**
* A typeahead that, when an option is selected, instead of simply filling
@@ -49,10 +49,12 @@ export default class TypeaheadTokenizer extends Component {
this.skipCategorySet = new Set();
this.state = {
selected: [],
conditional: "",
category: "",
operator: "",
options: this.props.options,
focused: false
focused: this.props.autoFocus || false,
ediTableTokenId: null
};
this.state.selected = this.getDefaultSelectedValue();
}
@@ -75,13 +77,16 @@ export default class TypeaheadTokenizer extends Component {
(typeof selected.value == "string" ? selected.value : selected.value[fuzzySearchKeyAttribute]) +
index;
return (
<Token
this.state.ediTableTokenId === index ? this._getTypeahed ({mykey, show: true}):
<Token
key={mykey}
className={classList}
renderTokenItem={this.props.renderTokenItem}
fuzzySearchKeyAttribute={fuzzySearchKeyAttribute}
fuzzySearchIdAttribute={this.props.fuzzySearchIdAttribute}
onRemoveToken={this._removeTokenForValue}
onEditToken={this._editTokenForValue.bind(this)}
{...this.props}
>
{selected}
</Token>
@@ -151,7 +156,6 @@ export default class TypeaheadTokenizer extends Component {
if (event.keyCode !== KeyEvent.DOM_VK_BACK_SPACE) {
return;
}

// Remove token ONLY when bksp pressed at beginning of line
// without a selection
var entry = this.typeaheadRef.getInputRef();
@@ -160,9 +164,14 @@ export default class TypeaheadTokenizer extends Component {
this.setState({ operator: "" });
} else if (this.state.category != "") {
this.setState({ category: "" });
} else if (this.state.conditional != "") {
this.setState({ conditional: "" });
} else {
// No tokens
if (!this.state.selected.length) {
if (this.props.emptyParentCategoryState) {
this.props.emptyParentCategoryState();
}
return;
}
this._removeTokenForValue(this.state.selected[this.state.selected.length - 1]);
@@ -184,6 +193,11 @@ export default class TypeaheadTokenizer extends Component {
return;
};

_editTokenForValue = value => {
const index = this.state.selected.indexOf(value);
this.setState({ ediTableTokenId: index}, () => setTimeout(() => {this._focusInput()}, 0));
}

_addTokenForValue = value => {
if (this.state.category == "") {
this.setState({ category: value });
@@ -224,7 +238,7 @@ export default class TypeaheadTokenizer extends Component {
}
}

_getTypeahed({ classList }) {
_getTypeahed() {
return (
<Typeahead
ref={ref => (this.typeaheadRef = ref)}
@@ -242,27 +256,33 @@ export default class TypeaheadTokenizer extends Component {
}

render() {
var classes = {};
classes[this.props.customClasses.typeahead] = !!this.props.customClasses.typeahead;
var classList = classNames(classes);
var classes = {
'filter-tokenizer': true
};
classes[this.props.customClasses.query] = this.props.customClasses.query;
var classList = classNames(classes, {
"padding-for-clear-all" : this.props.isAllowClearAll,
"disabled": this.props.disabled
});
return (
<div
className={`filter-tokenizer ${this.props.isAllowClearAll ? "padding-for-clear-all" : ""} ${this.props.disabled ? "disabled" : ""}`}
className={classList}
ref={node => {
this.node = node;
}}
>
<div className="token-collection" onClick={this.onClickOfDivFocusInput}>
{this._renderTokens()}
<div className="filter-input-group">
<div className="filter-category">{this.state.category} </div>
<div className="filter-operator">{this.state.operator} </div>

{this._getTypeahed({ classList })}
</div>
{ this._renderTokens()}
{ this.state.ediTableTokenId === null && <div className="filter-input-group">
<div className="filter-conditional">{this.state.conditional}</div>
<div className="filter-category">{this.state.category}</div>
<div className="filter-operator">{this.state.operator}</div>
{this._getTypeahed ({show: false})}
</div>
}
</div>
{this.props.isAllowClearAll ? this._getClearAllButton() : null}
</div>
);
}
}
}
33 changes: 27 additions & 6 deletions src/lib/react-structured-filter/react-typeahead/tokenizer/token.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Component } from "react";
import React, { Component, Fragment } from "react";
import propTypes from "prop-types";

/**
@@ -29,30 +29,51 @@ export default class Token extends Component {
);
}

_makeEditButton() {
if (!this.props.onRemoveToken) {
return "";
}
return (
<a
className="typeahead-token-edit"
href="javascript:void(0)"
onClick={function(event) {
event.stopPropagation();
event.preventDefault();
this.props.onEditToken(this.props.children);
}.bind(this)}
>
&#x1F589;
</a>
);
}

getTokenValue() {
let value = this.props.children["value"];
if (value && typeof value == "object") {
return value[this.props.fuzzySearchKeyAttribute];
} else {
return value;
return value.trim();
}
}

getTokenItem() {
if (this.props.renderTokenItem) {
return this.props.renderTokenItem(this.props);
} else {
let val = this.props.children;
return `${val["category"]} ${val.operator == undefined ? "" : val.operator} "${this.getTokenValue()}" `;
let val = this.props.children,
tokenVal = (val.conditional && val.conditional.includes(')')) ? this.getTokenValue() : `"${this.getTokenValue()}"`;
return `${val.conditional == undefined ? "" : val.conditional} ${val["category"]} ${val.operator == undefined ? "" : val.operator} ${tokenVal} `;
}
}

render() {
return (
<div className="typeahead-token">
{this.getTokenItem()}
{this._makeCloseButton()}
{this.getTokenItem()}
{this._makeEditButton()}
</div>
);
}
}
}
28 changes: 21 additions & 7 deletions src/lib/react-structured-filter/react-typeahead/typeahead/index.js
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ import TypeaheadSelector from "./selector";
import KeyEvent from "../keyevent";
import fuzzy from "fuzzy";
import DatePicker from "../../react-datepicker/datepicker.js";
import classNames from "classNames";
import classNames from "classnames";

/**
* A "typeahead", an auto-completing text input
@@ -70,11 +70,17 @@ export default class Typeahead extends Component {

// A valid typeahead value
selection: null,
focused: false
focused: this.props.isElemenFocused || false
};
this.fuzzySearchKeyAttribute = this.props.fuzzySearchKeyAttribute;
}

componentDidMount () {
if (this.props.isElemenFocused) {
this.entryRef.focus();
}
}

componentWillReceiveProps(nextProps) {
this.fuzzySearchKeyAttribute = nextProps.fuzzySearchKeyAttribute || this.props.fuzzySearchKeyAttribute;
this.setState({
@@ -175,15 +181,22 @@ export default class Typeahead extends Component {
this.props.onOptionSelected(option);
}

_onTextEntryUpdated = () => {
_onTextEntryUpdated = (val) => {
var value = "";
if (this.entryRef != null) {
value = this.entryRef.value;
}
if (this.state.datatype === "custom") {
value = val;
}
this.setState({
visible: this.getOptionsForValue(value, this.state.options),
selection: null,
entryValue: value
}, () => {
if (this.state.datatype === "custom" && val !== undefined) {
this.props.onOptionSelected(value);
}
});
};

@@ -225,9 +238,11 @@ export default class Typeahead extends Component {
// If no options were provided so we can match on anything
if (this.props.options.length === 0) {
this._onOptionSelected(this.state.entryValue);
} else if (this.props.options.indexOf(this.state.entryValue) > -1 || (this.state.entryValue.trim() != "" && this.props.isAllowCustomValue)) {
} else if (this.props.options.indexOf(this.state.entryValue) > -1 || (this.state.entryValue && this.state.entryValue.trim() != "" && this.props.isAllowCustomValue)) {
// If what has been typed in is an exact match of one of the options
this._onOptionSelected(this.state.entryValue);
} else if (this.props.customQuery && this.props.bracketHasClosed()) {
this.props.updateParentInputText();
}
}

@@ -251,9 +266,8 @@ export default class Typeahead extends Component {
_onFocus = event => {
if (this.props.onElementFocused) {
this.props.onElementFocused({ focused: true });
} else {
this.setState({ focused: true });
}
this.setState({ focused: true });
};

isDescendant(parent, child) {
@@ -330,4 +344,4 @@ export default class Typeahead extends Component {
}
return this._getTypeaheadInput({ classList, inputClassList });
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { Component } from "react";
import propTypes from "prop-types";
import classNames from "classNames";
import classNames from "classnames";

/**
* A single option within the TypeaheadSelector
@@ -59,4 +59,4 @@ export default class TypeaheadOption extends Component {
</li>
);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { Component } from "react";
import propTypes from "prop-types";
import TypeaheadOption from "./option";
import classNames from "classNames";
import classNames from "classnames";

/**
* Container for the options rendered as part of the autocompletion process
32 changes: 29 additions & 3 deletions src/lib/react-structured-filter/scss/style.scss
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@
}
ul.typeahead-selector {
z-index: 100;
position: absolute;
position: fixed;
list-style: none;
margin: 0;
padding: 0;
@@ -156,10 +156,33 @@
margin: 3px;
padding: 5px;
font-weight: bold;
.typeahead-token-close,
.typeahead-token-edit {
visibility: hidden;
text-decoration: none;
}

&:hover > .typeahead-token-close,
&:hover > .typeahead-token-edit {
visibility: visible;
}
}

.filter-tokenizer.custom-query {
border: none;
margin-top: -7px;
.token-collection{
.typeahead-token {
background-color: transparent;
background-image: none;
border: none;
margin: 3px 0;
}
}
}
ul.typeahead-selector {
z-index: 100;
position: absolute;
position: fixed;
list-style: none;
margin: 0;
padding: 0;
@@ -213,6 +236,9 @@
margin: 3px;
padding: 5px;
float: left;
.filter-conditional {
@extend %filter-popup;
}
.filter-category {
@extend %filter-popup;
}
@@ -344,4 +370,4 @@
&:focus {
outline: none;
}
}
}