# 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`