├── .gitignore
├── .DS_Store
├── cover.png
├── logo.png
├── .babelrc
├── webpack.config.js
├── package.json
├── README.md
└── src
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | .idea
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodHeK/qsearch/HEAD/.DS_Store
--------------------------------------------------------------------------------
/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodHeK/qsearch/HEAD/cover.png
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodHeK/qsearch/HEAD/logo.png
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env"],
3 | "plugins": [
4 | "transform-object-rest-spread",
5 | "transform-react-jsx"
6 | ]
7 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 |
2 | var path = require('path');
3 | module.exports = {
4 | entry: './src/index.js',
5 | output: {
6 | path: path.resolve(__dirname, 'build'),
7 | filename: 'index.js',
8 | libraryTarget: 'commonjs2'
9 | },
10 | module: {
11 | rules: [
12 | {
13 | test: /\.js$/,
14 | include: path.resolve(__dirname, 'src'),
15 | exclude: /(node_modules|bower_components|build)/,
16 | use: {
17 | loader: 'babel-loader',
18 | options: {
19 | presets: ['env']
20 | }
21 | }
22 | }
23 | ]
24 | },
25 | externals: {
26 | 'react': 'commonjs react'
27 | }
28 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "qsearch",
3 | "version": "1.0.4",
4 | "description": "",
5 | "main": "build/index.js",
6 | "keywords": ["search", "trie", "suggestions", "filtering", "searching"],
7 | "peerDependencies": {
8 | "react": "^16.8.6"
9 | },
10 | "scripts": {
11 | "start": "webpack --watch",
12 | "build": "webpack"
13 | },
14 | "author": {
15 | "name": "codhek"
16 | },
17 | "license": "ISC",
18 | "dependencies": {
19 | "react": "^16.8.6",
20 | "webpack": "^4.12.0"
21 | },
22 | "devDependencies": {
23 | "babel-cli": "^6.26.0",
24 | "babel-core": "^6.26.3",
25 | "babel-loader": "^7.1.4",
26 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
27 | "babel-plugin-transform-react-jsx": "^6.24.1",
28 | "babel-preset-env": "^1.7.0",
29 | "webpack-cli": "^3.0.4"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |  
4 |
5 | ### Installation
6 |
7 | ```
8 | npm install --save qsearch
9 | ```
10 |
11 | ### Demo
12 |
13 | Try out the demo [App](https://nifty-elion-04a803.netlify.com/).
14 |
15 | Link to the Demo [App repository](https://github.com/CodHeK/qsearch-demo-app).
16 |
17 | ### How To Use
18 |
19 | First import this component where you want to use it
20 |
21 | ```
22 | import Search from 'qsearch';
23 | ```
24 |
25 | Then just render it as :
26 |
27 | ```
28 |
29 | ```
30 |
31 | ### Props
32 |
33 | ```
34 | /*
35 | CONFIG PASSED AS PROPS:
36 |
37 | data: The data that needs to be searched upon.
38 | styles: Add custom styles to your search bar Component
39 | onEnter: Enable search on ENTER or on the fly!
40 | callback: mention a callback function to
41 | receive your search data
42 |
43 | */
44 | ```
45 |
46 |
47 | #### Example data :
48 | ```
49 | [
50 | {
51 | "id": 1,
52 | "name": "Leanne Graham",
53 | "username": "Bret",
54 | "email": "Sincere@april.biz",
55 | "address": {
56 | "street": "Kulas Light",
57 | "suite": "Apt. 556",
58 | "city": "Gwenborough",
59 | "zipcode": "92998-3874",
60 | "geo": {
61 | "lat": "-37.3159",
62 | "lng": "81.1496"
63 | }
64 | },
65 | "phone": "1-770-736-8031 x56442",
66 | "website": "hildegard.org",
67 | "company": {
68 | "name": "Romaguera-Crona",
69 | "catchPhrase": "Multi-layered client-server neural-net",
70 | "bs": "harness real-time e-markets"
71 | }
72 | },
73 | ]
74 |
75 | ```
76 |
77 | #### Example use :
78 |
79 | ```
80 | import React, { Component } from 'react';
81 | import Search from 'qsearch';
82 |
83 | class App extends Component {
84 | constructor(props) {
85 | super(props);
86 | this.state = {
87 | data: null,
88 | }
89 | }
90 |
91 | componentWillMount() {
92 | fetch('https://jsonplaceholder.typicode.com/users')
93 | .then(resp => resp.json())
94 | .then(data => this.setState({ data, }));
95 | }
96 |
97 | /* Specify a callback to receive the
98 | filtered entries and suggested words back from the Component */
99 |
100 | getSearchData = (data) => {
101 | const { filtered, suggested } = data;
102 | this.setState({ filtered, suggested });
103 | };
104 |
105 | render() {
106 | const { data } = this.state;
107 |
108 | const SearchBarStyles = {
109 | width: '300px',
110 | height: '50px',
111 | margin: '2%',
112 | borderRadius: '10px',
113 | paddingLeft: '5px'
114 | };
115 |
116 | const config = {
117 | data: data,
118 | styles: SearchBarStyles,
119 | onEnter: true,
120 | callback: this.getSearchData
121 | };
122 |
123 | return (
124 | data &&
125 | );
126 | }
127 | }
128 |
129 | export default App;
130 | ```
131 | ### Also ...
132 |
133 | If you find any bugs/edge-cases not taken care of :see_no_evil:, feel free to open an [issue](https://github.com/CodHeK/qsearch/issues). :smiley:
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Search = props => {
4 | let { data, styles, onEnter, callback } = props.config;
5 |
6 | let Trie = null;
7 |
8 | const check = (obj) => {
9 | for(const [_, v] of Object.entries(obj)) {
10 | if(JSON.stringify(v)[0] === '{')
11 | return false;
12 | }
13 | return true;
14 | };
15 |
16 | const add = (v, index) => {
17 | let words = String(v).replace(/[^a-z0-9]/gi,' ').split(" ");
18 | for(const word of words) {
19 | if(word.length > 0) {
20 | insert(Trie, word.toLowerCase(), index);
21 | }
22 | }
23 | }
24 |
25 | const dfs = (obj, index) => {
26 | if(check(obj)) {
27 | for(const [_, v] of Object.entries(obj))
28 | add(v, index);
29 | return;
30 | }
31 |
32 | for(const [_, v] of Object.entries(obj)) {
33 | if(JSON.stringify(v)[0] === '{')
34 | dfs(v, index);
35 | else
36 | add(v, index);
37 | }
38 | };
39 |
40 | const genNode = () => {
41 | let temp = {
42 | 'isLeaf': false,
43 | 'map': new Map(),
44 | 'indexes': new Map()
45 | };
46 | return temp;
47 | };
48 |
49 | const insert = (root, str, index) => {
50 | if(root === null) root = genNode();
51 |
52 | let temp = root;
53 | for(const x of str) {
54 | if(!temp.map.has(x))
55 | temp.map.set(x, genNode());
56 |
57 | temp.indexes.set(index, true);
58 | temp = temp.map.get(x);
59 | }
60 | temp.isLeaf = true;
61 | temp.indexes.set(index, true)
62 | Trie = root;
63 | };
64 |
65 | const search = (root, str) => {
66 | if(root === null) return false;
67 |
68 | let temp = root;
69 | for(const x of str) {
70 | temp = temp.map.get(x);
71 |
72 | if(!temp) return [];
73 | }
74 |
75 | return temp.indexes;
76 | };
77 |
78 | let suggested = [];
79 |
80 | const traverse = (root, str) => {
81 | // console.log(root, str, "81");
82 | if(root.isLeaf) {
83 | suggested.push(str);
84 | return;
85 | }
86 | for(const [k, v] of root.map) {
87 | traverse(v, str+String(k));
88 | }
89 | };
90 |
91 | const suggestions = (root, str) => {
92 | if(root === null) return false;
93 |
94 | let temp = root;
95 | for(let i = 0; i < str.length; i++) {
96 | temp = temp.map.get(str[i]);
97 |
98 | if(!temp) return [];
99 | }
100 |
101 | console.log(temp);
102 | if(!temp.isLeaf) {
103 | for(const [k, v] of temp.map) {
104 | traverse(v, str+String(k));
105 | }
106 | }
107 | };
108 |
109 | for(let i = 0; i < data.length; i++) {
110 | dfs(data[i], i);
111 | };
112 |
113 | const filteredData = (e, onEnter) => {
114 | let searchedVal = search(Trie, e.target.value.toLowerCase());
115 | let filtered = [];
116 | suggested = [];
117 | for (const [idx, _] of searchedVal)
118 | filtered.push(data[idx]);
119 |
120 | if (!onEnter)
121 | suggestions(Trie, e.target.value.toLowerCase(), suggested);
122 |
123 | //console.log(filtered, suggested, "120")
124 | return {
125 | filtered,
126 | suggested,
127 | };
128 | };
129 |
130 | const inputSearch = (e) => {
131 | if(e.target.value !== "")
132 | callback(filteredData(e, false));
133 | else
134 | callback({ filtered: [], suggested: [] });
135 | };
136 |
137 | const inputSearchOnEnter = (e) => {
138 | if(e.which === 13)
139 | if(e.target.value !== "")
140 | callback(filteredData(e, true));
141 | else
142 | callback({ filtered: [], suggested: [] });
143 |
144 | };
145 |
146 | let SearchBar;
147 | if(onEnter === false)
148 | SearchBar = inputSearch(e) } />;
150 | else
151 | SearchBar = inputSearchOnEnter(e) } />;
153 |
154 | return (
155 |
156 | {SearchBar}
157 |
158 | );
159 | };
160 |
161 | export default Search;
162 |
--------------------------------------------------------------------------------