# Ruby(13/14) 五倍紅寶石(第八屆共筆)(2021/8/23) ## 貓貓 - find JS 找單一值,用 find 去找,接一個 callback list.find( el => el === "d") 用 get 跟 set 可以讓函式不用呼叫也能用屬性方式印出 main.js ```js import "bootstrap/dist/css/bootstrap.css" import "@fortawesome/fontawesome-free/css/all.css" import axios from "axios" import Cart from "./cart" // 前端也可以有 MVC 架構 // main.js 相當於 view // cart.js 跟 cart-item.js 相當於 model const cart = new Cart() // renderItems // 按下去瞬間把內容清掉再根據剛剛的內容畫出來 // 根據你傳進來的東西轉圈圈 function renderItems(items) { const cartList = document.querySelector("tbody") cartList.innerHTML = "" // items items.forEach((item) => { const t = createCartItem(item) cartList.insertAdjacentHTML("beforeend", t) }) // total const totalEl = document.querySelector(".total-price") totalEl.innerHTML = "$" + cart.total } // 從 function setCatCartEvent(cats) 塞單一貓貓進來 // 每次點擊都會新增該貓貓且重新渲染 function addToCart(cat) { cart.addItem(cat) renderItems(cart.items) } // 有 id 就刪掉,接著重新渲染 function setItemEvent() { document.querySelector("tbody").addEventListener("click", (e) => { if (e.target.hasAttribute("data-id")) { const itemId = e.target.dataset.id cart.removeItem(itemId) renderItems(cart.items) } }) // 點擊上下按鈕更新資料 document.querySelector("tbody").addEventListener("change", (e) => { if (e.target.hasAttribute("data-item-id")) { const itemId = e.target.dataset.itemId const quantity = e.target.value cart.updateQuantity(itemId, quantity) renderItems(cart.items) } }) } // click 事件 function setCatCartEvent(cats) { const btns = document.querySelectorAll(".card .btn") // btn 是每個貓貓按鈕本身,i 是 index(索引值) = 0~5 // 點擊後新增各別的貓貓到 function addToCart btns.forEach((btn, i) => { btn.addEventListener("click", () => { addToCart(cats[i]) console.log(btn) }) }) } // 頁面下面的購物車區塊 // 按下去會長出來,跟上面的貓貓一樣 // 先把 HTML 的 tr 刪掉 // 設定點完上面按鈕會自己動態長出來 // 跟剛剛上面做法一樣 // 建立購物車 function // 傳入我要的東西進來,動態產生貓貓購物車 function createCartItem(item) { const { id, name, price, quantity, total } = item // min = 1,用HTML設定最小值是1 const el = `<tr id="${id}"> <td>${name}</td> <td> <input data-item-id="${id}" min="1" type="number" class="quantity" value="${quantity}" /> </td> <td>$${price}</td> <td>$${total}</td> <td> <button data-id="${id}" class="remove-item-btn btn btn-danger btn-sm"> <i data-id="${id}" class="fas fa-trash-alt"></i> </button> </td> </tr>` return el } // 先把 HTMl 檔砍到沒有資料 // 建立貓貓 function (我想在畫面上新增貓貓cart) // 傳入我要的東西進來,動態產生貓貓 // 用解構丟到參數 function createCatCard({ id, name, price, photo }) { // 用反單引號處理多行比較適合,比較不會出問題 // 建立一個元素塞資料進去 // 先抓到原始圖片才顯示的出來 // http 沒辦法抓到 https 的圖片,會有安全性問題 // 最後回傳字串 const el = `<div class="col-sm-2"> <div class="card" data-product-id="${id}"> <img src="" class="card-img-top" alt="" /> <div class="card-body"> <h5 class="title card-title fs-6 fw-light">${name}</h5> <p class="price">$${price}</p> <button class="btn btn-sm btn-warning fw-light"> <i class="fas fa-cat"></i> </button> </div> </div> </div>` return el } document.addEventListener("DOMContentLoaded", () => { // 剛載入就先渲染,一開始是空陣列 renderItems(cart.items) // 抓 api const endPoint = "https://raw.githubusercontent.com/5xTraining/shopping-cat-v1/main/data/cats.json" // 先選到 HTML 的貓貓,用 .items 去選 const itemList = document.querySelector(".items") // 綁定click事件,按完會顯示那張卡的 id // 在迴圈載完後呼叫才合理 // 不然會被綁定多次 // 先跑圖示出來才有按鈕可以點 // 用兩次 parentelement 找到父父層,也就是有寫 data-product-id 那層 // 幫所有的按鈕加 click 事件 // 讓他點擊後能顯示 id // 途中可用 console.log 檢查自己有沒有找錯物件 // 先去抓資料,會抓到 json 形式的資料 // 用解構塞 data,同名才能這樣解 // 接著用迴圈方式一個一個加進來顯示 axios .get(endPoint) // 用解構塞給他 .then(({ data }) => { data.forEach((c) => { // 把要加的東西放進來 // 用insertAdjacentHTML(beforeend, 要加入的資料) 加在子層最後面 let cat = createCatCard(c) itemList.insertAdjacentHTML("beforeend", cat) }) // data 是 endPoint 拿到的 json 檔,裡面的所有貓貓 // 先塞全部的貓給 setCatCartEvent() // 拿到之後轉迴圈一個一個塞回來 setCatCartEvent(data) setItemEvent() }) .catch((err) => { console.log(err) }) }) ``` cart.js ```js import CartItem from "./cart-item" // 要判斷的東西很多,所以寫一個類別 // 把邏輯放這做判斷 // 出生先給他一個空陣列 class Cart { constructor() { this.items = [] } // 從 main.js 的 function addToCart(cat) 把每隻貓咪塞進去 addItem(cat) { // find 裡面的 item 是 cartItem const foundItem = this.items.find((item) => item.id === cat.id) // 如果有找到他就 + 1 // 沒有就新增一個 if (foundItem) { foundItem.increment() } else { const item = new CartItem(cat) this.items.push(item) } } // 如果點到外面紅色區塊跟垃圾桶圖案都會抓得到 // 刪掉之後還要再重新渲染一次 // filter 試過濾出我要的 // filter !== 除了我現在點到的都留下來 removeItem(id) { this.items = this.items.filter((item) => item.id !== id) } updateQuantity(id, quantity) { const foundItem = this.items.find((item) => item.id === id) if (foundItem) { foundItem.setQuantity(quantity) } } get total() { let result = this.items.reduce((acc, item) => { return acc + item.total }, 0) return Math.floor(result * 100) / 100 } } export default Cart ``` cart-item.js ```js // 看起來像是另外的資料結構,所以再創一個 // 原本資料結構是陣列 // 但這邊要用物件才放得下 // 每一條東西都是一個cartitem // 讓 cart.js 裡面被 push 進去的是物件 class CartItem { constructor({ id, name, price, quantity = 1 }) { this.id = id this.name = name this.price = price this.quantity = quantity } // 預設值 = 1 ,被呼叫就 + 1 increment(n = 1) { this.quantity += n } // 不給他數量的話預設值是一,有寫就給他數量 setQuantity(quantity = 1) { let q = Number(quantity) if (q > 0) { this.quantity = q } } // 每一條都要能自己算錢 // total() get total() { return this.price * this.quantity } } export default CartItem ``` ## evernote 專案 ### AASM 套件 [aasm](https://github.com/aasm/aasm) 有限狀態機:管理狀態的轉換,可以切換狀態,如果狀態大於兩個可以用(除了true false 以外還有的話) 資料的狀態不要自己去資料庫改,讓 rails 幫你改 可以先畫出流程圖,AASM 讓流程圖變成程式碼 用 AASM 可以做出連續動作,比較方便 使用前要先去 gem 安裝套件 published 大家都看得到 hidden 知道連結才看得到 draft 草稿,只有作者自己才看得到 當你定義 state 後,AASM 會送你上面這些方法 ### 專案內容 20210823075732_add_state_to_notes.rb ```rb # 相較 int 用 string 來表示狀態會比較清楚 # 看名稱就知道他現在是甚麼狀態,不然單看0 1 2 3 不知道是啥 def change add_column :notes, :state, :string end ``` routes.rb ```rb # 這邊是要切換狀態,在get,post,patch/put,delete 當中比較適合的就 patch/put 而已 # 用來更新狀態 member do patch :publish end ``` note.rb ```rb # 先畫好關聯圖再來做 AASM 會比較清楚 # 引入 AASM 套件 # 預設是使用 aasm_state 當欄位名,用來儲存狀態 # 這邊是自訂名稱為 state(在migration檔自訂的) # 用 initial: true 讓 draft 是預設值 # event 表示要做的動作 # transitions 表示從哪個狀態過渡到哪個狀態 include AASM aasm column: 'state' do state :draft, initial: true state :published, :hidden event :publish do transitions from: :draft, to: :published # after do # puts "發送簡訊" # end end event :hide do transitions from: :published, to: :hidden end event :recall do transitions from: [:published, :hidden], to: :draft end end ``` notes_controller.rb ```rb # 先找到筆記才能發布 before_action :find_user_note, only: [:edit, :update, :destroy, :publish] def publish # @note = Note.new # 加 ! 讓他可以自動 save,類似create # 否則要再後面加.save 才能存到資料庫 @note.publish! # flash[:notice] = "更新成功",印出訊息,只會印出一次,重整後就消失 # redirect_to notes_path # flash[:notice] # 特化版,可以寫在 redirect_to 後面,寫成一行 redirect_to notes_path, notice: "更新成功" end ``` index.html.erb ```html <%# may_publish? = 詢問 Rails 目前狀態可不可以做publish 這個動作 %> <%= link_to "發佈", publish_note_path(note), method: 'patch', data: { confirm: '確認發佈嗎?'} if note.may_publish? %> ``` application.html.erb ```html 執行完 publish 後顯示 flash 內容 <%= render "shared/flash" %> ``` _flash.html.erb ```html <%# 很常用所以拉出來寫 %> <div> <%= flash[:notice] %> </div> ``` --- ###### tags: `Ruby` `Rails` `JavaScript`