In this post of the series “building real-estate app” we will continue the remaining parts of the search functionality.
In the previous part we processed the search functionality and added the related composables to bind and submit the search form. In this part we will add the search Api in backend and then start displaying search results.
Search Api
Create a new controller for search named SearchController:
app/Http/Controllers/SearchPropertyController.php
<?php namespace App\Http\Controllers; use App\Models\Property; use Illuminate\Http\Request; class SearchPropertyController extends Controller { public function index(Request $request) { $query = Property::with("pictures"); $this->setFilters($request, $query); $this->setOrdering($request, $query); $properties = $query->paginate($request->per_page ?: 20); return response()->json(['properties' => $properties]); } private function setFilters(Request $request, $query) { if($request->keyword) { $query->where("title", 'LIKE','%'.urldecode($request->keyword).'%'); } if($request->country) { $query->where("country", $request->country); } if($request->state) { $query->where("state", $request->state); } if($request->city) { $query->where("city", $request->city); } if($request->ids) { $query->whereIn("id", explode(",", $request->ids)); } $this->setStatusFilter($request, $query); $this->setFeaturesFilters($request, $query); } private function setStatusFilter(Request $request, $query) { if($request->status) { $query->where("status", $request->status); if($request->status === 'Sale' && $request->price) { $priceSplitted = explode(",", $request->price); if($priceSplitted[0] && $priceSplitted[1]) { $query->where(fn($q) => $q->where('price', '>=', $priceSplitted[0])->where('price', '<=', $priceSplitted[1])); } else if($priceSplitted[0] && !$priceSplitted[1]) { $query->where('price', '>=', $priceSplitted[0]); } else if(!$priceSplitted[0] && $priceSplitted[1]) { $query->where('price', '<=', $priceSplitted[1]); } } else if($request->status === 'Rent' && $request->rent) { $rentSplitted = explode(",", $request->rent); if($rentSplitted[0] && $rentSplitted[1]) { $query->where(fn($q) => $q->where('price', '>=', $rentSplitted[0])->where('price', '<=', $rentSplitted[1])); } else if($rentSplitted[0] && !$rentSplitted[1]) { $query->where('price', '>=', $rentSplitted[0]); } else if(!$rentSplitted[0] && $rentSplitted[1]) { $query->where('price', '<=', $rentSplitted[1]); } if($request->rent_per) { $query->where('rent_amount_per', $request->rent_per); } } } } private function setFeaturesFilters(Request $request, $query) { if($request->property_finalizing) { $query->where("property_finalizing", urldecode($request->property_finalizing)); } if($request->features) { $query->whereHas('features', fn($q) => $q->whereIn('feature_id', explode(",", $request->features))); } $rangeFields = ["bathrooms", "bedrooms", "area", "rooms", "garages", "units", "floor_number", "year_built"]; foreach ($rangeFields as $rangeField) { if($request->{$rangeField}) { $rangeVal = explode(",", $request->{$rangeField}); if($rangeVal[0] && $rangeVal[1]) { $query->where(fn($q) => $q->where($rangeField, '>=', $rangeVal[0])->where($rangeField, '<=', $rangeVal[1])); } else if($rangeVal[0] && !$rangeVal[1]) { $query->where($rangeField, '>=', $rangeVal[0]); } else if(!$rangeVal[0] && $rangeVal[1]) { $query->where($rangeField, '<=', $rangeVal[1]); } } } } private function setOrdering($request, $query) { if($request->sorting_field) { $query->orderBy($request->sorting_field, $request->sorting_order); } else { $query->orderBy("id", "desc"); } } }
The index() method does the search functionality. First i obtained an instance of Property, then i create two helper private methods, setFilters() and setOrdering() which set the search filters by checking for the incoming parameters, and sorting respectively. Finally i call the paginate() laravel method to return a paginated collection or results.
Add the route for this Api in routes/api.php
Route::get('/search', [\App\Http\Controllers\SearchPropertyController::class, 'index']);
Now let’s start display the search results.
Displaying Search Results
The displayed properties located in Properties.vue component. So open this page and update with this code:
resources/js/components/pages/Properties.vue
<template> <div class="page-head"> <div class="container"> <div class="row"> <div class="page-head-content"> <h1 class="page-title">Search Properties</h1> </div> </div> </div> </div> <div class="properties-area recent-property" style="background-color: #FFF;"> <div class="container"> <div class="row"> <SearchSidebar /> <div class="col-md-9 pr0 padding-top-40 properties-page"> <div class="col-md-12 clear"> <SortingBar :per_page="paginateParams.per_page" :default_sort="sortingParams" :default_layout="defaultLayout" :show_layout="true" container_class="col-xs-10 pl0" @onSelectPerPage="handlePerPage" @onSelectSort="handleSorting" @onSelectLayout="handleLayout" /> </div> <div class="col-md-12 clear"> <div class="fa-3x" v-if="loading"> <i class="fa fa-spinner fa-spin"></i> </div> <div id="list-type" v-if="!loading && properties && properties.data && properties.data.length > 0" :class="defaultLayout === 'list' ? 'proerty-th-list':'proerty-th'"> <div class="col-sm-6 col-md-4 p0" v-for="property of properties.data" :key="property.id"> <PropertyTemplate :property="property" :show_actions="false" /> </div> </div> <div v-else class="proerty-th"> <p class="text-center" style="margin-top: 10px">No properties match your search</p> </div> </div> <div class="col-md-12" v-if="!loading && properties && properties.data && properties.data.length > 0"> <div class="pull-right"> <Pagination :item="properties" @onPaginate="handlePaginate" /> </div> </div> </div> </div> </div> </div> </template> <script> import {useSubmitSearch} from "../../composables/useSubmitSearch"; import SearchSidebar from "../partials/SearchSidebar"; import PropertyTemplate from "../partials/PropertyTemplate"; import SortingBar from "../partials/SortingBar"; import Pagination from "../partials/Pagination"; import {onMounted} from "vue"; import {useRoute} from "vue-router"; import {useStore} from "vuex"; export default { name: "Properties", components: {Pagination, SortingBar, PropertyTemplate, SearchSidebar}, setup() { const route = useRoute(); const store = useStore(); const {properties, loading, sortingParams, paginateParams, defaultLayout, handleLayout, handlePerPage, handleSorting, handlePaginate, doSearch} = useSubmitSearch(); store.dispatch('search/setSearchDefaults'); if(Object.keys(route.query).length > 0) { store.dispatch('search/bulkStoreUpdate', {...route.query}); } onMounted(() => { doSearch(); }); return { properties, loading, sortingParams, paginateParams, defaultLayout, handleLayout, handlePerPage, handleSorting, handlePaginate } } } </script>
In this component i included the <SortingBar /> partial component and passed the required props to display the sorting buttons and layout type and per page dropdown.
Also i included the spinner to be displayed when making search request.
Next i displayed the results by iterating over properties.data and including the <PropertyTemplate /> component. And finally i included the <Pagination /> component to display page links.
In the setup() hook i called useSubmitSearch() again and retrieved the necessary variables and handlers like handleSorting(), handlePerPage(), etc.
Then i dispatched the store action “search/setSearchDefaults” to reset the store before performing any search. Then checking for the query string and updating the store if there is any query string parameters.
I called the doSearch() function on the onMounted() hook to search on the page first load.
Now experiment the search page by running:
npm run watch
Launce the server
php artisan serve
Updating Home Search Form
another thing related to the search page is the home search form located above the slider
resources/js/components/pages/Home.vue
<template> <div class="slider-area"> <div class="slider"> <div id="bg-slider" class="owl-carousel owl-theme"> <div class="item"><img src="/template/assets/img/slide1/slider-image-1.jpg" alt="Mirror Edge"></div> <div class="item"><img src="/template/assets/img/slide1/slider-image-2.jpg" alt="The Last of us"></div> <div class="item"><img src="/template/assets/img/slide1/slider-image-4.jpg" alt="GTA V"></div> </div> </div> <div class="container slider-content"> <div class="row"> <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1 col-sm-12"> <h2>property Searching Just Got So Easy</h2> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eligendi deserunt deleniti, ullam commodi sit ipsam laboriosam velit adipisci quibusdam aliquam teneturo!</p> <div class="search-form wow pulse" data-wow-delay="0.8s"> <form action="#" class=" form-inline" @submit.prevent="redirectToSearchPage"> <div class="form-group"> <input type="text" class="form-control" placeholder="Key word" v-model="searchForm.keyword" /> </div> <div class="form-group"> <select id="basic" class="selectpicker show-tick form-control" v-model="searchForm.status"> <option value=""> -Status- </option> <option value="Rent">Rent</option> <option value="Sale">Sale</option> </select> </div> <button class="btn search-btn" type="submit"><i class="fa fa-search"></i></button> </form> </div> </div> </div> </div> </div> <!-- property area --> <div class="content-area recent-property" style="padding-bottom: 60px; background-color: rgb(252, 252, 252);"> <div class="container"> <div class="row"> <div class="col-md-12 padding-top-40 properties-page"> <div class="col-md-12 "> <SortingBar :per_page="params.per_page" :default_sort="params.sorting" :default_layout="defaultLayout" :show_layout="true" container_class="col-xs-10 pl0" sorting_class="pull-left" per_page_class="pull-right" @onSelectPerPage="handlePerPage" @onSelectSort="handleSorting" @onSelectLayout="handleLayout" /> </div> <div class="col-md-12 " v-if="properties"> <div id="list-type" :class="defaultLayout === 'list' ? 'proerty-th-list':'proerty-th'"> <div class="col-sm-6 col-md-3 p0" v-for="property of properties" :key="property.id"> <PropertyTemplate :property="property" :show_actions="false" /> </div> </div> </div> </div> </div> </div> </div> </template> <script> import {reactive, ref} from "vue"; import {useFetchListings} from "../../composables/useFetchListings"; import SortingBar from "../partials/SortingBar"; import PropertyTemplate from "../partials/PropertyTemplate"; import {useRouter} from "vue-router"; export default { name: "Home", components: {PropertyTemplate, SortingBar}, setup() { const router = useRouter(); const {params, loadData, properties} = useFetchListings('/api/latest_properties'); const defaultLayout = ref('list'); const searchForm = reactive({keyword: "", status: ""}); const handleSorting = (sortObj) => { const rawObject = {...sortObj}; params.sorting.sortBy = rawObject.sortBy; params.sorting.sortOrder = rawObject.sortOrder; params.page = 1; loadData(); } const handlePerPage = perPage => { params.per_page = perPage; params.page = 1; loadData(); } const handleLayout = layoutType => { defaultLayout.value = layoutType; } const redirectToSearchPage = function () { if(!searchForm.keyword && !searchForm.status) { router.push({path: '/properties'}); return false; } const query = {}; if(searchForm.keyword) { query.keyword = searchForm.keyword; } if(searchForm.status) { query.status = searchForm.status; } router.push({path: '/properties', query}); } loadData(); return { properties, params, defaultLayout, handlePerPage, handleSorting, handleLayout, redirectToSearchPage, searchForm } }, mounted() { $(document).ready(function () { $("#bg-slider").owlCarousel({ navigation: false, // Show next and prev buttons slideSpeed: 100, autoPlay: 5000, paginationSpeed: 100, singleItem: true, mouseDrag: false, transitionStyle: "fade" }); }); } } </script> <style scoped> .page-subheader h4 { background-color: #FDC600; display: inline-block; padding: 5px 10px; color: #fff; font-weight: bold; font-size: 21px; } </style>
The user can type a keyword and select the status and click search, then he will be redirected to search page with form parameters included in the url.
What i have done in the above component i cleared most of the form stuff and kept only the keyword and status fields.
Next in the setup() hook i added new function redirectToSearchPage() which fires on form submit and takes the form parameters and redirect to the search page.
At this point the search functionality is fully completed. In the next part we will display the property details.
Continue to part 12: Display Property Details>>>