Step.14 重頭戲:購物車的AJAX - Azraeil/JCcart_Note GitHub Wiki

要先挖洞放JavaScript與CSS的地方

fix app/views/layouts/application.html.erb

<html>
<head>
  <title>JCcart</title>
  <%= stylesheet_link_tag    'application', media: 'all' %>
  <%= javascript_include_tag 'application' %>
  <%= csrf_meta_tags %>
  <%= yield :header %>
</head>

剛剛挖好的洞,他的code運作的位置

fix app/views/items/index.html.erb

<% content_for :header do %>

<% end %>

<h1>Listing Items</h1>

<table>

  ...
  ...
  ...
</table>

開始寫AJAX

先寫jQuery的語法

<% content_for :header do %>
<script>
  jQuery(function($){

  })
</script>
<% end %>

用<a>來做加入購物車的選項

<tbody>
  <% @items.each do |item| %>
    <tr>
      ...
      ...
      <td><a href="#" class="add_cart" data-value="<%= item.id %>">加入購物車</a></td>
    </tr>
  <% end %>

我們就能用jQuery選取器來選<a>的class add_cart

<script>
  jQuery(function($){
   $('.add_cart').click(function(){

   });
  })
</script>

我們要rake routes查看網址

add_cart_item GET    /items/:id/add_cart(.:format)                items#add_cart
        items GET    /items(.:format)                             items#index
             POST   /items(.:format)                             items#create
     new_item GET    /items/new(.:format)                         items#new
    edit_item GET    /items/:id/edit(.:format)                    items#edit
         item GET    /items/:id(.:format)                         items#show
            PATCH  /items/:id(.:format)                         items#update
              PUT    /items/:id(.:format)                         items#update
           DELETE /items/:id(.:format)                         items#destroy

看URI Pattern那一欄,我們剛剛談member時有說過,要從item的idd去增加購物車數量,所以我們找/items/:id/add_cart(.:format),他的HTML Verb是GET

由於我們剛剛在items_controller的add_cartaction最後是傳json,他的網址就是我們剛剛查到的/items/:id/add_cart

<script>
  jQuery(function($){
   $('.add_cart').click(function(){
     $.getJSON('/items/:id/add_cart')
   });
  })
</script>

不過由於:id是變數,我們要取的是item的id,也就是<a>下的data-value="<%= item.id %>"

<script>
  jQuery(function($){
   $('.add_cart').click(function(){
     $.getJSON('/items/' + $(this).attr('data-value') + '/add_cart')
   });
  })
</script>

我們預計會得到一個json回來,但json回來預計要更新到某一個地方

<script>
  jQuery(function($){
   $('.add_cart').click(function(){
     $.getJSON('/items/' + $(this).attr('data-value') + '/add_cart', function(json){

     })
   });
  })
</script>

這地方就是localhost:3000/這頁上面的購物車數量:0,我們一樣用chrome工具查看

<span class="cart_counter">0</span>

於是我們知道,他的class是cart_counter

於是

<script>
  jQuery(function($){
   $('.add_cart').click(function(){
     $.getJSON('/items/' + $(this).attr('data-value') + '/add_cart', function(json){
      $('#cart_counter').html();
     })
   });
  })
</script>

但我們取了的json究竟傳了什麼回來呢,我們回去看app/controllers/items_controller.rb

def add_cart
  session[:cart] ||= {}
  item = Item.where(:id => params[:id]).first

  if item
    session[:cart][item.id] ||= 0
    session[:cart][item.id] += 1
  end

  render :json => {:counter => session[:cart].length}.to_json
end

json回傳了一個:counter的Key,對一個Value session[:cart].length。所以我們AJAX得到的json就會是

$('#cart_counter').html(json.counter);

最後記得,如果不要click去觸發event,記得要用return false;

jQuery(function($){
 $('.add_cart').click(function(){
   $.getJSON('/items/' + $(this).attr('data-value') + '/add_cart', function(json){
    $('#cart_counter').html(json.counter);
  });

  return false;
 });
})
</script>

如果沒用return false;,他會觸發<a href="#">導致我們重新讀一次這個頁面

解購物車沒即時AJAX的蟲

重整頁面後,按了幾次加入購物車沒反應,重整後才發現購物車數字有增加,抓了許久蟲後發現,application.html.erb我要AJAX的span應該用#cart_counter我卻寫成.cart_counter

fix app/views/layouts/application.html.erb

from

<h1>對外頁面:public</h1>
<div>購物車數量:<span class="cart_counter"><%= get_cart_count %></span></div>

to

<h1>對外頁面:public</h1>
<div>購物車數量:<span id="cart_counter"><%= get_cart_count %></span></div>

購物車數字怪怪的問題

我們可以debug他,把它印出來

add to app/views/layouts/application.html.erb

<h1>對外頁面:public</h1>
<div>購物車數量:<span id="cart_counter"><%= get_cart_count %></span></div>
<br>
<%= session[:cart] %>

然後我們去看localhost:3000/的最上面可看到

{"1"=>9, "2"=>3, "3"=>1, "4"=>1}

從印出的畫面我們知道,session[:cart]的key是string,所以我們應該要把session[:cart]的key都轉成string

fix app/controllers/items_controller.rb

from

  if item
    session[:cart][item.id] ||= 0
    session[:cart][item.id] += 1  
  end

to

if item
  key = item.id.to_s
  session[:cart][key] ||= 0
  session[:cart][key] += 1
end

改掉用table顯示商品的寫法,加上CSS

fix app/views/items/index.html.erb

<% content_for :header do %>
<style>
  ul.items, ul.items li{
    list-style: none;
    padding: 0;
    margin: 0;
  }
</style>

<script>
...
...

原本的table這寫法很醜,通常上production會拆成ul跟li

fix app/views/items/index.html.erb

from

<table>
  <thead>
    <tr>
      <th>圖片</th>
      <th>名稱</th>
      <th>管理</th>
    </tr>
  </thead>

  <tbody>
    <% @items.each do |item| %>
      <tr>
        <td><%= image_tag item.cover.url(:icon) %></td>
        <td><%= item.name %>(<%= item.price %>)</td>
        <td><a href="#" class="add_cart" data-value="<%= item.id %>">加入購物車</a></td>
      </tr>
    <% end %>
  </tbody>
</table>

to

<ul class="items">
  <% @items.each do |item| %>
    <li>
      <%= image_tag item.cover.url(:icon) %>
      <span class="name"><%= item.name %>(<%= item.price %>)</span>
      <a href="#" class="add_cart" data-value="<%= item.id %>">加入購物車</a>
    </li>
  <% end %>
</ul>

接著我們繼續改CSS

<style>
  ul.items, ul.items li{
    list-style: none;
    padding: 0;
    margin: 0;
  }

  ul.items li{
    display: block;
    float: left;
    border: 1px #ccc solid;
    border-radius: 6px;
    width: 120px;
    height: 180px;
    margin: 4px 0 0 4px;
    text-align: center;
  }

  ul.items li img{
    width: 120px;
    height: 120px;
    display: block;
    border-radius: 8px;
  }

  ul.items li .name{
    display: block;
  }

  ul.items li a{
    display: inner-block;
    background: #ccccff;
    padding: 4px 8px;
    border-radius: 3px;
  }
</style>

然後重整localhost:3000,work!!!

由於時間不夠,沒實作串金流,只有口述,請看影片2:36:24,可看 tienshunlo的筆記

結束 ^_^

⚠️ **GitHub.com Fallback** ⚠️