Laravel 5.5 Ajax problem

Published 3 months ago by AlenV

Hello,

The problem is that I want to sort CarFeatures by name, but when I change listing from A-Z to Z-A nothing happens, I see the request in developer tools, but no changes.

Route

    Route::get('/carfeature/{sortBy}', ['uses' => '[email protected]']);

CarFeatureController

public function listCarFeatures(Request $request, $sortBy) {
        if($request->has('sortBy')){
            $sortBy = $request->input('sortBy');

        }
        if($sortBy == 'az') {
            // Get car features from Database
            $carFeatures = DB::table('car_features')->orderBy('car_feature_name', 'asc')->paginate(5);
        } else {
            $carFeatures = DB::table('car_features')->orderBy('car_feature_name', 'desc')->paginate(5);
        }

        // Return view with CarFeatures
        return view('private.carfeature.carfeature_show')
        ->with(['carFeatures' => $carFeatures]);
    }

AJAX

function sortCarFeatures() {
    x = document.getElementById('carFeatureSort').value;

    $.ajax({
        url: 'carfeature',
        type: 'GET',
        data: { sortBy: x },
        success: function(response){
            console.log('Success: ' + response);
        },
        error: function(response){
            console.log('Error: ' + response);
        }
    });

}

HTML

<!-- Sort By -->
    <div class="col-md-6 d-flex justify-content-right">

        <div class="col-md-5 mt-3">
            <h4 class="text-center">Sort by</h4>
        </div>

        <div class="col-md-7 mt-3">
            <select id="carFeatureSort" class="form-control" onchange="sortCarFeatures()">
                <option selected value="az">
                    Name A-Z
                </option>
                <option value="za">
                    Name Z-A
                </option>
            </select>
        </div>
    </div>
Best Answer (As Selected By AlenV)
tykus

You do not @extend the layout, and do not define @section form the AJAX the response content.

First, extract a partial view template for the <tbody>

// resources/views/private/carfeature/carfeature_show.blade.php
@extends('layouts.main')

@section('title')
Car feature
@endsection

@section('content')

<?php $number=1;?>

<!-- Page title and Order by -->
<div class="row mt-5">

    <!-- Page title -->
    <div class="col-md-6 d-flex justify-content-left">
        <h1 class="text-left">Car feature</h1>
    </div>

    <!-- Sort By -->
    <div class="col-md-6 d-flex justify-content-right">

        <div class="col-md-5 mt-3">
            <h4 class="text-center">Sort by</h4>
        </div>

        <div class="col-md-7 mt-3">
            <select id="carFeatureSort" class="form-control" onchange="sortCarFeatures()">
                <option value="az">
                    Name A-Z
                </option>
                <option value="za">
                    Name Z-A
                </option>
            </select>
        </div>
    </div>
</div>

<div class="row justify-content-center mt-5">
    <div class="col-md-12">
        <table class="table table-striped table-inverse carfeature_table">
            <thead>
                <tr>
                    <th class="text-center">
                        #
                    </th>
                    <th class="text-center">
                        Feature name
                    </th>
                    @if(Auth::user()->user_rights==='Admin')
                    <th class="text-center">
                        Option
                    </th>
                    @endif
                </tr>
            </thead>

            {-- The entire table body is contained in a partial --}         
            @include('private.carfeature.carfeature_data')
        
    </table>


        <!-- Paginator-->
        <div class="row mt-5 justify-content-center">
            <?php
            $number = $number;
            echo $carFeatures->render();
            ?>
        </div>

    </div>
</div>

<p id="sorttest">{{$sortBy}}</p>

@endsection

Now, create a new file in the same directory called carfeature_data.blade.php which represents only the <tbody> html, no @extends not @section directives:

// resources/views/private/carfeature/carfeature_data.blade.php
<tbody class="cars-data">
    @foreach($carFeatures as $carFeature)
        <tr>
            <td class="text-center">
                {{$number}}<?php $number++;?>
            </td>
            <td class="text-center">
                {{$carFeature->car_feature_name}}
            </td>
            @if(Auth::user()->user_rights==='Admin')
            <td class="text-center">
                <a href="/carfeature_edit/{{$carFeature->car_feature_id}}">Edit</a> | <a href="/carfeature_remove/{{$carFeature->car_feature_id}}">Remove</a>
            </td>
            @endif
        </tr>
    @endforeach
    @if(Auth::user()->user_rights==='Admin')
        <tr>
            <td class="text-center" colspan="3">
                <a href="/carfeature_add">Add new feature</a>
            </td>
        </tr>
    @endif
</tbody>

This partial will have access to any data in its parent, so it will continue to render as it did previously; check it before proceeding.

Next, the Controller action which responds to the AJAX request must return this partial view only:

public function listCarFeatures(Request $request) {
    $sortBy = 'az';
    if($request->has('sortBy')){
        $sortBy = $request->input('sortBy');
    }

    if($sortBy == 'az') {
        $carFeatures = DB::table('car_features')->orderBy('car_feature_name', 'asc')->paginate(5);
    } else {
        $carFeatures = DB::table('car_features')->orderBy('car_feature_name', 'desc')->paginate(5);
    }

    $view = view('private.carfeature.carfeature_data', compact('carFeatures', 'sortBy'));
    return $view->render();
    }

Finally, a small modification to the javascript to find the <tbody> element with class cars-data to swap out with the HTML:

function sortCarFeatures() {
    x = document.getElementById('carFeatureSort').value;

    $.ajax({
        url: '/carfeature',
        type: 'GET',
        data: { sortBy: x },
        success: function (response) {
            $('tbody.cars-data').replaceWith(response);
        },
        error: function(response){
            console.log('Error: ' + response);
        }
    });
}
tykus
tykus
3 months ago (521,110 XP)

The last thing you are doing before returning the view is assigning to $carFeatures again so effectively ignoring the previous assignment that depends on sort direction.

if (...)
else (...)

// this is overwriting the previous assignment
$carFeatures = DB::table('car_features')->orderBy('car_feature_name', 'asc')->paginate(5);

Fix this before we start looking at the AJAX request, and what you are doing with it!

AlenV

Oh my bad, forgot to delete it. I removed it, but still not working

AlenV

Hmm now when I put order by Z-A it gives me response what it should, but it doesn't update the view

tykus
tykus
3 months ago (521,110 XP)

Why would it update the view; your success callback function only logs the response to the console?

    success: function(response){
            console.log('Success: ' + response);
        },

Two things (i) success and fail have been removed from jQuery 3.0 and above so you'll want to change that if you are using jQuery > 3.0, and (ii) you have returned a view as your response - did you want to replace the existing HTML with the response or were you intending to do something else?

AlenV

Alright. Yes, I want it to change the existing HTML.

tykus
tykus
3 months ago (521,110 XP)

Ok, in that case you first need to ensure that the view you return is not a complete response, but instead a html string:

// Controller
public function listCarFeatures(Request $request, $sortBy) {
    // ... 

    $view = view('private.carfeature.carfeature_show', compact('carFeatures'));
    return $view->render();
} 

Next, your jQuery success callback needs to find the old html and replace it with the response - I am assuming the original content is nested within a <div class="cars">

$.ajax({
    // ...
    success: function (response) {
        $( "div.cars" ).replaceWith( response );
    }
AlenV

Okay, I've done that, and now it changes but I get like duplicate page. https://imgur.com/a/5Dbxh

HTML

@extends('layouts.main')

@section('title')
Car feature
@endsection

@section('content')

<?php $number=1;?>

<!-- Page title and Order by -->
<div class="row mt-5">

    <!-- Page title -->
    <div class="col-md-6 d-flex justify-content-left">
        <h1 class="text-left">Car feature</h1>
    </div>

    <!-- Sort By -->
    <div class="col-md-6 d-flex justify-content-right">

        <div class="col-md-5 mt-3">
            <h4 class="text-center">Sort by</h4>
        </div>

        <div class="col-md-7 mt-3">
            <select id="carFeatureSort" class="form-control" onchange="sortCarFeatures()">
                <option value="az">
                    Name A-Z
                </option>
                <option value="za">
                    Name Z-A
                </option>
            </select>
        </div>
    </div>
</div>

<div class="row justify-content-center mt-5">
    <div class="col-md-12">
        <table class="table table-striped table-inverse carfeature_table">
            <thead>
                <tr>
                    <th class="text-center">
                        #
                    </th>
                    <th class="text-center">
                        Feature name
                    </th>
                    @if(Auth::user()->user_rights==='Admin')
                    <th class="text-center">
                        Option
                    </th>
                    @endif
                </tr>
            </thead>

            <tbody>
                @foreach($carFeatures as $carFeature)
                <tr>
                    <td class="text-center">
                        {{$number}}<?php $number++;?>
                    </td>
                    <td class="text-center">
                        {{$carFeature->car_feature_name}}
                    </td>
                    @if(Auth::user()->user_rights==='Admin')
                    <td class="text-center">
                        <a href="/carfeature_edit/{{$carFeature->car_feature_id}}">Edit</a> | <a href="/carfeature_remove/{{$carFeature->car_feature_id}}">Remove</a>
                    </td>
                    @endif
                </tr>

                @endforeach

                @if(Auth::user()->user_rights==='Admin')
                <tr>
                    <td class="text-center" colspan="3">
                        <a href="/carfeature_add">Add new feature</a>
                    </td>
                </tr>
                @endif

            </tbody>
        </table>


        <!-- Paginator-->
        <div class="row mt-5 justify-content-center">
            <?php
            $number = $number;
            echo $carFeatures->render();
            ?>
        </div>

    </div>
</div>

<p id="sorttest">{{$sortBy}}</p>

@endsection

Controller

// Function to fetch all car features for listing
    public function listCarFeatures(Request $request) {
        $sortBy = 'az';
        if($request->has('sortBy')){
            $sortBy = $request->input('sortBy');
        }

        if($sortBy == 'az') {
            // Get car features from Database
            $carFeatures = DB::table('car_features')->orderBy('car_feature_name', 'asc')->paginate(5);
        } else {
            $carFeatures = DB::table('car_features')->orderBy('car_feature_name', 'desc')->paginate(5);
        }

        $view = view('private.carfeature.carfeature_show', compact('carFeatures', 'sortBy'));
        return $view->render();
    }

AJAX

function sortCarFeatures() {
    x = document.getElementById('carFeatureSort').value;

    $.ajax({
        url: '/carfeature',
        type: 'GET',
        data: { sortBy: x },
        success: function (response) {
            $('div.carfeature_table').replaceWith(response);
        },
        error: function(response){
            console.log('Error: ' + response);
        }
    });
}
tykus
tykus
3 months ago (521,110 XP)

You do not @extend the layout, and do not define @section form the AJAX the response content.

First, extract a partial view template for the <tbody>

// resources/views/private/carfeature/carfeature_show.blade.php
@extends('layouts.main')

@section('title')
Car feature
@endsection

@section('content')

<?php $number=1;?>

<!-- Page title and Order by -->
<div class="row mt-5">

    <!-- Page title -->
    <div class="col-md-6 d-flex justify-content-left">
        <h1 class="text-left">Car feature</h1>
    </div>

    <!-- Sort By -->
    <div class="col-md-6 d-flex justify-content-right">

        <div class="col-md-5 mt-3">
            <h4 class="text-center">Sort by</h4>
        </div>

        <div class="col-md-7 mt-3">
            <select id="carFeatureSort" class="form-control" onchange="sortCarFeatures()">
                <option value="az">
                    Name A-Z
                </option>
                <option value="za">
                    Name Z-A
                </option>
            </select>
        </div>
    </div>
</div>

<div class="row justify-content-center mt-5">
    <div class="col-md-12">
        <table class="table table-striped table-inverse carfeature_table">
            <thead>
                <tr>
                    <th class="text-center">
                        #
                    </th>
                    <th class="text-center">
                        Feature name
                    </th>
                    @if(Auth::user()->user_rights==='Admin')
                    <th class="text-center">
                        Option
                    </th>
                    @endif
                </tr>
            </thead>

            {-- The entire table body is contained in a partial --}         
            @include('private.carfeature.carfeature_data')
        
    </table>


        <!-- Paginator-->
        <div class="row mt-5 justify-content-center">
            <?php
            $number = $number;
            echo $carFeatures->render();
            ?>
        </div>

    </div>
</div>

<p id="sorttest">{{$sortBy}}</p>

@endsection

Now, create a new file in the same directory called carfeature_data.blade.php which represents only the <tbody> html, no @extends not @section directives:

// resources/views/private/carfeature/carfeature_data.blade.php
<tbody class="cars-data">
    @foreach($carFeatures as $carFeature)
        <tr>
            <td class="text-center">
                {{$number}}<?php $number++;?>
            </td>
            <td class="text-center">
                {{$carFeature->car_feature_name}}
            </td>
            @if(Auth::user()->user_rights==='Admin')
            <td class="text-center">
                <a href="/carfeature_edit/{{$carFeature->car_feature_id}}">Edit</a> | <a href="/carfeature_remove/{{$carFeature->car_feature_id}}">Remove</a>
            </td>
            @endif
        </tr>
    @endforeach
    @if(Auth::user()->user_rights==='Admin')
        <tr>
            <td class="text-center" colspan="3">
                <a href="/carfeature_add">Add new feature</a>
            </td>
        </tr>
    @endif
</tbody>

This partial will have access to any data in its parent, so it will continue to render as it did previously; check it before proceeding.

Next, the Controller action which responds to the AJAX request must return this partial view only:

public function listCarFeatures(Request $request) {
    $sortBy = 'az';
    if($request->has('sortBy')){
        $sortBy = $request->input('sortBy');
    }

    if($sortBy == 'az') {
        $carFeatures = DB::table('car_features')->orderBy('car_feature_name', 'asc')->paginate(5);
    } else {
        $carFeatures = DB::table('car_features')->orderBy('car_feature_name', 'desc')->paginate(5);
    }

    $view = view('private.carfeature.carfeature_data', compact('carFeatures', 'sortBy'));
    return $view->render();
    }

Finally, a small modification to the javascript to find the <tbody> element with class cars-data to swap out with the HTML:

function sortCarFeatures() {
    x = document.getElementById('carFeatureSort').value;

    $.ajax({
        url: '/carfeature',
        type: 'GET',
        data: { sortBy: x },
        success: function (response) {
            $('tbody.cars-data').replaceWith(response);
        },
        error: function(response){
            console.log('Error: ' + response);
        }
    });
}
AlenV

I did as you said, and now it returns only that 'carfeature_data' file. So I think the problem is here in controller.

$view = view('private.carfeature.carfeature_data', compact('carFeatures', 'sortBy'));

Should I create new function in controller which will be called by ajax?

tykus
tykus
3 months ago (521,110 XP)

No, you can check if the request is made using AJAX and return the appropriate view:

public function listCarFeatures(Request $request) {
    $sortBy = 'az';
    if($request->has('sortBy')){
        $sortBy = $request->input('sortBy');
    }

    if($sortBy == 'az') {
        $carFeatures = DB::table('car_features')->orderBy('car_feature_name', 'asc')->paginate(5);
    } else {
        $carFeatures = DB::table('car_features')->orderBy('car_feature_name', 'desc')->paginate(5);
    }

    if ($request->ajax()) {
        $view = view('private.carfeature.carfeature_data', compact('carFeatures', 'sortBy'));
            return $view->render();
    }

    return view('private.carfeature.carfeature_show')->with(['carFeatures' => $carFeatures]);
}
AlenV

It works, thank you for helping and teaching me, much appreciated. Now I have to make it work with paginator, but won't bother you anymore. Thank you once again.

Please sign in or create an account to participate in this conversation.