reeju619's avatar

Not working multi-filter system in my real estate laravel 10 project

Hello everyone! i hope you all are doing well. I am developing a real estate website, there is a property page. In this page the requirement is that there will be a multi filtering system. User can filter properties by category, price range, parking spots, and BHK(Bedroom, Hall and Kitchen). Suppose user first will select apartment category, according to that category every property will show, after that suppose user will select 3 BHK, then 3 BHK apartment properties will show, after that suppose user will select a price range between 2000 and 3000 USD, then 3 BHK apartment properties which has a price range between 2000 and 3000 USD will show. Right now Category filter is working but other filters are not working accordingly. Here is the property.blade.php :-



@extends('frontend.layouts.main')

@section('main-container')

<div class="page-heading header-text">
  <div class="container">
    <div class="row">
      <div class="col-lg-12">
        <span class="breadcrumb"><a href="#">Home</a> / Properties</span>
        <h3>Properties</h3>
      </div>
    </div>
  </div>
</div>

<div class="section properties">
  <div class="container">
    <ul class="properties-filter">
      <li><a class="is_active" href="#!" data-filter="*">Show All</a></li>
      <li><a href="#!" data-filter=".apartment">Apartment</a></li>
      <li><a href="#!" data-filter=".luxury-villa">Luxury Villa</a></li>
      <li><a href="#!" data-filter=".penthouse">Penthouse</a></li>
      <li><a href="#!" data-filter=".modern-condo">Modern Condo</a></li>
    </ul>
    <div class="filters">
      <!-- Price Filter -->
      <div class="filter-item">
        <label for="priceRange">Price Range:</label>
        <input type="range" id="priceRange" min="500" max="3000" value="500" class="slider">
        <span id="priceValue">0</span>
    </div>


      <!-- Parking Lots Filter -->
      <div class="filter-item">
          <label>Parking Spots:</label>
          <div>
              @foreach([3, 6, 8, 10] as $spots)
              <input type="radio" id="parking{{ $spots }}" name="parkingSpots" value="{{ $spots }}">
              <label for="parking{{ $spots }}">{{ $spots }} spots</label>
              @endforeach
          </div>
      </div>

      <!-- BHK Filter -->
      <div class="filter-item">
          <label for="bhk">BHK:</label>
          <select id="bhk" class="form-select">
              <option value="*">All</option>
              @foreach([2, 3, 4] as $bhk)
              <option value="{{ $bhk }}">{{ $bhk }} BHK</option>
              @endforeach
          </select>
      </div>
  </div>

    <div class="row properties-box">
      @foreach ($properties as $property)
      <div class="col-lg-4 col-md-6 align-self-center mb-30 properties-items {{ strtolower(str_replace(' ', '-',   $property->category_name)) }}"
        data-price="{{ $property->price }}"
        data-bhk="{{ $property->bhk }}"
        data-parking="{{ $property->parking }}">

        <div class="item">
            <a href="{{ route('property-details', $property->id) }}"><img src="{{ asset('storage/' . $property->property_image) }}" alt=""></a>
            <span class="category">{{ $property->category_name }}</span>
            <h6>{{ $property->price }}</h6>
            <h4><a href="{{ route('property-details', $property->id) }}">{{$property->property_name}}</a></h4>
            <ul>
                <li>Address: <span><a href="{{ route('property-details', $property->id) }}">{{ $property->address }}</a></span></li>
                <li><span>{{ $property->bhk }} BHK</span></li>
                <li>Area: <span>{{ $property->area }}</span></li>
                @if (in_array($property->category_name, ["Apartment", "Penthouse", "Modern Condo"]))
                <li>Floor: <span>{{ $property->floor }}th</span></li>
                @else
                <li>Floor: <span>{{ $property->floor }}</span></li>
                @endif
                <li>Parking: <span>{{ $property->parking }}</span></li>
            </ul>
            <div class="main-button">
                <a href="{{ route('property-details', $property->id) }}">Schedule a visit</a>
            </div>
        </div>
    </div>
@endforeach
</div>
    </div>
     <!-- Pagination -->
     <div class="row">
      <div class="col-lg-12">
          {{ $properties->links() }}
      </div>
  </div>
  </div>
</div>

    @endsection

    @section('title')
    Property
    @endsection


property-fliter-script.js :-

document.addEventListener("DOMContentLoaded", function() {
    const filters = {
        category: document.querySelectorAll('.properties-filter a'),
        priceRange: document.getElementById('priceRange'),
        parkingSpots: document.querySelectorAll('input[name="parkingSpots"]'),
        bhk: document.getElementById('bhk')
    };

    const priceValueDisplay = document.getElementById('priceValue');

    // Function to initially show all properties when the page loads
    function showAllProperties() {
        document.querySelectorAll('.properties-items').forEach(item => {
            item.style.display = "block";
        });
    }

    // Initial function call to show all properties
    showAllProperties();

    // Update price display
    filters.priceRange.oninput = function() {
        priceValueDisplay.textContent = `$${this.value}`;
        applyFilters();
    }

    function applyFilters() {
        const selectedPrice = parseInt(filters.priceRange.value, 10);
        let selectedParking = null;
        filters.parkingSpots.forEach(radio => {
            if (radio.checked) selectedParking = parseInt(radio.value, 10);
        });
        const selectedBhk = parseInt(filters.bhk.value, 10);

        document.querySelectorAll('.properties-items').forEach(item => {
            const price = parseInt(item.dataset.price, 10);
            const parking = parseInt(item.dataset.parking, 10);
            const bhk = parseInt(item.dataset.bhk, 10);

            const priceMatch = !selectedPrice || (price <= selectedPrice);
            const parkingMatch = !selectedParking || (parking === selectedParking);
            const bhkMatch = !selectedBhk || (bhk === selectedBhk);

            item.style.display = (priceMatch && parkingMatch && bhkMatch) ? "block" : "none";
        });
    }

    // Event listeners for BHK and Parking changes
    filters.bhk.addEventListener('change', applyFilters);
    filters.parkingSpots.forEach(radio => radio.addEventListener('change', applyFilters));

    // Apply category filters independently from the other filters
    filters.category.forEach(filter => {
        filter.addEventListener('click', function(e) {
            e.preventDefault();
            const category = this.getAttribute('data-filter');
            document.querySelectorAll('.properties-items').forEach(item => {
                if (category === "*" || item.classList.contains(category.substring(1))) {
                    item.style.display = "block";
                } else {
                    item.style.display = "none";
                }
            });
            filters.category.forEach(f => f.classList.remove('is_active'));
            this.classList.add('is_active');
        });
    });
});


PropertyController.php :-

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\PropertyDetail; 

class PropertyController extends Controller
{
    public function index(){
        // Fetch 6 properties from the database
        $properties = PropertyDetail::paginate(6);

        // Pass the properties to the view
        return view('frontend.property', compact('properties'));
    }

    /**
     * Show the details for a specific property.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function showPropertyDetails($id)
    {
        // Fetch the property detail by ID
        $property = PropertyDetail::findOrFail($id);

        // Pass the property detail to the view
        return view('frontend.property-details', compact('property'));
    }


}


Please help me. Is there a way to resolve this ? are there any libraries or any other way to fix this issue.

0 likes
5 replies
martinbean's avatar

@reeju619 You would be better off using query string parameters to filter properties, and that way your filtering will work without JavaScript as well.

Given the below form:

<form action="{{ route('your.property.index.route') }}" method="get">
  <fieldset>
    <legend>Property type</legend>
    <label for="type_all">
      <input id="type_all" name="filter[type]" type="radio" value="">
      All
    </label>
    <label for="type_apartment">
      <input id="type_apartment" name="filter[type]" type="radio" value="apartment">
      Apartment
    </label>
    <label for="type_villa">
      <input id="type_villa" name="filter[type]" type="radio" value="villa">
      Luxury villa
    </label>
    <label for="type_penthouse">
      <input id="type_penthouse" name="filter[type]" type="radio" value="penthouse">
      Penthouse
    </label>
    <label for="type_condo">
      <input id="type_condo" name="filter[type]" type="radio" value="condo">
      Modern condo
    </label>
  </fieldset>
  <fieldset>
    <legend>Price range</legend>
    <label for="price_minimum">Minimum</label>
    <input id="price_minimum" min="0" name="filter[min_price]" type="number">
    <label for="price_maximum">Maximum</label>
    <input id="price_maximum" min="0" name="filter[max_price]" type="number">
  </fieldset>
  <!-- Parking lots filter -->
  <!-- BHK filter -->
  <button type="submit">Search</button>
</form>

You will end up with a URI like: /properties?filter[type]=villa&filter[min_price]=0&filter[max_price]=10000

You can then use something like Spatie’s Laravel query builder package to filter results in your controller:

public function index()
{
    $properties = QueryBuilder::for(Property::query())
        ->allowedFilters([
            AllowedFilter::exact('type'),
            AllowedFilter::callback('min_price', function (Builder $query, $value): void {
                $query->where('price', '>=', $value);
            }),
            AllowedFilter::callback('max_price', function (Builder $query, $value): void {
                $query->where('price', '<=', $value);
            }),
            AllowedFilter::exact('parking_spots'),
            AllowedFilter::exact('bak'),
        ])
        ->allowedSorts([
            'price',
        ])
        ->defaultSort('-price')
        ->paginate();

    return view('properties.index', compact('properties'));
}
1 like
reeju619's avatar

@martinbean thank you very much. Everyhting in your code is working fine, after clicking search button. But according to the requirement if user clicks on the category filter or selects BHK, it will show the result instantly without clicking on the search button or page reloading. So I need that type of filtering system. In this case I think JavaScript or jQuery is needed, or if there are other ways to do it, I am open to your suggestions and help.

martinbean's avatar

@reeju619 Like I say, you should ensure your filtering works without JavaScript first. Then you can use JavaScript to progressively enhance your UI, such as submitting the filter form via AJAX and then replacing the results in your page with the response from your AJAX request.

You could use Blade fragments for this purpose:

<!-- resources/views/properties/index.blade.php -->
<html>
  <body>
    @fragment('property-results')
      @forecah($properties as $property)
        <!-- Property result mark-up -->
      @endforeach
    @endfragment
  </body>
</html>
1 like
reeju619's avatar

@martinbean Yes without JavaScript it is working fine. I understand what you are saying. I will implement it step by step using AJAX.

1 like
reeju619's avatar

The major issue was with price filter. Price isnt matching because price data includes a dollar sign, which parseInt cant handle. so the price one was the main problem here. When I will fix the price data, the rest of your filters will work. After that I removed the $'s from price from all properties from backend admin section, now the filter works correctly. Everything is working perfectly.

1 like

Please or to participate in this conversation.