hi_lili
@hi_lili

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!

非同期処理で検索をした後、IDが取得されません

解決したいこと

index.blade.phpの絞り込みボタンを押下した後、
表示されるレコードのidを取得したいです:bow_tone1:

使用環境
laravel9
composer
MacOS
Ajax
JQuery

LaravelでAjaxとJQueryを利用し、非同期処理を行っております。
非同期で検索処理を行い該当のレコードを表示することはできております。
しかし取得したレコードの詳細、編集画面へ移動することができません:fearful:
「詳細表示ボタン」「編集ボタン」を押下すると下記のエラーがブラウザにて確認されます・・・

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

GET http://localhost:8888/products/undefined 404 (Not Found)

Chromeの検証で上記が確認されているため、
該当のidを取得すればいいと思うのですが、うまく取得ができていない状況です。

該当するソースコード

search.js
$(function() {
  $('#search-form').submit(function(event) {
    event.preventDefault();
    
    var csrfToken = $('meta[name="csrf-token"]').attr('content');

    $.ajax({
      url: "http://localhost:8888/product/public/products/search", 
      type: 'GET',
      dataType: "json",
      data: {
        search: $('#search').val(),
        company_id: $('#company').val(),
        priceMin: $('input[name="price_min"]').val(),
        priceMax: $('input[name="price_max"]').val(),
        stockMin: $('input[name="stock_min"]').val(),
        stockMax: $('input[name="stock_max"]').val(),
      }
    }).done((response) => {
        console.log(response);
        var $result  = $('#search-results');
        $result.empty();
        $.each(response.products, function(index, product){
          var productId = product.id;
          var html = `
          <tr>
              <td>${product.product_name}</td>
              <td>${product.company_name}</td>
              <td>${product.price}</td>
              <td>${product.stock}</td>
              <td>${product.comment}</td>
              <td><img src="${product.img_path}" alt="商品画像" width="100"></td>
              <td>
                  <a href="/products/${productId}" class="btn btn-info btn-sm mx-1">詳細表示</a>
                  <a href="/products/${productId}/edit" class="btn btn-primary btn-sm mx-1">編集</a>
                  <form method="POST" action="/products/${productId}" class="d-inline">
                      <input type="hidden" name="_token" value="${csrfToken}">
                      <input type="hidden" name="_method" value="DELETE">
                      <button type="submit" class="btn btn-danger btn-sm mx-1">削除</button>
                  </form>
              </td>
          </tr>`;
          $result.append(html);
      });
      }).fail((error) => {
        console.log('AJAXが失敗しました');
      });
    });
  });

ProductController.php
class ProductController extends Controller
{

    public function index(Request $request){

        $products = Product::search($request->search, $request->company_id, $request->price_min, $request->price_max, $request->stock_min, $request->stock_max)
        ->paginate(10);
        $company_lists = Company::pluck('company_name', 'id');
        $company = $request->company_id;
    
        return view('products.index', compact('products', 'company_lists', 'company'));
    }

    public function search(Request $request)
    {
        $products = Product::search($request->search, $request->company_id, $request->price_min, $request->price_max, $request->stock_min, $request->stock_max)
        ->paginate(10);
    $company_lists = Company::pluck('company_name', 'id');
    $company_id = $request->company_id;

    $products = $products->map(function ($product) {
        return [
            'product_name' => $product->product_name ?? '',
            'company_name' => $product->company->company_name ?? '',
            'price' => $product->price ?? '',
            'stock' => $product->stock ?? '',
            'comment' => $product->comment ?? '',
            'img_path' => asset($product->img_path) ?? '',
        ];
    });
    
        return response()->json([
            'products' => $products,
            'company_id' => $company_id,
            'company_lists' => $company_lists
        ]);
    }
}
Product.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;


class Product extends Model
{
    use HasFactory;

    protected $fillable = [
        'id',
        '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);
    }

    public function getCompany()
    {
        return $this->company;
    }
        

    public static function createProduct($requestData){

        $product = new Product();
        $product->product_name = $requestData['product_name'];
        $product->company_id = $requestData['company_id'];
        $product->price = $requestData['price'];
        $product->stock = $requestData['stock'];
        $product->comment = $requestData['comment'];

        // idフィールドは自動インクリメントなので、明示的に設定する必要はありません。

        if (isset($requestData['img_path'])) {
            $img_path = $requestData['img_path']->store('products', 'public');
            $product->img_path = 'storage/' . $img_path;
        }

        $product->save();
        return $product;
    }
    

   public function scopeSearch($query, $search, $company, $priceMin, $priceMax, $stockMin, $stockMax)
    {
        if ($search) {
        $query->where('product_name', 'LIKE', "%{$search}%");
        }

        if ($company) {
        $query->whereHas('company', function ($query) use ($company) {
            $query->where('company_id', $company);
        });
        }

        if ($priceMin) {
            $query->where('price', '>=', $priceMin);
        }
        
        if ($priceMax) {
            $query->where('price', '<=', $priceMax);
        }
        
        if ($stockMin) {
            $query->where('stock', '>=', $stockMin);
        }
        
        if ($stockMax) {
            $query->where('stock', '<=', $stockMax);
        }

        return $query;
    }

}

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


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

    <a href="{{ route('products.create') }}" class="btn btn-primary mb-3">商品新規登録</a>

    <div class="search mt-5">
    
    <h2>検索条件で絞り込み</h2>
    
    <form action="{{ route('products.index') }}" method="GET" class="row g-3" id="search-form">
        <div class="col-sm-12 col-md-3">
            <input type="text" name="search" class="form-control" placeholder="商品名" value="{{ request('search') }}" id="search">
        </div>
        <div class="col-sm-12 col-md-3">
            <select name="company_id" class="form-control" id="company">
                <option value="">全て</option>
                @foreach($company_lists as $key => $value)
                <option value="{{ $key }}" @if($company == $key) selected="selected" @endif>{{ $value }}</option>
                @endforeach
            </select>
        </div>
        <div class="col-sm-12 col-md-3">
            <input type="number" name="price_min" class="form-control" placeholder="価格の下限" value="{{ request('price_min') }}">
        </div>
        <div class="col-sm-12 col-md-3">
            <input type="number" name="price_max" class="form-control" placeholder="価格の上限" value="{{ request('price_max') }}">
        </div>
        <div class="col-sm-12 col-md-3">
            <input type="number" name="stock_min" class="form-control" placeholder="在庫数の下限" value="{{ request('stock_min') }}">
        </div>
        <div class="col-sm-12 col-md-3">
            <input type="number" name="stock_max" class="form-control" placeholder="在庫数の上限" value="{{ request('stock_max') }}">
        </div>
        <div class="col-sm-12 col-md-1">
            <button class="btn btn-outline-secondary" type="submit">絞り込み</button>
        </div>
    </form>
</div>

<a href="{{ route('products.index') }}" class="btn btn-success mt-3">検索条件を元に戻す</a>
    <div class="products mt-5" >
        <h2>商品情報</h2>
        <table class="table table-striped" >
            <thead>
                <tr>
                    <th>商品名</th>
                    <th>メーカー</th>
                    <th>価格</th>
                    <th>在庫数</th>
                    <th>コメント</th>
                    <th>商品画像</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody id="search-results">
            @foreach ($products as $product)
                <tr>
                    <td>{{ $product->product_name }}</td>
                    <td>{{ $product->company->company_name }}</td>
                    <td>{{ $product->price }}</td>
                    <td>{{ $product->stock }}</td>
                    <td>{{ $product->comment }}</td>
                    <td><img src="{{ asset($product->img_path) }}" alt="商品画像" width="100"></td>
                    </td>
                    <td>
                        <a href="{{ route('products.show', $product->id) }}" class="btn btn-info btn-sm mx-1">詳細表示</a>
                        <a href="{{ route('products.edit', $product->id) }}" class="btn btn-primary 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">削除</button>
                        </form>
                    </td>
                </tr>
            @endforeach
            </tbody>
        </table>
    </div>
    
    {{ $products->appends(request()->query())->links() }} 
</div>
@endsection

何卒、ご教授いただけますと幸いです。
よろしくお願い申し上げます:bow_tone1:

0

3Answer

以下の処理でidも返す必要が有りますかね?レスポンスのデータが問題だと思うので、ブラウザの開発者ツールとか、ログに出してみるとかして確認してみてください。
存在しないキー(id)を参照してるので、undefinedになっているかと、、、

$products = $products->map(function ($product) {
return [
'product_name' => $product->product_name ?? '',
'company_name' => $product->company->company_name ?? '',
'price' => $product->price ?? '',
'stock' => $product->stock ?? '',
'comment' => $product->comment ?? '',
'img_path' => asset($product->img_path) ?? '',
];
});

2Like

Comments

  1. 404 応答が返ってきているのでサーバー側では php のコードが動くところまで行ってないと思いますが?

    エラーメッセージは、

    GET http://localhost:8888/products/undefined 404 (Not Found)

    ということなので、多分 search.js のコード、

    <a href="/products/${productId}" class="btn btn-info btn-sm mx-1">詳細表示</a>
    

    で、productId が undefined になっていて、それをクリックすると存在しない URL が要求されるからだと思います。

非同期で検索処理を行い該当のレコードを表示することはできております。しかし取得したレコードの詳細、編集画面へ移動することができません

「詳細表示ボタン」「編集ボタン」を押下すると下記のエラーがブラウザにて確認されます・・・
GET http://localhost:8888/products/undefined 404 (Not Found)

404 応答が返ってくるということは、ブラウザからの要求は Web サーバーに届いて、Web サーバーで URL に指定されるリソースを探したが見つからなかったということです。ほとんどの場合、原因は指定した URL が間違っているということです。

「詳細表示ボタン」「編集ボタン」というのは search.js がレンダリングする a タグのことですよね。であれば、その href 属性に指定した URL が間違っているのでしょう。まずはそこを調べてください。

エラーメッセージからすると、多分 search.js のコード、

<a href="/products/${productId}" class="btn btn-info btn-sm mx-1">詳細表示</a>

で、productId が undefined になっていて、結果レンダリングされる html は <a href="/products/undefined" ...> となり、その a タグをクリックすると存在しない URL が要求されるから 404 応答になるのだと思います。


【追伸】

ひょっとして上に書いたことは質問者さんはすでに分かっていて、知りたいことは search.js のコード、

var productId = product.id;

・・・のところで何故 productId が undefined になるかですか?

であれば、$.ajax で要求をかけた結果返ってくる JSON 文字列がどうなっているかと、<a href="/products/${productId}" ...> が html にレンダリングされた時どうなればいいかを書いてください。

1Like

GET http://localhost:8888/products/undefined 404 (Not Found)

URLのidの部分がundefinedになっているので、URLを生成する/products/${productId}に問題があるのでしょう。

search.jsでそのURLを生成しているようですが、productIdproduct.idの値が想定されたものになっているか確認されましたか?

またproductsの情報を返す処理について明記されていませんが、おそらくProductController.phpserachなのだと思います。その部分を見たところ、productのIDが含まれていないように見えます。

        return [
            'product_name' => $product->product_name ?? '',
            'company_name' => $product->company->company_name ?? '',
            'price' => $product->price ?? '',
            'stock' => $product->stock ?? '',
            'comment' => $product->comment ?? '',
            'img_path' => asset($product->img_path) ?? '',
        ];
1Like

Your answer might help someone💌