├── .gitignore
├── LICENSE.txt
├── README.md
├── demo
├── demo.mp4
├── index.html
├── scripts.js
└── styles.css
├── octopalm.js
└── octopalm.ladybug.js
/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eddiegulay/octopalm/f9b9ad8ecefc738c39ebb8d6fac830302fb1c9c8/.gitignore
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Eddie Gulay
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eddiegulay/octopalm/f9b9ad8ecefc738c39ebb8d6fac830302fb1c9c8/README.md
--------------------------------------------------------------------------------
/demo/demo.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eddiegulay/octopalm/f9b9ad8ecefc738c39ebb8d6fac830302fb1c9c8/demo/demo.mp4
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Real-Time Search
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/demo/scripts.js:
--------------------------------------------------------------------------------
1 | // scripts.js
2 |
3 | // Sample data
4 | const items = [
5 | // Food
6 | { label: "Apple", link: "/food/apple", emoji: "🍎" },
7 | { label: "Banana", link: "/food/banana", emoji: "🍌" },
8 | { label: "Orange Juice", link: "/food/orange-juice", emoji: "🍊" },
9 | { label: "Burger", link: "/food/burger", emoji: "🍔" },
10 | { label: "Pizza", link: "/food/pizza", emoji: "🍕" },
11 | { label: "Coffee", link: "/food/coffee", emoji: "☕" },
12 | { label: "Hotdog", link: "/food/hotdog", emoji: "🌭" },
13 | { label: "Ice Cream", link: "/food/ice-cream", emoji: "🍦" },
14 | { label: "Pasta", link: "/food/pasta", emoji: "🍝" },
15 | { label: "Sushi", link: "/food/sushi", emoji: "🍣" },
16 |
17 | // Clothes
18 | { label: "T-Shirt", link: "/clothes/t-shirt", emoji: "👕" },
19 | { label: "Jeans", link: "/clothes/jeans", emoji: "👖" },
20 | { label: "Jacket", link: "/clothes/jacket", emoji: "🧥" },
21 | { label: "Sneakers", link: "/clothes/sneakers", emoji: "👟" },
22 | { label: "Dress", link: "/clothes/dress", emoji: "👗" },
23 | { label: "Sweater", link: "/clothes/sweater", emoji: "🧣" },
24 |
25 | // Vehicles
26 | { label: "Car", link: "/vehicles/car", emoji: "🚗" },
27 | { label: "Motorcycle", link: "/vehicles/motorcycle", emoji: "🏍️" },
28 | { label: "Bicycle", link: "/vehicles/bicycle", emoji: "🚲" },
29 | { label: "Truck", link: "/vehicles/truck", emoji: "🚚" },
30 |
31 | // Health
32 | { label: "Vitamins", link: "/health/vitamins", emoji: "💊" },
33 | { label: "First Aid Kit", link: "/health/first-aid-kit", emoji: "🩹" },
34 | { label: "Yoga Mat", link: "/health/yoga-mat", emoji: "🧘♀️" },
35 | { label: "Dumbbells", link: "/health/dumbbells", emoji: "🏋️♂️" },
36 | { label: "Thermometer", link: "/health/thermometer", emoji: "🌡️" }
37 | ];
38 |
39 |
40 | new OctoPalm('search-input', items);
--------------------------------------------------------------------------------
/demo/styles.css:
--------------------------------------------------------------------------------
1 | /* styles.css */
2 | body {
3 | font-family: Arial, sans-serif;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | .search-container {
9 | position: relative;
10 | width: 300px;
11 | margin: 50px auto;
12 | }
13 |
14 | #search-input {
15 | width: 100%;
16 | padding: 8px;
17 | box-sizing: border-box;
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/octopalm.js:
--------------------------------------------------------------------------------
1 | class OctoPalm {
2 | constructor(inputId, items) {
3 | this.inputElement = document.getElementById(inputId);
4 | this.items = items;
5 | this.resultsContainer = this.createResultsContainer();
6 | this.injectStyles();
7 |
8 | this.initialize();
9 | }
10 |
11 | initialize() {
12 | if (!this.inputElement) {
13 | console.error(`Input element with id ${this.inputElement.id} not found.`);
14 | return;
15 | }
16 |
17 | this.inputElement.addEventListener('input', () => this.performSearch());
18 | }
19 |
20 | createResultsContainer() {
21 | const container = document.createElement('div');
22 | container.className = 'opalm-search-results';
23 | this.inputElement.parentElement.appendChild(container);
24 | return container;
25 | }
26 |
27 | injectStyles() {
28 | const style = document.createElement('style');
29 | style.type = 'text/css';
30 | style.textContent = `
31 | .opalm-search-results {
32 | position: absolute;
33 | top: 100%;
34 | left: 0;
35 | width: 100%;
36 | border: 1px solid #ccc;
37 | background: #fff;
38 | max-height: 320px;
39 | overflow-y: auto;
40 | z-index: 1000;
41 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
42 | border-radius: 5px;
43 | transition: opacity 0.3s ease, transform 0.3s ease;
44 | opacity: 0;
45 | transform: scaleY(0);
46 | transform-origin: top;
47 | scrollbar-width: thin;
48 | scrollbar-color: #000 #fff;
49 | }
50 | .opalm-search-results.show {
51 | opacity: 1;
52 | transform: scaleY(1);
53 | }
54 | .opalm-search-result-item {
55 | padding: 12px 16px;
56 | border-bottom: 1px solid #eee;
57 | display: flex;
58 | align-items: center;
59 | transition: background-color 0.3s ease, transform 0.3s ease;
60 | cursor: pointer;
61 | }
62 | .opalm-search-result-item:hover {
63 | background-color: #f0f0f0;
64 | transform: translateX(5px);
65 | }
66 | .opalm-search-result-item a {
67 | text-decoration: none;
68 | color: #333;
69 | font-weight: 600;
70 | }
71 | .opalm-search-result-item a:hover {
72 | text-decoration: underline;
73 | }
74 |
75 | .opalm-search-results::-webkit-scrollbar {
76 | width: 8px;
77 | }
78 | .opalm-search-results::-webkit-scrollbar-track {
79 | background: #fff;
80 | }
81 | .opalm-search-results::-webkit-scrollbar-thumb {
82 | background: #333;
83 | border-radius: 10px;
84 | }
85 | .opalm-search-results::-webkit-scrollbar-thumb:hover {
86 | background: #000;
87 | }
88 | `;
89 | document.head.appendChild(style);
90 | }
91 |
92 | performSearch() {
93 | const query = this.inputElement.value.toLowerCase();
94 | this.resultsContainer.innerHTML = '';
95 |
96 | if (query.length === 0) {
97 | this.resultsContainer.classList.remove('show');
98 | return;
99 | }
100 |
101 | const filteredItems = this.items.filter(item =>
102 | item.itemName.toLowerCase().includes(query)
103 | );
104 |
105 | filteredItems.forEach(item => {
106 | const resultItem = document.createElement('div');
107 | resultItem.className = 'opalm-search-result-item';
108 | resultItem.innerHTML = `${item.itemName} `;
109 | this.resultsContainer.appendChild(resultItem);
110 | });
111 |
112 | this.resultsContainer.classList.add('show');
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/octopalm.ladybug.js:
--------------------------------------------------------------------------------
1 | class OctoPalm {
2 | constructor(inputId, items) {
3 | this.inputElement = document.getElementById(inputId);
4 | this.items = items;
5 | this.itemsByKey = {};
6 | this.resultsContainer = this.createResultsContainer();
7 | this.injectStyles();
8 | this.initialize();
9 | }
10 |
11 | initialize() {
12 | if (!this.inputElement) {
13 | console.error(`Input element with id ${this.inputElement.id} not found.`);
14 | return;
15 | }
16 | this.organizeItemsByKey();
17 | this.inputElement.addEventListener('input', () => this.performSearch());
18 | }
19 |
20 | organizeItemsByKey() {
21 | this.items
22 | .filter(item => item && typeof item === 'object')
23 | .forEach(item => {
24 | Object.keys(item).forEach(key => {
25 | if (!this.itemsByKey[key]) {
26 | this.itemsByKey[key] = [];
27 | }
28 | this.itemsByKey[key].push(item);
29 | });
30 | });
31 | }
32 |
33 | createResultsContainer() {
34 | const container = document.createElement('div');
35 | container.className = 'opalm-search-results';
36 | this.inputElement.parentElement.appendChild(container);
37 | return container;
38 | }
39 |
40 | injectStyles() {
41 | const style = document.createElement('style');
42 | style.type = 'text/css';
43 | style.textContent = `
44 | .opalm-search-results {
45 | position: absolute;
46 | top: 100%;
47 | left: 0;
48 | width: 100%;
49 | border: 1px solid #ccc;
50 | background: #fff;
51 | max-height: 320px;
52 | overflow-y: auto;
53 | z-index: 1000;
54 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
55 | border-radius: 5px;
56 | transition: opacity 0.3s ease, transform 0.3s ease;
57 | opacity: 0;
58 | transform: scaleY(0);
59 | transform-origin: top;
60 | scrollbar-width: thin;
61 | scrollbar-color: #000 #fff;
62 | }
63 | .opalm-search-results.show {
64 | opacity: 1;
65 | transform: scaleY(1);
66 | }
67 | .opalm-search-result-item {
68 | padding: 12px 16px;
69 | border-bottom: 1px solid #eee;
70 | display: flex;
71 | align-items: center;
72 | transition: background-color 0.3s ease, transform 0.3s ease;
73 | cursor: pointer;
74 | }
75 | .opalm-search-result-item:hover {
76 | background-color: #f0f0f0;
77 | transform: translateX(5px);
78 | }
79 | .opalm-search-result-item a {
80 | text-decoration: none;
81 | color: #333;
82 | font-weight: 600;
83 | }
84 | .opalm-search-result-item a:hover {
85 | text-decoration: underline;
86 | }
87 |
88 | .opalm-search-results::-webkit-scrollbar {
89 | width: 8px;
90 | }
91 | .opalm-search-results::-webkit-scrollbar-track {
92 | background: #fff;
93 | }
94 | .opalm-search-results::-webkit-scrollbar-thumb {
95 | background: #333;
96 | border-radius: 10px;
97 | }
98 | .opalm-search-results::-webkit-scrollbar-thumb:hover {
99 | background: #000;
100 | }
101 | `;
102 | document.head.appendChild(style);
103 | }
104 |
105 | performSearch() {
106 | const query = this.inputElement.value.toLowerCase();
107 | this.resultsContainer.innerHTML = '';
108 |
109 | if (query.length === 0) {
110 | this.resultsContainer.classList.remove('show');
111 | return;
112 | }
113 |
114 | const filteredResults = this.getFilteredResults(query);
115 | const uniqueResults = this.removeDuplicates(filteredResults);
116 | this.displayResults(uniqueResults);
117 | }
118 |
119 | getFilteredResults(query) {
120 | const filteredResults = [];
121 | for (let key in this.itemsByKey) {
122 | const filteredItems = this.itemsByKey[key].filter(item => {
123 | const value = item[key];
124 | return value && typeof value === 'string' && value.toLowerCase().includes(query);
125 | });
126 | filteredResults.push(...filteredItems);
127 | }
128 | return filteredResults;
129 | }
130 |
131 | removeDuplicates(items) {
132 | const uniqueItems = [];
133 | const seen = new Set();
134 |
135 | items.forEach(item => {
136 | const identifier = item.itemName || item.name || item.label || item.item || 'Unknown Item';
137 | if (!seen.has(identifier)) {
138 | seen.add(identifier);
139 | uniqueItems.push(item);
140 | }
141 | });
142 |
143 | return uniqueItems;
144 | }
145 |
146 | displayResults(filteredResults) {
147 | filteredResults.forEach(item => {
148 | const resultItem = document.createElement('div');
149 | resultItem.className = 'opalm-search-result-item';
150 | let itemName = item.itemName || item.name || item.label || item.item || 'Unknown Item';
151 |
152 | try {
153 | if (item.emoji) {
154 | itemName = item.emoji + " " + itemName;
155 | }
156 | } catch (error) {}
157 |
158 | resultItem.innerHTML = `${itemName} `;
159 | this.resultsContainer.appendChild(resultItem);
160 | });
161 |
162 | if (filteredResults.length > 0) {
163 | this.resultsContainer.classList.add('show');
164 | } else {
165 | this.resultsContainer.classList.remove('show');
166 | }
167 | }
168 | }
169 |
--------------------------------------------------------------------------------