某フリマサイトのコピー制作でAjaxを勉強したくて以下のような機能を実装しました。
振り返りをかねて、メモしておきます。
書き方が間違っている、コードが汚いなど、色々とご指摘をいただけると嬉しいです。
(javascriptの変数はvarにしてます。。)
完成イメージ
それぞれのタブを押すたびにAjaxが発火して、タブが切り替わるイメージです。
ポイント整理
自分なりに挑戦しようとしたところは以下。
JQueryのオブジェクト指定を正規表現で行う
今回は、指定する条件(評価種別)が異なるだけで、全く同じコンテンツを表示させるものでした。
そのため、オブジェクト指定に正規表現をかけ、出来るだけ省力化をはかったつもり。。。
前方、後方、部分一致などが使えるようなので、書き方例を以下に例示します。
// ①前方一致 ※今回私が使った方法
$("[id^='rating-tab-']").on('click',function(e){});
// ②後方一致
$("[id$='-rating-tab']").on('click',function(e){});
// ③部分一致
$("[id*='rating-tab']").on('click',function(e){});
その他にも沢山指定方法があるみたいです。
参考:JQueryのセレクタに正規表現・属性フィルタを使う方法
カスタムデータ属性を使って評価種別を判別する
カスタムデータ属性に評価判別のための値を仕込みました。
ただ、hamlで書くの初めてで少し戸惑いました。下記のように書くとうまく行くみたいです。'-'で表現していたところを:(コロン)と{}(中括弧)で表現する模様。
%li.rating__nav--all.selected-tab#rating-tab-all{ data: {rating: '0'}}
上述のカスタムデータ属性は、js内で以下のようにすれば取得できます。
var target = $(this).data('rating');
※thisはイベントの発火対象を指します。
それ以外なら、id又はクラスでオブジェクト指定してあげてください。
参考:haml に HTML5 のカスタムデータ属性(data-*) を定義する方法
コーディング例
railsのcontroller部分は割愛して、載せています。
JavaScriptメインで参考にしていただければと思います。。
##JavaScriptファイル
正規表現などを利用して、出来るだけコンパクトにまとめられるように努めました。
html部分は、Ajaxじゃないページを作って、ブラウザ検証ツールで引っこ抜いてくる感じです。
一番楽チンかなと。
$(function(){
// ul作成用のHTMLBuildメソッド
function UlBuildHTML(){
var html = `<ul id="rating-result-list">`
return html;
}
//li作成用のHTMLBuildメソッド
function ListBuildHTML(data){
var html = `<li class="rating__contents__content">
<a href="/users/${data.rater_id}/show_profile">
<img class="rating__contents__content__image" height="48px" width="48px" src="${data.rater_avatar}">
<div class="rating__contents__content__text">
<i class="fas ${data.rate}"></i>
${data.stance}
<p>${data.rater_nickname}</p>
<p>${data.comment}</p>
<div class="rating__contents__content__text__time">
<i class="far fa-lg fa-clock"></i>
<p>${data.created_at}</p>
</div>
</div>
<div class="rating__contents__content__link-icon">
<i class="fas fa-lg fa-angle-right"></i>
</div>
</a>
</li>`
return html;
}
//データが何も無い時用のHTMLBuildメソッド
function NoneListBuildHTML(message){
var html = `<div class="rating__contents__none" id="rating-result-none">
<h3>
${message}
</h3>
</div>`
return html;
}
//Ajaxの本体
//idに特定のキーワードが含まれた要素をクリックした時に発火する
$("[id^='rating-tab-']").on('click',function(e){
//"rating-tab-"に前方一致するidをもつ全ての要素から"selected-tab"クラス削除
$("[id^='rating-tab-']").removeClass('selected-tab');
//clickされたタブに"selected-tab"クラスを追加
$(this).addClass('selected-tab');
//カスタムdata属性に仕込んだ検索対象の評価種別(all/normal/good/bad)を取得する
var target = $(this).data('rating');
//ajaxを発火させる
//検体条件となる評価種別は明示的にdataとして埋め込む
//ユーザ情報も必要だけど、私はdeviseを使っているのでサーバー側でcurrent_user.idを使うから不要
$.ajax({
url: '/ratings/index_api',
type: "GET",
data: {rating: target},
dataType:'json',
})
.done(function(list){
// Ajaxが正常に戻ってきたので一旦リストを空にする
var result_all = $('#rating-result-all');
result_all.empty();
// 取得データの状況によって表示内容を切り替える
if ( list.length > 0 ){
//データがあった場合はリスト表示開始
//まずulタグをビルド
var ul_html = UlBuildHTML();
result_all.append(ul_html);
var result_list = $('#rating-result-list');
//生成したulタグにデータ数分のliを追加
list.forEach(function(data){
var html = ListBuildHTML(data);
result_list.append(html);
});
}else{
//データがない場合は無いよとメッセージを出してあげる
var message ='評価はまだありません'
var html = NoneListBuildHTML(message);
result_all.empty();
result_all.append(html);
}
})
.fail(function(){
alert('データ取得に失敗しました');
});
});
});
##Jbuilderファイル
view上でrubyを使って整形している箇所は、全てJbuilderで吸収してあげる必要あり。
フォーマットヘルパーを使うなら、ここで。
#複数結果が取得できる可能性があるため、繰り返し処理を行う
json.array! @ratings do | rate |
json.rater_id rate.rater_user_id
json.rater_nickname rate.rater_user.nickname
# イメージ有無で仕込むデータを切り替え。
# 本番環境はフィンガープリントがくっつくので、それも付加してファイル名記載・・・(多分もっとスマートなやり方あります)
if rate.rater_user.avatar.attached?
json.rater_avatar url_for(rate.rater_user.avatar)
else
json.rater_avatar "/assets/common/member_photo_noimage_thumb-224a733c50d48aba6d9fdaded809788bbeb5ea5f6d6b8368adaebb95e58bcf53.png"
end
json.stance stance_check(rate) #独自ヘルパーメソッド
json.comment rate.comment
#javascript内でrubyのフォーマットヘルパーが使えないので、jbuilder内で事前に整形。
json.created_at rate.created_at.strftime("%m月%d日 %H:%M")
# font-awesomeの指定をここで割り当てる。
# 普通に文字列として指定したらいけました。ヘルパーメソッド にしてあげたほうが親切かも。。
case rate.rate
when 1
json.rate 'fa-smile good'
when 2
json.rate 'fa-meh normal'
when 3
json.rate 'fa-frown bad'
end
end
##html.hamlファイル
部分テンプレート部分だけを抜粋しました
どうしてもBEM表記がスマートに書けない病です・・・
.rating
.rating__title
%h2
評価一覧
%ul.rating__nav
//hamlの場合、属性名に'-'を使うと文法エラーになるため下記の表記。
%li.rating__nav--all.selected-tab#rating-tab-all{ data: {rating: '0'}}
%h3
全て
%li.rating__nav--good#rating-tab-good{ data: {rating: '1'}}
= icon 'fas fa-lg', 'smile', class: "good"
%h3
良い
%li.rating__nav--normal#rating-tab-normal{ data: {rating: '2'}}
= icon 'fas fa-lg', 'meh', class: "normal"
%h3
普通
%li.rating__nav--bad#rating-tab-bad{ data: {rating: '3'}}
= icon 'fas fa-lg', 'frown', class: "bad"
%h3
悪い
.rating__contents#rating-result-all
- if @ratings&.present? && @ratings.size > 0
%ul#rating-result-list
- @ratings.each do | rate |
%li.rating__contents__content
= link_to show_profile_user_path(rate.rater_user_id) do
- if rate.rater_user.avatar.attached?
= image_tag rate.rater_user.avatar, class:'rating__contents__content__image', height: '48px', width: '48px'
- else
= image_tag 'common/member_photo_noimage_thumb.png', class:'rating__contents__content__image', height: '48px', width: '48px'
.rating__contents__content__text
= rating_check(rate) #独自ヘルパーメソッド
= stance_check(rate) #独自ヘルパーメソッド
%p
= rate.rater_user.nickname
%p
= rate.comment
.rating__contents__content__text__time
= icon 'far fa-lg','clock'
%p
= rate.created_at.strftime("%m月%d日 %H:%M")
.rating__contents__content__link-icon
= icon 'fas fa-lg','angle-right'
-else
.rating__contents__none#rating-result-none
%h3
評価はまだありません
##CSSファイル
全くレスポンシブじゃあ、ありません。。
mixinを利用することで短くなりそうですが、非常にプレーンに記述しています。
.wrapper{
.rating{
//タイトル(評価一覧)
&__title{
height:72;
padding-left:16px;
background-color:#fafafa;
h2{
line-height:72px;
font-size:16px;
font-weight:bold;
}
}
//タブ
&__nav{
display:flex;
width:700px;
height:74px;
background-color:#eee;
cursor: pointer;
h3{
font-weight:bold;
}
&--all{
display:flex;
justify-content: center;
text-align: center;
align-items: center;
width:175px;
}
&--good{
display:flex;
justify-content: center;
text-align: center;
align-items: center;
width:175px;
i{
margin-right:10px;
color:#ef5185;
}
}
&--normal{
display:flex;
justify-content: center;
text-align: center;
align-items: center;
width:175px;
i{
margin-right:10px;
color:#fba933;
}
}
&--bad{
display:flex;
justify-content: center;
text-align: center;
align-items: center;
width:175px;
i{
margin-right:10px;
color:#6ab5d8;
}
}
//選択されたタブのみデザインを変えられる用に別だし
.selected-tab {
border-top: 2px solid #dd0e0e;
background-color: #fff;
}
}
//評価リスト
&__contents{
&__content{
box-sizing: border-box;
padding:16px;
background-color: #ffffff;
border-bottom:solid 1px #ccc;
a{
display:flex;
align-items:center;
text-decoration: none;
width:668px;
color:#333;
&__image {
width:48px;
height:48px;
display:inline-block;
}
&__text {
display:inline-block;
}
&__link-icon{
display:inline-block;
}
}
&__text {
margin: 0 40px 0 20px;
width:85%;
font-size:13px;
color:#333;
.good{
color:#ef5185;
}
.normal{
color:#fba933;
}
.bad{
color:#6ab5d8;
}
&__time{
display:flex;
align-items:center;
font-size:12px;
i{
display:inline-block;
margin:0 5px;
}
}
}
}
&__content:hover{
background-color:#f5f5f5;
}
&__content:last-child{
border:none;
}
//表示するリストが存在しない場合のメッセージ表示用
&__none{
box-sizing: border-box;
height:236px;
padding: 160px 0 60px;
background-color: #ffffff;
background:#fff image-url('logo-gray-icon.svg') no-repeat;
background-position:center 50px;
background-size:78px 85px;
h3{
font-size:16px;
font-weight:600;
color:#ccc;
text-align:center;
}
}
}
}
}
まとめ
今回は特にないのですが、もう少し正規表現やフィルタなどを駆使すれば、スマートに分かりやすいJavaScriptやJQueryが書けそうだなぁ。というのが正直な感想です。(要するに、まだまだ勉強不足)
初心者から抜け出すためにも、引き続き頑張ります。
参考
haml に HTML5 のカスタムデータ属性(data-*) を定義する方法
JQueryのセレクタに正規表現・属性フィルタを使う方法
Railsガイド:アセットパイプライン