09. Search Functionality
Search Functionality
Searching is a very common feature with almost all the applications. For example, Amazon, Netflix, and many more. Even the browser we use has search functionality. In this step we will add the search functionality to our application. In large scale application search involves a lot of complex logic. Like making request to multiple servers, filtering the results, etc. We will implement search on the dashboard of each type of user and the search functionality will only help to search the products.
Let's update the dashboard of each type of users.
Search
Admin Dashboard
js filename=static/views/AdminApp.js
const AdminApp = {
name: "AdminApp",
template: `
<div>
<nav class="navbar navbar-expand-lg navbar-dark bg-success">
<div class="container">
<a class="navbar-brand" href="#">Eat Fresh</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
...
<!-- Search Bar -->
<form class="d-flex ms-auto" @submit.prevent="search">
<input class="form-control me-2" type="search" v-model="query" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-light" type="submit">Search</button>
</form>
</div>
</div>
</nav>
</div>
`,
data() {
return {
...
query: "",
};
},
methods: {
...
async search() {
try {
const response = await fetch("http://127.0.0.1:5000/search/for", {
method: "POST",
headers: {
"Content-type": "application/json",
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
body: JSON.stringify({
query: this.query,
}),
});
if (response.status === 200) {
const data = await response.json();
console.log(data.cat, "searched for category");
this.$store.commit("setCategories", data.cat);
this.$store.commit("setProducts", data.pro);
} else {
const data = await response.json();
alert(data.message);
}
} catch (error) {
console.error(error);
}
}
},
...
};
export default AdminApp;
Manager Dashboard
js filename=static/views/ManagerApp.js
const ManagerApp = {
name: "ManagerApp",
template: `
<div>
<nav class="navbar navbar-expand-lg navbar-dark bg-success">
<div class="container">
<a class="navbar-brand" href="#">Eat Fresh</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
...
<!-- Search Bar -->
<form class="d-flex ms-auto" @submit.prevent="search">
<input class="form-control me-2" type="search" v-model="query" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-light" type="submit">Search</button>
</form>
</div>
</div>
</nav>
</div>
`,
data() {
return {
...
query: "",
};
},
methods: {
...
async search() {
try {
const response = await fetch("http://127.0.0.1:5000/search/for", {
method: "POST",
headers: {
"Content-type": "application/json",
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
body: JSON.stringify({
query: this.query,
}),
});
if (response.status === 200) {
const data = await response.json();
console.log(data.cat, "searched for category");
this.$store.commit("setCategories", data.cat);
this.$store.commit("setProducts", data.pro);
} else {
const data = await response.json();
alert(data.message);
}
} catch (error) {
console.error(error);
}
}
},
...
};
export default ManagerApp;
User Dashboard
js filename=static/views/UserApp.js
const UserApp = {
name: "UserApp",
template: `
<div>
<nav class="navbar navbar-expand-lg navbar-dark bg-success">
<div class="container">
<a class="navbar-brand" href="#">Eat Fresh</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
...
<!-- Search Bar -->
<form class="d-flex ms-auto" @submit.prevent="search">
<input class="form-control me-2" type="search" v-model="query" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-light" type="submit">Search</button>
</form>
</div>
</div>
</nav>
</div>
`,
data() {
return {
...
query: "",
};
},
methods: {
...
async search() {
try {
const response = await fetch("http://127.0.0.1:5000/search/for", {
method: "POST",
headers: {
"Content-type": "application/json",
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
body: JSON.stringify({
query: this.query,
}),
});
if (response.status === 200) {
const data = await response.json();
console.log(data.cat, "searched for category");
this.$store.commit("setCategories", data.cat);
this.$store.commit("setProducts", data.pro);
} else {
const data = await response.json();
alert(data.message);
}
} catch (error) {
console.error(error);
}
}
},
...
};
export default UserApp;
Here what we are doing is that we are just making a call to the backend and getting the filtered results for both, categories and products and storing them in the state. And I have already mentioned that this vuex store is reactive. So whenever we make a change in the state the vue app will automatically update. Thus we can see the filtered results in the UI.
Backend Implementation
py filename=app.py
...
@app.route('/search/for', methods=['POST'])
def search():
# Get the search query parameters from the request
data = request.get_json()
query = data.get('query')
# Search for products and categories based on the query
products = Product.query.filter(or_(
Product.name.ilike(f'%{query}%'),
Product.rpu.ilike(f'%{query}%'),
Product.manufacture.ilike(f'%{query}%'),
Product.description.ilike(f'%{query}%'),
Product.expiry.ilike(f'%{query}%'),
Product.category.has(Category.name.ilike(f'%{query}%'))
)).all()
product_list = []
categories = []
for new_product in products:
prod_data = {
'id': new_product.id,
'quantity': new_product.quantity,
'name': new_product.name,
'manufacture': new_product.manufacture,
'expiry': new_product.expiry,
'description': new_product.description,
'rpu': new_product.rpu,
'unit': new_product.unit,
# Assuming image is stored as a base64-encoded string
'image': base64.b64encode(new_product.image).decode('utf-8')
}
if new_product.category not in categories:
categories.append(new_product.category)
product_list.append(prod_data)
categories_list = []
for category in categories:
cat = {
'id': category.id,
'name': category.name,
}
categories_list.append(cat)
return jsonify({"cat": categories_list, 'pro': product_list}), 200
Note We are using
or_
function from SQLAlchemy to combine multiple queries.ilike
function is used to perform case-insensitive search. It will match based on each character of the query string, so if the query string is "apple", it will match with "app", "ap", "al", etc.
Filter by Category
We will also add filter by category feature on the category sidebar of each type of users` dashboard.
Admin Dashboard
js filename=static/views/AdminApp.js
const AdminApp = {
name: "AdminApp",
template: `
<div>
...
<main>
<div class="sidebar bg-black">
...
<ul class="list-group">
<li class="list-group-item" v-for="category in this.$store.state.categories" :key="category.id">
<input @click="searchByCat(category.name, category.id)" class="form-check-input me-1 pointer-on-hover" type="radio" :name="category.name" :value="category.id" :id="category.id" :checked="checkedValue === category.id">
<label class="form-check-label" :for="category.id"> {{ category.name }} </label>
<a class="pointer-on-hover" @click="editCat(category.id)">edit</a>
</li>
</ul>
</div>
<router-view></router-view>
</main>
</div>
`,
data() {
return {
...
checkedValue: -1, // Replace this with the actual count of items in your shopping cart
};
},
methods: {
...
async searchByCat(catName, catId) {
this.checkedValue = catId;
try {
const response = await fetch(
"http://127.0.0.1:5000/search/by/catgory",
{
method: "POST",
headers: {
"Content-type": "application/json",
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
body: JSON.stringify({
query: catName,
}),
}
);
if (response.status === 200) {
const data = await response.json();
this.$store.commit("setProducts", data.pro);
console.log(data.resource);
} else {
const data = await response.json();
alert(data.message);
}
} catch (error) {
console.error(error);
}
},
},
...
};
export default AdminApp;
Manager Dashboard
js filename=static/views/ManagerApp.js
const ManagerApp = {
name: "ManagerApp",
template: `
<div>
...
<main>
<div class="sidebar bg-black">
...
<ul class="list-group">
<li class="list-group-item" v-for="category in this.$store.state.categories" :key="category.id">
<input @click="searchByCat(category.name, category.id)" class="form-check-input me-1 pointer-on-hover" type="radio" :name="category.name" :value="category.id" :id="category.id" :checked="checkedValue === category.id">
<label class="form-check-label" :for="category.id"> {{ category.name }} </label>
<a class="pointer-on-hover" @click="editCat(category.id)">edit</a>
</li>
</ul>
</div>
<router-view></router-view>
</main>
</div>
`,
data() {
return {
...
checkedValue: -1, // Replace this with the actual count of items in your shopping cart
};
},
methods: {
...
async searchByCat(catName, catId) {
this.checkedValue = catId;
try {
const response = await fetch(
"http://127.0.0.1:5000/search/by/catgory",
{
method: "POST",
headers: {
"Content-type": "application/json",
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
body: JSON.stringify({
query: catName,
}),
}
);
if (response.status === 200) {
const data = await response.json();
this.$store.commit("setProducts", data.pro);
console.log(data.resource);
} else {
const data = await response.json();
alert(data.message);
}
} catch (error) {
console.error(error);
}
},
},
...
};
export default ManagerApp;
User Dashboard
js filename=static/views/UserApp.js
const UserApp = {
name: "UserApp",
template: `
<div>
...
<main>
<div class="sidebar bg-black">
...
<ul class="list-group">
<li class="list-group-item" v-for="category in this.$store.state.categories" :key="category.id">
<input @click="searchByCat(category.name, category.id)" class="form-check-input me-1 pointer-on-hover" type="radio" :name="category.name" :value="category.id" :id="category.id" :checked="checkedValue === category.id">
<label class="form-check-label" :for="category.id"> {{ category.name }} </label>
<a class="pointer-on-hover" @click="editCat(category.id)">edit</a>
</li>
</ul>
</div>
<router-view></router-view>
</main>
</div>
`,
data() {
return {
...
checkedValue: -1, // Replace this with the actual count of items in your shopping cart
};
},
methods: {
...
async searchByCat(catName, catId) {
this.checkedValue = catId;
try {
const response = await fetch(
"http://127.0.0.1:5000/search/by/catgory",
{
method: "POST",
headers: {
"Content-type": "application/json",
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
body: JSON.stringify({
query: catName,
}),
}
);
if (response.status === 200) {
const data = await response.json();
this.$store.commit("setProducts", data.pro);
console.log(data.resource);
} else {
const data = await response.json();
alert(data.message);
}
} catch (error) {
console.error(error);
}
},
},
...
};
export default UserApp;
This search by category will filter the products based on the selected category.
Testing it out
Now we can start the app and test all the feature implemented on this page.
Run the app.
python3 app.py
Commit the changes to the local git.
git add .
git commit -m "added admin features"