Skip to content

Commit ba02f4e

Browse files
authored
Feat: add rest api support (#6)
1 parent 38ac483 commit ba02f4e

File tree

7 files changed

+669
-443
lines changed

7 files changed

+669
-443
lines changed

Diff for: README.md

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<p align="center"><a href="https://canopas.com/contact"><img src="./assets/banner.png"></a></p>
22

3-
<h1><strong>Tagsinput plugin for strapi</strong></h1>
3+
<h1><strong>Tagsinput plugin for strapi with suggestions</strong></h1>
44

55
This plugin is used to add tagsinput in your strapi admin panel.
66
Read more about it at [tagsinput guidence](https://blog.canopas.com/the-simple-guidance-how-to-add-tagsinput-customfield-plugin-in-strapi-b5d2b5af7c3b).
@@ -21,6 +21,30 @@ Using yarn,
2121
yarn add strapi-plugin-tagsinput
2222
```
2323

24+
## How to use
25+
26+
After installation, you can add tagsinput as custom field.
27+
28+
#### Suggestions for tag
29+
30+
While adding tagsInput, you will see `API URL` field.
31+
32+
If you want to use REST API for suggestions, then add your API url in this field.
33+
34+
**Notes:**
35+
36+
- If API domain is different, then full API URL is required. i.e `http://localhost:1337/api/v1/tags?fields[0]=name` (Make sure API CORS are enabled for your strapi domain in this case).
37+
- Otherwise add only path of API i.e `/api/v1/tags?fields[0]=name`
38+
- API response should contain `name` field.
39+
For example,
40+
41+
```
42+
[
43+
{ name: "tag1" },
44+
{ name: "tag2" }
45+
]
46+
```
47+
2448
## Showcase
2549

2650
How to use tagsinput?

Diff for: admin/src/components/Input/index.js

+95-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import TagsInput from "react-tagsinput";
2-
import React, { useState } from "react";
2+
import Autosuggest from "react-autosuggest";
3+
import React, { useState, useRef, useEffect } from "react";
34
import PropTypes from "prop-types";
45
import "../styles/global.css";
6+
import axios from "axios";
57

68
import {
79
Flex,
@@ -32,6 +34,30 @@ const Tags = ({
3234
return [];
3335
}
3436
});
37+
const [suggestions, setSuggestions] = useState([]);
38+
let inputEle = useRef(null);
39+
40+
useEffect(() => {
41+
document.getElementsByClassName(
42+
"react-autosuggest__suggestions-container"
43+
)[0].style.top = inputEle.current.offsetHeight + 5 + "px";
44+
45+
function handleClickOutside(event) {
46+
if (inputEle.current && !inputEle.current.contains(event.target)) {
47+
document
48+
.getElementsByClassName("react-tagsinput")[0]
49+
.classList.remove("react-tagsinput--focused");
50+
} else {
51+
document
52+
.getElementsByClassName("react-tagsinput")[0]
53+
.classList.add("react-tagsinput--focused");
54+
}
55+
}
56+
document.addEventListener("mousedown", handleClickOutside);
57+
return () => {
58+
document.removeEventListener("mousedown", handleClickOutside);
59+
};
60+
});
3561

3662
const handleTagsChange = (tags) => {
3763
setTags(tags);
@@ -44,6 +70,59 @@ const Tags = ({
4470
});
4571
};
4672

73+
const getSuggestions = async () => {
74+
if (!attribute.options || !attribute.options["apiUrl"]) {
75+
return [];
76+
}
77+
try {
78+
const res = await axios.get(attribute.options["apiUrl"]);
79+
setSuggestions(res.data);
80+
} catch (err) {
81+
console.log(err);
82+
}
83+
};
84+
85+
const autocompleteRenderInput = (props) => {
86+
const handleOnChange = (e, { newValue, method }) => {
87+
if (method === "enter") {
88+
e.preventDefault();
89+
} else {
90+
props.onChange(e);
91+
}
92+
};
93+
94+
const inputValue = (props.value && props.value.trim().toLowerCase()) || "";
95+
const inputLength = inputValue.length;
96+
97+
let s = suggestions;
98+
99+
if (suggestions.length <= 0) {
100+
getSuggestions();
101+
}
102+
103+
if (inputLength > 0) {
104+
s = suggestions.filter((state) => {
105+
return state.name.toLowerCase().slice(0, inputLength) === inputValue;
106+
});
107+
}
108+
109+
return (
110+
<Autosuggest
111+
ref={props.ref}
112+
suggestions={s}
113+
shouldRenderSuggestions={(value) => value && value.trim().length > 0}
114+
getSuggestionValue={(s) => s.name}
115+
renderSuggestion={(s) => <span>{s.name}</span>}
116+
inputProps={{ ...props, onChange: handleOnChange }}
117+
onSuggestionSelected={(e, { suggestion }) => {
118+
props.addTag(suggestion.name);
119+
}}
120+
onSuggestionsClearRequested={() => this.setTags([])}
121+
onSuggestionsFetchRequested={() => {}}
122+
/>
123+
);
124+
};
125+
47126
return (
48127
<Field
49128
name={name}
@@ -52,10 +131,22 @@ const Tags = ({
52131
error={error}
53132
hint={description && formatMessage(description)}
54133
required={required}>
55-
<Flex direction="column" alignItems="stretch" gap={1}>
134+
<Flex
135+
direction="column"
136+
alignItems="stretch"
137+
gap={1}
138+
style={{
139+
position: `relative`,
140+
}}
141+
ref={inputEle}>
56142
<FieldLabel action={labelAction}>{formatMessage(intlLabel)}</FieldLabel>
57-
<Flex>
58-
<TagsInput value={tags} onChange={handleTagsChange} />
143+
<Flex direction="column">
144+
<TagsInput
145+
value={tags}
146+
onChange={handleTagsChange}
147+
onlyUnique={true}
148+
renderInput={autocompleteRenderInput}
149+
/>
59150
</Flex>
60151
<FieldHint />
61152
<FieldError />

Diff for: admin/src/components/styles/global.css

+36-1
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,40 @@
5151
margin-top: 1px;
5252
outline: none;
5353
padding: 5px;
54-
width: 80px;
54+
width: 100%;
55+
}
56+
57+
.react-tagsinput > span {
58+
display: flex;
59+
flex-flow: wrap;
60+
}
61+
62+
.react-autosuggest__container {
63+
display: flex;
64+
flex-direction: column;
65+
flex: auto;
66+
}
67+
68+
.react-autosuggest__suggestions-container {
69+
position: absolute;
70+
z-index: 200;
71+
width: 280px;
72+
margin: 0;
73+
padding: 0;
74+
list-style-type: none;
75+
background-color: #fff;
76+
}
77+
78+
.react-autosuggest__suggestions-container--open {
79+
border: 1px solid #aaa;
80+
}
81+
82+
.react-autosuggest__suggestion {
83+
cursor: pointer;
84+
padding: 10px 20px;
85+
}
86+
87+
.react-autosuggest__suggestion--highlighted,
88+
.react-autosuggest__suggestion--focused {
89+
background-color: #ccc;
5590
}

Diff for: admin/src/index.js

+22
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,28 @@ export default {
2020
/* webpackChunkName: "input-component" */ "./components/Input"
2121
),
2222
},
23+
options: {
24+
base: [
25+
{
26+
sectionTitle: {
27+
id: "tagsinput.tags.section.apiUrl",
28+
defaultMessage: "API Url",
29+
},
30+
items: [
31+
{
32+
intlLabel: {
33+
id: "tagsinput.tags.section.apiUrl",
34+
defaultMessage: "Rest API URL for suggestions",
35+
},
36+
name: "options.apiUrl",
37+
type: "text",
38+
value: "",
39+
options: [],
40+
},
41+
],
42+
},
43+
],
44+
},
2345
});
2446
},
2547
};

Diff for: assets/showcase.gif

950 KB
Loading

Diff for: package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "strapi-plugin-tagsinput",
3-
"version": "1.0.3",
3+
"version": "1.0.4",
44
"description": "Tagsinput plugin for your strapi project",
55
"strapi": {
66
"name": "tagsinput",
@@ -18,12 +18,14 @@
1818
"@strapi/helper-plugin": "^4.20.2",
1919
"@strapi/icons": "^1.14.1",
2020
"prop-types": "^15.8.1",
21+
"react-autosuggest": "^10.1.0",
2122
"react-tagsinput": "^3.20.3"
2223
},
2324
"devDependencies": {
2425
"@babel/cli": "^7.23.9",
2526
"@babel/core": "^7.23.9",
2627
"@babel/preset-env": "^7.23.9",
28+
"axios": "^1.6.7",
2729
"react": "^18.2.0",
2830
"react-dom": "^18.2.0",
2931
"react-router-dom": "^5.3.4",

0 commit comments

Comments
 (0)