sena9718
@sena9718

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

検索後もソート機能を機能させたい

解決したいこと

Laravelを用いて商品管理システムを作成中です。
現状、検索後のソート機能において問題があります。
検索後のソートの対象が全商品になってしまうので、検索にて絞り込みされた商品のみをソートできるよう改修したいです。
ソートの機能に関しては検索前の商品一覧画面同様にテーブルヘッダーを押下することで対応したカラムにてソートを行え、押下回数によって昇順/降順が切り替えできる(初期表示時はid降順)ようにしたいです。
解決法を教えてください。

発生している問題・エラー

検索後にソート機能において、対象が全商品になってしまいます。
検索後のソート対象は、検索にて絞り込みされた商品のみにしたいです。

スクリーンショット 2024-02-08 18.52.14.png

該当するソースコード

index.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <h1 class="mb-4">商品一覧画面</h1>

    {{-- 検索フォーム     --}}
    <div class="search mt-5">

        <!-- 検索フォーム。GETメソッドで、商品一覧のルートにデータを送信 -->
        <form id="search-form" action="{{ route('products.search') }}" method="GET" class="row g-3">

            <!-- 商品名検索用の入力欄 -->
            <div class="col-sm-12 col-md-4">
                <input type="text" name="keyword" class="form-control" placeholder="検索キーワード" value="{{ request('keyword') }}">
            </div>
            <!-- メーカー名検索用の入力欄 -->
            <div class="col-sm-12 col-md-4">
                <select class="form-select" name="search-company" value="{{ request('searchCompany') }}" placeholder="メーカーを選択">
                    <option value="" selected>メーカーを選択してください</option>
                    @foreach($companies as $company)
                        <option value="{{ $company->id }}">{{ $company->company_name }}</option>
                    @endforeach
                </select>    
            </div>
            <!-- 価格(下限〜上限)検索用の入力欄 -->
            <div class="col-sm-12 col-md-4">
                <input type="text" name="min_price" class="form-control" placeholder="最小価格" value="{{ request('min_price') }}">
            </div>
            <div class="col-sm-12 col-md-4">
                <input type="text" name="max_price" class="form-control" placeholder="最大価格" value="{{ request('max_price') }}">
            </div>
            <!-- 在庫数(下限〜上限)検索用の入力欄 -->
            <div class="col-sm-12 col-md-4">
                <input type="text" name="min_stock" class="form-control" placeholder="最小在庫数" value="{{ request('min_stock') }}">
            </div>
            <div class="col-sm-12 col-md-4">
                <input type="text" name="max_stock" class="form-control" placeholder="最大在庫数" value="{{ request('max_stock') }}">
            </div>
            <!-- 検索ボタン -->
            <div class="col-sm-12 col-md-1">
                <button id="search-btn" class="btn btn-outline-secondary" type="button">検索</button>
            </div>
        </form>

        <div id="search-results" class="mt-5">

        </div>
    </div>

    <div class="products mt-5">
        <table class="table table-striped">
            <thead>
                <tr>
                    <th><a href="{{ route('products.index', ['sort' => 'id', 'order' => $sortColumn == 'id' && $sortOrder == 'asc' ? 'desc' : 'asc']) }}">ID</a></th>
                    <th>商品画像</th>
                    <th><a href="{{ route('products.index', ['sort' => 'product_name', 'order' => $sortColumn == 'product_name' && $sortOrder == 'asc' ? 'desc' : 'asc']) }}">商品名</a></th>
                    <th><a href="{{ route('products.index', ['sort' => 'price', 'order' => $sortColumn == 'price' && $sortOrder == 'asc' ? 'desc' : 'asc']) }}">価格</a></th>
                    <th><a href="{{ route('products.index', ['sort' => 'stock', 'order' => $sortColumn == 'stock' && $sortOrder == 'asc' ? 'desc' : 'asc']) }}">在庫数</th>
                    <th>メーカー名</th>
                    <th><a href="{{ route('products.create') }}" class="btn btn-warning">新規登録</a></th>                
                </tr>
            </thead>
            <tbody>
            @foreach ($products as $product)
                <tr data-product-id="{{ $product->id }}">
                    <td>{{ $product->id }}</td>
                    <td><img src="{{ asset($product->img_path) }}" alt="商品画像" width="100"></td>
                    <td>{{ $product->product_name }}</td>
                    <td>{{ $product->price }}</td>
                    <td>{{ $product->stock }}</td>
                    <td>{{ $product->company_name }}</td>
                    </td>
                    <td>
                        {{-- 詳細ボタン --}}
                        <a href="{{ route('products.show', $product->id) }}" class="btn btn-info btn-sm mx-1">詳細</a>
                        <form method="POST" action="{{ route('products.destroy', $product->id) }}" class="d-inline">
                            @csrf
                            @method('DELETE')
                            {{-- 削除ボタン --}}
                            <button type="submit" class="btn btn-danger btn-sm mx-1 delete-product" data-product-id="{{ $product->id }}">削除</button>
                        </form>
                    </td>
                </tr>
            @endforeach
            </tbody>
        </table>
    </div>

</div>

@section('scripts')
<script>
    $(document).ready(function () {
        // 商品データを削除するための Ajax リクエスト
        $('.delete-product').on('click', 'delete-product', function (event) {
            event.preventDefault();
            
            var productId = $(this).data('product-id');

            if (confirm("削除しますか?")) {
                $.ajax({
                    type: 'POST',
                    data:{'_method':'delete'},
                    url: '/products/' + productId,
                    headers: {
                        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                    },
                    success: function (data) {
                        // 削除された行を非表示にする
                        $('tr[data-product-id="' + productId + '"]').hide();
                        alert('削除しました。');
                    },
                    error: function (data) {
                        alert('削除に失敗しました。');
                    }
                });
            }
        });

        $('#search-btn').on('click', function () {
            var formData = $('#search-form').serialize();

            // 検索ルートにAjaxリクエストを送信
            $.ajax({
                type: 'GET',
                url: '/products/search',
                data: formData,
                dataType: 'json',
                success: function (data) {
                    // 検索結果のdivを更新
                    $('#search-results').empty(); // 既存の内容をクリア

                    if(data.products.length > 0) {
                        // データが存在する場合の処理
                        var newTableHtml = '<table class="table table-striped">' +
                            '<thead><tr>' +
                            '<th><a href="{{ route('products.index', ['sort' => 'id', 'order' => $sortColumn == 'id' && $sortOrder == 'asc' ? 'desc' : 'asc']) }}">ID</a></th>' +
                            '<th>商品画像</th>' +
                            '<th><a href="{{ route('products.index', ['sort' => 'product_name', 'order' => $sortColumn == 'product_name' && $sortOrder == 'asc' ? 'desc' : 'asc']) }}">商品名</a></th>' +
                            '<th><a href="{{ route('products.index', ['sort' => 'price', 'order' => $sortColumn == 'price' && $sortOrder == 'asc' ? 'desc' : 'asc']) }}">価格</a></th>' +
                            '<th><a href="{{ route('products.index', ['sort' => 'stock', 'order' => $sortColumn == 'stock' && $sortOrder == 'asc' ? 'desc' : 'asc']) }}">在庫数</a></th>' +
                            '<th>メーカー名</th>' +
                            '<th></th>' +
                            '</tr></thead><tbody>';

                        $.each(data.products, function (index, product) {
                            // 適切な方法でデータを表示するための処理
                            newTableHtml += '<tr>' +
                                '<td>' + product.id + '</td>' +
                                '<td><img src="' + product.img_path + '" alt="商品画像" width="100"></td>' +
                                '<td>' + product.product_name + '</td>' +
                                '<td>' + product.price + '</td>' +
                                '<td>' + product.stock + '</td>' +
                                '<td>' + product.company_name + '</td>' +
                                '<td>' +
                                '<a href="/products/show/' + product.id + '" class="btn btn-info btn-sm mx-1">詳細</a>' +
                                '<form method="POST" action="/products/' + product.id + '" class="d-inline">' +
                                '@csrf' +
                                '@method("DELETE")' +
                                '<button type="submit" class="btn btn-danger btn-sm mx-1 delete-product" data-product-id="' + product.id + '">削除</button>' +
                                '</form>' +
                                '</td>' +
                                '</tr>';
                        });
                        
                        newTableHtml += '</tbody></table>';

                        $('.products').hide();

                        $('#search-results').html(newTableHtml);
                    } else {
                        // データが存在しない場合の処理
                        $('#search-results').html('<p>該当する商品が見つかりませんでした。</p>');
                    }
                },
                error: function (data) {
                    alert('検索に失敗しました。もう一度お試しください。');
                }
            });
        });
    });
</script>
@endsection
search.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <h1 class="mb-4">検索結果</h1>

        <div class="products mt-5">
            <table class="table table-striped">
              <thead>
                <tr>
                    <th><a href="{{ route('products.index', ['sort' => 'id', 'order' => $sortColumn == 'id' && $sortOrder == 'asc' ? 'desc' : 'asc']) }}">ID</a></th>
                    <th>商品画像</th>
                    <th><a href="{{ route('products.index', ['sort' => 'product_name', 'order' => $sortColumn == 'product_name' && $sortOrder == 'asc' ? 'desc' : 'asc']) }}">商品名</a></th>
                    <th><a href="{{ route('products.index', ['sort' => 'price', 'order' => $sortColumn == 'price' && $sortOrder == 'asc' ? 'desc' : 'asc']) }}">価格</a></th>
                    <th><a href="{{ route('products.index', ['sort' => 'stock', 'order' => $sortColumn == 'stock' && $sortOrder == 'asc' ? 'desc' : 'asc']) }}">在庫数</th>
                    <th>メーカー名</th>
                    <th><a href="{{ route('products.create') }}" class="btn btn-warning">新規登録</a></th>                
                </tr>
            </thead>
                <tbody>
                    @foreach ($products as $product)
                    <tr>
                      <td>{{ $product->id }}</td>
                      <td><img src="{{ asset($product->img_path) }}" alt="商品画像" width="100"></td>
                      <td>{{ $product->product_name }}</td>
                      <td>{{ $product->price }}</td>
                      <td>{{ $product->stock }}</td>
                      <td>{{ $product->company_name }}</td>
                      </td>
                      <td>
                          {{-- 詳細ボタン --}}
                          <a href="{{ route('products.show', $product->id) }}" class="btn btn-info btn-sm mx-1">詳細</a>
                          <form method="POST" action="{{ route('products.destroy', $product->id) }}" class="d-inline">
                              @csrf
                              @method('DELETE')
                              {{-- 削除ボタン --}}
                              <button type="submit" class="btn btn-danger btn-sm mx-1 delete-product" data-product-id="{{ $product->id }}">削除</button>
                          </form>
                      </td>
                    </tr>
                    @endforeach
                </tbody>
            </table>
        </div>
    </div>
@endsection
ProductController.php
<?php

namespace App\Http\Controllers;

use App\Models\Product;
use App\Models\Company;
use App\Http\Requests\ProductRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;

class ProductController extends Controller
{
    // 一覧表示
    public function index(Request $request)
    {
        $keyword = $request->input('keyword');
        $searchCompany = $request->input('search-company');
        $min_price = $request->input('min_price');
        $max_price = $request->input('max_price');
        $min_stock = $request->input('min_stock');
        $max_stock = $request->input('max_stock');
        $sortColumn = $request->input('sort', 'id');
        $sortOrder = $request->input('order', 'desc');

        $model = new Product;
        $products = $model->searchList($keyword, $searchCompany, $min_price, $max_price, $min_stock, $max_stock, $sortColumn, $sortOrder);

        $companies = DB::table('companies')->get();

        return view('products.index', compact('products', 'companies', 'sortColumn', 'sortOrder'));
    }
    
    public function search(Request $request)
    {    
        // リクエストから検索パラメータを抽出
        $keyword = $request->input('keyword');
        $searchCompany = $request->input('search-company');
        $min_price = $request->input('min_price');
        $max_price = $request->input('max_price');
        $min_stock = $request->input('min_stock');
        $max_stock = $request->input('max_stock');
        $sortColumn = $request->input('sort', 'id');
        $sortOrder = $request->input('order', 'desc');

        // 既存の searchList メソッドを使用して検索を実行
        $model = new Product;
        $products = $model->searchList($keyword, $searchCompany, $min_price, $max_price, $min_stock, $max_stock, $sortColumn, $sortOrder);

        // 検索結果を表示するビューを返す
        return response()->json(['products' => $products]);
    }

    // 新規登録画面表示
    public function create()
    {
        $companies = DB::table('companies')->get();

        return view('products.create', compact('companies'));
    }

    // 新規登録処理
    public function store(ProductRequest $request)
    {   
        $model = new Product;
        DB::beginTransaction();
        try {
            $image = $request->file('img_path');
            if($image) {
                $filename = $image->getClientOriginalName();
                $image->storeAs('public/images', $filename);
                $img_path = 'storage/images/'.$filename;
                $model->storeProduct($request, $img_path);
            } else {
                $model->storeProductNoImg($request);
                // $img_path = null;
            }

            // $model->storeProduct($request, $img_path);

            DB::commit();
            return redirect()->route('products.create')->with('success', 'Product created successfully');
        } catch (\Exception $e) {
            DB::rollback();
            return redirect()->route('products.create')->with('error', 'Error creating the product');
        }
    }

    // 詳細表示
    public function show(Product $product)
    {
        return view('products.show', compact('product'));
    }

    // 商品情報編集
    public function edit($id)
    {
        $companies = DB::table('companies')->get();
        $model = New Product;
        $product = $model->getProductById($id);

        return view('products.edit', compact('product', 'companies'));
    }

    // 商品情報更新処理
    public function update(ProductRequest $request, $id)
    {
        $model = New Product;
        DB::beginTransaction();
        try {
            $image = $request->file('img_path');
            if($image) {
                $filename = $image->getClientOriginalName();
                $image->storeAs('public/images', $filename);
                $img_path = 'storage/images/'.$filename;
                $model->updateProduct($request, $img_path, $id);
            } else {
                $model->updateProductNoImg($request, $id);
            }

            DB::commit();
            return redirect(route('products.show', $id))->with('success', 'Product updated successfully');
        } catch(Exception $e) {
            DB::rollBack();
        }
    }

    // 削除処理
    public function destroy($id)
    {
        DB::beginTransaction();
        try {
            $model = new Product;
            $model->deleteProduct($id);
            DB::commit();
        } catch(\Exception $e) {
            DB::rollBack();
            return back();
        }

        return redirect('/products/index');
    }
}
Product.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use App\Http\Requests\ProductRequest;

class Product extends Model
{
    use HasFactory;

     protected $table = 'products';
     
     // 検索処理
     public function searchList($keyword, $searchCompany, $min_price, $max_price, $min_stock, $max_stock, $sortColumn = 'id', $sortOrder ='desc') 
     {
        // productsテーブルからデータ取得。companiesテーブルをjoin
         $query = DB::table('products')
                     ->join('companies', 'products.company_id', '=', 'companies.id')
                     ->select('products.*', 'companies.company_name');
         
         if($keyword) {
             $query->where('products.product_name', 'LIKE', "%".$keyword."%");
         }
         if($searchCompany) {
             $query->where('products.company_id', '=', $searchCompany);
         }
        //  価格下限〜上限
        if($min_price) {
            $query->where('products.price', '>=', $min_price);
        }
        if($max_price) {
            $query->where('products.price', '>=', $max_price);
        }
        // 在庫数下限〜上限
        if($min_stock) {
            $query->where('products.stock', '>=', $min_stock);
        }
        if($max_stock) {
            $query->where('products.stock', '>=', $max_stock);
        }
         $query->orderBy($sortColumn, $sortOrder);

         return $query->get();
     }
     public function getProductById($id)
     {
        return $this->findOrFail($id);
     }

    //バリデーション処理追加
     public function storeProduct(ProductRequest $request, $img_path) {
        // DB::table('products')->insert([
           $this->create([ 
            'product_name' => $request->product_name,
            'company_id' => $request->company_id,
            'price' => $request->price,
            'stock' => $request->stock,
            'comment' => $request->comment, 
            'img_path' => $img_path,
        ]);
     }

    //  新規登録処理
     public function storeProductNoImg(ProductRequest $request) {
        // DB::table('products')->insert([
           $this->create([  
            'product_name' => $request->product_name,
            'company_id' => $request->company_id,
            'price' => $request->price,
            'stock' => $request->stock,
            'comment' => $request->comment,
        ]);
     }
     
    //  商品情報編集処理
     public function updateProduct(ProductRequest $request, $img_path, $id) {
        $this->findOrFail($id)->update([
            'product_name' => $request->product_name,
            'company_id' => $request->company_id,
            'price' => $request->price,
            'stock' => $request->stock,
            'comment' => $request->comment, 
            'img_path' => $request->img_path,
        ]);
     }

     public function updateProductNoImg(ProductRequest $request, $id) {
        $this->findOrFail($id)->update([
            'product_name' => $request->product_name,
            'company_id' => $request->company_id,
            'price' => $request->price,
            'stock' => $request->stock,
            'comment' => $request->comment, 
        ]);
     }

     public function deleteProduct($id) {
        $product = $this->findOrFail($id);
        $product->delete();
     }
   
    protected $fillable = [
        'product_name',
        'price',
        'stock',
        'company_id',
        'comment',
        'img_path',
    ];

    public function sales() {
        return $this->hasMany(Sale::class);
    }

    public function company() {
        return $this->belongsTo(Company::class);
    }
}
web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController;
// use Illuminate\Support\Fecades\Auth;


// Route::get('/', function () {
//     if(Auth::check()) {
//         return redirect()->route('products.index');
//     } else {
//         return redirect()->route('login');
//     }
// });

Auth::routes();

// Route::group(['middleware' => 'auth'], function() {
//     Route::resource('products', ProductController::class);
// });

Route::get('/', function () {
    return view('welcome');
});

Route::get('/products/index', 'App\Http\Controllers\ProductController@index')->name('products.index');

Route::get('/products/create', 'App\Http\Controllers\ProductController@create')->name('products.create');
Route::post('/products/store', 'App\Http\Controllers\ProductController@store')->name('products.store');

Route::get('/products/show/{product}', 'App\Http\Controllers\ProductController@show')->name('products.show');

Route::get('/products/edit/{id}', 'App\Http\Controllers\ProductController@edit')->name('products.edit');
Route::put('/products/edit/{id}', 'App\Http\Controllers\ProductController@update')->name('products.update');

Route::delete('/products/{product}', 'App\Http\Controllers\ProductController@destroy')->name('products.destroy');

Route::get('/products/search', 'App\Http\Controllers\ProductController@search')->name('products.search');

自分で試したこと

chatgptに尋ねて、下記のようなコードで解決できるとのことだったので、下記コードをindex.blade.phpのJavaScriptコードに付け加えました。

// ソートボタンのクリック時に検索結果を使用する処理
$('th a').on('click', function(event) {
    event.preventDefault();
    var sortColumn = $(this).attr('href').split('sort=')[1].split('&')[0];
    var sortOrder = $(this).attr('href').split('order=')[1];

    // ソート関数を呼び出して、検索結果をソート
    searchResults = sortResults(searchResults, sortColumn, sortOrder);

    // ソート後の結果を表示
    displaySearchResults(searchResults);
});

// 検索結果を指定の列でソートする関数
function sortResults(results, column, order) {
    return results.sort(function (a, b) {
        var aValue = a[column];
        var bValue = b[column];
        if (order === 'asc') {
            return aValue.localeCompare(bValue);
        } else {
            return bValue.localeCompare(aValue);
        }
    });
}

// ソート後の検索結果を表示する関数
function displaySearchResults(results) {
    var newTableHtml = '<table class="table table-striped">' +
        '<thead><tr>' +
        '<th><a href="#" onclick="sortResults(\'id\', \'' + (sortColumn === 'id' && sortOrder === 'asc' ? 'desc' : 'asc') + '\')">ID</a></th>' +
        '<th>商品画像</th>' +
        '<th><a href="#" onclick="sortResults(\'product_name\', \'' + (sortColumn === 'product_name' && sortOrder === 'asc' ? 'desc' : 'asc') + '\')">商品名</a></th>' +
        '<th><a href="#" onclick="sortResults(\'price\', \'' + (sortColumn === 'price' && sortOrder === 'asc' ? 'desc' : 'asc') + '\')">価格</a></th>' +
        '<th><a href="#" onclick="sortResults(\'stock\', \'' + (sortColumn === 'stock' && sortOrder === 'asc' ? 'desc' : 'asc') + '\')">在庫数</a></th>' +
        '<th>メーカー名</th>' +
        '<th></th>' +
        '</tr></thead><tbody>';

    $.each(results, function (index, product) {
        // 適切な方法でデータを表示するための処理
        newTableHtml += '<tr>' +
            '<td>' + product.id + '</td>' +
            '<td><img src="' + product.img_path + '" alt="商品画像" width="100"></td>' +
            '<td>' + product.product_name + '</td>' +
            '<td>' + product.price + '</td>' +
            '<td>' + product.stock + '</td>' +
            '<td>' + product.company_name + '</td>' +
            '<td>' +
            '<a href="/products/show/' + product.id + '" class="btn btn-info btn-sm mx-1">詳細</a>' +
            '<form method="POST" action="/products/' + product.id + '" class="d-inline">' +
            '@csrf' +
            '@method("DELETE")' +
            '<button type="submit" class="btn btn-danger btn-sm mx-1 delete-product" data-product-id="' + product.id + '">削除</button>' +
            '</form>' +
            '</td>' +
            '</tr>';
    });

    newTableHtml += '</tbody></table>';

    // 新しいHTMLを #search-results 要素に追加
    $('#search-results').html(newTableHtml);
}
0

2Answer

sort と order パラメータが、テーブルヘッダーのリンクにしか存在しないように見受けられます
form に sort と order パラメータ追加で解決しませんか?

<form id="search-form" action="{{ route('products.search') }}" method="GET" class="row g-3">
  <input type="hidden" name="sort" value="{{ $sortColumn }}">
  <input type="hidden" name="order" value="{{ $sortOrder }}">
0Like

Comments

  1. @sena9718

    Questioner

    コメントありがとうございます。
    formにsort・orderパラメーター追加だけでは上手く機能しませんでした。

sort後の

        $keyword = $request->input('keyword');
        $searchCompany = $request->input('search-company');
        $min_price = $request->input('min_price');
        $max_price = $request->input('max_price');
        $min_stock = $request->input('min_stock');
        $max_stock = $request->input('max_stock');
        $sortColumn = $request->input('sort', 'id');
        $sortOrder = $request->input('order', 'desc');

これらにちゃんと値がセットされているか確認してみるとどうでしょう?

var_dump

などでデバッグしてみると解決しそうな気がします

0Like

Your answer might help someone💌