##実装したい機能
特定のクラスを持つエレメントから商品IDを取得し
それぞれの商品詳細ページからカテゴリを取得して
対応する商品IDを持つエレメントに表示する。
##使用するコード
jquery > 1.7.2
####表示するページ
...
<ul>
<li class="products pid_111">
<p>商品名①</p>
<p class="cate"><!--カテゴリを表示したい--></p>
</li>
<li class="products pid_222">
<p>商品名②</p>
<p class="cate"><!--カテゴリを表示したい--></p>
</li>
<li class="products pid_333">
<p>商品名③</p>
<p class="cate"><!--カテゴリを表示したい--></p>
</li>
</ul>
...
####商品詳細ページ
...
<div>
<p>商品名①</p>
<p class="p_cate">カテゴリ①</p><!--これを取得したい-->
</div>
...
##書いてみたコード
まず、自分の知識で書いてみました。
var id_name = $('.products');
for (var i = 0; i < $(id_name).length; i++) {
var id_sp = id_name.eq(i).attr('class').split(" ")[1];
var id = id_sp.substring(id_sp.indexOf("_")+1,id_sp.length);
(function(i){
$.ajax({
url: id + '.html',
type: 'GET',
dataType: 'html',
})
.done(function (data) {
$('.' + id_sp).html($(data).find('.p_cate').text());
})
})(i);
}
####実行結果
<ul>
<li class="products pid_111">
<p>商品名①</p>
<p class="cate"></p>
</li>
<li class="products pid_222">
<p>商品名②</p>
<p class="cate"></p>
</li>
<li class="products pid_333">
<p>商品名③</p>
<p class="cate">カテゴリ③</p>
</li>
</ul>
最後の商品にだけカテゴリが入ってしまいます。
####コンソール実行
...
.done(function (data) {
$('.' + id_sp).html($(data).find('.p_cate').text());
console.log(data + ':' + id_sp);
})
/*
=> 結果
p_333:カテゴリ①
p_333:カテゴリ②
p_333:カテゴリ③
*/
ajax自体は正常に動いている様子です。
ループも商品の個数分回っています。
しかし、商品IDがすべて最後の商品のIDになっています。
####考察
ajax処理が完了する前にループが回りきってしまうため
商品IDを取得するタイミングと商品カテゴリを取得するタイミングにずれがある?
##追記
@netebakari 様にコメントを頂き
そもそも「クロージャの性質」によるものだとわかりました。
(コメント参照)
forループ内の1つ目の変数
var id_name = $('.products');
を
const
かlet
で宣言することで
forループ内でも変数(定数)を維持することができて
適応した商品IDを取得することができました。
####動くコード
var id_name = $('.products');
for (var i = 0; i < $(id_name).length; i++) {
const id_sp = id_name.eq(i).attr('class').split(" ")[1]; // var→constへ変更
var id = id_sp.substring(id_sp.indexOf("_")+1,id_sp.length);
$.ajax({
url: id + '.html',
type: 'GET',
dataType: 'html',
})
.done(function (data) {
$('.' + id_sp).html($(data).find('.p_cate').text());
});
}
コンソールを実行
...
.done(function (data) {
$('.' + id_sp).html($(data).find('.p_cate').text());
console.log(data + ':' + id_sp);
})
/*
=> 結果
p_111:カテゴリ①
p_222:カテゴリ②
p_333:カテゴリ③
*/
今度こそ、想定の動きをしてくれました!
対策を調べる
グーグル先生に聞いてみたところ、大きく3つの対応策がありました。
ajaxを非同期にするソース:https://it-engineer-info.com/language/javascript/1123/無名関数で囲うソース:http://web-trash.info/2017/01/30/jquery-loop-in-ajax/取得した値を配列に含んでeachで吐き出しソースはありませんが、teratailのコメントで発見しました。
ajaxを非同期にする
こちらは特に変化なしです。
これが原因ではないようです。
無名関数で囲う
すでに囲っていました。
これも原因ではないようです。
取得した値を配列に含んでeachで吐き出し
もう、これしかありません。
頑張って書いてみます。
var id_name = $('.products');
var arr = [ ];// <- 追加
for (var i = 0; i < $(id_name).length; i++) {
var id_sp = id_name.eq(i).attr('class').split(" ")[1];
var id = id_sp.substring(id_sp.indexOf("_")+1,id_sp.length);
(function(i){
$.ajax({
url: id + '.html',
type: 'GET',
dataType: 'html',
})
.done(function (data) {
var cate = $(data).find('.p_cate').text();// <- 変更
arr.push( {p_id:id_sp, cate_name:cate} );
})
})(i);
$.each(arr, function(index, value) { // <- 追加
var find = '.' + value.p_id
var cate_in = value.cate_name
$(find).html(cate_in);
});
}
コンソール実行
...
$.each(arr, function(index, value) {
var find = '.' + value.p_id
var cate_in = value.cate_name
$(find).html(cate_in);
console.log(find + ':' + cate_in);
});
/*
=> 結果
p_111:カテゴリ①
p_222:カテゴリ②
p_333:カテゴリ③
*/
うまく回っています!
商品IDとカテゴリが適合しています!
実行
<ul>
<li class="products pid_111">
<p>商品名①</p>
<p class="cate">カテゴリ①</p>
</li>
<li class="products pid_222">
<p>商品名②</p>
<p class="cate">カテゴリ②</p>
</li>
<li class="products pid_333">
<p>商品名③</p>
<p class="cate">カテゴリ③</p>
</li>
</ul>
しっかりと表示されました!
一安心です…笑
##まとめ
一見単純な動きでも、ループや非同期通信が絡み合うと想定した動きにならないということを体感できました。
取得したデータを配列に格納して吐き出す、という考え方も知ることができました。
思考の幅がぐっと広がりました!
初学者が手探りで書いたコードなので、間違っている可能性も高いです。
もっと合理的な書き方をご存知の方は是非コメントを下さい!