In the previous part we created the property form and added the code to submit the data to the backend. In this part we will display the created user properties and the home properties.
Displaying The User Properties
At first we need an api to get the current user properties. So i have done this in the PropertyController.php.
Open app/Http/Controllers/PropertyController.php and update the userProperties() method like so:
public function userProperties(Request $request) { $query = auth()->user()->properties()->with("pictures"); if($request->sorting_field) { $query->orderBy($request->sorting_field, $request->sorting_order); } else { $query->orderBy("id", "desc"); } $properties = $query->paginate($request->per_page ?: 10); return response()->json(['properties' => $properties]); }
Then add the route for this endpoint
routes/api.php:
Route::get('/user_properties', [\App\Http\Controllers\PropertyController::class, 'userProperties']);
Next update resources/js/components/pages/user/MyProperties.vue
<template> <div class="page-head"> <div class="container"> <div class="row"> <div class="page-head-content"> <h1 class="page-title">Your Properties</h1> </div> </div> </div> </div> <div class="content-area recent-property" style="background-color: #FFF;"> <div class="container"> <div class="row"> <div class="col-md-9 pr-30 padding-top-40 properties-page user-properties"> <div class="section"> </div> <div class="section" v-if="properties"> <div id="list-type" class="proerty-th-list"> <div class="col-md-4 p0" v-for="property of properties.data" :key="property.id"> <PropertyTemplate :property="property" :show_actions="true" /> </div> </div> </div> <div class="section" v-if="properties"> <div class="pull-right"> <Pagination :item="properties" @onPaginate="paginate" /> </div> </div> </div> <div class="col-md-3 p0 padding-top-40"> <div class="blog-asside-right"> <div class="panel panel-default sidebar-menu wow fadeInRight animated" > </div> <div class="panel panel-default sidebar-menu wow fadeInRight animated"> </div> </div> </div> </div> </div> </div> </template> <script> import {useRouter, useRoute} from "vue-router"; import PropertyTemplate from "../../partials/PropertyTemplate"; import Pagination from "../../partials/Pagination"; import {useFetchListings} from "../../../composables/useFetchListings"; export default { name: "MyProperties", components: {Pagination, PropertyTemplate}, setup() { const router = useRouter(); const route = useRoute(); const {params, loadData, properties} = useFetchListings('/api/user_properties'); const paginate = page => { params.page = page; loadData(); const query = {...route.query, page}; router.push({ path: route.path, query }); }; loadData(); return { properties, params, paginate } } } </script>
In this component i imported several files. First i imported the <PropertyTemplate />, <Pagination /> . Also i imported another file useFetchListings, we will see it below. In the setup() function i retrieved instances of the router and route using the useRouter() and useRoute() vue composables respectively.
Next i invoked the useFetchListings() function to retrieve the properties list, the loadData() function and the search params.
Then i add a handle fired on the pagination component which is the paginate().
Now create this file resources/js/composables/useFetchListings.js
import {reactive, ref} from "vue"; import {useRoute} from "vue-router"; export const useFetchListings = (endpoint) => { const route = useRoute(); const properties = ref(null); const params = reactive({sorting: {sortBy: 'updated_at', sortOrder: 'DESC'}, page: 1, per_page: 6}); if(route.query.sorting_field) { params.sorting.sortBy = route.query.sorting_field; } if(route.query.sorting_order) { params.sorting.sortOrder = route.query.sorting_order; } if(route.query.page) { params.page = route.query.page; } if(route.query.per_page) { params.per_page = route.query.per_page; } const loadData = () => { window.axios.get( `${endpoint}?${getParamStr()}`).then(response => { properties.value = response.data.properties; }); } const getParamStr = () => { let paramArr = []; for(let p in params) { if(p === 'sorting') { paramArr.push(`sorting_field=${params[p].sortBy}`); paramArr.push(`sorting_order=${params[p].sortOrder}`); } else { paramArr.push(`${p}=${params[p]}`); } } return paramArr.join("&"); } return { loadData, properties, params, getParamStr } }
That’s it now check the your user properties page, you will see your created properties if any.
Sorting Bar
If you return to original template in the user properties page you will see at the top a sorting buttons which enable the user to sort by the property date or the price, let’s create this component.
Create resources/js/components/partials/SortingBar.vue
<template> <div class="page-subheader sorting" :class="container_class"> <ul class="sort-by-list" :class="sorting_class"> <li v-for="sort in sortingParams" :key="sort.field" :class="{active: defaultSort.sortBy === sort.field}"> <a href="javascript:void(0);" @click.prevent="handleSorting(sort.field, defaultSort.sortOrder === 'ASC' ? 'DESC' : 'ASC')"> {{sort.text}} <i :class="`fa fa-sort-amount-${defaultSort.sortOrder === 'ASC' ? 'asc' : 'desc'}`"></i> </a> </li> </ul><!--/ .sort-by-list--> <div class="items-per-page" :class="per_page_class"> <label for="items_per_page"><b>Property per page :</b></label> <div class="sel"> <select id="items_per_page" name="per_page" @change="handlePerPage"> <option v-for="page of pagesList" :key="page" :value="page" :selected="page === perPage">{{page}}</option> </select> </div><!--/ .sel--> </div><!--/ .items-per-page--> </div> <div class="col-xs-2 layout-switcher" v-if="show_layout"> <a v-for="(layout, index) of layouts" :key="layout.type" :class="'layout-'+layout.type + (layout.type === defaultLayout ? ' active':'')" :style="index > 0 ? 'margin-left: 5px':''" href="javascript:void(0);" @click="handleLayout(layout.type)"> <i class="fa" :class="layout.classname"></i> </a> </div><!--/ .layout-switcher--> </template> <script> export default { name: "SortingBar", props: [ "per_page", "default_sort", "default_layout", "show_layout", "container_class", "sorting_class", "per_page_class" ], emits: ["onSelectPerPage", "onSelectSort", "onSelectLayout"], data() { return { sortingParams: [ {text: 'Property Date', field: 'updated_at'}, {text: 'Property Price', field: 'price'} ], pagesList: [6, 9, 12, 15, 30, 45, 60], layouts: [ {type: 'list', classname: 'fa-th-list'}, {type: 'grid', classname: 'fa-th'} ], perPage: parseInt(this.per_page), defaultSort: this.default_sort, defaultLayout: this.default_layout } }, methods: { handlePerPage(e) { this.perPage = parseInt(e.target.value); this.$emit('onSelectPerPage', this.perPage); }, handleSorting(sortBy, sortOrder) { this.defaultSort = {sortBy, sortOrder}; this.$emit('onSelectSort', this.defaultSort); }, handleLayout(type) { this.defaultLayout = type; this.$emit('onSelectLayout', this.defaultLayout); } }, mounted() { this.perPage = parseInt(this.per_page); this.defaultSort = this.default_sort; this.defaultLayout = this.default_layout; }, watch: { per_page(newVal, oldVal) { if(newVal != oldVal) { this.perPage = parseInt(newVal); } }, default_sort(newVal, oldVal) { if({...newVal}.sortBy != {...oldVal}.sortBy || {...newVal}.sortOrder != {...oldVal}.sortOrder) { this.defaultSort = {...newVal}; } } } } </script>
This helper components displays the sorting options and the per page dropdown and the layout switcher. We will use it in several places like the user properties page, the home page and search page. It accepts some properties and emits several events whenever the user performs an action.
Now update the MyProperties.vue to include this component:
<template> <div class="page-head"> <div class="container"> <div class="row"> <div class="page-head-content"> <h1 class="page-title">Your Properties</h1> </div> </div> </div> </div> <div class="content-area recent-property" style="background-color: #FFF;"> <div class="container"> <div class="row"> <div class="col-md-9 pr-30 padding-top-40 properties-page user-properties"> <div class="section"> <SortingBar :per_page="params.per_page" :default_sort="params.sorting" :show_layout="false" container_class="pl0 pr-10" sorting_class="pull-left" per_page_class="pull-right" @onSelectPerPage="handlePerPage" @onSelectSort="handleSorting" /> </div> <div class="section" v-if="properties"> <div id="list-type" class="proerty-th-list"> <div class="col-md-4 p0" v-for="property of properties.data" :key="property.id"> <PropertyTemplate :property="property" :show_actions="true" @onPropertyDeleted="onPropertyDeleted" /> </div> </div> </div> <div class="section" v-if="properties"> <div class="pull-right"> <Pagination :item="properties" @onPaginate="paginate" /> </div> </div> </div> <div class="col-md-3 p0 padding-top-40"> <div class="blog-asside-right"> <div class="panel panel-default sidebar-menu wow fadeInRight animated" > </div> <div class="panel panel-default sidebar-menu wow fadeInRight animated"> </div> </div> </div> </div> </div> </div> </template> <script> import {useRouter, useRoute} from "vue-router"; import PropertyTemplate from "../../partials/PropertyTemplate"; import Pagination from "../../partials/Pagination"; import SortingBar from "../../partials/SortingBar"; import {useFetchListings} from "../../../composables/useFetchListings"; export default { name: "MyProperties", components: {SortingBar, Pagination, PropertyTemplate}, setup() { const router = useRouter(); const route = useRoute(); const {params, loadData, properties} = useFetchListings('/api/user_properties'); const paginate = page => { params.page = page; loadData(); const query = {...route.query, page}; router.push({ path: route.path, query }); }; const handleSorting = (sortObj) => { const rawObject = {...sortObj}; params.sorting.sortBy = rawObject.sortBy; params.sorting.sortOrder = rawObject.sortOrder; params.page = 1; loadData(); const query = {...route.query, page: 1, sorting_field: rawObject.sortBy, sorting_order: rawObject.sortOrder}; router.push({ path: route.path, query }); } const handlePerPage = perPage => { params.per_page = perPage; params.page = 1; loadData(); const query = {...route.query, per_page: params.per_page, page: 1}; router.push({ query}); } const onPropertyDeleted = pid => { //properties.value.data = properties.value.data.filter(p => p.id !== parseInt(pid)); loadData(); } loadData(); return { properties, params, paginate, handlePerPage, handleSorting, onPropertyDeleted } } } </script>
Now the user properties page is fully functional, you can view your created listings, sort by date or price in ascending or descending orders and select the number of per page items.
Displaying The Home Properties
By using the same procedure we followed in the user properties page, we can display the home properties in the middle area section. We will use the same partial components and composable.
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"> <button class="btn toggle-btn" type="button"><i class="fa fa-bars"></i></button> <div class="form-group"> <input type="text" class="form-control" placeholder="Key word"> </div> <div class="form-group"> <select id="lunchBegins" class="selectpicker" data-live-search="true" data-live-search-style="begins" title="Select your city"> <option>New york, CA</option> <option>Paris</option> <option>Casablanca</option> <option>Tokyo</option> <option>Marraekch</option> <option>kyoto , shibua</option> </select> </div> <div class="form-group"> <select id="basic" class="selectpicker show-tick form-control"> <option> -Status- </option> <option>Rent </option> <option>Boy</option> <option>used</option> </select> </div> <button class="btn search-btn" type="submit"><i class="fa fa-search"></i></button> <div style="display: none;" class="search-toggle"> <div class="search-row"> <div class="form-group mar-r-20"> <label for="price-range">Price range ($):</label> <input type="text" class="span2" value="" data-slider-min="0" data-slider-max="600" data-slider-step="5" data-slider-value="[0,450]" id="price-range" ><br /> <b class="pull-left color">2000$</b> <b class="pull-right color">100000$</b> </div> <!-- End of --> <div class="form-group mar-l-20"> <label for="property-geo">Property geo (m2) :</label> <input type="text" class="span2" value="" data-slider-min="0" data-slider-max="600" data-slider-step="5" data-slider-value="[50,450]" id="property-geo" ><br /> <b class="pull-left color">40m</b> <b class="pull-right color">12000m</b> </div> <!-- End of --> </div> <div class="search-row"> <div class="form-group mar-r-20"> <label for="price-range">Min baths :</label> <input type="text" class="span2" value="" data-slider-min="0" data-slider-max="600" data-slider-step="5" data-slider-value="[250,450]" id="min-baths" ><br /> <b class="pull-left color">1</b> <b class="pull-right color">120</b> </div> <!-- End of --> <div class="form-group mar-l-20"> <label for="property-geo">Min bed :</label> <input type="text" class="span2" value="" data-slider-min="0" data-slider-max="600" data-slider-step="5" data-slider-value="[250,450]" id="min-bed" ><br /> <b class="pull-left color">1</b> <b class="pull-right color">120</b> </div> <!-- End of --> </div> <br> <div class="search-row"> <div class="form-group"> <div class="checkbox"> <label> <input type="checkbox"> Fire Place(3100) </label> </div> </div> <!-- End of --> <div class="form-group"> <div class="checkbox"> <label> <input type="checkbox"> Dual Sinks(500) </label> </div> </div> <!-- End of --> <div class="form-group"> <div class="checkbox"> <label> <input type="checkbox"> Hurricane Shutters(99) </label> </div> </div> <!-- End of --> </div> <div class="search-row"> <div class="form-group"> <div class="checkbox"> <label> <input type="checkbox"> Swimming Pool(1190) </label> </div> </div> <!-- End of --> <div class="form-group"> <div class="checkbox"> <label> <input type="checkbox"> 2 Stories(4600) </label> </div> </div> <!-- End of --> <div class="form-group"> <div class="checkbox"> <label> <input type="checkbox"> Emergency Exit(200) </label> </div> </div> <!-- End of --> </div> <div class="search-row"> <div class="form-group"> <div class="checkbox"> <label> <input type="checkbox"> Laundry Room(10073) </label> </div> </div> <!-- End of --> <div class="form-group"> <div class="checkbox"> <label> <input type="checkbox"> Jog Path(1503) </label> </div> </div> <!-- End of --> <div class="form-group"> <div class="checkbox"> <label> <input type="checkbox"> 26' Ceilings(1200) </label> </div> </div> <!-- End of --> </div> </div> </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 {ref} from "vue"; import {useFetchListings} from "../../composables/useFetchListings"; import SortingBar from "../partials/SortingBar"; import PropertyTemplate from "../partials/PropertyTemplate"; export default { name: "Home", components: {PropertyTemplate, SortingBar}, setup() { const {params, loadData, properties} = useFetchListings('/api/latest_properties'); const defaultLayout = ref('list'); 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; } loadData(); return { properties, params, defaultLayout, handlePerPage, handleSorting, handleLayout } }, 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>
As you see in this component i used the same composable file useFetchListings.js and this is the benefit of using composition Api and composables which is to reuse the same again and again i different components.
The only difference here is that i enabled the layout switcher to be displayed in the sorting bar. Now try to restart the server and refresh the home page you will see the latest properties.
app/Http/Controllers/HomeController.php
<?php namespace App\Http\Controllers; use App\Models\Property; use Illuminate\Http\Request; class HomeController extends Controller { public function __construct() { } public function getLatestProperties(Request $request) { $query = Property::with("pictures"); if($request->sorting_field) { $query->orderBy($request->sorting_field, $request->sorting_order); } else { $query->orderBy("id", "desc"); } $properties = $query->limit($request->per_page ?: 10)->get(); return response()->json(['properties' => $properties]); } }
routes/api.php
Route::get('/latest_properties', [\App\Http\Controllers\HomeController::class, 'getLatestProperties']);
Continue to part 9: Search Properties>>>